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