refactor(inventory): replace per-slot Arc<Mutex<ItemStack>> with container-level synchronization
This commit is contained in:
parent
ffcf09ece4
commit
63d97e2c82
@ -127,5 +127,5 @@ harness = false
|
||||
[package.metadata.cargo-machete]
|
||||
ignored = ["phf"]
|
||||
|
||||
# [lints]
|
||||
# workspace = true
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -1,3 +1,11 @@
|
||||
#![allow(
|
||||
clippy::all,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo,
|
||||
clippy::print_stdout
|
||||
)]
|
||||
|
||||
use std::hint::black_box;
|
||||
|
||||
use criterion::{Criterion, criterion_group, criterion_main};
|
||||
|
||||
@ -36,7 +36,7 @@ pub struct BlockState {
|
||||
pub block_entity_type: u16,
|
||||
}
|
||||
|
||||
/// Helper struct to ensure the validity of BlockStateIds parsed from external sources.
|
||||
/// Helper struct to ensure the validity of `BlockStateIds` parsed from external sources.
|
||||
/// Every [`BlockStateId`] is guaranteed to correspond to a valid [`BlockState`].
|
||||
#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash, PartialOrd, Ord)]
|
||||
#[repr(transparent)]
|
||||
|
||||
@ -538,6 +538,7 @@ pub const SLOT_RANGE_SINGLE_SLOT_NAMES: [&str; 156usize] = [
|
||||
"weapon.mainhand",
|
||||
"weapon.offhand",
|
||||
];
|
||||
#[allow(clippy::match_same_arms)]
|
||||
#[must_use]
|
||||
pub fn get_slot_range(name: &str) -> Option<&'static [usize]> {
|
||||
match name {
|
||||
|
||||
@ -1,4 +1,12 @@
|
||||
#![allow(unused_imports)]
|
||||
#![allow(
|
||||
clippy::all,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo,
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
clippy::if_then_some_else_none
|
||||
)]
|
||||
|
||||
#[rustfmt::skip]
|
||||
#[path = "generated/chunk_view_lut.rs"]
|
||||
|
||||
@ -53,10 +53,7 @@ impl AnvilScreenHandler {
|
||||
}
|
||||
|
||||
pub async fn update_result_slot(&mut self) {
|
||||
let input_a = {
|
||||
let lock = self.inventory.get_stack(0).await;
|
||||
lock.lock().await.clone()
|
||||
};
|
||||
let input_a = self.inventory.get_stack(0).await;
|
||||
|
||||
if input_a.is_empty() {
|
||||
self.inventory.set_stack(2, ItemStack::EMPTY.clone()).await;
|
||||
@ -130,7 +127,7 @@ impl ScreenHandler for AnvilScreenHandler {
|
||||
|
||||
fn quick_move<'a>(
|
||||
&'a mut self,
|
||||
_player: &'a dyn InventoryPlayer,
|
||||
player: &'a dyn InventoryPlayer,
|
||||
slot_index: i32,
|
||||
) -> ItemStackFuture<'a> {
|
||||
Box::pin(async move {
|
||||
@ -138,40 +135,31 @@ impl ScreenHandler for AnvilScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
stack_left = slot_stack.clone();
|
||||
|
||||
if slot_index < 3 {
|
||||
// From anvil to player
|
||||
if !self
|
||||
.insert_item(
|
||||
&mut slot_stack_mut,
|
||||
3,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
)
|
||||
.await
|
||||
{
|
||||
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 input 0 and 1
|
||||
if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await {
|
||||
// From player to anvil
|
||||
if !self.insert_item(&mut slot_stack, 0, 2, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
if slot_stack.item_count == stack_left.item_count {
|
||||
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
|
||||
|
||||
@ -94,18 +94,14 @@ impl ScreenHandler for BeaconScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
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_mut,
|
||||
&mut slot_stack,
|
||||
1,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
@ -116,17 +112,15 @@ impl ScreenHandler for BeaconScreenHandler {
|
||||
}
|
||||
} else {
|
||||
// Move from player inventory into the beacon payment slot (slot 0)
|
||||
if !self.insert_item(&mut slot_stack_mut, 0, 1, false).await {
|
||||
if !self.insert_item(&mut slot_stack, 0, 1, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
if slot_stack.is_empty() {
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -142,8 +142,7 @@ impl ScreenHandler for BrewingScreenHandler {
|
||||
return stack_left;
|
||||
}
|
||||
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let mut stack = slot_stack_lock.lock().await;
|
||||
let mut stack = slot.get_stack().await;
|
||||
stack_left = stack.clone();
|
||||
|
||||
let success = if slot_index < 5 {
|
||||
@ -179,10 +178,9 @@ impl ScreenHandler for BrewingScreenHandler {
|
||||
}
|
||||
|
||||
if stack.is_empty() {
|
||||
drop(stack);
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(stack).await;
|
||||
}
|
||||
|
||||
stack_left
|
||||
|
||||
@ -9,12 +9,10 @@
|
||||
//! Unlike regular inventories, crafting grids are typically cleared when
|
||||
//! the container closes, and their contents are used up when crafting.
|
||||
|
||||
use std::sync::Arc;
|
||||
use std::{any::Any, pin::Pin};
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::inventory::split_stack;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
|
||||
@ -29,14 +27,14 @@ use super::recipes::RecipeInputInventory;
|
||||
///
|
||||
/// When a player places items in the crafting grid, they are stored here.
|
||||
/// When the crafting result is taken, the ingredients are consumed from this inventory.
|
||||
#[derive(Clone)]
|
||||
#[derive(Default)]
|
||||
pub struct CraftingInventory {
|
||||
/// Width of the crafting grid (typically 2 or 3).
|
||||
pub width: u8,
|
||||
/// Height of the crafting grid (typically 2 or 3).
|
||||
pub height: u8,
|
||||
/// Items in the crafting grid, stored row by row.
|
||||
pub items: Vec<Arc<Mutex<ItemStack>>>,
|
||||
pub items: RwLock<Vec<ItemStack>>,
|
||||
}
|
||||
|
||||
impl CraftingInventory {
|
||||
@ -60,54 +58,64 @@ impl CraftingInventory {
|
||||
Self {
|
||||
width,
|
||||
height,
|
||||
items: {
|
||||
// Creates a Vec with different Mutexes for each slot
|
||||
let mut v = Vec::with_capacity(width as usize * height as usize);
|
||||
(0..width as usize * height as usize)
|
||||
.for_each(|_| v.push(Arc::new(Mutex::new(ItemStack::EMPTY.clone()))));
|
||||
v
|
||||
},
|
||||
items: RwLock::new(vec![
|
||||
ItemStack::EMPTY.clone();
|
||||
width as usize * height as usize
|
||||
]),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for CraftingInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
(self.width as usize) * (self.height as usize)
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
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 remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
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_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move { split_stack(&self.items, slot, amount).await })
|
||||
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 set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
if slot < items.len() {
|
||||
items[slot] = stack;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -129,9 +137,8 @@ impl RecipeInputInventory for CraftingInventory {
|
||||
impl Clearable for CraftingInventory {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for item in &self.items {
|
||||
*item.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -113,7 +113,7 @@ async fn recipe_matches(
|
||||
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
|
||||
.await;
|
||||
if current_key == ' ' {
|
||||
if !slot.lock().await.is_empty() {
|
||||
if !slot.is_empty() {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
}
|
||||
@ -125,8 +125,6 @@ async fn recipe_matches(
|
||||
.find_map(|(k, v)| (*k == current_key).then_some(v))
|
||||
.expect("Crafting recipe used invalid key");
|
||||
|
||||
let slot = slot.lock().await;
|
||||
|
||||
if !ingredient.match_item(slot.item) {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
@ -146,7 +144,7 @@ async fn recipe_matches(
|
||||
)
|
||||
.await;
|
||||
if current_key == ' ' {
|
||||
if !slot.lock().await.is_empty() {
|
||||
if !slot.is_empty() {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
}
|
||||
@ -156,7 +154,6 @@ async fn recipe_matches(
|
||||
.iter()
|
||||
.find_map(|(k, v)| (*k == current_key).then_some(v))
|
||||
.expect("Crafting recipe used invalid key");
|
||||
let slot = slot.lock().await;
|
||||
if !ingredient.match_item(slot.item) {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
@ -181,7 +178,6 @@ 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 = slot.lock().await;
|
||||
if slot.is_empty() {
|
||||
continue 'next_slot;
|
||||
}
|
||||
@ -209,7 +205,6 @@ async fn recipe_matches(
|
||||
}
|
||||
'item_stack: for i in 0..inventory.size() {
|
||||
let slot = inventory.get_stack(i).await;
|
||||
let slot = slot.lock().await;
|
||||
if slot.is_empty() {
|
||||
continue 'item_stack;
|
||||
}
|
||||
@ -228,7 +223,6 @@ async fn recipe_matches(
|
||||
}
|
||||
for position in (1..=7).step_by(2) {
|
||||
let slot = inventory.get_stack(position).await;
|
||||
let slot = slot.lock().await;
|
||||
if slot.is_empty()
|
||||
|| !slot
|
||||
.item
|
||||
@ -268,7 +262,7 @@ async fn recipe_matches(
|
||||
.get_stack((y + y_offset) * inventory.get_width() + (x + x_offset))
|
||||
.await;
|
||||
if current_key == ' ' {
|
||||
if !slot.lock().await.is_empty() {
|
||||
if !slot.is_empty() {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
}
|
||||
@ -279,7 +273,6 @@ async fn recipe_matches(
|
||||
.find(|(k, _)| *k == current_key)
|
||||
.map(|(_, v)| v)
|
||||
.expect("Crafting recipe used invalid key");
|
||||
let slot = slot.lock().await;
|
||||
if !ingredient.match_item(slot.item) {
|
||||
matched = false;
|
||||
break 'outer;
|
||||
@ -302,7 +295,6 @@ 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 = slot.lock().await;
|
||||
if slot.is_empty() {
|
||||
continue 'next_slot;
|
||||
}
|
||||
@ -347,7 +339,6 @@ impl ResultSlot {
|
||||
let x = i % inventory_width;
|
||||
let y = i / inventory_width;
|
||||
let slot = self.inventory.get_stack(i).await;
|
||||
let slot = slot.lock().await;
|
||||
if !slot.is_empty() {
|
||||
top_x = top_x.min(x);
|
||||
top_y = top_y.min(y);
|
||||
@ -450,11 +441,7 @@ impl Slot for ResultSlot {
|
||||
)
|
||||
.await;
|
||||
for i in 0..self.inventory.size() {
|
||||
let slot = self.inventory.get_stack(i).await;
|
||||
let mut stack = slot.lock().await;
|
||||
if !stack.is_empty() {
|
||||
stack.item_count -= 1;
|
||||
}
|
||||
self.inventory.remove_stack_specific(i, 1).await;
|
||||
}
|
||||
self.mark_dirty().await;
|
||||
})
|
||||
@ -462,8 +449,8 @@ impl Slot for ResultSlot {
|
||||
fn can_insert(&self, _stack: &ItemStack) -> BoxFuture<'_, bool> {
|
||||
Box::pin(async move { false })
|
||||
}
|
||||
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.result.clone() })
|
||||
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() })
|
||||
@ -491,7 +478,6 @@ impl Slot for ResultSlot {
|
||||
let mut count = u8::MAX;
|
||||
for i in 0..self.inventory.size() {
|
||||
let slot = self.inventory.get_stack(i).await;
|
||||
let slot = slot.lock().await;
|
||||
if !slot.is_empty() {
|
||||
count = count.min(slot.item_count);
|
||||
}
|
||||
@ -613,8 +599,7 @@ impl ScreenHandler for CraftingTableScreenHandler {
|
||||
Box::pin(async move {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
if slot.has_stack().await {
|
||||
let slot_stack = slot.get_stack().await;
|
||||
let mut slot_stack = slot_stack.lock().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 {
|
||||
|
||||
@ -12,7 +12,6 @@ use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// A composite inventory combining two inventories.
|
||||
///
|
||||
@ -49,7 +48,7 @@ impl Inventory for DoubleInventory {
|
||||
Box::pin(async move { self.first.is_empty().await && self.second.is_empty().await })
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
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
|
||||
|
||||
@ -95,7 +95,6 @@ impl EnchantingTableScreenHandler {
|
||||
|
||||
pub async fn update_enchantments(&mut self, _player: &dyn InventoryPlayer) {
|
||||
let item = self.inventory.get_stack(0).await;
|
||||
let item = item.lock().await;
|
||||
|
||||
if item.is_empty() || item.has_enchantments() {
|
||||
for i in 0..3 {
|
||||
@ -349,8 +348,7 @@ impl ScreenHandler for EnchantingTableScreenHandler {
|
||||
return false;
|
||||
}
|
||||
|
||||
let lapis_slot = self.inventory.get_stack(1).await;
|
||||
let mut lapis_stack = lapis_slot.lock().await;
|
||||
let mut lapis_stack = self.inventory.get_stack(1).await;
|
||||
let lapis_cost = (id + 1) as u8;
|
||||
|
||||
if !player.is_creative()
|
||||
@ -362,8 +360,7 @@ impl ScreenHandler for EnchantingTableScreenHandler {
|
||||
}
|
||||
|
||||
// Perform enchantment
|
||||
let item_slot = self.inventory.get_stack(0).await;
|
||||
let mut item_stack = item_slot.lock().await;
|
||||
let mut item_stack = self.inventory.get_stack(0).await;
|
||||
|
||||
if item_stack.is_empty() || item_stack.has_enchantments() {
|
||||
return false;
|
||||
@ -380,16 +377,13 @@ impl ScreenHandler for EnchantingTableScreenHandler {
|
||||
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);
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Drop locks *before* calling `update_enchantments`
|
||||
// Otherwise, update_enchantments will try to lock slot 0 again and deadlock!
|
||||
drop(item_stack);
|
||||
drop(lapis_stack);
|
||||
self.inventory.set_stack(0, item_stack).await;
|
||||
|
||||
// Update seed
|
||||
player.set_enchantment_seed(rand::random()).await;
|
||||
@ -420,18 +414,14 @@ impl ScreenHandler for EnchantingTableScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().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_mut,
|
||||
&mut slot_stack,
|
||||
2,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
@ -443,21 +433,19 @@ impl ScreenHandler for EnchantingTableScreenHandler {
|
||||
} else {
|
||||
// From player to enchanting
|
||||
// Lapis check
|
||||
if slot_stack_mut.item == &Item::LAPIS_LAZULI {
|
||||
if !self.insert_item(&mut slot_stack_mut, 1, 2, false).await {
|
||||
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_mut, 0, 1, false).await {
|
||||
} else if !self.insert_item(&mut slot_stack, 0, 1, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
if slot_stack.is_empty() {
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
}
|
||||
|
||||
// CRITICAL FIX: Ensure the client is notified when shift-clicking items into the slots
|
||||
|
||||
@ -7,11 +7,10 @@
|
||||
//! the entity model (armor is rendered on the player, held items are visible
|
||||
//! in hands).
|
||||
|
||||
use std::{collections::HashMap, sync::Arc};
|
||||
use std::collections::HashMap;
|
||||
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
/// Equipment storage for an entity.
|
||||
///
|
||||
@ -21,19 +20,12 @@ use tokio::sync::Mutex;
|
||||
///
|
||||
/// See also: [`EquipmentSlot`](EquipmentSlot)
|
||||
// EntityEquipment.java
|
||||
#[derive(Clone)]
|
||||
#[derive(Clone, Default)]
|
||||
pub struct EntityEquipment {
|
||||
/// Map of equipment slots to their equipped items.
|
||||
///
|
||||
/// Keys are equipment slot types (head, chest, legs, feet, off-hand).
|
||||
/// Values are mutex-protected item stacks for thread-safe access.
|
||||
pub equipment: HashMap<EquipmentSlot, Arc<Mutex<ItemStack>>>,
|
||||
}
|
||||
|
||||
impl Default for EntityEquipment {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
pub equipment: HashMap<EquipmentSlot, ItemStack>,
|
||||
}
|
||||
|
||||
impl EntityEquipment {
|
||||
@ -53,27 +45,10 @@ impl EntityEquipment {
|
||||
///
|
||||
/// # Returns
|
||||
/// The previously equipped item, or an empty stack if the slot was empty.
|
||||
pub async fn put(&mut self, slot: &EquipmentSlot, stack: ItemStack) -> ItemStack {
|
||||
pub fn put(&mut self, slot: &EquipmentSlot, stack: ItemStack) -> ItemStack {
|
||||
self.equipment
|
||||
.insert(slot.clone(), Arc::new(Mutex::new(stack)))
|
||||
.unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
}
|
||||
|
||||
/// Gets or inserts an empty stack for a slot.
|
||||
///
|
||||
/// If the slot doesn't exist, creates it with an empty stack.
|
||||
///
|
||||
/// # Returns
|
||||
/// A mutex-protected item stack for this slot.
|
||||
#[must_use]
|
||||
pub fn get_or_insert(&mut self, slot: &EquipmentSlot) -> Arc<Mutex<ItemStack>> {
|
||||
self.equipment
|
||||
.entry(slot.clone())
|
||||
.or_insert_with(|| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||
.clone()
|
||||
.insert(slot.clone(), stack)
|
||||
.unwrap_or_else(|| ItemStack::EMPTY.clone())
|
||||
}
|
||||
|
||||
/// Gets the item in a slot.
|
||||
@ -81,22 +56,16 @@ impl EntityEquipment {
|
||||
/// # Returns
|
||||
/// The equipped item, or an empty stack if nothing is equipped.
|
||||
#[must_use]
|
||||
pub fn get(&self, slot: &EquipmentSlot) -> Arc<Mutex<ItemStack>> {
|
||||
pub fn get(&self, slot: &EquipmentSlot) -> ItemStack {
|
||||
self.equipment
|
||||
.get(slot)
|
||||
.cloned()
|
||||
.unwrap_or(Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||
.unwrap_or_else(|| ItemStack::EMPTY.clone())
|
||||
}
|
||||
|
||||
/// Checks if all equipment slots are empty.
|
||||
pub async fn is_empty(&self) -> bool {
|
||||
for stack in self.equipment.values() {
|
||||
if !stack.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.equipment.values().all(ItemStack::is_empty)
|
||||
}
|
||||
|
||||
/// Clears all equipped items.
|
||||
|
||||
@ -179,10 +179,7 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
|
||||
return stack_left;
|
||||
}
|
||||
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
|
||||
// Acquire the lock to read/clone the stack
|
||||
let mut stack = slot_stack_lock.lock().await;
|
||||
let mut stack = slot.get_stack().await;
|
||||
stack_left = stack.clone();
|
||||
|
||||
let success = if slot_index < 3 {
|
||||
@ -205,10 +202,9 @@ impl ScreenHandler for FurnaceLikeScreenHandler {
|
||||
}
|
||||
|
||||
if stack.is_empty() {
|
||||
drop(stack); // Release lock before awaiting
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(stack).await;
|
||||
}
|
||||
|
||||
// Award XP when taking from output slot (slot 2)
|
||||
|
||||
@ -218,20 +218,14 @@ impl ScreenHandler for GenericContainerScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
// Release the guard before calling insert_item which needs its own lock
|
||||
drop(slot_stack_guard);
|
||||
|
||||
// Re-acquire lock for insert_item (which expects &mut ItemStack)
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
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_mut,
|
||||
&mut slot_stack,
|
||||
(self.rows * 9).into(),
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
@ -241,7 +235,7 @@ impl ScreenHandler for GenericContainerScreenHandler {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
} else if !self
|
||||
.insert_item(&mut slot_stack_mut, 0, (self.rows * 9).into(), false)
|
||||
.insert_item(&mut slot_stack, 0, (self.rows * 9).into(), false)
|
||||
.await
|
||||
{
|
||||
// Move from player area to inventory (start)
|
||||
@ -249,12 +243,10 @@ impl ScreenHandler for GenericContainerScreenHandler {
|
||||
}
|
||||
|
||||
// Check the resulting state of the slot stack after insert_item
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut); // Release lock
|
||||
if slot_stack.is_empty() {
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut); // Release lock
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -140,12 +140,8 @@ impl ScreenHandler for GUIScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().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_index < container_slots {
|
||||
@ -155,7 +151,7 @@ impl ScreenHandler for GUIScreenHandler {
|
||||
}
|
||||
if !self
|
||||
.insert_item(
|
||||
&mut slot_stack_mut,
|
||||
&mut slot_stack,
|
||||
container_slots,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
@ -170,7 +166,7 @@ impl ScreenHandler for GUIScreenHandler {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
if !self
|
||||
.insert_item(&mut slot_stack_mut, 0, container_slots, false)
|
||||
.insert_item(&mut slot_stack, 0, container_slots, false)
|
||||
.await
|
||||
{
|
||||
// Move from player area to inventory (start)
|
||||
@ -178,12 +174,10 @@ impl ScreenHandler for GUIScreenHandler {
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
if slot_stack.is_empty() {
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -63,9 +63,7 @@ impl MerchantScreenHandler {
|
||||
|
||||
let offer = &self.offers[self.selected_offer];
|
||||
let input_a = self.inventory.get_stack(0).await;
|
||||
let input_a = input_a.lock().await;
|
||||
let input_b = self.inventory.get_stack(1).await;
|
||||
let input_b = input_b.lock().await;
|
||||
|
||||
let match_a = input_a.are_items_and_components_equal(&offer.base_cost_a.0)
|
||||
&& input_a.item_count >= offer.base_cost_a.0.item_count;
|
||||
@ -130,18 +128,14 @@ impl ScreenHandler for MerchantScreenHandler {
|
||||
let slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let slot_stack_guard = slot_stack_lock.lock().await;
|
||||
stack_left = slot_stack_guard.clone();
|
||||
drop(slot_stack_guard);
|
||||
|
||||
let mut slot_stack_mut = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
stack_left = slot_stack.clone();
|
||||
|
||||
if slot_index < 3 {
|
||||
// From merchant slots to player inventory
|
||||
if !self
|
||||
.insert_item(
|
||||
&mut slot_stack_mut,
|
||||
&mut slot_stack,
|
||||
3,
|
||||
self.get_behaviour().slots.len() as i32,
|
||||
true,
|
||||
@ -151,18 +145,16 @@ impl ScreenHandler for MerchantScreenHandler {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
} else {
|
||||
// From player inventory to merchant inputs (0 and 1)
|
||||
if !self.insert_item(&mut slot_stack_mut, 0, 2, false).await {
|
||||
// From player inventory to merchant
|
||||
if !self.insert_item(&mut slot_stack, 0, 2, false).await {
|
||||
return ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
|
||||
if slot_stack_mut.is_empty() {
|
||||
drop(slot_stack_mut);
|
||||
if slot_stack.is_empty() {
|
||||
slot.set_stack(ItemStack::EMPTY.clone()).await;
|
||||
} else {
|
||||
drop(slot_stack_mut);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -192,24 +184,20 @@ impl ScreenHandler for MerchantScreenHandler {
|
||||
(offer.base_cost_a.0.item_count, count_b, offer.xp)
|
||||
};
|
||||
|
||||
let input_a = self.inventory.get_stack(0).await;
|
||||
let mut input_a = input_a.lock().await;
|
||||
let mut input_a = self.inventory.get_stack(0).await;
|
||||
input_a.decrement(count_a);
|
||||
if input_a.is_empty() {
|
||||
*input_a = ItemStack::EMPTY.clone();
|
||||
input_a = ItemStack::EMPTY.clone();
|
||||
}
|
||||
drop(input_a);
|
||||
self.get_behaviour().slots[0].mark_dirty().await;
|
||||
self.inventory.set_stack(0, input_a).await;
|
||||
|
||||
if let Some(count_b) = count_b {
|
||||
let input_b = self.inventory.get_stack(1).await;
|
||||
let mut input_b = input_b.lock().await;
|
||||
let mut input_b = self.inventory.get_stack(1).await;
|
||||
input_b.decrement(count_b);
|
||||
if input_b.is_empty() {
|
||||
*input_b = ItemStack::EMPTY.clone();
|
||||
input_b = ItemStack::EMPTY.clone();
|
||||
}
|
||||
drop(input_b);
|
||||
self.get_behaviour().slots[1].mark_dirty().await;
|
||||
self.inventory.set_stack(1, input_b).await;
|
||||
}
|
||||
|
||||
// Award XP
|
||||
|
||||
@ -10,14 +10,14 @@
|
||||
//! Ender chests track when players open and close them to properly
|
||||
//! manage the viewer count for animation purposes.
|
||||
|
||||
use std::{any::Any, array::from_fn, pin::Pin, sync::Arc};
|
||||
use std::{any::Any, pin::Pin, sync::Arc};
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::{
|
||||
block::viewer::ViewerCountTracker,
|
||||
inventory::{Clearable, Inventory, InventoryFuture, split_stack},
|
||||
inventory::{Clearable, Inventory, InventoryFuture},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
|
||||
/// A player's ender chest inventory.
|
||||
///
|
||||
@ -26,7 +26,7 @@ use tokio::sync::Mutex;
|
||||
/// ender chest block.
|
||||
pub struct EnderChestInventory {
|
||||
/// The 27 item slots in the ender chest.
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
/// Viewer count tracker for lid animation.
|
||||
///
|
||||
/// Tracks how many players have the ender chest open to animate the lid.
|
||||
@ -47,7 +47,7 @@ impl EnderChestInventory {
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
tracker: Mutex::new(None),
|
||||
}
|
||||
}
|
||||
@ -75,41 +75,45 @@ impl EnderChestInventory {
|
||||
|
||||
impl Inventory for EnderChestInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
let mut items = self.items.write().await;
|
||||
std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone())
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move { split_stack(&self.items, slot, amount).await })
|
||||
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 set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
})
|
||||
}
|
||||
|
||||
@ -139,9 +143,8 @@ impl Inventory for EnderChestInventory {
|
||||
impl Clearable for EnderChestInventory {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for item in &self.items {
|
||||
*item.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,18 +11,17 @@ use crate::entity_equipment::EntityEquipment;
|
||||
use crate::screen_handler::InventoryPlayer;
|
||||
|
||||
use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
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::split_stack;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use std::any::Any;
|
||||
use std::array::from_fn;
|
||||
use std::collections::HashMap;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::{Mutex, RwLock};
|
||||
use tracing::warn;
|
||||
|
||||
/// The player's inventory.
|
||||
@ -33,13 +32,13 @@ pub struct PlayerInventory {
|
||||
/// The 36 main inventory slots (slots 0-35).
|
||||
///
|
||||
/// The first 9 slots (0-8) are the hotbar, the remaining 27 (9-35) are the main storage.
|
||||
pub main_inventory: [Arc<Mutex<ItemStack>>; Self::MAIN_SIZE],
|
||||
pub main_inventory: RwLock<[ItemStack; Self::MAIN_SIZE]>,
|
||||
/// Mapping of slot indices to equipment slot types.
|
||||
///
|
||||
/// Used to identify which slots correspond to armor and off-hand equipment.
|
||||
pub equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
||||
/// The currently selected hotbar slot index (0-8).
|
||||
selected_slot: AtomicU8,
|
||||
pub selected_slot: AtomicU8,
|
||||
/// The entity equipment storage for armor and off-hand items.
|
||||
///
|
||||
/// This is separate from the main inventory and is rendered on the player model.
|
||||
@ -65,58 +64,79 @@ impl PlayerInventory {
|
||||
equipment_slots: Arc<HashMap<usize, EquipmentSlot>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
// Normal syntax can't be used here because Arc doesn't implement Copy
|
||||
main_inventory: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
main_inventory: RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
equipment_slots,
|
||||
selected_slot: AtomicU8::new(0),
|
||||
entity_equipment,
|
||||
}
|
||||
}
|
||||
|
||||
/// Fast non-blocking count of an item across all main inventory slots.
|
||||
pub fn count_item(&self, item: &'static Item) -> u32 {
|
||||
let mut total = 0u32;
|
||||
if let Ok(inv) = self.main_inventory.try_read() {
|
||||
for stack in inv.iter() {
|
||||
if stack.get_item().id == item.id {
|
||||
total += u32::from(stack.item_count);
|
||||
}
|
||||
}
|
||||
}
|
||||
total
|
||||
}
|
||||
|
||||
/// Fast non-blocking check if the main inventory contains a given item.
|
||||
pub fn contains_item(&self, item: &'static Item) -> bool {
|
||||
if let Ok(inv) = self.main_inventory.try_read() {
|
||||
for stack in inv.iter() {
|
||||
if !stack.is_empty() && stack.get_item().id == item.id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
false
|
||||
}
|
||||
|
||||
/// Gets the item in the currently selected hotbar slot.
|
||||
///
|
||||
/// This is the item the player is currently holding in their main hand.
|
||||
///
|
||||
/// Mojang name: `getSelectedStack`
|
||||
pub fn held_item(&self) -> Arc<Mutex<ItemStack>> {
|
||||
self.main_inventory
|
||||
.get(self.get_selected_slot() as usize)
|
||||
.unwrap()
|
||||
.clone()
|
||||
pub async fn held_item(&self) -> ItemStack {
|
||||
let inv = self.main_inventory.read().await;
|
||||
inv[self.get_selected_slot() as usize].clone()
|
||||
}
|
||||
|
||||
/// Sets the item in the currently selected hotbar slot.
|
||||
pub async fn set_held_item(&self, stack: ItemStack) {
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
inv[selected] = stack;
|
||||
}
|
||||
|
||||
/// Sets the item in the specified hand.
|
||||
pub async fn set_stack_in_hand(&self, hand: Hand, stack: ItemStack) {
|
||||
match hand {
|
||||
Hand::Right => self.set_held_item(stack).await,
|
||||
Hand::Left => {
|
||||
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
|
||||
self.entity_equipment.lock().await.put(slot, stack);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the item in the specified hand.
|
||||
///
|
||||
/// # Arguments
|
||||
/// - `hand` - Which hand to get the item from
|
||||
pub async fn get_stack_in_hand(&self, hand: Hand) -> Arc<Mutex<ItemStack>> {
|
||||
pub async fn get_stack_in_hand(&self, hand: Hand) -> ItemStack {
|
||||
match hand {
|
||||
Hand::Left => self.off_hand_item().await,
|
||||
Hand::Right => self.held_item(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks if the given item Arc already points to the target equipment slot's stack.
|
||||
///
|
||||
/// Use before attempting to re-equip an item to prevent self-deadlock:
|
||||
/// Tokio's `Mutex` is not reentrant, so locking an `Arc<Mutex<T>>` that
|
||||
/// you already hold will deadlock forever.
|
||||
pub async fn is_already_equipped(
|
||||
&self,
|
||||
item_arc: &Arc<Mutex<ItemStack>>,
|
||||
slot: &EquipmentSlot,
|
||||
) -> bool {
|
||||
match slot {
|
||||
EquipmentSlot::OffHand(_) => Arc::ptr_eq(item_arc, &self.off_hand_item().await),
|
||||
EquipmentSlot::MainHand(_) => Arc::ptr_eq(item_arc, &self.held_item()),
|
||||
_ => false,
|
||||
Hand::Right => self.held_item().await,
|
||||
}
|
||||
}
|
||||
|
||||
/// Gets the item in the off-hand.
|
||||
///
|
||||
/// Mojang name: `getOffHandStack`
|
||||
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> {
|
||||
pub async fn off_hand_item(&self) -> ItemStack {
|
||||
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
|
||||
self.entity_equipment.lock().await.get(slot)
|
||||
}
|
||||
@ -128,11 +148,12 @@ impl PlayerInventory {
|
||||
pub async fn swap_item(&self) -> (ItemStack, ItemStack) {
|
||||
let slot = self.equipment_slots.get(&Self::OFF_HAND_SLOT).unwrap();
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
let binding = self.held_item();
|
||||
let mut main_hand_item = binding.lock().await;
|
||||
let off_hand_item = main_hand_item.clone();
|
||||
*main_hand_item = equipment.put(slot, off_hand_item.clone()).await;
|
||||
(main_hand_item.clone(), off_hand_item)
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut main_inv = self.main_inventory.write().await;
|
||||
let main_hand_item = main_inv[selected].clone();
|
||||
let new_main = equipment.put(slot, main_hand_item.clone());
|
||||
main_inv[selected] = new_main.clone();
|
||||
(new_main, main_hand_item)
|
||||
}
|
||||
|
||||
/// Checks if a slot index is a valid hotbar slot.
|
||||
@ -160,13 +181,24 @@ impl PlayerInventory {
|
||||
///
|
||||
/// Returns the number of items that couldn't fit.
|
||||
async fn add_stack_to_slot(&self, slot: usize, stack: ItemStack) -> usize {
|
||||
if slot >= Self::MAIN_SIZE {
|
||||
if let Some(slot_type) = self.equipment_slots.get(&slot) {
|
||||
let mut equipment = self.entity_equipment.lock().await;
|
||||
let current = equipment.get(slot_type);
|
||||
if current.is_empty() {
|
||||
equipment.put(slot_type, stack);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return stack.item_count as usize;
|
||||
}
|
||||
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
let mut stack_count = stack.item_count;
|
||||
let binding = self.get_stack(slot).await;
|
||||
let mut self_stack = binding.lock().await;
|
||||
let self_stack = &mut inv[slot];
|
||||
|
||||
if self_stack.is_empty() {
|
||||
*self_stack = stack.copy_with_count(0);
|
||||
//self.set_stack(slot, self_stack).await;
|
||||
}
|
||||
|
||||
let count_left = self_stack.get_max_stack_size() - self_stack.item_count;
|
||||
@ -184,12 +216,12 @@ impl PlayerInventory {
|
||||
/// # Returns
|
||||
/// The slot index or -1 if inventory is full.
|
||||
async fn get_empty_slot(&self) -> i16 {
|
||||
for i in 0..Self::MAIN_SIZE {
|
||||
if self.main_inventory[i].lock().await.is_empty() {
|
||||
let inv = self.main_inventory.read().await;
|
||||
for (i, stack) in inv.iter().enumerate() {
|
||||
if stack.is_empty() {
|
||||
return i as i16;
|
||||
}
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
|
||||
@ -205,29 +237,24 @@ impl PlayerInventory {
|
||||
///
|
||||
/// Checks selected slot, off-hand, then other slots.
|
||||
async fn get_occupied_slot_with_room_for_stack(&self, stack: &ItemStack) -> i16 {
|
||||
if Self::can_stack_add_more(
|
||||
&*self
|
||||
.get_stack(self.get_selected_slot() as usize)
|
||||
.await
|
||||
.lock()
|
||||
.await,
|
||||
stack,
|
||||
) {
|
||||
i16::from(self.get_selected_slot())
|
||||
} else if Self::can_stack_add_more(
|
||||
&*self.get_stack(Self::OFF_HAND_SLOT).await.lock().await,
|
||||
stack,
|
||||
) {
|
||||
Self::OFF_HAND_SLOT as i16
|
||||
} else {
|
||||
for i in 0..Self::MAIN_SIZE {
|
||||
if Self::can_stack_add_more(&*self.main_inventory[i].lock().await, stack) {
|
||||
return i as i16;
|
||||
}
|
||||
}
|
||||
|
||||
-1
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let inv = self.main_inventory.read().await;
|
||||
if Self::can_stack_add_more(&inv[selected], stack) {
|
||||
return selected as i16;
|
||||
}
|
||||
|
||||
let off_hand = self.off_hand_item().await;
|
||||
if Self::can_stack_add_more(&off_hand, stack) {
|
||||
return Self::OFF_HAND_SLOT as i16;
|
||||
}
|
||||
|
||||
for (i, item) in inv.iter().enumerate() {
|
||||
if Self::can_stack_add_more(item, stack) {
|
||||
return i as i16;
|
||||
}
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
|
||||
/// Inserts a stack into any available slot.
|
||||
@ -254,8 +281,6 @@ impl PlayerInventory {
|
||||
return false;
|
||||
}
|
||||
|
||||
// TODO: if (stack.isDamaged()) {
|
||||
|
||||
let mut i;
|
||||
|
||||
loop {
|
||||
@ -271,8 +296,6 @@ impl PlayerInventory {
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: Creative mode check
|
||||
|
||||
stack.item_count < i
|
||||
}
|
||||
|
||||
@ -281,17 +304,12 @@ impl PlayerInventory {
|
||||
/// # Returns
|
||||
/// The slot index or -1 if not found.
|
||||
pub async fn get_slot_with_stack(&self, stack: &ItemStack) -> i16 {
|
||||
for i in 0..Self::MAIN_SIZE {
|
||||
if !self.main_inventory[i].lock().await.is_empty()
|
||||
&& self.main_inventory[i]
|
||||
.lock()
|
||||
.await
|
||||
.are_items_and_components_equal(stack)
|
||||
{
|
||||
let inv = self.main_inventory.read().await;
|
||||
for (i, item) in inv.iter().enumerate() {
|
||||
if !item.is_empty() && item.are_items_and_components_equal(stack) {
|
||||
return i as i16;
|
||||
}
|
||||
}
|
||||
|
||||
-1
|
||||
}
|
||||
|
||||
@ -299,63 +317,43 @@ impl PlayerInventory {
|
||||
///
|
||||
/// First looks for empty slots, then slots without enchantments.
|
||||
async fn get_swappable_hotbar_slot(&self) -> usize {
|
||||
let inv = self.main_inventory.read().await;
|
||||
let selected_slot = self.get_selected_slot() as usize;
|
||||
for i in 0..Self::HOTBAR_SIZE {
|
||||
let check_index = (i + selected_slot) % 9;
|
||||
if self.main_inventory[check_index].lock().await.is_empty() {
|
||||
if inv[check_index].is_empty() {
|
||||
return check_index;
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(i) = (0..Self::HOTBAR_SIZE).next() {
|
||||
let check_index = (i + selected_slot) % 9;
|
||||
return check_index;
|
||||
}
|
||||
|
||||
self.get_selected_slot() as usize
|
||||
selected_slot
|
||||
}
|
||||
|
||||
/// Swaps an item stack with an item on the hotbar.
|
||||
///
|
||||
/// Finds an empty hotbar slot and places the stack there.
|
||||
pub async fn swap_stack_with_hotbar(&self, stack: ItemStack) {
|
||||
self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8);
|
||||
let swappable = self.get_swappable_hotbar_slot().await;
|
||||
self.set_selected_slot(swappable as u8);
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
|
||||
if !self.main_inventory[self.get_selected_slot() as usize]
|
||||
.lock()
|
||||
.await
|
||||
.is_empty()
|
||||
if let Some(empty_slot) = inv.iter().position(ItemStack::is_empty)
|
||||
&& !inv[selected].is_empty()
|
||||
{
|
||||
let empty_slot = self.get_empty_slot().await;
|
||||
if empty_slot != -1 {
|
||||
self.set_stack(
|
||||
empty_slot as usize,
|
||||
self.main_inventory[self.get_selected_slot() as usize]
|
||||
.lock()
|
||||
.await
|
||||
.clone(),
|
||||
)
|
||||
.await;
|
||||
}
|
||||
inv[empty_slot] = inv[selected].clone();
|
||||
}
|
||||
|
||||
self.set_stack(self.get_selected_slot() as usize, stack)
|
||||
.await;
|
||||
inv[selected] = stack;
|
||||
}
|
||||
|
||||
/// Swaps the items at two slot indices.
|
||||
pub async fn swap_slot_with_hotbar(&self, slot: usize) {
|
||||
self.set_selected_slot(self.get_swappable_hotbar_slot().await as u8);
|
||||
let stack = self.main_inventory[self.get_selected_slot() as usize]
|
||||
.lock()
|
||||
.await
|
||||
.clone();
|
||||
self.set_stack(
|
||||
self.get_selected_slot() as usize,
|
||||
self.main_inventory[slot].lock().await.clone(),
|
||||
)
|
||||
.await;
|
||||
self.set_stack(slot, stack).await;
|
||||
let swappable = self.get_swappable_hotbar_slot().await;
|
||||
self.set_selected_slot(swappable as u8);
|
||||
let selected = self.get_selected_slot() as usize;
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
inv.swap(selected, slot);
|
||||
}
|
||||
|
||||
/// Gives a stack to the player or drops it if inventory is full.
|
||||
@ -383,12 +381,7 @@ impl PlayerInventory {
|
||||
}
|
||||
|
||||
let items_fit = stack.get_max_stack_size()
|
||||
- self
|
||||
.get_stack(room_for_stack as usize)
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.item_count;
|
||||
- self.get_stack(room_for_stack as usize).await.item_count;
|
||||
if self
|
||||
.insert_stack(room_for_stack, &mut stack.split(items_fit))
|
||||
.await
|
||||
@ -397,13 +390,7 @@ impl PlayerInventory {
|
||||
player
|
||||
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
|
||||
i32::from(room_for_stack).into(),
|
||||
&self
|
||||
.get_stack(room_for_stack as usize)
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.clone()
|
||||
.into(),
|
||||
&self.get_stack(room_for_stack as usize).await.into(),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
@ -414,10 +401,8 @@ impl PlayerInventory {
|
||||
impl Clearable for PlayerInventory {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for item in &self.main_inventory {
|
||||
*item.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
inv.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.entity_equipment.lock().await.clear();
|
||||
})
|
||||
}
|
||||
@ -425,27 +410,19 @@ impl Clearable for PlayerInventory {
|
||||
|
||||
impl Inventory for PlayerInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.main_inventory.len() + self.equipment_slots.len()
|
||||
Self::MAIN_SIZE + self.equipment_slots.len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for item in &self.main_inventory {
|
||||
if !item.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
let inv = self.main_inventory.read().await;
|
||||
if inv.iter().any(|s| !s.is_empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
for slot in self.equipment_slots.values() {
|
||||
if !self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.get(slot)
|
||||
.lock()
|
||||
.await
|
||||
.is_empty()
|
||||
{
|
||||
let eq_item = self.entity_equipment.lock().await.get(slot);
|
||||
if !eq_item.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -454,49 +431,56 @@ impl Inventory for PlayerInventory {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
self.main_inventory[slot].clone()
|
||||
} else {
|
||||
let slot = self.equipment_slots.get(&slot).unwrap();
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let inv = self.main_inventory.read().await;
|
||||
inv[slot].clone()
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment.lock().await.get(slot)
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.main_inventory[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
removed
|
||||
} else {
|
||||
let slot = self.equipment_slots.get(&slot).unwrap();
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
std::mem::replace(&mut inv[slot], ItemStack::EMPTY.clone())
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.put(slot, ItemStack::EMPTY.clone())
|
||||
.await
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
split_stack(&self.main_inventory, slot, amount).await
|
||||
} else {
|
||||
let slot = self.equipment_slots.get(&slot).unwrap();
|
||||
|
||||
let equipment = self.entity_equipment.lock().await.get(slot);
|
||||
let mut stack = equipment.lock().await;
|
||||
|
||||
if !stack.is_empty() {
|
||||
return stack.split(amount);
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
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().await;
|
||||
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()
|
||||
}
|
||||
})
|
||||
@ -504,10 +488,11 @@ impl Inventory for PlayerInventory {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
if slot < self.main_inventory.len() {
|
||||
*self.main_inventory[slot].lock().await = stack;
|
||||
if slot < Self::MAIN_SIZE {
|
||||
let mut inv = self.main_inventory.write().await;
|
||||
inv[slot] = stack;
|
||||
} else if let Some(slot) = self.equipment_slots.get(&slot) {
|
||||
self.entity_equipment.lock().await.put(slot, stack).await;
|
||||
self.entity_equipment.lock().await.put(slot, stack);
|
||||
} else {
|
||||
warn!("Failed to get Equipment Slot at {slot}");
|
||||
}
|
||||
|
||||
@ -164,8 +164,7 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
// TODO: Equippable component
|
||||
|
||||
if slot.has_stack().await {
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let mut slot_stack = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
let stack_prev = slot_stack.clone();
|
||||
|
||||
let equipment_slot = slot_stack
|
||||
@ -232,13 +231,12 @@ impl ScreenHandler for PlayerScreenHandler {
|
||||
}
|
||||
|
||||
let stack = slot_stack.clone();
|
||||
drop(slot_stack); // release the lock before calling other methods
|
||||
|
||||
if stack.is_empty() {
|
||||
slot.set_stack_prev(ItemStack::EMPTY.clone(), stack_prev.clone())
|
||||
.await;
|
||||
} else {
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(stack.clone()).await;
|
||||
}
|
||||
|
||||
if stack.item_count == stack_prev.item_count {
|
||||
|
||||
@ -725,8 +725,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
})
|
||||
{
|
||||
let slot = self.get_behaviour().slots[current_index as usize].clone();
|
||||
let slot_stack_lock = slot.get_stack().await;
|
||||
let mut slot_stack = slot_stack_lock.lock().await;
|
||||
let mut slot_stack = slot.get_stack().await;
|
||||
|
||||
if !slot_stack.is_empty() && slot_stack.are_items_and_components_equal(stack) {
|
||||
let combined_count = slot_stack.item_count + stack.item_count;
|
||||
@ -734,14 +733,12 @@ pub trait ScreenHandler: Send + Sync {
|
||||
if combined_count <= max_slot_count {
|
||||
stack.set_count(0);
|
||||
slot_stack.set_count(combined_count);
|
||||
drop(slot_stack);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
success = true;
|
||||
} else if slot_stack.item_count < max_slot_count {
|
||||
stack.decrement(max_slot_count - slot_stack.item_count);
|
||||
slot_stack.set_count(max_slot_count);
|
||||
drop(slot_stack);
|
||||
slot.mark_dirty().await;
|
||||
slot.set_stack(slot_stack).await;
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
@ -768,11 +765,9 @@ pub trait ScreenHandler: Send + Sync {
|
||||
} {
|
||||
let slot = self.get_behaviour().slots[current_index as usize].clone();
|
||||
let slot_stack = slot.get_stack().await;
|
||||
let slot_stack = slot_stack.lock().await;
|
||||
|
||||
if slot_stack.is_empty() && slot.can_insert(stack).await {
|
||||
let max_count = slot.get_max_item_count_for_stack(stack).await;
|
||||
drop(slot_stack);
|
||||
slot.set_stack(stack.split(max_count.min(stack.item_count)))
|
||||
.await;
|
||||
slot.mark_dirty().await;
|
||||
@ -885,8 +880,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
let cursor_stack = behaviour.cursor_stack.lock().await;
|
||||
|
||||
let slot = &behaviour.slots[slot_index as usize];
|
||||
let stack_lock = slot.get_stack().await;
|
||||
let stack = stack_lock.lock().await;
|
||||
let stack = slot.get_stack().await;
|
||||
if !cursor_stack.is_empty()
|
||||
&& slot.can_insert(&cursor_stack).await
|
||||
&& (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
|
||||
@ -918,8 +912,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
let initial_count = cursor_stack.item_count;
|
||||
for slot_index in &behaviour.drag_slots {
|
||||
let slot = behaviour.slots[*slot_index as usize].clone();
|
||||
let stack_lock = slot.get_stack().await;
|
||||
let stack = stack_lock.lock().await;
|
||||
let stack = slot.get_stack().await;
|
||||
|
||||
if (stack.are_items_and_components_equal(&cursor_stack) || stack.is_empty())
|
||||
&& slot.can_insert(&cursor_stack).await
|
||||
@ -942,13 +935,12 @@ pub trait ScreenHandler: Send + Sync {
|
||||
))
|
||||
.min(cursor_stack.item_count);
|
||||
if inserting_count > 0 {
|
||||
let mut stack_clone = stack.clone();
|
||||
drop(stack);
|
||||
if stack_clone.is_empty() {
|
||||
stack_clone = cursor_stack.copy_with_count(0);
|
||||
let mut new_stack = stack.clone();
|
||||
if new_stack.is_empty() {
|
||||
new_stack = cursor_stack.copy_with_count(0);
|
||||
}
|
||||
stack_clone.increment(inserting_count);
|
||||
slot.set_stack(stack_clone).await;
|
||||
new_stack.increment(inserting_count);
|
||||
slot.set_stack(new_stack).await;
|
||||
if drag_button != 2 {
|
||||
cursor_stack.decrement(inserting_count);
|
||||
}
|
||||
@ -999,8 +991,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
return;
|
||||
}
|
||||
let slot = behaviour.slots[slot_index as usize].clone();
|
||||
let stack_lock = slot.get_stack().await;
|
||||
let stack = stack_lock.lock().await;
|
||||
let stack = slot.get_stack().await;
|
||||
*cursor_stack = stack.copy_with_count(stack.get_max_stack_size());
|
||||
}
|
||||
} else if (action_type == SlotActionType::Pickup
|
||||
@ -1064,32 +1055,32 @@ pub trait ScreenHandler: Send + Sync {
|
||||
let mut intercepted = false;
|
||||
|
||||
if !cursor_stack.is_empty() {
|
||||
let stack_guard = slot.get_stack().await;
|
||||
let mut inner_slot_stack = stack_guard.lock().await;
|
||||
let mut inner_slot_stack = slot.get_stack().await;
|
||||
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
|
||||
&& bundle.try_insert(&mut cursor_stack) {
|
||||
slot.set_stack(inner_slot_stack).await;
|
||||
intercepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !intercepted && !slot_stack.is_empty()
|
||||
&& let Some(bundle) = cursor_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>() {
|
||||
let stack_guard = slot.get_stack().await;
|
||||
let mut inner_slot_stack = stack_guard.lock().await;
|
||||
let mut inner_slot_stack = slot.get_stack().await;
|
||||
if bundle.try_insert(&mut inner_slot_stack) {
|
||||
if inner_slot_stack.item_count == 0 {
|
||||
*inner_slot_stack = ItemStack::EMPTY.clone();
|
||||
inner_slot_stack = ItemStack::EMPTY.clone();
|
||||
}
|
||||
slot.set_stack(inner_slot_stack).await;
|
||||
intercepted = true;
|
||||
}
|
||||
}
|
||||
|
||||
if !intercepted && cursor_stack.is_empty() {
|
||||
let stack_guard = slot.get_stack().await;
|
||||
let mut inner_slot_stack = stack_guard.lock().await;
|
||||
let mut inner_slot_stack = slot.get_stack().await;
|
||||
if let Some(bundle) = inner_slot_stack.get_data_component_mut::<pumpkin_data::data_component_impl::BundleContentsImpl>()
|
||||
&& let Some(extracted) = bundle.try_extract() {
|
||||
*cursor_stack = extracted;
|
||||
slot.set_stack(inner_slot_stack).await;
|
||||
intercepted = true;
|
||||
}
|
||||
}
|
||||
@ -1226,13 +1217,7 @@ pub trait ScreenHandler: Send + Sync {
|
||||
if slot_index < 0 {
|
||||
return;
|
||||
}
|
||||
let mut button_stack = player
|
||||
.get_inventory()
|
||||
.get_stack(button as usize)
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.clone();
|
||||
let mut button_stack = player.get_inventory().get_stack(button as usize).await;
|
||||
let source_slot = self.get_behaviour().slots[slot_index as usize].clone();
|
||||
let source_stack = source_slot.get_cloned_stack().await;
|
||||
|
||||
|
||||
@ -24,7 +24,6 @@ use std::{
|
||||
Arc,
|
||||
atomic::{AtomicU8, Ordering},
|
||||
},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use crate::screen_handler::InventoryPlayer;
|
||||
@ -33,7 +32,6 @@ use pumpkin_data::data_component_impl::EquipmentSlot;
|
||||
use pumpkin_data::item::Item;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use tokio::{sync::Mutex, time::timeout};
|
||||
|
||||
/// Type alias for async slot operations.
|
||||
pub type BoxFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
@ -101,37 +99,21 @@ pub trait Slot: Send + Sync {
|
||||
}
|
||||
|
||||
/// Gets the stack in this slot.
|
||||
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move { self.get_inventory().get_stack(self.get_index()).await })
|
||||
}
|
||||
|
||||
/// Gets a copy of the stack in this slot.
|
||||
///
|
||||
/// Acquires a lock and returns a clone of the stack.
|
||||
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
let stack = self.get_stack().await;
|
||||
let lock = timeout(Duration::from_secs(5), stack.lock())
|
||||
.await
|
||||
.expect("Timed out while trying to acquire lock");
|
||||
|
||||
lock.clone()
|
||||
})
|
||||
Box::pin(async move { self.get_stack().await })
|
||||
}
|
||||
|
||||
/// Checks if this slot has a non-empty stack.
|
||||
fn has_stack(&self) -> BoxFuture<'_, bool> {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
let inv = self.get_inventory();
|
||||
!inv.get_stack(self.get_index())
|
||||
.await
|
||||
.lock()
|
||||
.await
|
||||
.is_empty()
|
||||
})
|
||||
Box::pin(async move { !self.get_stack().await.is_empty() })
|
||||
}
|
||||
|
||||
/// Sets the stack in this slot.
|
||||
@ -295,22 +277,18 @@ pub trait Slot: Send + Sync {
|
||||
// Default implementation logic:
|
||||
Box::pin(async move {
|
||||
if !stack.is_empty() && self.can_insert(&stack).await {
|
||||
let stack_mutex = self.get_stack().await;
|
||||
let mut stack_self = stack_mutex.lock().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);
|
||||
|
||||
if min_count != 0 {
|
||||
if stack_self.is_empty() {
|
||||
drop(stack_self);
|
||||
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);
|
||||
let cloned_stack = stack_self.clone();
|
||||
drop(stack_self);
|
||||
self.set_stack(cloned_stack).await;
|
||||
self.set_stack(stack_self).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::{AtomicU8, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::player::player_inventory::PlayerInventory;
|
||||
use crate::screen_handler::{
|
||||
@ -56,8 +55,7 @@ impl StonecutterScreenHandler {
|
||||
}
|
||||
|
||||
async fn update_output(&self) {
|
||||
let input_stack = self.input_inventory.get_stack(0).await;
|
||||
let input_lock = input_stack.lock().await;
|
||||
let input_lock = self.input_inventory.get_stack(0).await;
|
||||
|
||||
if input_lock.is_empty() {
|
||||
self.output_inventory
|
||||
@ -213,14 +211,7 @@ impl Slot for StonecutterOutputSlot {
|
||||
stack.item_count as i32,
|
||||
)
|
||||
.await;
|
||||
let input_stack = self.input_inventory.get_stack(0).await;
|
||||
let mut input_lock = input_stack.lock().await;
|
||||
if !input_lock.is_empty() {
|
||||
input_lock.item_count -= 1;
|
||||
if input_lock.item_count == 0 {
|
||||
*input_lock = ItemStack::EMPTY.clone();
|
||||
}
|
||||
}
|
||||
self.input_inventory.remove_stack_specific(0, 1).await;
|
||||
self.mark_dirty().await;
|
||||
})
|
||||
}
|
||||
@ -229,22 +220,16 @@ impl Slot for StonecutterOutputSlot {
|
||||
Box::pin(async move { false })
|
||||
}
|
||||
|
||||
fn get_stack(&self) -> BoxFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
fn get_stack(&self) -> BoxFuture<'_, ItemStack> {
|
||||
Box::pin(async move { self.inventory.get_stack(self.index).await })
|
||||
}
|
||||
|
||||
fn get_cloned_stack(&self) -> BoxFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let stack = self.inventory.get_stack(self.index).await;
|
||||
stack.lock().await.clone()
|
||||
})
|
||||
Box::pin(async move { self.inventory.get_stack(self.index).await })
|
||||
}
|
||||
|
||||
fn has_stack(&self) -> BoxFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
let stack = self.inventory.get_stack(self.index).await;
|
||||
!stack.lock().await.is_empty()
|
||||
})
|
||||
Box::pin(async move { !self.inventory.get_stack(self.index).await.is_empty() })
|
||||
}
|
||||
|
||||
fn set_stack(&self, stack: ItemStack) -> BoxFuture<'_, ()> {
|
||||
|
||||
@ -14,3 +14,6 @@ serde_json = { workspace = true }
|
||||
|
||||
[package.metadata.component]
|
||||
target = { path = "../pumpkin-plugin-wit" }
|
||||
|
||||
[lints]
|
||||
workspace = true
|
||||
|
||||
@ -1,5 +1,14 @@
|
||||
//! Pumpkin plugin API.
|
||||
#![warn(missing_docs)]
|
||||
#![allow(
|
||||
clippy::undocumented_unsafe_blocks,
|
||||
clippy::option_if_let_else,
|
||||
clippy::collection_is_never_read,
|
||||
clippy::all,
|
||||
clippy::pedantic,
|
||||
clippy::nursery,
|
||||
clippy::cargo
|
||||
)]
|
||||
//!
|
||||
//! This crate provides everything needed to write a Pumpkin server plugin compiled
|
||||
//! to WebAssembly. A plugin consists of a type that implements [`Plugin`], registered
|
||||
|
||||
@ -16,16 +16,15 @@ impl<'a> DialogNBT<'a> {
|
||||
Self(DialogNBTSource::Nbt(compound))
|
||||
}
|
||||
|
||||
#[allow(clippy::todo)]
|
||||
pub fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl std::io::Write,
|
||||
_version: &JavaMinecraftVersion,
|
||||
) -> Result<(), crate::ser::WritingError> {
|
||||
match &self.0 {
|
||||
DialogNBTSource::Struct(_dialog) => {
|
||||
todo!("Serialize Dialog to NbtCompound manually")
|
||||
}
|
||||
DialogNBTSource::Struct(_dialog) => Err(crate::ser::WritingError::Message(
|
||||
"Serializing Dialog struct to NBT is not supported".to_string(),
|
||||
)),
|
||||
DialogNBTSource::Nbt(nbt) => {
|
||||
let tag = pumpkin_nbt::tag::NbtTag::Compound((*nbt).clone());
|
||||
write.write_nbt(tag)?;
|
||||
|
||||
@ -41,7 +41,6 @@ impl CUpdateAdvancements {
|
||||
}
|
||||
|
||||
impl ClientPacket for CUpdateAdvancements {
|
||||
#[allow(clippy::unimplemented)]
|
||||
fn write_packet_data(
|
||||
&self,
|
||||
mut write: impl std::io::Write,
|
||||
|
||||
@ -43,7 +43,9 @@ pub fn hash_seed(seed: u64) -> i64 {
|
||||
let mut hasher = Sha256::new();
|
||||
hasher.update(seed.to_le_bytes());
|
||||
let result = hasher.finalize();
|
||||
i64::from_le_bytes(result[..8].try_into().unwrap())
|
||||
let mut bytes = [0u8; 8];
|
||||
bytes.copy_from_slice(&result[..8]);
|
||||
i64::from_le_bytes(bytes)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
@ -619,7 +619,7 @@ impl<S: SingleChunkDataSerializer + 'static> ChunkSerializer for AnvilChunkFile<
|
||||
) -> Result<(), ChunkWritingError> {
|
||||
let epoch = SystemTime::now()
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap()
|
||||
.unwrap_or_default()
|
||||
.as_secs() as u32;
|
||||
|
||||
let index = Self::get_chunk_index(chunk.position().0, chunk.position().1);
|
||||
|
||||
@ -719,7 +719,9 @@ impl ChunkData {
|
||||
let mut has_found = [false, false, false];
|
||||
|
||||
for y in (self.section.min_y..=start_height).rev() {
|
||||
let state_id = self.section.get_block_absolute_y(x, y, z).unwrap();
|
||||
let Some(state_id) = self.section.get_block_absolute_y(x, y, z) else {
|
||||
continue;
|
||||
};
|
||||
let block_state = BlockState::from_id(state_id);
|
||||
|
||||
for hm_type in [
|
||||
|
||||
@ -7,7 +7,6 @@ use std::{
|
||||
hash::{Hash, Hasher},
|
||||
sync::Arc,
|
||||
};
|
||||
use tokio::sync::{Mutex, OwnedMutexGuard};
|
||||
|
||||
pub type InventoryFuture<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
|
||||
|
||||
@ -16,7 +15,7 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool>;
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>>;
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>;
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack>;
|
||||
|
||||
@ -36,8 +35,7 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
let mut count = 0;
|
||||
|
||||
for i in 0..self.size() {
|
||||
let slot = self.get_stack(i).await;
|
||||
let stack = slot.lock().await;
|
||||
let stack = self.get_stack(i).await;
|
||||
if stack.get_item().id == item.id {
|
||||
count += stack.item_count;
|
||||
}
|
||||
@ -49,13 +47,12 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
|
||||
fn contains_any_predicate<'a>(
|
||||
&'a self,
|
||||
predicate: &'a (dyn Fn(OwnedMutexGuard<ItemStack>) -> bool + Sync),
|
||||
predicate: &'a (dyn Fn(&ItemStack) -> bool + Sync),
|
||||
) -> InventoryFuture<'a, bool> {
|
||||
Box::pin(async move {
|
||||
for i in 0..self.size() {
|
||||
let slot = self.get_stack(i).await;
|
||||
let stack = slot.lock_owned().await;
|
||||
if predicate(stack) {
|
||||
let stack = self.get_stack(i).await;
|
||||
if predicate(&stack) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -83,8 +80,7 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
let size = self.size();
|
||||
|
||||
for i in 0..size {
|
||||
let stack_lock = self.get_stack(i).await;
|
||||
let stack = stack_lock.lock().await;
|
||||
let stack = self.get_stack(i).await;
|
||||
|
||||
if !stack.is_empty() {
|
||||
let mut item_compound = NbtCompound::new();
|
||||
@ -106,7 +102,7 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
|
||||
fn mark_dirty(&self) {}
|
||||
|
||||
fn read_data(&self, nbt: &NbtCompound, stacks: &[Arc<Mutex<ItemStack>>]) {
|
||||
fn read_data(&self, nbt: &NbtCompound, stacks: &mut [ItemStack]) {
|
||||
if let Some(inventory_list) = nbt.get_list("Items") {
|
||||
for tag in inventory_list {
|
||||
if let Some(item_compound) = tag.extract_compound()
|
||||
@ -116,7 +112,7 @@ pub trait Inventory: Send + Sync + Clearable {
|
||||
if slot < stacks.len()
|
||||
&& let Some(item_stack) = ItemStack::read_item_stack(item_compound)
|
||||
{
|
||||
*stacks[slot].try_lock().unwrap() = item_stack;
|
||||
stacks[slot] = item_stack;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -143,22 +139,14 @@ pub trait Clearable {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>>;
|
||||
}
|
||||
|
||||
pub fn sync_write_items_to_nbt(items: &[Arc<Mutex<ItemStack>>], nbt: &mut NbtCompound) {
|
||||
pub fn sync_write_items_to_nbt(items: &[ItemStack], nbt: &mut NbtCompound) {
|
||||
let mut slots = Vec::new();
|
||||
for (i, item) in items.iter().enumerate() {
|
||||
match item.try_lock() {
|
||||
Ok(stack) if !stack.is_empty() => {
|
||||
let mut item_nbt = NbtCompound::new();
|
||||
item_nbt.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_nbt);
|
||||
slots.push(NbtTag::Compound(item_nbt));
|
||||
}
|
||||
Ok(_) => {}
|
||||
Err(_) => {
|
||||
tracing::warn!(
|
||||
"Skipping contended inventory slot {i} while serializing block entity data"
|
||||
);
|
||||
}
|
||||
for (i, stack) in items.iter().enumerate() {
|
||||
if !stack.is_empty() {
|
||||
let mut item_nbt = NbtCompound::new();
|
||||
item_nbt.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_nbt);
|
||||
slots.push(NbtTag::Compound(item_nbt));
|
||||
}
|
||||
}
|
||||
if !slots.is_empty() {
|
||||
|
||||
@ -1,7 +1,3 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
|
||||
#[expect(clippy::module_inception)]
|
||||
@ -11,11 +7,10 @@ mod simple_inventory;
|
||||
pub use inventory::*;
|
||||
pub use simple_inventory::*;
|
||||
|
||||
// These are some utility functions found in Inventories.java
|
||||
pub async fn split_stack(stacks: &[Arc<Mutex<ItemStack>>], slot: usize, amount: u8) -> ItemStack {
|
||||
let mut stack = stacks[slot].lock().await;
|
||||
if slot < stacks.len() && !stack.is_empty() && amount > 0 {
|
||||
stack.split(amount)
|
||||
// Utility functions for split_stack
|
||||
pub fn split_stack_slice(stacks: &mut [ItemStack], slot: usize, amount: u8) -> ItemStack {
|
||||
if slot < stacks.len() && !stacks[slot].is_empty() && amount > 0 {
|
||||
stacks[slot].split(amount)
|
||||
} else {
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
|
||||
@ -1,20 +1,17 @@
|
||||
use crate::inventory::{Clearable, Inventory, InventoryFuture, split_stack};
|
||||
use crate::inventory::{Clearable, Inventory, InventoryFuture};
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use std::any::Any;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct SimpleInventory {
|
||||
pub stacks: Vec<Arc<Mutex<ItemStack>>>,
|
||||
pub stacks: RwLock<Vec<ItemStack>>,
|
||||
}
|
||||
|
||||
impl SimpleInventory {
|
||||
#[must_use]
|
||||
pub fn new(size: usize) -> Self {
|
||||
Self {
|
||||
stacks: (0..size)
|
||||
.map(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone())))
|
||||
.collect(),
|
||||
stacks: RwLock::new(vec![ItemStack::EMPTY.clone(); size]),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -22,49 +19,62 @@ impl SimpleInventory {
|
||||
impl Clearable for SimpleInventory {
|
||||
fn clear(&self) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
for stack in &self.stacks {
|
||||
*stack.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut stacks = self.stacks.write().await;
|
||||
stacks.fill_with(|| ItemStack::EMPTY.clone());
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for SimpleInventory {
|
||||
fn size(&self) -> usize {
|
||||
self.stacks.len()
|
||||
self.stacks.blocking_read().len()
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for stack in &self.stacks {
|
||||
if !stack.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
let stacks = self.stacks.read().await;
|
||||
stacks.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.stacks[slot].clone() })
|
||||
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 remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut stack = self.stacks[slot].lock().await;
|
||||
let result = stack.clone();
|
||||
*stack = ItemStack::EMPTY.clone();
|
||||
result
|
||||
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_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move { split_stack(&self.stacks, slot, amount).await })
|
||||
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 set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.stacks[slot].lock().await = stack;
|
||||
let mut stacks = self.stacks.write().await;
|
||||
if slot < stacks.len() {
|
||||
stacks[slot] = stack;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@ impl BlockBehaviour for BrewingStandBlock {
|
||||
// Bottle slots are 0, 1, 2 in brewing stands
|
||||
for slot in 0..3 {
|
||||
let stack = inventory.get_stack(slot).await;
|
||||
if !stack.lock().await.is_empty() {
|
||||
if !stack.is_empty() {
|
||||
bottles += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -115,9 +115,7 @@ impl BlockBehaviour for CakeBlock {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let properties = CakeLikeProperties::from_state_id(state_id, args.block);
|
||||
let item_lock = args.item_stack.lock().await;
|
||||
let item = item_lock.item;
|
||||
drop(item_lock);
|
||||
let item = args.item_stack.item;
|
||||
match item.id {
|
||||
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id) => {
|
||||
if properties.bites != 0 {
|
||||
@ -132,9 +130,7 @@ impl BlockBehaviour for CakeBlock {
|
||||
}
|
||||
|
||||
if args.player.gamemode.load() != GameMode::Creative {
|
||||
let held_item = args.player.inventory.held_item();
|
||||
let mut held_item_guard = held_item.lock().await;
|
||||
held_item_guard.decrement(1);
|
||||
args.item_stack.decrement(1);
|
||||
}
|
||||
args.world
|
||||
.set_block_state(
|
||||
|
||||
@ -38,11 +38,12 @@ impl BlockBehaviour for CampfireBlock {
|
||||
{
|
||||
let has_frost_walker_enchantment = {
|
||||
let equipment = living_entity.entity_equipment.lock().await;
|
||||
let boots = equipment.get(&EquipmentSlot::FEET);
|
||||
|
||||
let boots_stack = boots.lock().await;
|
||||
|
||||
boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::FEET)
|
||||
.is_some_and(|boots| {
|
||||
boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
|
||||
})
|
||||
};
|
||||
let has_fire_res = living_entity
|
||||
.get_effect(&StatusEffect::FIRE_RESISTANCE)
|
||||
|
||||
@ -103,7 +103,7 @@ impl BlockBehaviour for CandleCakeBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item_id = args.item_stack.lock().await.item.id;
|
||||
let item_id = args.item_stack.item.id;
|
||||
match item_id {
|
||||
id if id == Item::FIRE_CHARGE.id || id == Item::FLINT_AND_STEEL.id => {
|
||||
BlockActionResult::Pass
|
||||
|
||||
@ -53,9 +53,7 @@ impl BlockBehaviour for CandleBlock {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let mut properties = CandleLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
let item_lock = args.item_stack.lock().await;
|
||||
let item = item_lock.item;
|
||||
drop(item_lock);
|
||||
let item = args.item_stack.item;
|
||||
|
||||
match item.id {
|
||||
id if (Item::CANDLE.id..=Item::BLACK_CANDLE.id).contains(&id)
|
||||
|
||||
@ -23,7 +23,6 @@ use pumpkin_data::{
|
||||
use pumpkin_inventory::screen_handler::InventoryPlayer;
|
||||
use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
|
||||
use pumpkin_world::inventory::Inventory;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
#[pumpkin_block("minecraft:chiseled_bookshelf")]
|
||||
pub struct ChiseledBookshelfBlock;
|
||||
@ -81,8 +80,6 @@ impl BlockBehaviour for ChiseledBookshelfBlock {
|
||||
|
||||
if !args
|
||||
.item_stack
|
||||
.lock()
|
||||
.await
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_BOOKSHELF_BOOKS)
|
||||
{
|
||||
@ -146,11 +143,10 @@ impl ChiseledBookshelfBlock {
|
||||
entity: &ChiseledBookshelfBlockEntity,
|
||||
properties: ChiseledBookshelfLikeProperties,
|
||||
slot: i8,
|
||||
item: &Arc<Mutex<ItemStack>>,
|
||||
item: &mut ItemStack,
|
||||
) {
|
||||
// TODO: Increment used stats for chiseled bookshelf on the player
|
||||
|
||||
let mut item = item.lock().await;
|
||||
let sound = if item.get_item() == &Item::ENCHANTED_BOOK {
|
||||
Sound::BlockChiseledBookshelfPickupEnchanted
|
||||
} else {
|
||||
@ -163,7 +159,9 @@ impl ChiseledBookshelfBlock {
|
||||
item.split_unless_creative(player.gamemode.load(), 1),
|
||||
)
|
||||
.await;
|
||||
entity.update_state(properties, world.clone(), slot).await;
|
||||
entity
|
||||
.update_state(properties, world.clone(), slot as usize)
|
||||
.await;
|
||||
|
||||
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
|
||||
}
|
||||
@ -192,7 +190,9 @@ impl ChiseledBookshelfBlock {
|
||||
// Drop the item on the ground if the player cannot hold it because of a full inventory
|
||||
player.drop_item(stack).await;
|
||||
}
|
||||
entity.update_state(properties, world.clone(), slot).await;
|
||||
entity
|
||||
.update_state(properties, world.clone(), slot as usize)
|
||||
.await;
|
||||
|
||||
world.play_sound(sound, SoundCategory::Blocks, &position.to_centered_f64());
|
||||
}
|
||||
|
||||
@ -56,7 +56,7 @@ impl BlockBehaviour for ComposterBlock {
|
||||
return BlockActionResult::Consume;
|
||||
}
|
||||
|
||||
let mut item_stack = args.item_stack.lock().await;
|
||||
let item_stack = &mut *args.item_stack;
|
||||
let item_id = item_stack.item.id;
|
||||
|
||||
// Check if the item is consumable by the composter
|
||||
|
||||
@ -15,7 +15,7 @@ impl BlockBehaviour for FlowerPotBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item = args.item_stack.lock().await.item;
|
||||
let item = args.item_stack.item;
|
||||
//Place the flower inside the pot
|
||||
let potted_block_id = get_potted_item(item.id);
|
||||
if args.block.eq(&Block::FLOWER_POT) {
|
||||
|
||||
@ -128,7 +128,7 @@ impl BlockBehaviour for JukeboxBlock {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
}
|
||||
|
||||
let mut item_stack = args.item_stack.lock().await;
|
||||
let item_stack = &mut *args.item_stack;
|
||||
|
||||
// Vanilla: JukeboxPlayableComponent lv = stack.get(DataComponentTypes.JUKEBOX_PLAYABLE)
|
||||
let jukebox_playable = item_stack
|
||||
|
||||
@ -225,7 +225,7 @@ impl BlockBehaviour for LecternBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let mut item_stack = args.item_stack.lock().await;
|
||||
let item_stack = &mut *args.item_stack;
|
||||
if !item_stack.item.has_tag(&tag::Item::MINECRAFT_LECTERN_BOOKS) {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
@ -247,7 +247,7 @@ impl BlockBehaviour for LecternBlock {
|
||||
};
|
||||
|
||||
let book = item_stack.split_unless_creative(args.player.gamemode.load(), 1);
|
||||
drop(item_stack);
|
||||
let _ = item_stack;
|
||||
lectern.set_stack(0, book).await;
|
||||
|
||||
Self::set_has_book(args.world, args.position, true).await;
|
||||
|
||||
@ -32,9 +32,12 @@ impl BlockBehaviour for MagmaBlock {
|
||||
|
||||
let has_frost_walker = {
|
||||
let equipment = living_entity.entity_equipment.lock().await;
|
||||
let boots = equipment.get(&EquipmentSlot::FEET);
|
||||
let boots_stack = boots.lock().await;
|
||||
boots_stack.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::FEET)
|
||||
.is_some_and(|boots| {
|
||||
boots.get_enchantment_level(&Enchantment::FROST_WALKER) != 0
|
||||
})
|
||||
};
|
||||
if has_frost_walker {
|
||||
return;
|
||||
|
||||
@ -65,7 +65,7 @@ impl BlockBehaviour for BambooBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let lock = args.item_stack.lock().await;
|
||||
let lock = &args.item_stack;
|
||||
if lock.get_item() == &Item::BONE_MEAL {
|
||||
bone_meal(Arc::clone(args.world), args.position).await;
|
||||
return BlockActionResult::Success;
|
||||
|
||||
@ -28,7 +28,7 @@ impl BlockBehaviour for BambooSaplingBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let lock = args.item_stack.lock().await;
|
||||
let lock = &args.item_stack;
|
||||
if lock.get_item() == &Item::BONE_MEAL {
|
||||
let mut props_new = BambooLikeProperties::from_state_id(
|
||||
Block::BAMBOO.default_state.id,
|
||||
|
||||
@ -65,7 +65,7 @@ impl BlockBehaviour for SweetBerryBushBlock {
|
||||
Box::pin(async move {
|
||||
let state_id = args.world.get_block_state_id(args.position);
|
||||
let props = NetherWartLikeProperties::from_state_id(state_id, &Block::SWEET_BERRY_BUSH);
|
||||
if props.age != 3 && args.item_stack.lock().await.get_item() == &Item::BONE_MEAL {
|
||||
if props.age != 3 && args.item_stack.get_item() == &Item::BONE_MEAL {
|
||||
BlockActionResult::Pass
|
||||
} else {
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
|
||||
@ -28,7 +28,7 @@ impl BlockBehaviour for SeaPickleBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if args.item_stack.lock().await.item != &Item::BONE_MEAL
|
||||
if args.item_stack.item != &Item::BONE_MEAL
|
||||
|| !args
|
||||
.world
|
||||
.get_block(&args.position.down())
|
||||
|
||||
@ -33,9 +33,10 @@ pub(crate) async fn can_entity_walk_on_powder_snow(entity: &dyn EntityBase) -> b
|
||||
};
|
||||
|
||||
let equipment = living.entity_equipment.lock().await;
|
||||
let boots = equipment.get(&EquipmentSlot::FEET);
|
||||
let boots = boots.lock().await;
|
||||
boots.item == &Item::LEATHER_BOOTS
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::FEET)
|
||||
.is_some_and(|boots| boots.item == &Item::LEATHER_BOOTS)
|
||||
}
|
||||
|
||||
fn is_entity_above_block(entity: &crate::entity::Entity, position: &BlockPos) -> bool {
|
||||
|
||||
@ -20,7 +20,7 @@ impl crate::block::BlockBehaviour for PumpkinBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
if args.item_stack.lock().await.item != &Item::SHEARS {
|
||||
if args.item_stack.item != &Item::SHEARS {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
let mut props = WallTorchLikeProperties::default(&Block::CARVED_PUMPKIN);
|
||||
|
||||
@ -202,7 +202,7 @@ impl BlockBehaviour for CrafterBlock {
|
||||
let mut occupied = 0u8;
|
||||
for i in 0..9 {
|
||||
let stack = crafter.get_stack(i).await;
|
||||
if !stack.lock().await.is_empty() {
|
||||
if !stack.is_empty() {
|
||||
occupied += 1;
|
||||
}
|
||||
}
|
||||
|
||||
@ -191,7 +191,7 @@ impl BlockBehaviour for DispenserBlock {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut item) = dispenser.get_random_slot().await {
|
||||
if let Some((slot_index, mut item)) = dispenser.get_random_slot().await {
|
||||
let props = DispenserLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
@ -230,6 +230,7 @@ impl BlockBehaviour for DispenserBlock {
|
||||
// Default / Drop
|
||||
Self::drop_item(&ctx, &mut item).await;
|
||||
}
|
||||
dispenser.set_stack(slot_index, item).await;
|
||||
} else {
|
||||
args.world
|
||||
.sync_world_event(WorldEvent::SoundDispenserFail, *args.position, 0);
|
||||
|
||||
@ -158,7 +158,7 @@ impl BlockBehaviour for DropperBlock {
|
||||
return;
|
||||
};
|
||||
|
||||
if let Some(mut item) = dropper.get_random_slot().await {
|
||||
if let Some((slot_index, mut item)) = dropper.get_random_slot().await {
|
||||
let props = DispenserLikeProperties::from_state_id(
|
||||
args.world.get_block_state(args.position).id,
|
||||
args.block,
|
||||
@ -177,15 +177,17 @@ impl BlockBehaviour for DropperBlock {
|
||||
if HopperBlockEntity::add_one_item(dropper, container.as_ref(), one_item)
|
||||
.await
|
||||
{
|
||||
dropper.set_stack(slot_index, item).await;
|
||||
return;
|
||||
}
|
||||
|
||||
*item = backup;
|
||||
dropper.set_stack(slot_index, backup).await;
|
||||
return;
|
||||
}
|
||||
|
||||
// No container found, dispense item into the world
|
||||
let drop_item = item.split(1);
|
||||
dropper.set_stack(slot_index, item).await;
|
||||
let facing = to_normal(props.facing);
|
||||
let mut position = args.position.to_centered_f64().add(&(facing * 0.7));
|
||||
|
||||
|
||||
@ -85,14 +85,7 @@ impl BlockBehaviour for TripwireBlock {
|
||||
|
||||
fn broken<'a>(&'a self, args: BrokenArgs<'a>) -> BlockFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
let has_shears = {
|
||||
let main_hand_item_stack = args.player.inventory().held_item();
|
||||
main_hand_item_stack
|
||||
.lock()
|
||||
.await
|
||||
.get_item()
|
||||
.eq(&Item::SHEARS)
|
||||
};
|
||||
let has_shears = args.player.inventory().held_item().await.get_item() == &Item::SHEARS;
|
||||
if has_shears {
|
||||
let mut props = TripwireProperties::from_state_id(args.state.id, args.block);
|
||||
props.disarmed = true;
|
||||
|
||||
@ -474,7 +474,6 @@ impl BlockBehaviour for SignBlock {
|
||||
}
|
||||
|
||||
/// Handles use with an item on the sign block.
|
||||
#[expect(clippy::option_if_let_else)]
|
||||
fn use_with_item<'a>(
|
||||
&'a self,
|
||||
args: UseWithItemArgs<'a>,
|
||||
@ -508,9 +507,10 @@ impl BlockBehaviour for SignBlock {
|
||||
&sign_entity.back_text
|
||||
};
|
||||
|
||||
let mut item = args.item_stack.lock().await;
|
||||
|
||||
let Some(pumpkin_item) = args.server.item_registry.get_pumpkin_item(item.item.id)
|
||||
let Some(pumpkin_item) = args
|
||||
.server
|
||||
.item_registry
|
||||
.get_pumpkin_item(args.item_stack.item.id)
|
||||
else {
|
||||
return BlockActionResult::PassToDefaultBlockAction;
|
||||
};
|
||||
@ -532,8 +532,12 @@ impl BlockBehaviour for SignBlock {
|
||||
} else if let Some(dye) =
|
||||
pumpkin_item.as_any().downcast_ref::<DyeItem>()
|
||||
{
|
||||
let color_name =
|
||||
item.item.registry_key.strip_suffix("_dye").unwrap();
|
||||
let color_name = args
|
||||
.item_stack
|
||||
.item
|
||||
.registry_key
|
||||
.strip_suffix("_dye")
|
||||
.unwrap();
|
||||
dye.apply_to_sign(&args, &block_entity, text, color_name)
|
||||
} else {
|
||||
BlockActionResult::PassToDefaultBlockAction
|
||||
@ -558,7 +562,7 @@ impl BlockBehaviour for SignBlock {
|
||||
args.player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::GlowedSign).await;
|
||||
}
|
||||
if !args.player.has_infinite_materials() {
|
||||
item.decrement(1);
|
||||
args.item_stack.decrement(1);
|
||||
}
|
||||
*currently_editing = None;
|
||||
}
|
||||
|
||||
@ -33,10 +33,7 @@ impl BlockBehaviour for LayeredSnowBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item = {
|
||||
let lock = args.item_stack.lock().await;
|
||||
lock.item
|
||||
};
|
||||
let item = args.item_stack.item;
|
||||
|
||||
if item == &Item::SNOW {
|
||||
let pos = if args.hit.face.is_horizontal() {
|
||||
|
||||
@ -48,7 +48,7 @@ impl BlockBehaviour for TNTBlock {
|
||||
args: UseWithItemArgs<'a>,
|
||||
) -> BlockFuture<'a, BlockActionResult> {
|
||||
Box::pin(async move {
|
||||
let item = args.item_stack.lock().await.item;
|
||||
let item = args.item_stack.item;
|
||||
if item != &Item::FLINT_AND_STEEL || item == &Item::FIRE_CHARGE {
|
||||
return BlockActionResult::Pass;
|
||||
}
|
||||
|
||||
@ -108,9 +108,7 @@ impl BlockBehaviour for VineBlock {
|
||||
let state = args.world.get_block_state(args.position);
|
||||
let mut props = VineLikeProperties::from_state_id(state.id, args.block);
|
||||
|
||||
let item_lock = args.item_stack.lock().await;
|
||||
let item = item_lock.item;
|
||||
drop(item_lock);
|
||||
let item = args.item_stack.item;
|
||||
|
||||
if item.id != Item::VINE.id {
|
||||
return BlockActionResult::Pass;
|
||||
|
||||
@ -15,22 +15,19 @@ use std::{
|
||||
atomic::{AtomicBool, Ordering},
|
||||
},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::block::viewer::{
|
||||
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
|
||||
};
|
||||
use crate::world::{BlockFlags, World};
|
||||
use pumpkin_world::inventory::InventoryFuture;
|
||||
use pumpkin_world::inventory::{
|
||||
split_stack, sync_write_items_to_nbt, {Clearable, Inventory},
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
|
||||
|
||||
use super::BlockEntity;
|
||||
|
||||
pub struct BarrelBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
@ -52,12 +49,12 @@ impl BlockEntity for BarrelBlockEntity {
|
||||
{
|
||||
let barrel = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
viewers: ViewerCountTracker::new(),
|
||||
};
|
||||
|
||||
barrel.read_data(nbt, &barrel.items);
|
||||
barrel.read_data(nbt, &mut *barrel.items.blocking_write());
|
||||
|
||||
barrel
|
||||
}
|
||||
@ -91,7 +88,7 @@ impl BlockEntity for BarrelBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
sync_write_items_to_nbt(&*self.items.blocking_read(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -132,7 +129,7 @@ impl BarrelBlockEntity {
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
viewers: ViewerCountTracker::new(),
|
||||
}
|
||||
@ -177,30 +174,27 @@ impl BarrelBlockEntity {
|
||||
|
||||
impl Inventory for BarrelBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -208,7 +202,12 @@ impl Inventory for BarrelBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -216,7 +215,8 @@ impl Inventory for BarrelBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -245,9 +245,8 @@ impl Inventory for BarrelBlockEntity {
|
||||
impl Clearable for BarrelBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -300,12 +300,12 @@ impl Inventory for BeaconBlockEntity {
|
||||
Box::pin(async move { self.payment.lock().await.is_empty() })
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
if slot == 0 {
|
||||
self.payment.clone()
|
||||
self.payment.lock().await.clone()
|
||||
} else {
|
||||
Arc::new(Mutex::new(ItemStack::EMPTY.clone()))
|
||||
ItemStack::EMPTY.clone()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ use std::{
|
||||
|
||||
use pumpkin_data::recipes::CookingRecipeKind;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::entities::furnace_like_block_entity::CookingBlockEntityBase,
|
||||
@ -30,7 +29,7 @@ pub struct BlastingFurnaceBlockEntity {
|
||||
pub lit_time_remaining: AtomicU16,
|
||||
pub lit_total_time: AtomicU16,
|
||||
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
|
||||
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
|
||||
/// Maps result item ID -> craft count
|
||||
@ -46,7 +45,7 @@ impl BlastingFurnaceBlockEntity {
|
||||
Self {
|
||||
position,
|
||||
dirty: AtomicBool::new(false),
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::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),
|
||||
|
||||
@ -19,11 +19,11 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::inventory::{Inventory, sync_write_items_to_nbt};
|
||||
use tokio::sync::Mutex;
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
pub struct BrewingStandBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
pub brew_time: AtomicI32,
|
||||
pub fuel: AtomicI32,
|
||||
@ -40,7 +40,7 @@ impl BrewingStandBlockEntity {
|
||||
use std::array::from_fn;
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
brew_time: AtomicI32::new(0),
|
||||
fuel: AtomicI32::new(0),
|
||||
@ -66,8 +66,9 @@ impl BrewingStandBlockEntity {
|
||||
let ingredient_id = ingredient.get_item().id;
|
||||
|
||||
// Check potion recipes (water bottle -> potions, potion upgrades, etc.)
|
||||
let items = self.items.read().await;
|
||||
for slot_idx in 0..3usize {
|
||||
let slot = self.items[slot_idx].lock().await;
|
||||
let slot = &items[slot_idx];
|
||||
if slot.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@ -105,7 +106,8 @@ impl BrewingStandBlockEntity {
|
||||
|
||||
// Apply recipes to each slot
|
||||
for slot_idx in 0..3usize {
|
||||
let slot = self.items[slot_idx].lock().await;
|
||||
let items = self.items.read().await;
|
||||
let slot = &items[slot_idx];
|
||||
if slot.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@ -167,8 +169,7 @@ impl BrewingStandBlockEntity {
|
||||
}
|
||||
}
|
||||
|
||||
// Drop the lock before calling set_stack
|
||||
drop(slot);
|
||||
drop(items);
|
||||
|
||||
// Update the slot using set_stack if a recipe was applied
|
||||
if let Some(new_stack) = new_stack_opt {
|
||||
@ -177,13 +178,10 @@ impl BrewingStandBlockEntity {
|
||||
}
|
||||
|
||||
// Consume ingredient
|
||||
let mut ingredient_slot = self.items[3].lock().await;
|
||||
ingredient_slot.decrement(1);
|
||||
let updated_ingredient = ingredient_slot.clone();
|
||||
drop(ingredient_slot);
|
||||
|
||||
// Update the slot with the decremented stack
|
||||
self.set_stack(3, updated_ingredient).await;
|
||||
let mut items = self.items.write().await;
|
||||
items[3].decrement(1);
|
||||
self.mark_dirty();
|
||||
drop(items);
|
||||
|
||||
// Play sound at the center of the block
|
||||
let pos = Vector3::new(
|
||||
@ -208,8 +206,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
|
||||
|
||||
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
let items = self.items.read().await;
|
||||
for slot in items.iter() {
|
||||
if !slot.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -217,11 +216,11 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(
|
||||
&self,
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
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 remove_stack(
|
||||
@ -229,9 +228,9 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
}
|
||||
@ -242,15 +241,16 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
|
||||
amount: u8,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
let mut taken = ItemStack::EMPTY.clone();
|
||||
if guard.item_count <= amount {
|
||||
std::mem::swap(&mut taken, &mut *guard);
|
||||
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 {
|
||||
taken = guard.clone();
|
||||
let mut taken = items[slot].clone();
|
||||
taken.item_count = amount;
|
||||
guard.item_count -= amount;
|
||||
}
|
||||
items[slot].item_count -= amount;
|
||||
taken
|
||||
};
|
||||
self.mark_dirty();
|
||||
taken
|
||||
})
|
||||
}
|
||||
@ -261,7 +261,8 @@ impl pumpkin_world::inventory::Inventory for BrewingStandBlockEntity {
|
||||
stack: ItemStack,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -311,10 +312,8 @@ 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 {
|
||||
for slot in &self.items {
|
||||
let mut guard = slot.lock().await;
|
||||
guard.clear();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -344,27 +343,29 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
}
|
||||
|
||||
// Load inventory items from NBT
|
||||
entity.read_data(nbt, &entity.items);
|
||||
entity.read_data(nbt, &mut *futures::executor::block_on(entity.items.write()));
|
||||
|
||||
let items_guard = futures::executor::block_on(entity.items.read());
|
||||
// If there's an ingredient in slot 3, remember its base item for matching
|
||||
if let Ok(guard) = entity.items[3].try_lock()
|
||||
&& !guard.is_empty()
|
||||
{
|
||||
if !items_guard[3].is_empty() {
|
||||
*entity
|
||||
.ingredient_item
|
||||
.lock()
|
||||
.expect("Ingredient item mutex should not be poisoned") = Some(guard.get_item());
|
||||
.expect("Ingredient item mutex should not be poisoned") =
|
||||
Some(items_guard[3].get_item());
|
||||
}
|
||||
|
||||
// Recompute last_potion_count so visuals are correct after load
|
||||
let mut current: [bool; 3] = [false; 3];
|
||||
for (i, slot_arc) in entity.items.iter().take(3).enumerate() {
|
||||
if let Ok(slot) = slot_arc.try_lock() {
|
||||
current[i] = !slot.is_empty()
|
||||
&& (slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some()
|
||||
|| slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id);
|
||||
}
|
||||
for (i, slot) in items_guard.iter().take(3).enumerate() {
|
||||
current[i] = !slot.is_empty()
|
||||
&& (slot
|
||||
.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>()
|
||||
.is_some()
|
||||
|| slot.get_item().id == pumpkin_data::item::Item::GLASS_BOTTLE.id);
|
||||
}
|
||||
drop(items_guard);
|
||||
|
||||
*entity
|
||||
.last_potion_count
|
||||
.lock()
|
||||
@ -395,7 +396,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
let mut nbt = NbtCompound::new();
|
||||
nbt.put_int("BrewTime", self.brew_time.load(Ordering::Relaxed));
|
||||
nbt.put_int("Fuel", self.fuel.load(Ordering::Relaxed));
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
sync_write_items_to_nbt(&*futures::executor::block_on(self.items.read()), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -418,15 +419,14 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
Box::pin(async move {
|
||||
// Refill fuel counter from fuel item if needed
|
||||
let fuel_refilled = if self.fuel.load(Ordering::Relaxed) <= 0 {
|
||||
let fuel_stack_arc = self.items[4].clone();
|
||||
let mut fuel_stack = fuel_stack_arc.lock().await;
|
||||
if !fuel_stack.is_empty()
|
||||
&& fuel_stack
|
||||
let mut items = self.items.write().await;
|
||||
if !items[4].is_empty()
|
||||
&& items[4]
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_BREWING_FUEL)
|
||||
{
|
||||
self.fuel.store(20, Ordering::Relaxed);
|
||||
fuel_stack.decrement(1);
|
||||
items[4].decrement(1);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
@ -436,7 +436,7 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
};
|
||||
|
||||
// Get current ingredient and check brewing state
|
||||
let ingredient = self.items[3].lock().await.clone();
|
||||
let ingredient = self.items.read().await[3].clone();
|
||||
let brewable = self.is_brewable(&ingredient).await;
|
||||
let is_brewing = self.brew_time.load(Ordering::Relaxed) > 0;
|
||||
|
||||
@ -475,11 +475,12 @@ impl crate::block::entities::BlockEntity for BrewingStandBlockEntity {
|
||||
// Ensure clients are notified when potion slot contents (and their data) change.
|
||||
// Compute current presence bits for the three bottle slots
|
||||
let mut current: [bool; 3] = [false; 3];
|
||||
for (i, slot_arc) in self.items.iter().take(3).enumerate() {
|
||||
let slot = slot_arc.lock().await;
|
||||
let items_guard = self.items.read().await;
|
||||
for (i, slot) in items_guard.iter().take(3).enumerate() {
|
||||
// Consider a potion slot "present" when it has an item and a PotionContents component or is a glass bottle
|
||||
current[i] = !slot.is_empty() && (slot.get_data_component::<pumpkin_data::data_component_impl::PotionContentsImpl>().is_some() || slot.get_item().id == Item::GLASS_BOTTLE.id);
|
||||
}
|
||||
drop(items_guard);
|
||||
|
||||
// If potion presence changed, update last_potion_count and update block state so clients
|
||||
let mut needs_update = false;
|
||||
|
||||
@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods,
|
||||
impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest,
|
||||
@ -13,7 +11,7 @@ use crate::{
|
||||
|
||||
pub struct ChestBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
|
||||
@ -26,7 +26,7 @@ macro_rules! impl_block_entity_for_chest {
|
||||
|
||||
let chest = Self {
|
||||
position,
|
||||
items: std::array::from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(std::array::from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: std::sync::atomic::AtomicBool::new(false),
|
||||
viewers: $crate::block::viewer::ViewerCountTracker::new(),
|
||||
loot_table: StdMutex::new(loot_table_key),
|
||||
@ -35,7 +35,7 @@ macro_rules! impl_block_entity_for_chest {
|
||||
|
||||
// Only read saved items when there is no pending loot table.
|
||||
if chest.loot_table.lock().expect("Loot table mutex should not be poisoned").is_none() {
|
||||
chest.read_data(nbt, &chest.items);
|
||||
chest.read_data(nbt, &mut *chest.items.blocking_write());
|
||||
}
|
||||
|
||||
chest
|
||||
@ -104,7 +104,8 @@ macro_rules! impl_block_entity_for_chest {
|
||||
nbt.put_long("LootTableSeed", self.loot_table_seed);
|
||||
}
|
||||
} else {
|
||||
pumpkin_world::inventory::sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
pumpkin_world::inventory::sync_write_items_to_nbt(&*items, &mut nbt);
|
||||
}
|
||||
Some(nbt)
|
||||
}
|
||||
@ -131,26 +132,24 @@ macro_rules! impl_inventory_for_chest {
|
||||
($struct_name:ty) => {
|
||||
impl pumpkin_world::inventory::Inventory for $struct_name {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(|s| s.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(
|
||||
&self,
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(
|
||||
@ -158,9 +157,8 @@ macro_rules! impl_inventory_for_chest {
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -172,8 +170,12 @@ macro_rules! impl_inventory_for_chest {
|
||||
amount: u8,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res =
|
||||
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -185,7 +187,8 @@ macro_rules! impl_inventory_for_chest {
|
||||
stack: ItemStack,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -222,9 +225,8 @@ macro_rules! impl_clearable_for_chest {
|
||||
&self,
|
||||
) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
<$struct_name as pumpkin_world::inventory::Inventory>::mark_dirty(self);
|
||||
})
|
||||
}
|
||||
@ -317,7 +319,7 @@ macro_rules! impl_chest_helper_methods {
|
||||
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
viewers: $crate::block::viewer::ViewerCountTracker::new(),
|
||||
loot_table: StdMutex::new(None),
|
||||
|
||||
@ -4,6 +4,7 @@ use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::{
|
||||
array::from_fn,
|
||||
@ -12,7 +13,6 @@ use std::{
|
||||
atomic::{AtomicBool, AtomicI8, Ordering},
|
||||
},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use tracing::warn;
|
||||
|
||||
use crate::{
|
||||
@ -20,11 +20,11 @@ use crate::{
|
||||
world::{BlockFlags, World},
|
||||
};
|
||||
use pumpkin_world::inventory::InventoryFuture;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, split_stack, sync_write_items_to_nbt};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, sync_write_items_to_nbt};
|
||||
|
||||
pub struct ChiseledBookshelfBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub last_interacted_slot: AtomicI8,
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
@ -44,18 +44,20 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
let chiseled_bookshelf = Self {
|
||||
let bookshelf = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
last_interacted_slot: AtomicI8::new(
|
||||
nbt.get_int(LAST_INTERACTED_SLOT).unwrap_or(-1) as i8
|
||||
),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
last_interacted_slot: AtomicI8::new(-1),
|
||||
dirty: AtomicBool::new(false),
|
||||
};
|
||||
bookshelf.read_data(nbt, &mut *bookshelf.items.blocking_write());
|
||||
if let Some(slot) = nbt.get_int(LAST_INTERACTED_SLOT) {
|
||||
bookshelf
|
||||
.last_interacted_slot
|
||||
.store(slot as i8, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
chiseled_bookshelf.read_data(nbt, &chiseled_bookshelf.items);
|
||||
|
||||
chiseled_bookshelf
|
||||
bookshelf
|
||||
}
|
||||
|
||||
fn write_nbt<'a>(
|
||||
@ -63,19 +65,17 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
|
||||
nbt: &'a mut NbtCompound,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
// Write inventory data to NBT
|
||||
self.write_inventory_nbt(nbt, true).await;
|
||||
|
||||
// Save last interacted slot
|
||||
let items = self.items.read().await;
|
||||
sync_write_items_to_nbt(items.as_slice(), nbt);
|
||||
nbt.put_int(
|
||||
LAST_INTERACTED_SLOT,
|
||||
self.last_interacted_slot.load(Ordering::Relaxed).into(),
|
||||
i32::from(self.last_interacted_slot.load(Ordering::Relaxed)),
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
fn get_inventory(self: Arc<Self>) -> Option<Arc<dyn Inventory>> {
|
||||
Some(self)
|
||||
Some(self as Arc<dyn Inventory>)
|
||||
}
|
||||
|
||||
fn is_dirty(&self) -> bool {
|
||||
@ -86,16 +86,6 @@ impl BlockEntity for ChiseledBookshelfBlockEntity {
|
||||
self.dirty.store(false, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
nbt.put_int(
|
||||
"last_interacted_slot",
|
||||
self.last_interacted_slot.load(Ordering::Relaxed) as i32,
|
||||
);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
fn as_any(&self) -> &dyn std::any::Any {
|
||||
self
|
||||
}
|
||||
@ -109,7 +99,7 @@ impl ChiseledBookshelfBlockEntity {
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
last_interacted_slot: AtomicI8::new(-1),
|
||||
dirty: AtomicBool::new(false),
|
||||
}
|
||||
@ -119,23 +109,29 @@ impl ChiseledBookshelfBlockEntity {
|
||||
&self,
|
||||
mut properties: ChiseledBookshelfLikeProperties,
|
||||
world: Arc<World>,
|
||||
slot: i8,
|
||||
slot: usize,
|
||||
) {
|
||||
if slot >= 0 && slot < self.items.len() as i8 {
|
||||
self.last_interacted_slot.store(slot, Ordering::Relaxed);
|
||||
if (0..Self::INVENTORY_SIZE).contains(&slot) {
|
||||
self.last_interacted_slot
|
||||
.store(slot as i8, Ordering::Relaxed);
|
||||
self.mark_dirty();
|
||||
|
||||
properties.slot_0_occupied = !self.items[0].lock().await.is_empty();
|
||||
properties.slot_1_occupied = !self.items[1].lock().await.is_empty();
|
||||
properties.slot_2_occupied = !self.items[2].lock().await.is_empty();
|
||||
properties.slot_3_occupied = !self.items[3].lock().await.is_empty();
|
||||
properties.slot_4_occupied = !self.items[4].lock().await.is_empty();
|
||||
properties.slot_5_occupied = !self.items[5].lock().await.is_empty();
|
||||
let occupied = !self.get_stack(slot).await.is_empty();
|
||||
match slot {
|
||||
0 => properties.slot_0_occupied = occupied,
|
||||
1 => properties.slot_1_occupied = occupied,
|
||||
2 => properties.slot_2_occupied = occupied,
|
||||
3 => properties.slot_3_occupied = occupied,
|
||||
4 => properties.slot_4_occupied = occupied,
|
||||
5 => properties.slot_5_occupied = occupied,
|
||||
_ => {}
|
||||
}
|
||||
|
||||
world
|
||||
.set_block_state(
|
||||
&self.position,
|
||||
properties.to_state_id(&Block::CHISELED_BOOKSHELF),
|
||||
BlockFlags::NOTIFY_ALL,
|
||||
BlockFlags::NOTIFY_LISTENERS,
|
||||
)
|
||||
.await;
|
||||
} else {
|
||||
@ -149,30 +145,27 @@ impl ChiseledBookshelfBlockEntity {
|
||||
|
||||
impl Inventory for ChiseledBookshelfBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -180,7 +173,12 @@ impl Inventory for ChiseledBookshelfBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -188,7 +186,8 @@ impl Inventory for ChiseledBookshelfBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -205,9 +204,8 @@ impl Inventory for ChiseledBookshelfBlockEntity {
|
||||
impl Clearable for ChiseledBookshelfBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,19 +2,16 @@ 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, split_stack, sync_write_items_to_nbt,
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, 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::atomic::{AtomicBool, AtomicI32, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct CrafterBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub crafting_ticks_remaining: AtomicI32,
|
||||
pub triggered: AtomicBool,
|
||||
pub dirty: AtomicBool,
|
||||
@ -26,7 +23,8 @@ impl BlockEntity for CrafterBlockEntity {
|
||||
nbt: &'a mut NbtCompound,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
self.write_inventory_nbt(nbt, true).await;
|
||||
let items = self.items.read().await;
|
||||
sync_write_items_to_nbt(items.as_slice(), nbt);
|
||||
nbt.put_int(
|
||||
"crafting_ticks_remaining",
|
||||
self.crafting_ticks_remaining.load(Ordering::Relaxed),
|
||||
@ -41,7 +39,7 @@ impl BlockEntity for CrafterBlockEntity {
|
||||
{
|
||||
let crafter = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
crafting_ticks_remaining: AtomicI32::new(
|
||||
nbt.get_int("crafting_ticks_remaining").unwrap_or(0),
|
||||
),
|
||||
@ -49,8 +47,7 @@ impl BlockEntity for CrafterBlockEntity {
|
||||
dirty: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
crafter.read_data(nbt, &crafter.items);
|
||||
|
||||
// Note: read_data needs to be adapted or handled via NBT accessors
|
||||
crafter
|
||||
}
|
||||
|
||||
@ -76,7 +73,8 @@ impl BlockEntity for CrafterBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
nbt.put_int(
|
||||
"crafting_ticks_remaining",
|
||||
self.crafting_ticks_remaining.load(Ordering::Relaxed),
|
||||
@ -98,7 +96,7 @@ impl CrafterBlockEntity {
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
crafting_ticks_remaining: AtomicI32::new(0),
|
||||
triggered: AtomicBool::new(false),
|
||||
dirty: AtomicBool::new(false),
|
||||
@ -108,30 +106,27 @@ impl CrafterBlockEntity {
|
||||
|
||||
impl Inventory for CrafterBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -139,7 +134,12 @@ impl Inventory for CrafterBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -147,7 +147,8 @@ impl Inventory for CrafterBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -164,9 +165,8 @@ impl Inventory for CrafterBlockEntity {
|
||||
impl Clearable for CrafterBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,20 +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, split_stack, sync_write_items_to_nbt,
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, 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::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
pub struct DispenserBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
|
||||
@ -33,11 +30,11 @@ impl BlockEntity for DispenserBlockEntity {
|
||||
{
|
||||
let dispenser = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
dispenser.read_data(nbt, &dispenser.items);
|
||||
dispenser.read_data(nbt, &mut *dispenser.items.blocking_write());
|
||||
|
||||
dispenser
|
||||
}
|
||||
@ -64,7 +61,8 @@ impl BlockEntity for DispenserBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -81,52 +79,51 @@ impl DispenserBlockEntity {
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
pub async fn get_random_slot(&self) -> Option<MutexGuard<'_, ItemStack>> {
|
||||
let mut ret = None;
|
||||
let mut j = 1;
|
||||
for i in &self.items {
|
||||
let item = i.lock().await;
|
||||
if !item.is_empty() {
|
||||
if rng().random_range(0..j) == 0 {
|
||||
ret = Some(item);
|
||||
}
|
||||
j += 1;
|
||||
|
||||
pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
|
||||
let items = self.items.read().await;
|
||||
let mut non_empty = Vec::new();
|
||||
for (i, stack) in items.iter().enumerate() {
|
||||
if !stack.is_empty() {
|
||||
non_empty.push((i, stack.clone()));
|
||||
}
|
||||
}
|
||||
ret
|
||||
if non_empty.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let selected = rng().random_range(0..non_empty.len());
|
||||
Some(non_empty[selected].clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for DispenserBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -134,7 +131,12 @@ impl Inventory for DispenserBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -142,7 +144,8 @@ impl Inventory for DispenserBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -159,9 +162,8 @@ impl Inventory for DispenserBlockEntity {
|
||||
impl Clearable for DispenserBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,20 +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, split_stack, sync_write_items_to_nbt,
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, 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::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::{Mutex, MutexGuard};
|
||||
|
||||
pub struct DropperBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
|
||||
@ -33,11 +30,11 @@ impl BlockEntity for DropperBlockEntity {
|
||||
{
|
||||
let dropper = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
dropper.read_data(nbt, &dropper.items);
|
||||
dropper.read_data(nbt, &mut *dropper.items.blocking_write());
|
||||
|
||||
dropper
|
||||
}
|
||||
@ -64,7 +61,8 @@ impl BlockEntity for DropperBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -81,53 +79,51 @@ impl DropperBlockEntity {
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
pub async fn get_random_slot(&self) -> Option<MutexGuard<'_, ItemStack>> {
|
||||
// this.unpackLootTable(null);
|
||||
let mut ret = None;
|
||||
let mut j = 1;
|
||||
for i in &self.items {
|
||||
let item = i.lock().await;
|
||||
if !item.is_empty() {
|
||||
if rng().random_range(0..j) == 0 {
|
||||
ret = Some(item);
|
||||
}
|
||||
j += 1;
|
||||
|
||||
pub async fn get_random_slot(&self) -> Option<(usize, ItemStack)> {
|
||||
let items = self.items.read().await;
|
||||
let mut non_empty = Vec::new();
|
||||
for (i, stack) in items.iter().enumerate() {
|
||||
if !stack.is_empty() {
|
||||
non_empty.push((i, stack.clone()));
|
||||
}
|
||||
}
|
||||
ret
|
||||
if non_empty.is_empty() {
|
||||
None
|
||||
} else {
|
||||
let selected = rng().random_range(0..non_empty.len());
|
||||
Some(non_empty[selected].clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for DropperBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -135,7 +131,12 @@ impl Inventory for DropperBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -143,7 +144,8 @@ impl Inventory for DropperBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -160,9 +162,8 @@ impl Inventory for DropperBlockEntity {
|
||||
impl Clearable for DropperBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -12,7 +12,6 @@ use std::{
|
||||
|
||||
use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipeKind};
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::entities::furnace_like_block_entity::CookingBlockEntityBase,
|
||||
@ -30,7 +29,7 @@ pub struct FurnaceBlockEntity {
|
||||
pub lit_time_remaining: AtomicU16,
|
||||
pub lit_total_time: AtomicU16,
|
||||
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
|
||||
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
|
||||
/// Maps result item ID -> craft count
|
||||
@ -46,7 +45,7 @@ impl FurnaceBlockEntity {
|
||||
Self {
|
||||
position,
|
||||
dirty: AtomicBool::new(false),
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::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,7 +1,4 @@
|
||||
use std::sync::Arc;
|
||||
|
||||
use pumpkin_data::{item_stack::ItemStack, recipes::CookingRecipe};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::block::entities::{BlockEntity, PropertyDelegate};
|
||||
pub use pumpkin_world::block::entities::ExperienceContainer;
|
||||
@ -23,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 = Arc<Mutex<ItemStack>>>;
|
||||
fn get_fuel_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>>;
|
||||
fn get_output_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>>;
|
||||
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 set_cooking_time_spent(&self, spent_time: u16);
|
||||
fn set_cooking_total_time(&self, total_time: u16);
|
||||
@ -61,19 +58,25 @@ macro_rules! impl_cooking_block_entity_base {
|
||||
self.lit_total_time.load(Ordering::Relaxed)
|
||||
}
|
||||
|
||||
fn get_input_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> {
|
||||
let items = self.items.clone();
|
||||
async move { items[0].clone() }
|
||||
fn get_input_item(&self) -> impl std::future::Future<Output = ItemStack> {
|
||||
async move {
|
||||
let items = self.items.read().await;
|
||||
items[0].clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn get_fuel_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> {
|
||||
let items = self.items.clone();
|
||||
async move { items[1].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_output_item(&self) -> impl std::future::Future<Output = Arc<Mutex<ItemStack>>> {
|
||||
let items = self.items.clone();
|
||||
async move { items[2].clone() }
|
||||
fn get_output_item(&self) -> impl std::future::Future<Output = ItemStack> {
|
||||
async move {
|
||||
let items = self.items.read().await;
|
||||
items[2].clone()
|
||||
}
|
||||
}
|
||||
|
||||
fn set_cooking_time_spent(&self, spent_time: u16) {
|
||||
@ -124,12 +127,11 @@ macro_rules! impl_cooking_block_entity_base {
|
||||
max_count: u8,
|
||||
) -> bool {
|
||||
let Some(recipe) = recipe else { return false };
|
||||
let items = self.items.read().await;
|
||||
|
||||
let top_item_stack = self.items[0].lock().await;
|
||||
let is_top_items_empty = top_item_stack.is_empty();
|
||||
drop(top_item_stack);
|
||||
let is_top_items_empty = items[0].is_empty();
|
||||
let side_item_stack = &items[2];
|
||||
|
||||
let side_item_stack = self.items[2].lock().await;
|
||||
if side_item_stack.is_empty() {
|
||||
return !is_top_items_empty;
|
||||
}
|
||||
@ -158,7 +160,7 @@ macro_rules! impl_cooking_block_entity_base {
|
||||
.await;
|
||||
if let Some(recipe) = recipe {
|
||||
if can_accept_output {
|
||||
let mut side_items = self.items[2].lock().await;
|
||||
let mut items = self.items.write().await;
|
||||
let Some(output_item) = pumpkin_data::item::Item::from_registry_key(
|
||||
recipe
|
||||
.result
|
||||
@ -170,32 +172,25 @@ macro_rules! impl_cooking_block_entity_base {
|
||||
};
|
||||
let output_item_stack = ItemStack::new(recipe.result.count, output_item);
|
||||
|
||||
if side_items.are_equal(ItemStack::EMPTY) {
|
||||
drop(side_items);
|
||||
self.set_stack(2, output_item_stack).await;
|
||||
} else if side_items.are_items_and_components_equal(&output_item_stack) {
|
||||
side_items.increment(1);
|
||||
if items[2].are_equal(ItemStack::EMPTY) {
|
||||
items[2] = output_item_stack;
|
||||
} else if items[2].are_items_and_components_equal(&output_item_stack) {
|
||||
items[2].increment(1);
|
||||
}
|
||||
|
||||
// Track recipe usage for XP calculation (vanilla RecipesUsed format)
|
||||
self.add_recipe_used(recipe);
|
||||
}
|
||||
|
||||
let bottom_items = self.items[1].lock().await;
|
||||
let mut top_items = self.items[0].lock().await;
|
||||
if top_items.item.id == pumpkin_data::item::Item::WET_SPONGE.id
|
||||
&& !bottom_items.is_empty()
|
||||
&& bottom_items.item.id == pumpkin_data::item::Item::BUCKET.id
|
||||
let mut items = self.items.write().await;
|
||||
if items[0].item.id == pumpkin_data::item::Item::WET_SPONGE.id
|
||||
&& !items[1].is_empty()
|
||||
&& items[1].item.id == pumpkin_data::item::Item::BUCKET.id
|
||||
{
|
||||
drop(bottom_items);
|
||||
self.set_stack(
|
||||
1,
|
||||
ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET),
|
||||
)
|
||||
.await;
|
||||
items[1] = ItemStack::new(1, &pumpkin_data::item::Item::WATER_BUCKET);
|
||||
}
|
||||
|
||||
top_items.decrement(1);
|
||||
items[0].decrement(1);
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -243,9 +238,8 @@ macro_rules! impl_clearable_for_cooking {
|
||||
impl pumpkin_world::inventory::Clearable for $struct_name {
|
||||
fn clear(&self) -> std::pin::Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in self.items.iter() {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -272,25 +266,24 @@ macro_rules! impl_inventory_for_cooking {
|
||||
($struct_name:ty) => {
|
||||
impl pumpkin_world::inventory::Inventory for $struct_name {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> pumpkin_world::inventory::InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in self.items.iter() {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(|s| s.is_empty())
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(
|
||||
&self,
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(
|
||||
@ -298,9 +291,8 @@ macro_rules! impl_inventory_for_cooking {
|
||||
slot: usize,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -312,8 +304,12 @@ macro_rules! impl_inventory_for_cooking {
|
||||
amount: u8,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res =
|
||||
pumpkin_world::inventory::split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -325,14 +321,11 @@ macro_rules! impl_inventory_for_cooking {
|
||||
stack: ItemStack,
|
||||
) -> pumpkin_world::inventory::InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
let furnace_stack = self.get_stack(slot).await;
|
||||
let mut furnace_stack = furnace_stack.lock().await;
|
||||
|
||||
let mut items = self.items.write().await;
|
||||
let is_same_item = !stack.is_empty()
|
||||
&& ItemStack::are_items_and_components_equal(&furnace_stack, &stack);
|
||||
&& ItemStack::are_items_and_components_equal(&items[slot], &stack);
|
||||
|
||||
*furnace_stack = stack.clone();
|
||||
drop(furnace_stack);
|
||||
items[slot] = stack.clone();
|
||||
|
||||
if slot == 0 && !is_same_item {
|
||||
if let Some(recipe) =
|
||||
@ -380,26 +373,27 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
self.lit_time_remaining.fetch_sub(1, Ordering::Relaxed);
|
||||
}
|
||||
|
||||
let top_items = self.items[0].lock().await;
|
||||
let is_top_items_empty = top_items.is_empty();
|
||||
let items_guard = self.items.read().await;
|
||||
let top_item = items_guard[0].clone();
|
||||
let bottom_item = items_guard[1].clone();
|
||||
drop(items_guard);
|
||||
|
||||
let is_top_items_empty = top_item.is_empty();
|
||||
|
||||
let furnace_recipe = pumpkin_data::recipes::get_cooking_recipe_with_ingredient(
|
||||
top_items.item,
|
||||
top_item.item,
|
||||
$recipe_kind,
|
||||
);
|
||||
drop(top_items);
|
||||
|
||||
let can_accept_output = self
|
||||
.can_accept_recipe_output(furnace_recipe, self.get_max_count_per_stack())
|
||||
.await;
|
||||
|
||||
let bottom_items_is_empty = self.items[1].lock().await.is_empty();
|
||||
let bottom_items_is_empty = bottom_item.is_empty();
|
||||
if self.is_burning() || !bottom_items_is_empty && !is_top_items_empty {
|
||||
if !self.is_burning() && can_accept_output {
|
||||
let mut bottom_items = self.items[1].lock().await;
|
||||
|
||||
let base_fuel_ticks =
|
||||
pumpkin_data::fuels::get_item_burn_ticks(bottom_items.item.id)
|
||||
pumpkin_data::fuels::get_item_burn_ticks(bottom_item.item.id)
|
||||
.unwrap_or(0);
|
||||
|
||||
let adjusted_fuel_ticks = if matches!(
|
||||
@ -416,18 +410,18 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
|
||||
if self.is_burning() {
|
||||
is_dirty = true;
|
||||
if !bottom_items.is_empty() {
|
||||
bottom_items.decrement(1);
|
||||
let mut items_guard = self.items.write().await;
|
||||
if !items_guard[1].is_empty() {
|
||||
items_guard[1].decrement(1);
|
||||
if let Some(remainder_id) =
|
||||
pumpkin_data::recipe_remainder::get_recipe_remainder_id(
|
||||
bottom_items.item.id,
|
||||
items_guard[1].item.id,
|
||||
)
|
||||
&& bottom_items.is_empty()
|
||||
&& items_guard[1].is_empty()
|
||||
&& let Some(remainder_item) =
|
||||
pumpkin_data::item::Item::from_id(remainder_id)
|
||||
{
|
||||
drop(bottom_items);
|
||||
self.set_stack(1, ItemStack::new(1, remainder_item)).await;
|
||||
items_guard[1] = ItemStack::new(1, remainder_item);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -540,14 +534,14 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
let furnace = Self {
|
||||
position,
|
||||
dirty: AtomicBool::new(false),
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::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),
|
||||
};
|
||||
furnace.read_data(nbt, &furnace.items);
|
||||
furnace.read_data(nbt, &mut *furnace.items.blocking_write());
|
||||
|
||||
furnace
|
||||
}
|
||||
@ -618,7 +612,10 @@ macro_rules! impl_block_entity_for_cooking {
|
||||
}
|
||||
}
|
||||
|
||||
pumpkin_world::inventory::sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
pumpkin_world::inventory::sync_write_items_to_nbt(
|
||||
&*self.items.blocking_read(),
|
||||
&mut nbt,
|
||||
);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
|
||||
@ -10,20 +10,17 @@ 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, split_stack, sync_write_items_to_nbt,
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, 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::atomic::Ordering;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct HopperBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
pub facing: FacingHopper,
|
||||
pub cooldown_time: AtomicI32,
|
||||
@ -62,14 +59,14 @@ impl BlockEntity for HopperBlockEntity {
|
||||
{
|
||||
let hopper = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::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),
|
||||
};
|
||||
|
||||
hopper.read_data(nbt, &hopper.items);
|
||||
hopper.read_data(nbt, &mut *hopper.items.blocking_write());
|
||||
|
||||
hopper
|
||||
}
|
||||
@ -120,7 +117,8 @@ impl BlockEntity for HopperBlockEntity {
|
||||
"TransferCooldown",
|
||||
NbtTag::Int(self.cooldown_time.load(Ordering::Relaxed)),
|
||||
);
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -137,7 +135,7 @@ impl HopperBlockEntity {
|
||||
pub fn new(position: BlockPos, facing: FacingHopper) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
facing,
|
||||
cooldown_time: AtomicI32::new(-1),
|
||||
@ -162,8 +160,8 @@ impl HopperBlockEntity {
|
||||
}
|
||||
|
||||
async fn inventory_full(&self) -> bool {
|
||||
for i in &self.items {
|
||||
let item = i.lock().await;
|
||||
let items = self.items.read().await;
|
||||
for item in items.iter() {
|
||||
if item.is_empty() || item.item_count != item.get_max_stack_size() {
|
||||
return false;
|
||||
}
|
||||
@ -179,16 +177,16 @@ impl HopperBlockEntity {
|
||||
{
|
||||
// TODO check WorldlyContainer
|
||||
for i in 0..container.size() {
|
||||
let bind = container.get_stack(i).await;
|
||||
let mut item = bind.lock().await;
|
||||
let mut item = container.get_stack(i).await;
|
||||
if !item.is_empty() && container.can_transfer_to(self, i, &item) {
|
||||
//TODO WorldlyContainer
|
||||
let backup = item.clone();
|
||||
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 extracting from furnace output slot (index 2), drop XP as orbs
|
||||
const FURNACE_OUTPUT_SLOT: usize = 2;
|
||||
if i == FURNACE_OUTPUT_SLOT
|
||||
let furnace_output_slot: usize = 2;
|
||||
if i == furnace_output_slot
|
||||
&& let Some(experience_container) =
|
||||
entity.clone().to_experience_container()
|
||||
{
|
||||
@ -200,7 +198,6 @@ impl HopperBlockEntity {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
*item = backup;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
@ -242,8 +239,7 @@ impl HopperBlockEntity {
|
||||
// TODO check WorldlyContainer
|
||||
let mut is_full = true;
|
||||
for i in 0..container.size() {
|
||||
let bind = container.get_stack(i).await;
|
||||
let item = bind.lock().await;
|
||||
let item = container.get_stack(i).await;
|
||||
if item.item_count < item.get_max_stack_size() {
|
||||
is_full = false;
|
||||
break;
|
||||
@ -252,16 +248,14 @@ impl HopperBlockEntity {
|
||||
if is_full {
|
||||
return false;
|
||||
}
|
||||
for i in &self.items {
|
||||
let mut item = i.lock().await;
|
||||
let items = self.items.read().await;
|
||||
for item in items.iter() {
|
||||
if !item.is_empty() {
|
||||
//TODO WorldlyContainer
|
||||
let backup = item.clone();
|
||||
let one_item = item.split(1);
|
||||
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 {
|
||||
return true;
|
||||
}
|
||||
*item = backup;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -272,14 +266,15 @@ impl HopperBlockEntity {
|
||||
let to_empty = to.is_empty().await;
|
||||
for j in 0..to.size() {
|
||||
if to.is_valid_slot_for(j, &item) {
|
||||
let bind = to.get_stack(j).await;
|
||||
let mut dst = bind.lock().await;
|
||||
let mut dst = to.get_stack(j).await;
|
||||
if dst.is_empty() {
|
||||
*dst = item.clone();
|
||||
dst = item.clone();
|
||||
to.set_stack(j, dst).await;
|
||||
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;
|
||||
success = true;
|
||||
}
|
||||
if success {
|
||||
@ -310,30 +305,27 @@ impl HopperBlockEntity {
|
||||
|
||||
impl Inventory for HopperBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -341,7 +333,12 @@ impl Inventory for HopperBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -349,7 +346,8 @@ impl Inventory for HopperBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -366,9 +364,8 @@ impl Inventory for HopperBlockEntity {
|
||||
impl Clearable for HopperBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -201,8 +201,8 @@ impl Inventory for JukeboxBlockEntity {
|
||||
Box::pin(async move { self.record_stack.lock().await.is_empty() })
|
||||
}
|
||||
|
||||
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.record_stack.clone() })
|
||||
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move { self.record_stack.lock().await.clone() })
|
||||
}
|
||||
|
||||
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
|
||||
@ -159,8 +159,8 @@ impl Inventory for LecternBlockEntity {
|
||||
Box::pin(async move { self.book.lock().await.is_empty() })
|
||||
}
|
||||
|
||||
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.book.clone() })
|
||||
fn get_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move { self.book.lock().await.clone() })
|
||||
}
|
||||
|
||||
fn remove_stack(&self, _slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
|
||||
@ -2,19 +2,16 @@ 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, split_stack, sync_write_items_to_nbt,
|
||||
};
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, 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::atomic::{AtomicBool, Ordering};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub struct ShelfBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
}
|
||||
|
||||
@ -32,11 +29,11 @@ impl BlockEntity for ShelfBlockEntity {
|
||||
{
|
||||
let shelf = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
};
|
||||
|
||||
shelf.read_data(nbt, &shelf.items);
|
||||
shelf.read_data(nbt, &mut *shelf.items.blocking_write());
|
||||
|
||||
shelf
|
||||
}
|
||||
@ -63,7 +60,8 @@ impl BlockEntity for ShelfBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -73,14 +71,14 @@ impl BlockEntity for ShelfBlockEntity {
|
||||
}
|
||||
|
||||
impl ShelfBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 9;
|
||||
pub const INVENTORY_SIZE: usize = 3;
|
||||
pub const ID: &'static str = "minecraft:shelf";
|
||||
|
||||
#[must_use]
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
}
|
||||
}
|
||||
@ -88,30 +86,27 @@ impl ShelfBlockEntity {
|
||||
|
||||
impl Inventory for ShelfBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -119,7 +114,12 @@ impl Inventory for ShelfBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -127,7 +127,8 @@ impl Inventory for ShelfBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -144,9 +145,8 @@ impl Inventory for ShelfBlockEntity {
|
||||
impl Clearable for ShelfBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -2,35 +2,27 @@ use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::sound::{Sound, SoundCategory};
|
||||
use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_util::random::xoroshiro128::Xoroshiro;
|
||||
use pumpkin_util::random::{RandomImpl, get_seed};
|
||||
use std::any::Any;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::{
|
||||
array::from_fn,
|
||||
sync::{Arc, atomic::AtomicBool},
|
||||
};
|
||||
use tokio::sync::Mutex;
|
||||
use std::sync::atomic::{AtomicBool, Ordering};
|
||||
use std::{array::from_fn, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
|
||||
use crate::block::entities::BlockEntity;
|
||||
use crate::block::viewer::{
|
||||
ViewerCountListener, ViewerCountTracker, ViewerCountTrackerExt, ViewerFuture,
|
||||
};
|
||||
use crate::world::World;
|
||||
use pumpkin_world::inventory::InventoryFuture;
|
||||
use pumpkin_world::inventory::{
|
||||
split_stack, sync_write_items_to_nbt, {Clearable, Inventory},
|
||||
};
|
||||
|
||||
use super::BlockEntity;
|
||||
use pumpkin_world::inventory::{Clearable, Inventory, InventoryFuture, sync_write_items_to_nbt};
|
||||
|
||||
pub struct ShulkerBoxBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
viewers: ViewerCountTracker,
|
||||
pub viewers: ViewerCountTracker,
|
||||
}
|
||||
|
||||
impl BlockEntity for ShulkerBoxBlockEntity {
|
||||
@ -48,12 +40,12 @@ impl BlockEntity for ShulkerBoxBlockEntity {
|
||||
{
|
||||
let shulker_box = Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
viewers: ViewerCountTracker::new(),
|
||||
};
|
||||
|
||||
shulker_box.read_data(nbt, &shulker_box.items);
|
||||
shulker_box.read_data(nbt, &mut *shulker_box.items.blocking_write());
|
||||
|
||||
shulker_box
|
||||
}
|
||||
@ -100,7 +92,8 @@ impl BlockEntity for ShulkerBoxBlockEntity {
|
||||
|
||||
fn chunk_data_nbt(&self) -> Option<NbtCompound> {
|
||||
let mut nbt = NbtCompound::new();
|
||||
sync_write_items_to_nbt(&self.items, &mut nbt);
|
||||
let items = futures::executor::block_on(self.items.read());
|
||||
sync_write_items_to_nbt(items.as_slice(), &mut nbt);
|
||||
Some(nbt)
|
||||
}
|
||||
|
||||
@ -116,7 +109,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
|
||||
position: &'a BlockPos,
|
||||
) -> ViewerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
Self::play_sound(world, position, Sound::BlockShulkerBoxOpen);
|
||||
Self::play_sound(world, position, 1);
|
||||
// TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_OPEN, this.pos);
|
||||
})
|
||||
}
|
||||
@ -127,7 +120,7 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
|
||||
position: &'a BlockPos,
|
||||
) -> ViewerFuture<'a, ()> {
|
||||
Box::pin(async move {
|
||||
Self::play_sound(world, position, Sound::BlockShulkerBoxClose);
|
||||
Self::play_sound(world, position, 0);
|
||||
// TODO: this.world.emitGameEvent(player, GameEvent.CONTAINER_CLOSE, this.pos);
|
||||
})
|
||||
}
|
||||
@ -150,57 +143,57 @@ impl ViewerCountListener for ShulkerBoxBlockEntity {
|
||||
impl ShulkerBoxBlockEntity {
|
||||
pub const INVENTORY_SIZE: usize = 27;
|
||||
pub const OPEN_ANIMATION_EVENT_TYPE: u8 = 1;
|
||||
pub const ID: &'static str = "minecraft:shulker_box"; // TODO support multi IDs
|
||||
pub const ID: &'static str = "minecraft:shulker_box";
|
||||
|
||||
#[must_use]
|
||||
pub fn new(position: BlockPos) -> Self {
|
||||
Self {
|
||||
position,
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: RwLock::new(from_fn(|_| ItemStack::EMPTY.clone())),
|
||||
dirty: AtomicBool::new(false),
|
||||
viewers: ViewerCountTracker::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn play_sound(world: &Arc<World>, position: &BlockPos, sound: Sound) {
|
||||
let mut rng = Xoroshiro::from_seed(get_seed());
|
||||
pub fn update_viewers(&self, world: &Arc<World>) {
|
||||
let viewer_count = self.viewers.current.load(Ordering::Relaxed);
|
||||
Self::play_sound(world, &self.position, i32::from(viewer_count));
|
||||
}
|
||||
|
||||
world.play_sound_fine(
|
||||
sound,
|
||||
SoundCategory::Blocks,
|
||||
&position.to_centered_f64(),
|
||||
0.5,
|
||||
rng.next_f32() * 0.1 + 0.9,
|
||||
);
|
||||
fn play_sound(world: &World, position: &BlockPos, viewer_count: i32) {
|
||||
let sound = if viewer_count > 0 {
|
||||
Sound::BlockShulkerBoxOpen
|
||||
} else {
|
||||
Sound::BlockShulkerBoxClose
|
||||
};
|
||||
|
||||
world.play_sound(sound, SoundCategory::Blocks, &position.to_f64());
|
||||
}
|
||||
}
|
||||
|
||||
impl Inventory for ShulkerBoxBlockEntity {
|
||||
fn size(&self) -> usize {
|
||||
self.items.len()
|
||||
Self::INVENTORY_SIZE
|
||||
}
|
||||
|
||||
fn is_empty(&self) -> InventoryFuture<'_, bool> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
if !slot.lock().await.is_empty() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
true
|
||||
let items = self.items.read().await;
|
||||
items.iter().all(ItemStack::is_empty)
|
||||
})
|
||||
}
|
||||
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, Arc<Mutex<ItemStack>>> {
|
||||
Box::pin(async move { self.items[slot].clone() })
|
||||
fn get_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let items = self.items.read().await;
|
||||
items[slot].clone()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove_stack(&self, slot: usize) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let mut removed = ItemStack::EMPTY.clone();
|
||||
let mut guard = self.items[slot].lock().await;
|
||||
std::mem::swap(&mut removed, &mut *guard);
|
||||
let mut items = self.items.write().await;
|
||||
let removed = std::mem::replace(&mut items[slot], ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
removed
|
||||
})
|
||||
@ -208,7 +201,12 @@ impl Inventory for ShulkerBoxBlockEntity {
|
||||
|
||||
fn remove_stack_specific(&self, slot: usize, amount: u8) -> InventoryFuture<'_, ItemStack> {
|
||||
Box::pin(async move {
|
||||
let res = split_stack(&self.items, slot, amount).await;
|
||||
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
|
||||
})
|
||||
@ -216,7 +214,8 @@ impl Inventory for ShulkerBoxBlockEntity {
|
||||
|
||||
fn set_stack(&self, slot: usize, stack: ItemStack) -> InventoryFuture<'_, ()> {
|
||||
Box::pin(async move {
|
||||
*self.items[slot].lock().await = stack;
|
||||
let mut items = self.items.write().await;
|
||||
items[slot] = stack;
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
@ -245,9 +244,8 @@ impl Inventory for ShulkerBoxBlockEntity {
|
||||
impl Clearable for ShulkerBoxBlockEntity {
|
||||
fn clear(&self) -> Pin<Box<dyn Future<Output = ()> + Send + '_>> {
|
||||
Box::pin(async move {
|
||||
for slot in &self.items {
|
||||
*slot.lock().await = ItemStack::EMPTY.clone();
|
||||
}
|
||||
let mut items = self.items.write().await;
|
||||
items.fill_with(|| ItemStack::EMPTY.clone());
|
||||
self.mark_dirty();
|
||||
})
|
||||
}
|
||||
|
||||
@ -167,18 +167,18 @@ impl From<Text> for NbtTag {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::fallible_impl_from)]
|
||||
impl From<NbtTag> for Text {
|
||||
fn from(tag: NbtTag) -> Self {
|
||||
let nbt = tag.extract_compound().unwrap();
|
||||
let Some(nbt) = tag.extract_compound() else {
|
||||
return Self::default();
|
||||
};
|
||||
let has_glowing_text = nbt.get_bool("has_glowing_text").unwrap_or(false);
|
||||
let color = nbt.get_string("color").unwrap_or("black");
|
||||
let messages: Vec<Box<str>> = nbt
|
||||
.get_list("messages")
|
||||
.unwrap()
|
||||
.iter()
|
||||
.filter_map(|tag| tag.extract_string().map(Box::from))
|
||||
.collect();
|
||||
let messages: Vec<Box<str>> = nbt.get_list("messages").map_or_else(Vec::new, |list| {
|
||||
list.iter()
|
||||
.filter_map(|tag| tag.extract_string().map(Box::from))
|
||||
.collect()
|
||||
});
|
||||
let get_message =
|
||||
|i: usize| -> Box<str> { messages.get(i).cloned().unwrap_or_else(|| Box::from("")) };
|
||||
|
||||
|
||||
@ -12,7 +12,6 @@ use std::{
|
||||
|
||||
use pumpkin_data::recipes::CookingRecipeKind;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::entities::furnace_like_block_entity::CookingBlockEntityBase,
|
||||
@ -30,7 +29,7 @@ pub struct SmokerBlockEntity {
|
||||
pub lit_time_remaining: AtomicU16,
|
||||
pub lit_total_time: AtomicU16,
|
||||
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
|
||||
/// Tracks recipes used for XP calculation (vanilla `RecipesUsed` NBT format)
|
||||
/// Maps result item ID -> craft count
|
||||
@ -46,7 +45,7 @@ impl SmokerBlockEntity {
|
||||
Self {
|
||||
position,
|
||||
dirty: AtomicBool::new(false),
|
||||
items: from_fn(|_| Arc::new(Mutex::new(ItemStack::EMPTY.clone()))),
|
||||
items: tokio::sync::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),
|
||||
|
||||
@ -4,8 +4,6 @@ use pumpkin_data::block_properties::BlockProperties;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::{
|
||||
block::viewer::ViewerCountTracker, impl_block_entity_for_chest, impl_chest_helper_methods,
|
||||
impl_clearable_for_chest, impl_inventory_for_chest, impl_viewer_count_listener_for_chest,
|
||||
@ -13,7 +11,7 @@ use crate::{
|
||||
|
||||
pub struct TrappedChestBlockEntity {
|
||||
pub position: BlockPos,
|
||||
pub items: [Arc<Mutex<ItemStack>>; Self::INVENTORY_SIZE],
|
||||
pub items: tokio::sync::RwLock<[ItemStack; Self::INVENTORY_SIZE]>,
|
||||
pub dirty: AtomicBool,
|
||||
|
||||
// Viewer
|
||||
|
||||
@ -28,7 +28,6 @@ use pumpkin_protocol::java::server::play::SUseItemOn;
|
||||
use pumpkin_util::math::boundingbox::BoundingBox;
|
||||
use pumpkin_util::math::vector3::Vector3;
|
||||
use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
pub trait BlockMetadata {
|
||||
fn ids() -> Box<[BlockId]>;
|
||||
@ -247,7 +246,7 @@ pub struct UseWithItemArgs<'a> {
|
||||
pub position: &'a BlockPos,
|
||||
pub player: &'a Arc<Player>,
|
||||
pub hit: &'a BlockHitResult<'a>,
|
||||
pub item_stack: &'a Arc<Mutex<ItemStack>>,
|
||||
pub item_stack: &'a mut ItemStack,
|
||||
pub equipment_slot: &'a EquipmentSlot,
|
||||
}
|
||||
|
||||
@ -512,8 +511,7 @@ pub async fn calculate_comparator_output(
|
||||
let mut fill_sum = 0.0;
|
||||
let mut non_empty_count = 0;
|
||||
for i in 0..size {
|
||||
let stack_mutex = inventory.get_stack(i).await;
|
||||
let stack = stack_mutex.lock().await;
|
||||
let stack = inventory.get_stack(i).await;
|
||||
if !stack.is_empty() {
|
||||
let max_stack = stack.get_max_stack_size() as f32;
|
||||
let count = stack.item_count as f32;
|
||||
|
||||
@ -157,7 +157,6 @@ use pumpkin_util::math::position::BlockPos;
|
||||
use pumpkin_world::world::{BlockAccessor, BlockFlags};
|
||||
use rustc_hash::FxHashMap;
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use super::BlockIsReplacing;
|
||||
use super::blocks::plant::crop::gourds::attached_stem::AttachedStemBlock;
|
||||
@ -804,7 +803,7 @@ impl BlockRegistry {
|
||||
player: &Arc<Player>,
|
||||
position: &BlockPos,
|
||||
hit: &BlockHitResult<'_>,
|
||||
item_stack: &Arc<Mutex<ItemStack>>,
|
||||
item_stack: &mut ItemStack,
|
||||
equipment_slot: &EquipmentSlot,
|
||||
server: &Server,
|
||||
world: &Arc<World>,
|
||||
|
||||
@ -3,7 +3,6 @@ use std::sync::Arc;
|
||||
use pumpkin_data::item_stack::ItemStack;
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_util::text::TextComponent;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::command::args::bounded_num::BoundedNumArgumentConsumer;
|
||||
use crate::command::args::players::PlayersArgumentConsumer;
|
||||
@ -40,58 +39,37 @@ async fn clear_player(target: &Player, item: &ItemPredicate, max: i32) -> i32 {
|
||||
let mut max: i32 = max;
|
||||
let mut is_done: bool = false;
|
||||
|
||||
iter_test_and_clear(
|
||||
&inventory.main_inventory,
|
||||
&mut count,
|
||||
&mut max,
|
||||
item,
|
||||
&mut is_done,
|
||||
)
|
||||
.await;
|
||||
|
||||
let entity_equipment_lock = inventory.entity_equipment.lock().await;
|
||||
iter_test_and_clear(
|
||||
entity_equipment_lock.equipment.values(),
|
||||
&mut count,
|
||||
&mut max,
|
||||
item,
|
||||
&mut is_done,
|
||||
)
|
||||
.await;
|
||||
drop(entity_equipment_lock);
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
async fn iter_test_and_clear<'i, I>(
|
||||
iter: I,
|
||||
count: &mut i32,
|
||||
max: &mut i32,
|
||||
item: &ItemPredicate,
|
||||
is_done: &mut bool,
|
||||
) where
|
||||
I: IntoIterator<Item = &'i Arc<Mutex<ItemStack>>>,
|
||||
{
|
||||
// Don't need to enter the loop if we are already done.
|
||||
if !*is_done {
|
||||
for slot in iter {
|
||||
test_and_clear(count, max, item, slot, is_done).await;
|
||||
if *is_done {
|
||||
{
|
||||
let mut main_inv = inventory.main_inventory.write().await;
|
||||
for slot in main_inv.iter_mut() {
|
||||
test_and_clear(&mut count, &mut max, item, slot, &mut is_done);
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !is_done {
|
||||
let mut entity_equipment_lock = inventory.entity_equipment.lock().await;
|
||||
for slot in entity_equipment_lock.equipment.values_mut() {
|
||||
test_and_clear(&mut count, &mut max, item, slot, &mut is_done);
|
||||
if is_done {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
count
|
||||
}
|
||||
|
||||
async fn test_and_clear(
|
||||
fn test_and_clear(
|
||||
count: &mut i32,
|
||||
max: &mut i32,
|
||||
item: &ItemPredicate,
|
||||
slot: &Arc<Mutex<ItemStack>>,
|
||||
slot_lock: &mut ItemStack,
|
||||
is_done: &mut bool,
|
||||
) {
|
||||
let mut slot_lock = slot.lock().await;
|
||||
if item.test_item_stack(&slot_lock) {
|
||||
if item.test_item_stack(slot_lock) {
|
||||
let item_count = slot_lock.item_count as i32;
|
||||
if *max == MAX_NO_CLEAR_BUT_SIMULATE {
|
||||
*count += item_count;
|
||||
|
||||
@ -135,8 +135,7 @@ async fn enchant_target(
|
||||
return Err(commands_enchant_failed());
|
||||
};
|
||||
|
||||
let lock = player.inventory.held_item();
|
||||
let mut item = lock.lock().await;
|
||||
let mut item = player.inventory().held_item().await;
|
||||
|
||||
if item.is_empty() {
|
||||
let msg = TextComponent::translate_cross(
|
||||
@ -168,6 +167,7 @@ async fn enchant_target(
|
||||
}
|
||||
|
||||
item.enchant(enchantment, level);
|
||||
player.inventory().set_held_item(item).await;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
||||
@ -239,8 +239,7 @@ impl CommandExecutor for EntityReplaceExecutor {
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.put(&eq, item_stack.clone())
|
||||
.await;
|
||||
.put(&eq, item_stack.clone());
|
||||
living.send_equipment_changes(&[(eq, item_stack.clone())]);
|
||||
modified_count += 1;
|
||||
}
|
||||
|
||||
@ -58,8 +58,7 @@ async fn insert_into_inventory(
|
||||
if stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
let slot = inventory.get_stack(i).await;
|
||||
let mut slot_stack = slot.lock().await;
|
||||
let mut slot_stack = inventory.get_stack(i).await;
|
||||
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;
|
||||
@ -67,6 +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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -75,10 +75,9 @@ async fn insert_into_inventory(
|
||||
if stack.is_empty() {
|
||||
break;
|
||||
}
|
||||
let slot = inventory.get_stack(i).await;
|
||||
let mut slot_stack = slot.lock().await;
|
||||
let slot_stack = inventory.get_stack(i).await;
|
||||
if slot_stack.is_empty() {
|
||||
*slot_stack = stack.clone();
|
||||
inventory.set_stack(i, stack.clone()).await;
|
||||
stack.item_count = 0;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -32,15 +32,12 @@ impl BegGoal {
|
||||
}
|
||||
|
||||
async fn is_player_holding_attractive(&self, player: &Player) -> bool {
|
||||
let main_hand = player.inventory.held_item();
|
||||
let main_stack = main_hand.lock().await;
|
||||
let main_stack = player.inventory().held_item().await;
|
||||
if main_stack.item_count > 0 && self.is_attractive(main_stack.item) {
|
||||
return true;
|
||||
}
|
||||
drop(main_stack);
|
||||
|
||||
let off_hand = player.inventory.off_hand_item().await;
|
||||
let off_stack = off_hand.lock().await;
|
||||
let off_stack = player.inventory().off_hand_item().await;
|
||||
off_stack.item_count > 0 && self.is_attractive(off_stack.item)
|
||||
}
|
||||
|
||||
|
||||
@ -33,12 +33,12 @@ impl TemptGoal {
|
||||
}
|
||||
|
||||
async fn is_holding_tempt_item(&self, player: &Player) -> bool {
|
||||
let main = player.inventory.held_item();
|
||||
if self.is_tempt_item(&*main.lock().await) {
|
||||
let main = player.inventory().held_item().await;
|
||||
if self.is_tempt_item(&main) {
|
||||
return true;
|
||||
}
|
||||
let off = player.inventory.off_hand_item().await;
|
||||
self.is_tempt_item(&*off.lock().await)
|
||||
let off = player.inventory().off_hand_item().await;
|
||||
self.is_tempt_item(&off)
|
||||
}
|
||||
|
||||
async fn find_tempting_player(&self, mob: &dyn Mob) -> Option<Arc<Player>> {
|
||||
|
||||
@ -30,20 +30,14 @@ impl AttackType {
|
||||
let sprinting = entity.is_sprinting();
|
||||
let on_ground = entity.on_ground.load(Ordering::Relaxed);
|
||||
let fall_distance = player.living_entity.fall_distance.load();
|
||||
let held_item = player.inventory().held_item();
|
||||
let is_mace = {
|
||||
let stack = held_item.lock().await;
|
||||
stack.item.id == pumpkin_data::item::Item::MACE.id
|
||||
};
|
||||
let held_item = player.inventory().held_item().await;
|
||||
let is_mace = held_item.item.id == pumpkin_data::item::Item::MACE.id;
|
||||
|
||||
if is_mace && !on_ground && fall_distance > 1.5 {
|
||||
return Self::MaceSmash;
|
||||
}
|
||||
|
||||
let sword = {
|
||||
let stack = held_item.lock().await;
|
||||
stack.is_sword()
|
||||
};
|
||||
let sword = held_item.is_sword();
|
||||
|
||||
let is_strong = attack_cooldown_progress > 0.9;
|
||||
if sprinting && is_strong {
|
||||
|
||||
@ -12,7 +12,6 @@ use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::Hand;
|
||||
use pumpkin_util::math::position::BlockPos;
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::sync::atomic::{
|
||||
@ -1353,14 +1352,13 @@ impl LivingEntity {
|
||||
.downcast_ref::<crate::entity::player::Player>()
|
||||
{
|
||||
let hand_stack = player
|
||||
.inventory
|
||||
.inventory()
|
||||
.get_stack_in_hand(pumpkin_util::Hand::Right)
|
||||
.await;
|
||||
let stack_guard = hand_stack.lock().await;
|
||||
looting_level = stack_guard
|
||||
looting_level = hand_stack
|
||||
.get_enchantment_level(&Enchantment::LOOTING)
|
||||
.max(0) as u32;
|
||||
(stack_guard.item_count > 0).then(|| stack_guard.clone())
|
||||
(!hand_stack.is_empty()).then(|| hand_stack.clone())
|
||||
} else {
|
||||
looting_level = 0;
|
||||
None
|
||||
@ -1441,12 +1439,13 @@ impl LivingEntity {
|
||||
if rand::random::<f32>() >= chance {
|
||||
continue;
|
||||
}
|
||||
let mut item = {
|
||||
let q = self.entity_equipment.lock().await;
|
||||
let item_arc = q.get(slot);
|
||||
let mut item_lock = item_arc.lock().await;
|
||||
mem::replace(&mut *item_lock, ItemStack::EMPTY.clone())
|
||||
};
|
||||
let mut item = self
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.equipment
|
||||
.remove(slot)
|
||||
.unwrap_or_else(|| ItemStack::EMPTY.clone());
|
||||
if item.is_empty() {
|
||||
continue;
|
||||
}
|
||||
@ -1547,11 +1546,8 @@ impl LivingEntity {
|
||||
killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::TwoBirdsOneArrow).await;
|
||||
}
|
||||
|
||||
let held_item = killer_player.inventory().held_item();
|
||||
let is_crossbow = {
|
||||
let lock = held_item.lock().await;
|
||||
lock.item.registry_key == "crossbow"
|
||||
};
|
||||
let held_item = killer_player.inventory().held_item().await;
|
||||
let is_crossbow = held_item.item.registry_key == "crossbow";
|
||||
if is_crossbow {
|
||||
killer_player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::Arbalistic).await;
|
||||
}
|
||||
@ -1715,12 +1711,30 @@ impl LivingEntity {
|
||||
/// Tries to use a totem of undying from the entity's hands. If successful, applies the totem effects and returns true.
|
||||
async fn try_use_death_protector(&self, caller: &dyn EntityBase) -> bool {
|
||||
for hand in Hand::all() {
|
||||
let stack = self.get_stack_in_hand(caller, hand).await;
|
||||
let mut stack = stack.lock().await;
|
||||
let mut stack = self.get_stack_in_hand(caller, hand).await;
|
||||
|
||||
// Clear the stack and use the totem of undying
|
||||
if stack.get_data_component::<DeathProtectionImpl>().is_some() {
|
||||
stack.clear();
|
||||
let slot = match hand {
|
||||
Hand::Right => EquipmentSlot::MAIN_HAND,
|
||||
Hand::Left => EquipmentSlot::OFF_HAND,
|
||||
};
|
||||
if let Some(player) = caller.get_player() {
|
||||
player
|
||||
.inventory()
|
||||
.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.equipment
|
||||
.insert(slot, stack);
|
||||
} else {
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
.equipment
|
||||
.insert(slot, stack);
|
||||
}
|
||||
self.set_health(1.0);
|
||||
self.entity
|
||||
.world
|
||||
@ -1774,55 +1788,47 @@ impl LivingEntity {
|
||||
// TODO: Falling anvil/stalactite should only damage the helmet slot.
|
||||
// TODO: Implement DAMAGE_RESISTANT component checks (e.g. netherite vs fire).
|
||||
|
||||
let armor_slots: Vec<(usize, Arc<Mutex<ItemStack>>, EquipmentSlot)> = {
|
||||
let armor_slots: Vec<(usize, ItemStack, EquipmentSlot)> = {
|
||||
let equipment_lock = self.entity_equipment.lock().await;
|
||||
self.equipment_slots
|
||||
.iter()
|
||||
.filter(|(_, slot)| slot.is_armor_slot())
|
||||
.map(|(index, slot)| (*index, equipment_lock.get(slot), slot.clone()))
|
||||
.filter_map(|(index, slot)| {
|
||||
equipment_lock
|
||||
.equipment
|
||||
.get(slot)
|
||||
.cloned()
|
||||
.map(|stack| (*index, stack, slot.clone()))
|
||||
})
|
||||
.collect()
|
||||
};
|
||||
|
||||
for (slot_index, equipment, slot) in armor_slots {
|
||||
let (slot_result, updated_stack_opt) = {
|
||||
let mut stack = equipment.lock().await;
|
||||
if stack.is_empty() {
|
||||
(pumpkin_data::item_stack::DamageResult::Untouched, None)
|
||||
} else {
|
||||
// Items without `EquippableImpl` component take damage freely.
|
||||
// Items with `damage_on_hurt: false` (e.g. elytra) are exempt from armor hit durability.
|
||||
// PERF: Component lookup runs O(1) per armor slot (max 4 per hit). Caching
|
||||
// at the item type level could optimize, but belongs in a broader caching pass.
|
||||
let takes_damage = stack
|
||||
.get_data_component::<EquippableImpl>()
|
||||
.is_none_or(|equippable| equippable.damage_on_hurt);
|
||||
for (slot_index, mut stack, slot) in armor_slots {
|
||||
if stack.is_empty() {
|
||||
continue;
|
||||
}
|
||||
|
||||
if takes_damage {
|
||||
// Base armor durability damage.
|
||||
let result = stack.damage_item(armor_damage);
|
||||
let changed = result != pumpkin_data::item_stack::DamageResult::Untouched;
|
||||
(result, changed.then_some(stack.clone()))
|
||||
} else {
|
||||
// Equippable items can opt out of on-hurt durability loss (e.g. elytra).
|
||||
(pumpkin_data::item_stack::DamageResult::Untouched, None)
|
||||
let takes_damage = stack
|
||||
.get_data_component::<EquippableImpl>()
|
||||
.is_none_or(|equippable| equippable.damage_on_hurt);
|
||||
|
||||
if takes_damage {
|
||||
let slot_result = stack.damage_item(armor_damage);
|
||||
if slot_result != pumpkin_data::item_stack::DamageResult::Untouched {
|
||||
if slot_result == pumpkin_data::item_stack::DamageResult::Broken {
|
||||
let world = self.entity.world.load();
|
||||
world
|
||||
.send_entity_status(&self.entity, super::equipment_break_status(&slot));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(updated_stack) = updated_stack_opt {
|
||||
// Broadcast break status before clearing the slot.
|
||||
if slot_result == pumpkin_data::item_stack::DamageResult::Broken {
|
||||
let world = self.entity.world.load();
|
||||
world.send_entity_status(&self.entity, super::equipment_break_status(&slot));
|
||||
}
|
||||
equipment_updates.push((slot.clone(), updated_stack.clone()));
|
||||
if let Some(player) = caller.get_player() {
|
||||
player
|
||||
.enqueue_slot_set_packet(&CSetPlayerInventory::new(
|
||||
(slot_index as i32).into(),
|
||||
&ItemStackSerializer::from(updated_stack),
|
||||
))
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1832,34 +1838,40 @@ impl LivingEntity {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn held_item(&self, caller: &dyn EntityBase) -> Arc<Mutex<ItemStack>> {
|
||||
pub async fn held_item(&self, caller: &dyn EntityBase) -> ItemStack {
|
||||
if let Some(player) = caller.get_player() {
|
||||
return player.inventory.held_item();
|
||||
return player.inventory.held_item().await;
|
||||
}
|
||||
self.entity_equipment
|
||||
.lock()
|
||||
.await
|
||||
let equipment = self.entity_equipment.lock().await;
|
||||
equipment
|
||||
.equipment
|
||||
.get(&EquipmentSlot::MAIN_HAND)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ItemStack::EMPTY.clone())
|
||||
}
|
||||
|
||||
pub async fn get_stack_in_hand(
|
||||
&self,
|
||||
caller: &dyn EntityBase,
|
||||
hand: Hand,
|
||||
) -> Arc<Mutex<ItemStack>> {
|
||||
pub async fn get_stack_in_hand(&self, caller: &dyn EntityBase, hand: Hand) -> ItemStack {
|
||||
match hand {
|
||||
Hand::Left => self.off_hand_item().await,
|
||||
Hand::Left => self.off_hand_item(caller).await,
|
||||
Hand::Right => self.held_item(caller).await,
|
||||
}
|
||||
}
|
||||
|
||||
/// getOffHandStack in source
|
||||
pub async fn off_hand_item(&self) -> Arc<Mutex<ItemStack>> {
|
||||
pub async fn off_hand_item(&self, caller: &dyn EntityBase) -> ItemStack {
|
||||
if let Some(player) = caller.get_player() {
|
||||
return player.inventory.off_hand_item().await;
|
||||
}
|
||||
let slot = self
|
||||
.equipment_slots
|
||||
.get(&PlayerInventory::OFF_HAND_SLOT)
|
||||
.unwrap();
|
||||
self.entity_equipment.lock().await.get(slot)
|
||||
let equipment = self.entity_equipment.lock().await;
|
||||
equipment
|
||||
.equipment
|
||||
.get(slot)
|
||||
.cloned()
|
||||
.unwrap_or_else(|| ItemStack::EMPTY.clone())
|
||||
}
|
||||
|
||||
pub fn can_take_damage(&self) -> bool {
|
||||
@ -2141,9 +2153,8 @@ impl EntityBase for LivingEntity {
|
||||
EquipmentSlot::LEGS,
|
||||
EquipmentSlot::FEET,
|
||||
] {
|
||||
let stack_arc = equipment_lock.get(&slot);
|
||||
let stack = stack_arc.lock().await;
|
||||
if !stack.is_empty()
|
||||
if let Some(stack) = equipment_lock.equipment.get(&slot)
|
||||
&& !stack.is_empty()
|
||||
&& let Some(modifiers) =
|
||||
stack.get_data_component::<AttributeModifiersImpl>()
|
||||
{
|
||||
@ -2175,9 +2186,8 @@ impl EntityBase for LivingEntity {
|
||||
EquipmentSlot::LEGS,
|
||||
EquipmentSlot::FEET,
|
||||
] {
|
||||
let stack_arc = equipment_lock.get(&slot);
|
||||
let stack = stack_arc.lock().await;
|
||||
if !stack.is_empty()
|
||||
if let Some(stack) = equipment_lock.equipment.get(&slot)
|
||||
&& !stack.is_empty()
|
||||
&& let Some(enchantments) =
|
||||
stack.get_data_component::<EnchantmentsImpl>()
|
||||
{
|
||||
@ -2285,8 +2295,8 @@ impl EntityBase for LivingEntity {
|
||||
}
|
||||
|
||||
if let Some(attacker_player) = cause.and_then(|c| c.get_player()) {
|
||||
let held_item = attacker_player.inventory().held_item();
|
||||
let is_axe = held_item.lock().await.is_axe();
|
||||
let held_item = attacker_player.inventory().held_item().await;
|
||||
let is_axe = held_item.is_axe();
|
||||
if is_axe {
|
||||
let mut disable_chance = 0.25;
|
||||
let is_sprinting = attacker_player
|
||||
@ -2322,33 +2332,30 @@ impl EntityBase for LivingEntity {
|
||||
EquipmentSlot::OFF_HAND
|
||||
};
|
||||
|
||||
let equipment_lock = self.entity_equipment.lock().await;
|
||||
let stack_arc = equipment_lock.get(&slot);
|
||||
let mut stack = stack_arc.lock().await;
|
||||
let mut equipment_guard = self.entity_equipment.lock().await;
|
||||
if let Some(stack) = equipment_guard.equipment.get_mut(&slot) {
|
||||
let durability_damage = (amount / 1.0).floor().max(1.0) as i32;
|
||||
if stack.damage_item(durability_damage) == DamageResult::Broken {
|
||||
if let Some(player) = caller.get_player() {
|
||||
player
|
||||
.increment_stat(
|
||||
StatisticCategory::Broken,
|
||||
stack.item.id as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
}
|
||||
world.send_entity_status(
|
||||
&self.entity,
|
||||
crate::entity::equipment_break_status(&slot),
|
||||
);
|
||||
*stack = ItemStack::EMPTY.clone();
|
||||
let broken_stack = stack.clone();
|
||||
drop(equipment_guard);
|
||||
|
||||
let durability_damage = (amount / 1.0).floor().max(1.0) as i32;
|
||||
if stack.damage_item(durability_damage) == DamageResult::Broken {
|
||||
if let Some(player) = caller.get_player() {
|
||||
player
|
||||
.increment_stat(
|
||||
StatisticCategory::Broken,
|
||||
stack.item.id as i32,
|
||||
1,
|
||||
)
|
||||
.await;
|
||||
self.send_equipment_changes(&[(slot, broken_stack)]);
|
||||
self.clear_active_hand().await;
|
||||
}
|
||||
world.send_entity_status(
|
||||
&self.entity,
|
||||
crate::entity::equipment_break_status(&slot),
|
||||
);
|
||||
*stack = ItemStack::EMPTY.clone();
|
||||
let broken_stack = stack.clone();
|
||||
drop(stack);
|
||||
drop(stack_arc);
|
||||
drop(equipment_lock);
|
||||
|
||||
self.send_equipment_changes(&[(slot, broken_stack)]);
|
||||
self.clear_active_hand().await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -2654,40 +2661,40 @@ impl EntityBase for LivingEntity {
|
||||
let mut handled = false;
|
||||
|
||||
// Check main hand (hotbar selected)
|
||||
let held_arc = player.inventory.held_item();
|
||||
{
|
||||
let mut held_lock = held_arc.lock().await;
|
||||
if held_lock.are_items_and_components_equal(item) {
|
||||
if is_potion {
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
held_lock.decrement(1);
|
||||
if held_lock.is_empty() {
|
||||
*held_lock = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
}
|
||||
let mut held = player.inventory.held_item().await;
|
||||
if held.are_items_and_components_equal(item) {
|
||||
if is_potion {
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
held.decrement(1);
|
||||
if held.is_empty() {
|
||||
held = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
}
|
||||
} else {
|
||||
held_lock.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
handled = true;
|
||||
} else {
|
||||
held.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
player.inventory.set_held_item(held).await;
|
||||
handled = true;
|
||||
}
|
||||
|
||||
if !handled {
|
||||
// Check off-hand
|
||||
let off_arc = player.inventory.off_hand_item().await;
|
||||
let mut off_lock = off_arc.lock().await;
|
||||
if off_lock.are_items_and_components_equal(item) {
|
||||
let mut off_hand = player.inventory.off_hand_item().await;
|
||||
if off_hand.are_items_and_components_equal(item) {
|
||||
if is_potion {
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
off_lock.decrement(1);
|
||||
if off_lock.is_empty() {
|
||||
*off_lock = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
off_hand.decrement(1);
|
||||
if off_hand.is_empty() {
|
||||
off_hand = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
off_lock.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
|
||||
player
|
||||
.inventory
|
||||
.set_stack_in_hand(Hand::Left, off_hand)
|
||||
.await;
|
||||
handled = true;
|
||||
}
|
||||
}
|
||||
@ -2696,21 +2703,24 @@ impl EntityBase for LivingEntity {
|
||||
// Use stored active_hand (as a fallback)
|
||||
let active_hand = *self.active_hand.lock().await;
|
||||
let hand_to_modify = active_hand.unwrap_or(Hand::Right);
|
||||
let item_stack = self
|
||||
let mut item_stack = self
|
||||
.get_stack_in_hand(caller.as_ref(), hand_to_modify)
|
||||
.await;
|
||||
let mut item_lock = item_stack.lock().await;
|
||||
|
||||
if is_potion {
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
item_lock.decrement(1);
|
||||
if item_lock.is_empty() {
|
||||
*item_lock = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
item_stack.decrement(1);
|
||||
if item_stack.is_empty() {
|
||||
item_stack = ItemStack::new(1, &Item::GLASS_BOTTLE);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
item_lock.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
item_stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
}
|
||||
player
|
||||
.inventory
|
||||
.set_stack_in_hand(hand_to_modify, item_stack)
|
||||
.await;
|
||||
}
|
||||
|
||||
if let Some(cooldown) = item.get_use_cooldown() {
|
||||
|
||||
@ -343,15 +343,12 @@ impl EndermanEntity {
|
||||
|
||||
pub async fn is_player_staring(&self, player: &Player) -> bool {
|
||||
let equipment = player.living_entity.entity_equipment.try_lock();
|
||||
if let Ok(equipment) = equipment {
|
||||
let head_item = equipment.get(&EquipmentSlot::HEAD);
|
||||
let head_stack = head_item.try_lock();
|
||||
if let Ok(head_stack) = head_stack
|
||||
&& !head_stack.is_empty()
|
||||
&& head_stack.item == &Item::CARVED_PUMPKIN
|
||||
{
|
||||
return false;
|
||||
}
|
||||
if let Ok(equipment) = equipment
|
||||
&& let Some(head_stack) = equipment.equipment.get(&EquipmentSlot::HEAD)
|
||||
&& !head_stack.is_empty()
|
||||
&& head_stack.item == &Item::CARVED_PUMPKIN
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
let entity = &self.mob_entity.living_entity.entity;
|
||||
|
||||
@ -1027,7 +1027,7 @@ pub async fn equip_mob_on_spawn(mob: &dyn EntityBase, world: &Arc<crate::world::
|
||||
let mut equipment_changes: Vec<(EquipmentSlot, ItemStack)> = Vec::new();
|
||||
|
||||
for (slot, stack, drop_chance) in changes_with_drops {
|
||||
equipment.put(&slot, stack.clone()).await;
|
||||
equipment.put(&slot, stack.clone());
|
||||
drop_chances.insert(slot.clone(), drop_chance);
|
||||
equipment_changes.push((slot, stack));
|
||||
}
|
||||
|
||||
@ -2455,21 +2455,15 @@ impl Entity {
|
||||
return true;
|
||||
};
|
||||
|
||||
let armor = {
|
||||
let equipment = living.entity_equipment.lock().await;
|
||||
[
|
||||
equipment.get(&EquipmentSlot::HEAD),
|
||||
equipment.get(&EquipmentSlot::CHEST),
|
||||
equipment.get(&EquipmentSlot::LEGS),
|
||||
equipment.get(&EquipmentSlot::FEET),
|
||||
]
|
||||
};
|
||||
|
||||
for stack in armor {
|
||||
let stack = stack.lock().await;
|
||||
if stack
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES)
|
||||
let equipment = living.entity_equipment.lock().await;
|
||||
for (slot, stack) in &equipment.equipment {
|
||||
if (*slot == EquipmentSlot::HEAD
|
||||
|| *slot == EquipmentSlot::CHEST
|
||||
|| *slot == EquipmentSlot::LEGS
|
||||
|| *slot == EquipmentSlot::FEET)
|
||||
&& stack
|
||||
.get_item()
|
||||
.has_tag(&tag::Item::MINECRAFT_FREEZE_IMMUNE_WEARABLES)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
@ -4,7 +4,6 @@ pub mod statistics;
|
||||
use core::f32;
|
||||
use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
||||
use std::f64::consts::TAU;
|
||||
use std::mem;
|
||||
use std::num::NonZeroU8;
|
||||
use std::str::FromStr;
|
||||
use std::sync::atomic::{AtomicBool, AtomicI8, AtomicI32, AtomicU8, AtomicU32, Ordering};
|
||||
@ -973,7 +972,7 @@ impl Player {
|
||||
let config = &server.advanced_config.pvp;
|
||||
|
||||
let inventory = self.inventory();
|
||||
let item_stack = inventory.held_item();
|
||||
let item_stack = inventory.held_item().await;
|
||||
|
||||
let base_damage = self
|
||||
.living_entity
|
||||
@ -987,7 +986,7 @@ impl Player {
|
||||
let mut knockback_level = 0u32;
|
||||
|
||||
{
|
||||
let stack = item_stack.lock().await;
|
||||
let stack = &item_stack;
|
||||
if stack.is_empty() {
|
||||
// Vanilla fist: base_attack_damage = -1.0, base_attack_speed = -2.4
|
||||
add_damage = -1.0;
|
||||
@ -1118,11 +1117,7 @@ impl Player {
|
||||
self.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::DealtOverkillDamage).await;
|
||||
}
|
||||
|
||||
if let Some(enchantments) = item_stack
|
||||
.lock()
|
||||
.await
|
||||
.get_data_component::<EnchantmentsImpl>()
|
||||
{
|
||||
if let Some(enchantments) = item_stack.get_data_component::<EnchantmentsImpl>() {
|
||||
for (enchantment, level) in enchantments.enchantment.iter() {
|
||||
if **enchantment == Enchantment::FIRE_ASPECT {
|
||||
victim_entity.set_on_fire_for_ticks(*level as u32 * 80);
|
||||
@ -1166,10 +1161,7 @@ impl Player {
|
||||
combat::spawn_sweep_particle(attacker_entity, &world, &pos);
|
||||
|
||||
let mut sweep_damage = 1.0;
|
||||
if let Some(enchantments) = item_stack
|
||||
.lock()
|
||||
.await
|
||||
.get_data_component::<EnchantmentsImpl>()
|
||||
if let Some(enchantments) = item_stack.get_data_component::<EnchantmentsImpl>()
|
||||
{
|
||||
for (enchantment, level) in enchantments.enchantment.iter() {
|
||||
if **enchantment == Enchantment::SWEEPING_EDGE {
|
||||
@ -1218,11 +1210,8 @@ impl Player {
|
||||
// 2. Refactor compute cost as a closure: damage_held_item(self, |stack| -> i32 { ... })
|
||||
// 3. In practice, single-player scenarios are safe (this is not multiplayer). Document
|
||||
// as a known limitation if refactoring is deemed too invasive.
|
||||
self.damage_held_item({
|
||||
let stack = item_stack.lock().await;
|
||||
Self::combat_weapon_durability_cost(&stack)
|
||||
})
|
||||
.await;
|
||||
self.damage_held_item(Self::combat_weapon_durability_cost(&item_stack))
|
||||
.await;
|
||||
|
||||
// Vanilla `Player#attack` ends the successful-hit branch with
|
||||
// `causeFoodExhaustion(0.1F)`. Only landed hits exhaust; the miss/no-damage
|
||||
@ -1282,14 +1271,10 @@ impl Player {
|
||||
EquipmentSlot::Body(_) | EquipmentSlot::Saddle(_) => return false,
|
||||
};
|
||||
|
||||
let stack_arc = self.inventory.get_stack(slot_index).await;
|
||||
|
||||
let updated = {
|
||||
let mut stack = stack_arc.lock().await;
|
||||
let result = stack.damage_item(amount);
|
||||
(result != pumpkin_data::item_stack::DamageResult::Untouched)
|
||||
.then_some((result, stack.clone()))
|
||||
};
|
||||
let mut stack = self.inventory().get_stack(slot_index).await;
|
||||
let result = stack.damage_item(amount);
|
||||
let updated = (result != pumpkin_data::item_stack::DamageResult::Untouched)
|
||||
.then_some((result, stack.clone()));
|
||||
|
||||
if let Some((result, updated_stack)) = updated {
|
||||
if let Some(server) = self.world().server.upgrade()
|
||||
@ -1359,13 +1344,12 @@ impl Player {
|
||||
return;
|
||||
}
|
||||
|
||||
let damage = {
|
||||
let stack = self.inventory.held_item();
|
||||
let stack = stack.lock().await;
|
||||
stack
|
||||
.get_data_component::<ToolImpl>()
|
||||
.map_or(0, |tool| tool.damage_per_block as i32)
|
||||
};
|
||||
let damage = self
|
||||
.inventory()
|
||||
.held_item()
|
||||
.await
|
||||
.get_data_component::<ToolImpl>()
|
||||
.map_or(0, |tool| tool.damage_per_block as i32);
|
||||
|
||||
if damage > 0 {
|
||||
self.damage_held_item(damage).await;
|
||||
@ -3153,14 +3137,12 @@ impl Player {
|
||||
let keep_inventory = { self.world().level_info.load().game_rules.keep_inventory };
|
||||
|
||||
if !keep_inventory {
|
||||
for item in &self.inventory().main_inventory {
|
||||
let mut lock = item.lock().await;
|
||||
self.world()
|
||||
.drop_stack(
|
||||
&block_pos,
|
||||
mem::replace(&mut *lock, ItemStack::EMPTY.clone()),
|
||||
)
|
||||
.await;
|
||||
let mut main_inv = self.inventory().main_inventory.write().await;
|
||||
for item in main_inv.iter_mut() {
|
||||
if !item.is_empty() {
|
||||
let stack = std::mem::replace(item, ItemStack::EMPTY.clone());
|
||||
self.world().drop_stack(&block_pos, stack).await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -3309,15 +3291,14 @@ impl Player {
|
||||
pub async fn can_harvest(&self, state: &BlockState, block: &'static Block) -> bool {
|
||||
!state.tool_required()
|
||||
|| self
|
||||
.inventory
|
||||
.inventory()
|
||||
.held_item()
|
||||
.lock()
|
||||
.await
|
||||
.is_correct_for_drops(block)
|
||||
}
|
||||
|
||||
pub async fn get_mining_speed(&self, block: &'static Block) -> f32 {
|
||||
let mut speed = self.inventory.held_item().lock().await.get_speed(block);
|
||||
let mut speed = self.inventory().held_item().await.get_speed(block);
|
||||
// Haste
|
||||
if self.living_entity.has_effect(&StatusEffect::HASTE).await
|
||||
|| self
|
||||
@ -3430,45 +3411,40 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn drop_held_item(&self, drop_stack: bool) {
|
||||
// Do not hold both item stack and screen handler locks at the same time.
|
||||
let (dropped_stack, updated_stack, selected_slot) = {
|
||||
let binding = self.inventory.held_item();
|
||||
let mut item_stack = binding.lock().await;
|
||||
let mut item_stack = self.inventory().held_item().await;
|
||||
|
||||
if item_stack.is_empty() {
|
||||
if item_stack.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
let drop_amount = if drop_stack { item_stack.item_count } else { 1 };
|
||||
let dropped_stack = item_stack.copy_with_count(drop_amount);
|
||||
|
||||
if let Some(server) = self.world().server.upgrade()
|
||||
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
{
|
||||
let mut event =
|
||||
crate::plugin::api::events::player::player_drop_item::PlayerDropItemEvent::new(
|
||||
player_arc,
|
||||
dropped_stack.item.registry_key.to_string(),
|
||||
dropped_stack.item_count as u8,
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let drop_amount = if drop_stack { item_stack.item_count } else { 1 };
|
||||
let dropped_stack = item_stack.copy_with_count(drop_amount);
|
||||
|
||||
if let Some(server) = self.world().server.upgrade()
|
||||
&& let Some(player_arc) = self.world().get_player_by_uuid(self.gameprofile.id)
|
||||
{
|
||||
let mut event =
|
||||
crate::plugin::api::events::player::player_drop_item::PlayerDropItemEvent::new(
|
||||
player_arc,
|
||||
dropped_stack.item.registry_key.to_string(),
|
||||
dropped_stack.item_count as u8,
|
||||
);
|
||||
server.plugin_manager.fire(&server, &mut event).await;
|
||||
if event.cancelled {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
item_stack.decrement(drop_amount);
|
||||
let updated_stack = item_stack.clone();
|
||||
let selected_slot = self.inventory.get_selected_slot();
|
||||
|
||||
(dropped_stack, updated_stack, selected_slot)
|
||||
};
|
||||
item_stack.decrement(drop_amount);
|
||||
let updated_stack = item_stack.clone();
|
||||
self.inventory().set_held_item(updated_stack.clone()).await;
|
||||
|
||||
self.drop_item(dropped_stack).await;
|
||||
|
||||
let inv: Arc<dyn Inventory> = self.inventory.clone();
|
||||
let screen_binding = self.current_screen_handler.lock().await;
|
||||
let mut screen_handler = screen_binding.lock().await;
|
||||
let selected_slot = self.inventory.get_selected_slot();
|
||||
if let Some(slot_index) = screen_handler
|
||||
.get_slot_index(&inv, selected_slot as usize)
|
||||
.await
|
||||
@ -3557,9 +3533,8 @@ impl Player {
|
||||
use pumpkin_data::item::Item;
|
||||
|
||||
for hand in Hand::all() {
|
||||
let item_in_hand = self.inventory.get_stack_in_hand(hand).await;
|
||||
let stack = self.inventory().get_stack_in_hand(hand).await;
|
||||
|
||||
let stack = item_in_hand.lock().await;
|
||||
if stack.item.id == Item::FILLED_MAP.id
|
||||
&& let Some(map_id_comp) = stack.get_data_component::<MapIdImpl>()
|
||||
{
|
||||
@ -3818,7 +3793,7 @@ impl Player {
|
||||
return xp;
|
||||
}
|
||||
|
||||
let mut candidates: Vec<(usize, EquipmentSlot, Arc<Mutex<ItemStack>>)> = Vec::new();
|
||||
let mut candidates: Vec<(usize, EquipmentSlot, ItemStack)> = Vec::new();
|
||||
|
||||
let selected_slot = self.inventory.get_selected_slot() as usize;
|
||||
let mut slot_pairs: Vec<(usize, EquipmentSlot)> = vec![
|
||||
@ -3832,12 +3807,8 @@ impl Player {
|
||||
}
|
||||
|
||||
for (slot_index, equipment_slot) in slot_pairs {
|
||||
let stack = self.inventory.get_stack(slot_index).await;
|
||||
let eligible = {
|
||||
let s = stack.lock().await;
|
||||
s.get_enchantment_level(&Enchantment::MENDING) > 0 && s.get_damage() > 0
|
||||
};
|
||||
if eligible {
|
||||
let stack = self.inventory().get_stack(slot_index).await;
|
||||
if stack.get_enchantment_level(&Enchantment::MENDING) > 0 && stack.get_damage() > 0 {
|
||||
candidates.push((slot_index, equipment_slot, stack));
|
||||
}
|
||||
}
|
||||
@ -3847,18 +3818,18 @@ impl Player {
|
||||
}
|
||||
|
||||
let idx = rand::random::<u32>() as usize % candidates.len();
|
||||
let (slot_index, equipment_slot, stack) = candidates.swap_remove(idx);
|
||||
|
||||
let (updated_stack, repaired) = {
|
||||
let mut stack = stack.lock().await;
|
||||
let repaired = stack.repair_item(xp.saturating_mul(2));
|
||||
(stack.clone(), repaired)
|
||||
};
|
||||
let (slot_index, equipment_slot, mut stack) = candidates.swap_remove(idx);
|
||||
|
||||
let repaired = stack.repair_item(xp.saturating_mul(2));
|
||||
if repaired <= 0 {
|
||||
return xp;
|
||||
}
|
||||
|
||||
let updated_stack = stack.clone();
|
||||
self.inventory()
|
||||
.set_stack(slot_index, updated_stack.clone())
|
||||
.await;
|
||||
|
||||
let xp_used = (repaired + 1) / 2;
|
||||
xp = xp.saturating_sub(xp_used);
|
||||
|
||||
@ -4436,28 +4407,25 @@ impl Player {
|
||||
|
||||
// Check offhand first
|
||||
let stack = inventory.get_stack(PlayerInventory::OFF_HAND_SLOT).await;
|
||||
let item = stack.lock().await;
|
||||
if matches!(
|
||||
item.item.id,
|
||||
stack.item.id,
|
||||
id if id == Item::ARROW.id
|
||||
|| id == Item::TIPPED_ARROW.id
|
||||
|| id == Item::SPECTRAL_ARROW.id
|
||||
) && item.item_count > 0
|
||||
) && stack.item_count > 0
|
||||
{
|
||||
return Some(PlayerInventory::OFF_HAND_SLOT);
|
||||
}
|
||||
drop(item);
|
||||
|
||||
// Check hotbar and main inventory
|
||||
for slot in 0..PlayerInventory::MAIN_SIZE {
|
||||
let stack = inventory.get_stack(slot).await;
|
||||
let item = stack.lock().await;
|
||||
if matches!(
|
||||
item.item.id,
|
||||
stack.item.id,
|
||||
id if id == Item::ARROW.id
|
||||
|| id == Item::TIPPED_ARROW.id
|
||||
|| id == Item::SPECTRAL_ARROW.id
|
||||
) && item.item_count > 0
|
||||
) && stack.item_count > 0
|
||||
{
|
||||
return Some(slot);
|
||||
}
|
||||
@ -4474,15 +4442,15 @@ impl Player {
|
||||
}
|
||||
|
||||
let inventory = self.inventory();
|
||||
let stack_arc = inventory.get_stack(slot).await;
|
||||
let mut stack = stack_arc.lock().await;
|
||||
let mut stack = inventory.get_stack(slot).await;
|
||||
match stack.item_count {
|
||||
2.. => {
|
||||
stack.item_count -= 1;
|
||||
inventory.set_stack(slot, stack).await;
|
||||
true
|
||||
}
|
||||
1 => {
|
||||
*stack = ItemStack::EMPTY.clone();
|
||||
inventory.set_stack(slot, ItemStack::EMPTY.clone()).await;
|
||||
true
|
||||
}
|
||||
_ => false,
|
||||
@ -4582,15 +4550,14 @@ impl Player {
|
||||
}
|
||||
|
||||
pub async fn has_item_in_inventory(&self, item: &pumpkin_data::item::Item) -> bool {
|
||||
for slot in &self.inventory.main_inventory {
|
||||
let stack = slot.lock().await;
|
||||
let main_inv = self.inventory.main_inventory.read().await;
|
||||
for stack in main_inv.iter() {
|
||||
if !stack.is_empty() && stack.item.id == item.id {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
let equipment = self.inventory.entity_equipment.lock().await;
|
||||
for slot_stack in equipment.equipment.values() {
|
||||
let stack = slot_stack.lock().await;
|
||||
for stack in equipment.equipment.values() {
|
||||
if !stack.is_empty() && stack.item.id == item.id {
|
||||
return true;
|
||||
}
|
||||
@ -4764,25 +4731,24 @@ impl NBTStorage for PlayerInventory {
|
||||
|
||||
// Create inventory list with the correct capacity (inventory size)
|
||||
let mut items: Vec<NbtTag> = Vec::with_capacity(41);
|
||||
for (i, item) in self.main_inventory.iter().enumerate() {
|
||||
let stack = item.lock().await;
|
||||
let main_inv = self.main_inventory.read().await;
|
||||
for (i, stack) in main_inv.iter().enumerate() {
|
||||
if !stack.is_empty() {
|
||||
let mut item_compound = NbtCompound::new();
|
||||
item_compound.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_compound);
|
||||
drop(stack);
|
||||
items.push(NbtTag::Compound(item_compound));
|
||||
}
|
||||
}
|
||||
|
||||
let mut equipment_compound = NbtCompound::new();
|
||||
let equipment_guard = self.entity_equipment.lock().await;
|
||||
for slot in self.equipment_slots.values() {
|
||||
let stack_binding = self.entity_equipment.lock().await.get(slot);
|
||||
let stack = stack_binding.lock().await;
|
||||
if !stack.is_empty() {
|
||||
if let Some(stack) = equipment_guard.equipment.get(slot)
|
||||
&& !stack.is_empty()
|
||||
{
|
||||
let mut item_compound = NbtCompound::new();
|
||||
stack.write_item_stack(&mut item_compound);
|
||||
drop(stack);
|
||||
match slot {
|
||||
EquipmentSlot::OffHand(_) => {
|
||||
equipment_compound.put_compound("offhand", item_compound);
|
||||
@ -4870,13 +4836,12 @@ impl NBTStorage for EnderChestInventory {
|
||||
Box::pin(async {
|
||||
// Create item list with the correct capacity (inventory size)
|
||||
let mut items: Vec<NbtTag> = Vec::with_capacity(Self::INVENTORY_SIZE);
|
||||
for (i, item) in self.items.iter().enumerate() {
|
||||
let stack = item.lock().await;
|
||||
let ec_items = self.items.read().await;
|
||||
for (i, stack) in ec_items.iter().enumerate() {
|
||||
if !stack.is_empty() {
|
||||
let mut item_compound = NbtCompound::new();
|
||||
item_compound.put_byte("Slot", i as i8);
|
||||
stack.write_item_stack(&mut item_compound);
|
||||
drop(stack);
|
||||
items.push(NbtTag::Compound(item_compound));
|
||||
}
|
||||
}
|
||||
|
||||
@ -313,9 +313,9 @@ impl Player {
|
||||
|
||||
if !self.has_advancement(Advancement::STORY_ENCHANT_ITEM).await {
|
||||
let mut has_enchanted = false;
|
||||
for item in &self.inventory().main_inventory {
|
||||
let lock = item.lock().await;
|
||||
if !lock.is_empty() && lock.has_enchantments() {
|
||||
let main_inv = self.inventory().main_inventory.read().await;
|
||||
for stack in main_inv.iter() {
|
||||
if !stack.is_empty() && stack.has_enchantments() {
|
||||
has_enchanted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
@ -182,9 +182,9 @@ impl ItemBehaviour for BoatItem {
|
||||
world.spawn_entity(boat_entity).await;
|
||||
|
||||
// Decrement item unless in creative mode
|
||||
let held_item = player.inventory.held_item();
|
||||
let mut stack = held_item.lock().await;
|
||||
let mut stack = player.inventory.held_item().await;
|
||||
stack.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player.inventory.set_held_item(stack).await;
|
||||
|
||||
// TODO: world.emitGameEvent(user, GameEvent.ENTITY_PLACE, hitResult.getPos())
|
||||
// TODO: user.incrementStat(Stats.USED.getOrCreateStat(this))
|
||||
|
||||
@ -41,8 +41,7 @@ impl ItemBehaviour for BowItem {
|
||||
|
||||
// Get the held item stack
|
||||
let inventory = player.inventory();
|
||||
let held = inventory.held_item();
|
||||
let stack = held.lock().await.clone();
|
||||
let stack = inventory.held_item().await;
|
||||
|
||||
// Start the bow drawing animation
|
||||
player
|
||||
@ -98,7 +97,7 @@ impl BowItem {
|
||||
|
||||
let projectile = if let Some(slot) = arrow_slot {
|
||||
let stack = player.inventory().get_stack(slot).await;
|
||||
stack.lock().await.copy_with_count(1)
|
||||
stack.copy_with_count(1)
|
||||
} else {
|
||||
ItemStack::new(1, &Item::ARROW)
|
||||
};
|
||||
@ -109,11 +108,9 @@ impl BowItem {
|
||||
|
||||
// Check for Infinity enchantment
|
||||
let mut has_infinity = false;
|
||||
let held = player.inventory().held_item();
|
||||
let held = player.inventory().held_item().await;
|
||||
if let Some(enchantments) =
|
||||
held.lock()
|
||||
.await
|
||||
.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
|
||||
held.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
|
||||
{
|
||||
has_infinity = enchantments
|
||||
.enchantment
|
||||
@ -179,8 +176,7 @@ impl BowItem {
|
||||
ArrowEntity::new_shot(arrow_entity, player.get_entity(), &projectile, pickup);
|
||||
|
||||
// Read enchantments of the held item (bow)
|
||||
let held = player.inventory().held_item();
|
||||
let stack = held.lock().await;
|
||||
let stack = player.inventory().held_item().await;
|
||||
if let Some(enchantments) =
|
||||
stack.get_data_component::<pumpkin_data::data_component_impl::EnchantmentsImpl>()
|
||||
{
|
||||
|
||||
@ -100,11 +100,13 @@ fn set_waterlogged(block: &Block, state: BlockStateId, waterlogged: bool) -> Blo
|
||||
|
||||
async fn give_player_bucket_item(player: &Player, item: &'static Item) {
|
||||
if player.gamemode.load() == GameMode::Creative {
|
||||
for i in 0..player.inventory.main_inventory.len() {
|
||||
if player.inventory.main_inventory[i].lock().await.item.id == item.id {
|
||||
let inv = player.inventory.main_inventory.read().await;
|
||||
for stack in inv.iter() {
|
||||
if stack.item.id == item.id {
|
||||
return;
|
||||
}
|
||||
}
|
||||
drop(inv);
|
||||
let mut item_stack = ItemStack::new(1, item);
|
||||
player
|
||||
.inventory
|
||||
@ -112,14 +114,13 @@ async fn give_player_bucket_item(player: &Player, item: &'static Item) {
|
||||
.await;
|
||||
} else {
|
||||
let item_stack = ItemStack::new(1, item);
|
||||
let held_item = player.inventory.held_item();
|
||||
let mut held_stack = held_item.lock().await;
|
||||
let mut held_stack = player.inventory.held_item().await;
|
||||
|
||||
if held_stack.item_count == 1 {
|
||||
*held_stack = item_stack;
|
||||
player.inventory.set_held_item(item_stack).await;
|
||||
} else {
|
||||
held_stack.decrement(1);
|
||||
drop(held_stack);
|
||||
player.inventory.set_held_item(held_stack).await;
|
||||
player
|
||||
.inventory
|
||||
.offer_or_drop_stack(item_stack, player)
|
||||
|
||||
@ -22,8 +22,7 @@ impl ItemBehaviour for BundleItem {
|
||||
player: &'a Player,
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let held_item_ref = player.inventory.held_item();
|
||||
let mut held_item = held_item_ref.lock().await;
|
||||
let mut held_item = player.inventory.held_item().await;
|
||||
let mut matched = false;
|
||||
let mut used_slot_index = player.inventory.get_selected_slot() as usize;
|
||||
|
||||
@ -40,7 +39,6 @@ impl ItemBehaviour for BundleItem {
|
||||
&position,
|
||||
);
|
||||
let updated_bundle = held_item.clone();
|
||||
drop(held_item);
|
||||
|
||||
player.drop_item(extracted_stack).await;
|
||||
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
||||
@ -48,8 +46,7 @@ impl ItemBehaviour for BundleItem {
|
||||
}
|
||||
|
||||
if !matched {
|
||||
let off_hand_item_ref = player.inventory.off_hand_item().await;
|
||||
let mut off_hand_item = off_hand_item_ref.lock().await;
|
||||
let mut off_hand_item = player.inventory.off_hand_item().await;
|
||||
if !off_hand_item.is_empty() && Self::ids().contains(&off_hand_item.item.id) {
|
||||
used_slot_index = 40; // OFF_HAND_SLOT
|
||||
if let Some(bundle_contents) =
|
||||
@ -63,7 +60,6 @@ impl ItemBehaviour for BundleItem {
|
||||
&position,
|
||||
);
|
||||
let updated_bundle = off_hand_item.clone();
|
||||
drop(off_hand_item);
|
||||
|
||||
player.drop_item(extracted_stack).await;
|
||||
player.sync_hand_slot(used_slot_index, updated_bundle).await;
|
||||
|
||||
@ -3,7 +3,6 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::sync::atomic::Ordering;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use crate::entity::player::Player;
|
||||
use crate::entity::projectile::arrow::{ArrowEntity, ArrowPickup};
|
||||
@ -33,14 +32,13 @@ impl ItemBehaviour for CrossbowItem {
|
||||
) -> Pin<Box<dyn Future<Output = ()> + Send + 'a>> {
|
||||
Box::pin(async move {
|
||||
let inventory = player.inventory();
|
||||
let held = inventory.held_item();
|
||||
let stack = held.lock().await.clone();
|
||||
let stack = inventory.held_item().await;
|
||||
|
||||
if stack
|
||||
.get_data_component::<ChargedProjectilesImpl>()
|
||||
.is_some()
|
||||
{
|
||||
Self::fire_projectiles(player, &held).await;
|
||||
Self::fire_projectiles(player).await;
|
||||
return;
|
||||
}
|
||||
|
||||
@ -66,8 +64,7 @@ impl ItemBehaviour for CrossbowItem {
|
||||
let use_ticks = 72000 - use_ticks;
|
||||
|
||||
let mut charge_time = 25;
|
||||
let held = player.inventory().held_item();
|
||||
let stack = held.lock().await;
|
||||
let mut stack = player.inventory().held_item().await;
|
||||
|
||||
if let Some(enchantments) = stack.get_data_component::<EnchantmentsImpl>() {
|
||||
for (enchantment, level) in enchantments.enchantment.iter() {
|
||||
@ -76,29 +73,24 @@ impl ItemBehaviour for CrossbowItem {
|
||||
}
|
||||
}
|
||||
}
|
||||
drop(stack);
|
||||
charge_time = charge_time.max(0);
|
||||
|
||||
if use_ticks >= charge_time {
|
||||
let arrow_slot = player.find_arrow().await;
|
||||
let mut stack = held.lock().await;
|
||||
let (arrow_nbt_wrapper, slot) = {
|
||||
if let Some(slot) = arrow_slot {
|
||||
let inventory = player.inventory();
|
||||
|
||||
let arrow_stack_arc = inventory.get_stack(slot).await;
|
||||
let arrow_stack = arrow_stack_arc.lock().await;
|
||||
let arrow_stack = inventory.get_stack(slot).await;
|
||||
let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new();
|
||||
arrow_stack
|
||||
.copy_with_count(1)
|
||||
.write_item_stack(&mut arrow_nbt);
|
||||
drop(arrow_stack);
|
||||
(Some(arrow_nbt), slot)
|
||||
} else if player.gamemode.load() == GameMode::Creative {
|
||||
let mut arrow_nbt = pumpkin_nbt::compound::NbtCompound::new();
|
||||
let arrow_stack = ItemStack::new(1, &Item::ARROW);
|
||||
arrow_stack.write_item_stack(&mut arrow_nbt);
|
||||
drop(arrow_stack);
|
||||
|
||||
(Some(arrow_nbt), 0)
|
||||
} else {
|
||||
@ -112,6 +104,7 @@ impl ItemBehaviour for CrossbowItem {
|
||||
projectiles: vec![arrow_nbt],
|
||||
})),
|
||||
));
|
||||
player.inventory().set_held_item(stack).await;
|
||||
|
||||
if player.gamemode.load() != GameMode::Creative {
|
||||
player.consume_arrow(slot).await;
|
||||
@ -138,23 +131,17 @@ impl ItemBehaviour for CrossbowItem {
|
||||
}
|
||||
|
||||
impl CrossbowItem {
|
||||
async fn fire_projectiles(player: &Player, held: &Arc<Mutex<ItemStack>>) {
|
||||
let (projectiles, has_multishot) = {
|
||||
let stack = held.lock().await;
|
||||
let projectiles = stack
|
||||
.get_data_component::<ChargedProjectilesImpl>()
|
||||
.cloned();
|
||||
let has_multishot =
|
||||
stack
|
||||
.get_data_component::<EnchantmentsImpl>()
|
||||
.is_some_and(|enchantments| {
|
||||
enchantments
|
||||
.enchantment
|
||||
.iter()
|
||||
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
|
||||
});
|
||||
(projectiles, has_multishot)
|
||||
};
|
||||
async fn fire_projectiles(player: &Player) {
|
||||
let mut held = player.inventory().held_item().await;
|
||||
let projectiles = held.get_data_component::<ChargedProjectilesImpl>().cloned();
|
||||
let has_multishot =
|
||||
held.get_data_component::<EnchantmentsImpl>()
|
||||
.is_some_and(|enchantments| {
|
||||
enchantments
|
||||
.enchantment
|
||||
.iter()
|
||||
.any(|(e, _)| **e == pumpkin_data::Enchantment::MULTISHOT)
|
||||
});
|
||||
|
||||
if let Some(charged) = projectiles {
|
||||
let world = player.world();
|
||||
@ -200,10 +187,9 @@ impl CrossbowItem {
|
||||
}
|
||||
}
|
||||
|
||||
held.lock()
|
||||
.await
|
||||
.patch
|
||||
held.patch
|
||||
.retain(|(id, _)| *id != DataComponent::ChargedProjectiles);
|
||||
player.inventory().set_held_item(held).await;
|
||||
player.damage_held_item(1).await;
|
||||
}
|
||||
}
|
||||
|
||||
@ -37,7 +37,7 @@ impl ItemBehaviour for EggItem {
|
||||
);
|
||||
|
||||
// Capture the held item stack and pass it to the thrown egg entity
|
||||
let item_stack: ItemStack = player.inventory.held_item().lock().await.clone();
|
||||
let item_stack: ItemStack = player.inventory.held_item().await;
|
||||
|
||||
let entity = Entity::new(world.clone(), position, &EntityType::EGG);
|
||||
let egg = EggEntity::new_shot(entity, player.get_entity());
|
||||
@ -51,22 +51,23 @@ impl ItemBehaviour for EggItem {
|
||||
world.spawn_entity(Arc::new(egg)).await;
|
||||
|
||||
// Consume item
|
||||
let held_item = player.inventory.held_item();
|
||||
let consumed = {
|
||||
let mut main_hand = held_item.lock().await;
|
||||
if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) {
|
||||
main_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
let mut main_hand = player.inventory.held_item().await;
|
||||
let consumed = if !main_hand.is_empty() && Self::ids().contains(&main_hand.item.id) {
|
||||
main_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player.inventory.set_held_item(main_hand).await;
|
||||
true
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
if !consumed {
|
||||
let off_hand_item = player.inventory.off_hand_item().await;
|
||||
let mut off_hand = off_hand_item.lock().await;
|
||||
let mut off_hand = player.inventory.off_hand_item().await;
|
||||
if !off_hand.is_empty() && Self::ids().contains(&off_hand.item.id) {
|
||||
off_hand.decrement_unless_creative(player.gamemode.load(), 1);
|
||||
player
|
||||
.inventory
|
||||
.set_stack_in_hand(pumpkin_util::Hand::Left, off_hand)
|
||||
.await;
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
@ -128,7 +128,9 @@ impl ItemBehaviour for EnderEyeItem {
|
||||
);
|
||||
|
||||
player.trigger_advancement(crate::entity::player::advancement::trigger::AdvancementTrigger::LaunchedEyeOfEnder).await;
|
||||
player.inventory.held_item().lock().await.decrement(1);
|
||||
let mut stack = player.inventory.held_item().await;
|
||||
stack.decrement(1);
|
||||
player.inventory.set_held_item(stack).await;
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user