From 7180ea69eeacf91347705ad2c2d6aa2831ca1db8 Mon Sep 17 00:00:00 2001 From: 4lve <72332750+4lve@users.noreply.github.com> Date: Sat, 12 Apr 2025 11:28:36 +0200 Subject: [PATCH] Remove non-vanilla Slot and replace with ItemStackSeralizer (#726) * replace slot with ItemStackSeralizer * Fix ItemStack * Fix pickup and remove faulty crafting (getting reworked in inv revamp) * re-add component check --------- Co-authored-by: kralverde --- pumpkin-inventory/src/crafting.rs | 103 ---- pumpkin-inventory/src/lib.rs | 1 - pumpkin-inventory/src/open_container.rs | 5 +- pumpkin-inventory/src/player.rs | 5 +- .../src/client/play/set_container_content.rs | 10 +- .../src/client/play/set_container_slot.rs | 11 +- .../src/client/play/set_equipment.rs | 9 +- .../src/codec/item_stack_seralizer.rs | 121 ++++ pumpkin-protocol/src/codec/mod.rs | 2 +- pumpkin-protocol/src/codec/slot.rs | 170 ------ .../src/server/play/click_container.rs | 10 +- .../src/server/play/set_creative_slot.rs | 4 +- pumpkin-registry/src/lib.rs | 2 - pumpkin-registry/src/recipe/mod.rs | 89 --- pumpkin-registry/src/recipe/read.rs | 534 ------------------ pumpkin-registry/src/recipe/recipe_formats.rs | 92 --- pumpkin-world/src/item/mod.rs | 46 ++ pumpkin/src/entity/item.rs | 46 +- pumpkin/src/entity/living.rs | 6 +- pumpkin/src/net/container.rs | 19 +- pumpkin/src/net/packet/play.rs | 10 +- 21 files changed, 238 insertions(+), 1057 deletions(-) delete mode 100644 pumpkin-inventory/src/crafting.rs create mode 100644 pumpkin-protocol/src/codec/item_stack_seralizer.rs delete mode 100644 pumpkin-protocol/src/codec/slot.rs delete mode 100644 pumpkin-registry/src/recipe/mod.rs delete mode 100644 pumpkin-registry/src/recipe/read.rs delete mode 100644 pumpkin-registry/src/recipe/recipe_formats.rs diff --git a/pumpkin-inventory/src/crafting.rs b/pumpkin-inventory/src/crafting.rs deleted file mode 100644 index a7a6832f9..000000000 --- a/pumpkin-inventory/src/crafting.rs +++ /dev/null @@ -1,103 +0,0 @@ -use pumpkin_data::{ - item::Item, - tag::{RegistryKey, get_tag_values}, -}; -use pumpkin_registry::{RECIPES, RecipeResult, flatten_3x3}; -use pumpkin_util::registry::{RegistryEntryList, TagType}; -use pumpkin_world::item::ItemStack; -use rayon::prelude::*; - -#[inline(always)] -fn check_ingredient_type(ingredient_type: &TagType, input: &ItemStack) -> bool { - match ingredient_type { - TagType::Tag(tag) => { - let _items = match get_tag_values(RegistryKey::Item, tag) { - Some(items) => items, - None => return false, - }; - // TODO - false - // items.iter().any(|tag| check_ingredient_type(&tag, input)) - } - TagType::Item(item) => { - Item::from_registry_key(item).is_some_and(|item| item.id == input.item.id) - } - } -} - -pub fn check_if_matches_crafting(input: [[Option<&ItemStack>; 3]; 3]) -> Option { - let input = flatten_3x3(input); - RECIPES - .par_iter() - .find_any(|recipe| { - let patterns = recipe.pattern(); - if patterns - .iter() - .flatten() - .flatten() - .all(|slot| slot.is_none()) - { - false - } else if recipe.recipe_type.is_shapeless() { - shapeless_crafting_match(input, recipe.pattern()) - } else { - patterns.par_iter().any(|pattern| { - pattern.iter().enumerate().all(|(i, row)| { - row.iter() - .enumerate() - .all(|(j, item)| match (item, input[i][j]) { - (Some(item), Some(input)) => ingredient_slot_check(item, input), - (None, None) => true, - (Some(_), None) | (None, Some(_)) => false, - }) - }) - }) - } - }) - .map(|recipe| match recipe.result() { - RecipeResult::Single { id, .. } => Some(ItemStack { - item: Item::from_registry_key(id).unwrap(), - item_count: 1, - }), - RecipeResult::Many { id, count, .. } => Some(ItemStack { - item: Item::from_registry_key(id).unwrap(), - item_count: *count, - }), - RecipeResult::Special => None, - })? -} - -fn ingredient_slot_check(recipe_item: &RegistryEntryList, input: &ItemStack) -> bool { - match recipe_item { - RegistryEntryList::Single(ingredient) => check_ingredient_type(ingredient, input), - RegistryEntryList::Many(ingredients) => ingredients - .iter() - .any(|ingredient| check_ingredient_type(ingredient, input)), - } -} -fn shapeless_crafting_match( - input: [[Option<&ItemStack>; 3]; 3], - pattern: &[[[Option; 3]; 3]], -) -> bool { - let mut pattern: Vec = pattern - .iter() - .flatten() - .flatten() - .flatten() - .cloned() - .collect(); - for item in input.into_iter().flatten().flatten() { - if let Some(index) = pattern.iter().enumerate().find_map(|(i, recipe_item)| { - if ingredient_slot_check(recipe_item, item) { - Some(i) - } else { - None - } - }) { - pattern.remove(index); - } else { - return false; - } - } - pattern.is_empty() -} diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 2642d1fc3..2f5762f03 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -4,7 +4,6 @@ use pumpkin_data::screen::WindowType; use pumpkin_world::item::ItemStack; pub mod container_click; -mod crafting; pub mod drag_handler; mod error; mod open_container; diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 3949f727f..cab815697 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,5 +1,4 @@ use crate::Container; -use crate::crafting::check_if_matches_crafting; use pumpkin_data::block::Block; use pumpkin_data::screen::WindowType; use pumpkin_util::math::position::BlockPos; @@ -162,7 +161,7 @@ impl Container for CraftingTable { fn craft(&mut self) -> bool { // TODO: Is there a better way to do this? - let check = [ + let _check = [ [ self.input[0][0].as_ref(), self.input[0][1].as_ref(), @@ -180,7 +179,7 @@ impl Container for CraftingTable { ], ]; - let new_output = check_if_matches_crafting(check); + let new_output = None; //check_if_matches_crafting(check); let result = new_output != self.output || self.input.iter().flatten().any(|s| s.is_some()) || new_output.is_some(); diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index e9ff562f0..724c3e868 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -1,5 +1,4 @@ use crate::container_click::MouseClick; -use crate::crafting::check_if_matches_crafting; use crate::{Container, InventoryError, WindowType, handle_item_change}; use pumpkin_data::item::Item; use pumpkin_world::item::ItemStack; @@ -366,9 +365,9 @@ impl Container for PlayerInventory { let v1 = [self.crafting[0].as_ref(), self.crafting[1].as_ref(), None]; let v2 = [self.crafting[2].as_ref(), self.crafting[3].as_ref(), None]; let v3 = [const { None }; 3]; - let together = [v1, v2, v3]; + let _together = [v1, v2, v3]; - self.crafting_output = check_if_matches_crafting(together); + self.crafting_output = None; //check_if_matches_crafting(together); self.crafting.iter().any(|s| s.is_some()) } diff --git a/pumpkin-protocol/src/client/play/set_container_content.rs b/pumpkin-protocol/src/client/play/set_container_content.rs index 93e3cb906..3b0a0471d 100644 --- a/pumpkin-protocol/src/client/play/set_container_content.rs +++ b/pumpkin-protocol/src/client/play/set_container_content.rs @@ -1,5 +1,5 @@ use crate::VarInt; -use crate::codec::slot::Slot; +use crate::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_CONTENT; use pumpkin_macros::packet; @@ -10,16 +10,16 @@ use serde::Serialize; pub struct CSetContainerContent<'a> { window_id: VarInt, state_id: VarInt, - slot_data: &'a [Slot], - carried_item: &'a Slot, + slot_data: &'a [ItemStackSerializer<'a>], + carried_item: &'a ItemStackSerializer<'a>, } impl<'a> CSetContainerContent<'a> { pub fn new( window_id: VarInt, state_id: VarInt, - slots: &'a [Slot], - carried_item: &'a Slot, + slots: &'a [ItemStackSerializer], + carried_item: &'a ItemStackSerializer, ) -> Self { Self { window_id, diff --git a/pumpkin-protocol/src/client/play/set_container_slot.rs b/pumpkin-protocol/src/client/play/set_container_slot.rs index 22bb3bbc2..5a798dbf3 100644 --- a/pumpkin-protocol/src/client/play/set_container_slot.rs +++ b/pumpkin-protocol/src/client/play/set_container_slot.rs @@ -1,5 +1,5 @@ use crate::VarInt; -use crate::codec::slot::Slot; +use crate::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_data::packet::clientbound::PLAY_CONTAINER_SET_SLOT; use pumpkin_macros::packet; @@ -11,11 +11,16 @@ pub struct CSetContainerSlot<'a> { window_id: i8, state_id: VarInt, slot: i16, - slot_data: &'a Slot, + slot_data: &'a ItemStackSerializer<'a>, } impl<'a> CSetContainerSlot<'a> { - pub fn new(window_id: i8, state_id: i32, slot: i16, slot_data: &'a Slot) -> Self { + pub fn new( + window_id: i8, + state_id: i32, + slot: i16, + slot_data: &'a ItemStackSerializer<'a>, + ) -> Self { Self { window_id, state_id: state_id.into(), diff --git a/pumpkin-protocol/src/client/play/set_equipment.rs b/pumpkin-protocol/src/client/play/set_equipment.rs index 0bd996734..fec42753f 100644 --- a/pumpkin-protocol/src/client/play/set_equipment.rs +++ b/pumpkin-protocol/src/client/play/set_equipment.rs @@ -7,17 +7,20 @@ use serde::Serialize; use crate::{ ClientPacket, - codec::{slot::Slot, var_int::VarInt}, + codec::{item_stack_seralizer::ItemStackSerializer, var_int::VarInt}, }; #[packet(PLAY_SET_EQUIPMENT)] pub struct CSetEquipment { entity_id: VarInt, - equipment: Vec<(EquipmentSlot, Slot)>, + equipment: Vec<(EquipmentSlot, ItemStackSerializer<'static>)>, } impl CSetEquipment { - pub fn new(entity_id: VarInt, equipment: Vec<(EquipmentSlot, Slot)>) -> Self { + pub fn new( + entity_id: VarInt, + equipment: Vec<(EquipmentSlot, ItemStackSerializer<'static>)>, + ) -> Self { Self { entity_id, equipment, diff --git a/pumpkin-protocol/src/codec/item_stack_seralizer.rs b/pumpkin-protocol/src/codec/item_stack_seralizer.rs new file mode 100644 index 000000000..9b1d3972b --- /dev/null +++ b/pumpkin-protocol/src/codec/item_stack_seralizer.rs @@ -0,0 +1,121 @@ +use std::borrow::Cow; + +use crate::VarInt; +use pumpkin_data::item::Item; +use pumpkin_world::item::ItemStack; +use serde::{ + Deserialize, Serialize, Serializer, + de::{self, SeqAccess}, +}; + +#[derive(Debug, Clone)] +pub struct ItemStackSerializer<'a>(pub Cow<'a, ItemStack>); + +impl<'de> Deserialize<'de> for ItemStackSerializer<'static> { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + impl<'de> de::Visitor<'de> for Visitor { + type Value = ItemStackSerializer<'static>; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid Slot encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let item_count = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?; + + let slot = if item_count.0 == 0 { + ItemStackSerializer(Cow::Borrowed(&ItemStack::EMPTY)) + } else { + let item_id = seq + .next_element::()? + .ok_or(de::Error::custom("No item id VarInt!"))?; + let num_components_to_add = seq + .next_element::()? + .ok_or(de::Error::custom("No component add length VarInt!"))?; + let num_components_to_remove = seq + .next_element::()? + .ok_or(de::Error::custom("No component remove length VarInt!"))?; + + if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 { + return Err(de::Error::custom( + "Slot components are currently unsupported", + )); + } + + let item_id: u16 = item_id + .0 + .try_into() + .map_err(|_| de::Error::custom("Invalid item id!"))?; + + ItemStackSerializer(Cow::Owned(ItemStack::new( + item_count.0 as u8, + Item::from_id(item_id).unwrap_or(Item::AIR), + ))) + }; + + Ok(slot) + } + } + + deserializer.deserialize_seq(Visitor) + } +} + +impl Serialize for ItemStackSerializer<'_> { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.0.is_empty() { + VarInt(0).serialize(serializer) + } else { + // TODO: Components + + #[derive(Serialize)] + struct NetworkRepr { + item_count: VarInt, + item_id: VarInt, + components_to_add: VarInt, + components_to_remove: VarInt, + } + + NetworkRepr { + item_count: self.0.item_count.into(), + item_id: self.0.item.id.into(), + components_to_add: 0.into(), + components_to_remove: 0.into(), + } + .serialize(serializer) + } + } +} + +impl ItemStackSerializer<'_> { + pub fn to_stack(self) -> ItemStack { + self.0.into_owned() + } +} + +impl From for ItemStackSerializer<'_> { + fn from(item: ItemStack) -> Self { + ItemStackSerializer(Cow::Owned(item)) + } +} + +impl From> for ItemStackSerializer<'_> { + fn from(item: Option) -> Self { + match item { + Some(item) => ItemStackSerializer::from(item), + None => ItemStackSerializer(Cow::Borrowed(&ItemStack::EMPTY)), + } + } +} diff --git a/pumpkin-protocol/src/codec/mod.rs b/pumpkin-protocol/src/codec/mod.rs index 74dc4a4d6..447d96f5f 100644 --- a/pumpkin-protocol/src/codec/mod.rs +++ b/pumpkin-protocol/src/codec/mod.rs @@ -1,5 +1,5 @@ pub mod bit_set; pub mod identifier; -pub mod slot; +pub mod item_stack_seralizer; pub mod var_int; pub mod var_long; diff --git a/pumpkin-protocol/src/codec/slot.rs b/pumpkin-protocol/src/codec/slot.rs deleted file mode 100644 index f38dbcbc0..000000000 --- a/pumpkin-protocol/src/codec/slot.rs +++ /dev/null @@ -1,170 +0,0 @@ -use crate::VarInt; -use pumpkin_data::item::Item; -use pumpkin_world::item::ItemStack; -use serde::{ - Deserialize, Serialize, Serializer, - de::{self, SeqAccess}, - ser, -}; - -#[derive(Debug, Clone)] -pub enum Slot { - NoItem, - Item { - // This also handles items on the ground which can have >64 items - item_count: u32, - item_id: u16, - // TODO: Implement item components - }, -} - -impl<'de> Deserialize<'de> for Slot { - fn deserialize(deserializer: D) -> Result - where - D: de::Deserializer<'de>, - { - struct Visitor; - impl<'de> de::Visitor<'de> for Visitor { - type Value = Slot; - - fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { - formatter.write_str("a valid Slot encoded in a byte sequence") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let item_count = seq - .next_element::()? - .ok_or(de::Error::custom("Failed to decode VarInt"))?; - - let slot = if item_count.0 == 0 { - Slot::NoItem - } else { - let item_id = seq - .next_element::()? - .ok_or(de::Error::custom("No item id VarInt!"))?; - let num_components_to_add = seq - .next_element::()? - .ok_or(de::Error::custom("No component add length VarInt!"))?; - let num_components_to_remove = seq - .next_element::()? - .ok_or(de::Error::custom("No component remove length VarInt!"))?; - - if num_components_to_add.0 != 0 || num_components_to_remove.0 != 0 { - return Err(de::Error::custom( - "Slot components are currently unsupported", - )); - } - - let item_id: u16 = item_id - .0 - .try_into() - .map_err(|_| de::Error::custom("Invalid item id!"))?; - - Slot::Item { - // i32 can always be u32 - item_count: item_count.0 as u32, - item_id, - } - }; - - Ok(slot) - } - } - - deserializer.deserialize_seq(Visitor) - } -} - -impl Serialize for Slot { - fn serialize(&self, serializer: S) -> Result - where - S: Serializer, - { - match self { - Self::NoItem => VarInt(0).serialize(serializer), - Self::Item { - item_count, - item_id, - } => { - // TODO: Components - - #[derive(Serialize)] - struct NetworkRepr { - item_count: VarInt, - item_id: VarInt, - components_to_add: VarInt, - components_to_remove: VarInt, - } - - let item_count: i32 = (*item_count) - .try_into() - .map_err(|_| ser::Error::custom("Item count overflows an i32!"))?; - - NetworkRepr { - item_count: item_count.into(), - item_id: (*item_id).into(), - components_to_add: 0.into(), - components_to_remove: 0.into(), - } - .serialize(serializer) - } - } - } -} - -impl Slot { - pub fn new(item_id: u16, count: u32) -> Self { - Self::Item { - item_count: count, - item_id, - } - } - - pub fn to_stack(self) -> Result, &'static str> { - match self { - Self::NoItem => Ok(None), - Self::Item { - item_count, - item_id, - } => { - let item = Item::from_id(item_id).ok_or("Item id invalid")?; - if item_count > item.components.max_stack_size as u32 { - Err("Stack item count greater than allowed") - } else { - let stack = ItemStack { - item, - // This is checked above - item_count: item_count as u8, - }; - Ok(Some(stack)) - } - } - } - } - - pub const fn empty() -> Self { - Self::NoItem - } -} - -impl From<&ItemStack> for Slot { - fn from(item: &ItemStack) -> Self { - Slot::new(item.item.id, item.item_count as u32) - } -} - -impl From> for Slot { - fn from(item: Option<&ItemStack>) -> Self { - item.map(Slot::from).unwrap_or(Slot::empty()) - } -} - -// impl From<&Option> for Slot { -// fn from(item: &Option) -> Self { -// item.map(|stack| Self::from(&stack)) -// .unwrap_or(Slot::empty()) -// } -// } diff --git a/pumpkin-protocol/src/server/play/click_container.rs b/pumpkin-protocol/src/server/play/click_container.rs index 3f8382384..adb6e3f56 100644 --- a/pumpkin-protocol/src/server/play/click_container.rs +++ b/pumpkin-protocol/src/server/play/click_container.rs @@ -1,5 +1,5 @@ use crate::VarInt; -use crate::codec::slot::Slot; +use crate::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_data::packet::serverbound::PLAY_CONTAINER_CLICK; use pumpkin_macros::packet; use serde::de::SeqAccess; @@ -14,8 +14,8 @@ pub struct SClickContainer { pub button: i8, pub mode: SlotActionType, pub length_of_array: VarInt, - pub array_of_changed_slots: Vec<(i16, Slot)>, - pub carried_item: Slot, + pub array_of_changed_slots: Vec<(i16, ItemStackSerializer<'static>)>, + pub carried_item: ItemStackSerializer<'static>, } impl<'de> Deserialize<'de> for SClickContainer { @@ -60,13 +60,13 @@ impl<'de> Deserialize<'de> for SClickContainer { .next_element::()? .ok_or(de::Error::custom("Unable to parse slot"))?; let slot = seq - .next_element::()? + .next_element::()? .ok_or(de::Error::custom("Unable to parse item"))?; array_of_changed_slots.push((slot_number, slot)); } let carried_item = seq - .next_element::()? + .next_element::()? .ok_or(de::Error::custom("Failed to decode carried item"))?; Ok(SClickContainer { diff --git a/pumpkin-protocol/src/server/play/set_creative_slot.rs b/pumpkin-protocol/src/server/play/set_creative_slot.rs index 44a56dd84..b5b5d3e94 100644 --- a/pumpkin-protocol/src/server/play/set_creative_slot.rs +++ b/pumpkin-protocol/src/server/play/set_creative_slot.rs @@ -1,11 +1,11 @@ use pumpkin_data::packet::serverbound::PLAY_SET_CREATIVE_MODE_SLOT; use pumpkin_macros::packet; -use crate::codec::slot::Slot; +use crate::codec::item_stack_seralizer::ItemStackSerializer; #[derive(serde::Deserialize, Debug)] #[packet(PLAY_SET_CREATIVE_MODE_SLOT)] pub struct SSetCreativeSlot { pub slot: i16, - pub clicked_item: Slot, + pub clicked_item: ItemStackSerializer<'static>, } diff --git a/pumpkin-registry/src/lib.rs b/pumpkin-registry/src/lib.rs index ab17c92de..6b9ea4966 100644 --- a/pumpkin-registry/src/lib.rs +++ b/pumpkin-registry/src/lib.rs @@ -16,7 +16,6 @@ use jukebox_song::JukeboxSong; use paint::Painting; use pig::PigVariant; use pumpkin_protocol::{client::config::RegistryEntry, codec::identifier::Identifier}; -pub use recipe::{RECIPES, Recipe, RecipeResult, RecipeType, flatten_3x3}; use serde::{Deserialize, Serialize}; use trim_material::TrimMaterial; use trim_pattern::TrimPattern; @@ -36,7 +35,6 @@ mod instrument; mod jukebox_song; mod paint; mod pig; -mod recipe; mod trim_material; mod trim_pattern; mod wolf; diff --git a/pumpkin-registry/src/recipe/mod.rs b/pumpkin-registry/src/recipe/mod.rs deleted file mode 100644 index 5319dff7d..000000000 --- a/pumpkin-registry/src/recipe/mod.rs +++ /dev/null @@ -1,89 +0,0 @@ -mod read; -mod recipe_formats; - -pub use read::{Recipe, RecipeResult, RecipeType}; -use std::sync::LazyLock; -pub fn flatten_3x3(input: [[Option; 3]; 3]) -> [[Option; 3]; 3] { - let mut final_output = [const { [const { None }; 3] }; 3]; - - let mut row_alignment = 0; - let mut column_alignment = 2; - - for (i, row) in input.iter().enumerate() { - let mut row_values = [false; 3]; - for (i, item) in row.iter().enumerate().take(column_alignment + 1) { - if item.is_some() { - row_values[i] = true; - if i < column_alignment { - column_alignment = i; - } - } - } - if i == row_alignment && row_values.iter().all(|val| !val) { - row_alignment += 1; - } - } - - for (i, row) in &mut final_output.iter_mut().enumerate() { - let input_row = input.get(i + row_alignment); - for (j, item) in row.iter_mut().enumerate() { - let val = input_row.and_then(|val| val.get(j + column_alignment)); - *item = match val { - None => None, - Some(None) => None, - Some(Some(val)) => Some(val.clone()), - } - } - } - - final_output -} -pub static RECIPES: LazyLock> = - LazyLock::new(|| serde_json::from_str(include_str!("../../../assets/recipes.json")).unwrap()); - -#[cfg(test)] -mod test { - use super::flatten_3x3; - - #[test] - fn row_flatten() { - let input = [[None; 3], [None; 3], [Some(()), Some(()), Some(())]]; - let out = [[Some(()), Some(()), Some(())], [None; 3], [None; 3]]; - assert_eq!(flatten_3x3(input), out); - } - - #[test] - fn column_flatten() { - let one_row_right = [None, None, Some(())]; - let one_row_left = [Some(()), None, None]; - let input = [one_row_right, one_row_right, one_row_right]; - let output = [one_row_left, one_row_left, one_row_left]; - assert_eq!(flatten_3x3(input), output) - } - - #[test] - fn full_flatten() { - let input_1 = [[None; 3], [None; 3], [None, None, Some(())]]; - let output_1 = [[Some(()), None, None], [None; 3], [None; 3]]; - let input_2 = [ - [None; 3], - [None, None, Some(())], - [None, Some(()), Some(())], - ]; - let output_2 = [ - [None, Some(()), None], - [Some(()), Some(()), None], - [None; 3], - ]; - let input_3 = [[Some(()), None, None], [Some(()), None, None], [None; 3]]; - assert_eq!(flatten_3x3(input_1), output_1); - assert_eq!(flatten_3x3(input_2), output_2); - assert_eq!(flatten_3x3(input_3), input_3); - } - - // #[test] - // // This makes sure that all recipes are able to be deserialized properly. - // fn check_parsing() { - // assert!(!RECIPES.is_empty()) - // } -} diff --git a/pumpkin-registry/src/recipe/read.rs b/pumpkin-registry/src/recipe/read.rs deleted file mode 100644 index e045f8f71..000000000 --- a/pumpkin-registry/src/recipe/read.rs +++ /dev/null @@ -1,534 +0,0 @@ -use crate::flatten_3x3; -use crate::recipe::read::SpecialCraftingType::{ - ArmorDye, BannerDuplicate, BookCloning, Firework, RepairItem, ShieldDecoration, - ShulkerboxColoring, SuspiciousStew, TippedArrow, -}; -use crate::recipe::read::ingredients::Ingredients; -use crate::recipe::recipe_formats::{ShapedCrafting, ShapelessCrafting}; -use pumpkin_util::registry::RegistryEntryList; -use serde::de::{Error, MapAccess, Visitor}; -use serde::{Deserialize, Deserializer, de}; -use std::collections::HashMap; -use std::fmt::Formatter; -use std::str::FromStr; - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum RecipeType { - Blasting, - CampfireCooking, - Crafting(CraftingType), - Smelting, - Smithing(SmithingType), - Smoking, - StoneCutting, -} - -impl RecipeType { - pub const fn is_shapeless(&self) -> bool { - // I have not checked exactly which ones require shape and which don't! - !matches!(self, Self::Crafting(CraftingType::Shaped)) - } -} - -impl FromStr for RecipeType { - type Err = String; - - fn from_str(s: &str) -> Result { - use CraftingType::*; - use FireworkCrafting::*; - use MapCrafting::*; - use RecipeType::*; - let s = s.trim_start_matches("minecraft:"); - match s { - "blasting" => Ok(Blasting), - "campfire_cooking" => Ok(CampfireCooking), - "crafting_shaped" => Ok(Crafting(Shaped)), - "crafting_shapeless" => Ok(Crafting(Shapeless)), - "crafting_special_bookcloning" => Ok(Crafting(Special(BookCloning))), - "crafting_special_repairitem" => Ok(Crafting(Special(RepairItem))), - "crafting_special_armordye" => Ok(Crafting(Special(ArmorDye))), - "crafting_special_firework_rocket" => Ok(Crafting(Special(Firework(Rocket)))), - "crafting_special_firework_star" => Ok(Crafting(Special(Firework(Star)))), - "crafting_special_firework_star_fade" => Ok(Crafting(Special(Firework(StarFade)))), - "crafting_special_suspiciousstew" => Ok(Crafting(Special(SuspiciousStew))), - "crafting_special_mapextending" => { - Ok(Crafting(Special(SpecialCraftingType::Map(Extending)))) - } - "crafting_special_mapcloning" => { - Ok(Crafting(Special(SpecialCraftingType::Map(Cloning)))) - } - "crafting_special_shulkerboxcoloring" => Ok(Crafting(Special(ShulkerboxColoring))), - "crafting_special_bannerduplicate" => Ok(Crafting(Special(BannerDuplicate))), - "crafting_special_shielddecoration" => Ok(Crafting(Special(ShieldDecoration))), - "crafting_special_tippedarrow" => Ok(Crafting(Special(TippedArrow))), - "crafting_decorated_pot" => Ok(Crafting(DecoratedPot)), - "crafting_transmute" => Ok(Crafting(Transmute)), - "smelting" => Ok(Smelting), - "smithing" => Ok(Smithing(SmithingType::Normal)), - "smithing_trim" => Ok(Smithing(SmithingType::Trim)), - "smithing_transform" => Ok(Smithing(SmithingType::Transform)), - "smoking" => Ok(Smoking), - "stonecutting" => Ok(StoneCutting), - _ => Err(format!("Could not find recipe with id: \"{s}\"")), - } - } -} -pub mod ingredients { - use pumpkin_util::registry::RegistryEntryList; - use serde::de::{SeqAccess, Visitor}; - use serde::{Deserialize, Deserializer}; - use std::fmt::Formatter; - - pub struct Ingredients(pub Vec); - impl<'de> Deserialize<'de> for Ingredients { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct IngredientsVisitor; - impl<'de> Visitor<'de> for IngredientsVisitor { - type Value = Ingredients; - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "valid ingredients") - } - - fn visit_seq(self, mut seq: A) -> Result - where - A: SeqAccess<'de>, - { - let mut ingredients = vec![]; - while let Some(element) = seq.next_element()? { - ingredients.push(element) - } - - Ok(Ingredients(ingredients)) - } - } - deserializer.deserialize_seq(IngredientsVisitor) - } - } -} - -#[derive(Debug)] -pub enum RecipeResult { - Many { - count: u8, - id: String, - // TODO - components: Option, - }, - Single { - id: String, - // TODO - components: Option, - }, - Special, -} - -impl RecipeResult { - pub fn id(&self) -> &str { - match self { - Self::Many { id, .. } | Self::Single { id, .. } => id, - Self::Special => "minecraft:air", - } - } -} -impl<'de> Deserialize<'de> for RecipeResult { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] - enum Fields { - Count, - Id, - Components, - } - struct ResultVisitor; - impl<'de> Visitor<'de> for ResultVisitor { - type Value = RecipeResult; - - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "valid recipe result") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut id: Option<&str> = None; - let mut count: Option = None; - let mut components: Option = None; - while let Some(key) = map.next_key()? { - match key { - Fields::Id => visit_option(&mut map, &mut id, "id")?, - Fields::Count => visit_option(&mut map, &mut count, "count")?, - Fields::Components => { - visit_option(&mut map, &mut components, "components")? - } - } - } - - let id = id - .ok_or_else(|| de::Error::missing_field("id"))? - .to_string(); - if let Some(count) = count { - Ok(RecipeResult::Many { - id, - count, - components, - }) - } else { - Ok(RecipeResult::Single { id, components }) - } - } - - fn visit_str(self, v: &str) -> Result - where - E: Error, - { - Ok(RecipeResult::Single { - id: v.to_string(), - components: None, - }) - } - } - - // Evaluate putting type constraint on `RecipeResult`, because only Crafting Transmute can call visit_str - deserializer.deserialize_any(ResultVisitor) - } -} -pub struct RecipeKeys(pub(super) HashMap); - -impl<'de> Deserialize<'de> for RecipeKeys { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - struct KeyVisitor; - impl<'de> Visitor<'de> for KeyVisitor { - type Value = HashMap; - - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "existing key inside recipe") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut return_map = HashMap::new(); - while let Some(next) = map.next_key()? { - let test: &str = next; - let c: char = test.chars().next().unwrap(); - let ingredient_type: RegistryEntryList = map.next_value()?; - return_map.insert(c, ingredient_type); - } - Ok(return_map) - } - } - deserializer.deserialize_map(KeyVisitor).map(Self) - } -} -impl<'de> Deserialize<'de> for Recipe { - fn deserialize(deserializer: D) -> Result - where - D: Deserializer<'de>, - { - #[derive(Deserialize)] - #[serde(field_identifier, rename_all = "lowercase")] - enum Fields { - Type, - Category, - Group, - Key, - Pattern, - Result, - Ingredients, - Ingredient, - - // Only exists sometimes, at least on Shaped crafting - #[serde(rename = "show_notification")] - ShowNotification, - - // Armor - Addition, - Base, - Template, - - // Smelting - CookingTime, - Experience, - - // Transmute - Input, - Material, - } - - struct RecipeVisitor; - impl<'de> Visitor<'de> for RecipeVisitor { - type Value = Recipe; - - fn expecting(&self, formatter: &mut Formatter) -> std::fmt::Result { - write!(formatter, "valid recipe") - } - - fn visit_map(self, mut map: A) -> Result - where - A: MapAccess<'de>, - { - let mut recipe_type: Option<&str> = None; - let mut category: Option<&str> = None; - let mut group: Option<&str> = None; - let mut keys: Option = None; - let mut pattern: Option> = None; - let mut result: Option = None; - let mut ingredients: Option = None; - let mut ingredient: Option = None; - let mut addition: Option = None; - let mut base: Option = None; - let mut template: Option = None; - let mut cookingtime: Option = None; - let mut experience: Option = None; - let mut show_notification: Option = None; - let mut transmute_input: Option = None; - let mut transmute_material: Option = None; - while let Some(key) = map.next_key()? { - (match key { - Fields::Type => visit_option(&mut map, &mut recipe_type, "type"), - Fields::Group => visit_option(&mut map, &mut group, "group"), - Fields::Category => visit_option(&mut map, &mut category, "group"), - Fields::Key => visit_option(&mut map, &mut keys, "key"), - Fields::Pattern => visit_option(&mut map, &mut pattern, "pattern"), - Fields::Result => visit_option(&mut map, &mut result, "result"), - Fields::Ingredients => { - visit_option(&mut map, &mut ingredients, "ingredients") - } - Fields::Ingredient => visit_option(&mut map, &mut ingredient, "ingredient"), - Fields::Addition => visit_option(&mut map, &mut addition, "addition"), - Fields::Base => visit_option(&mut map, &mut base, "base"), - Fields::Template => visit_option(&mut map, &mut template, "template"), - Fields::CookingTime => { - visit_option(&mut map, &mut cookingtime, "cookingtime") - } - Fields::Experience => visit_option(&mut map, &mut experience, "experience"), - Fields::ShowNotification => { - visit_option(&mut map, &mut show_notification, "show_notification") - } - Fields::Input => visit_option(&mut map, &mut transmute_input, "input"), - Fields::Material => { - visit_option(&mut map, &mut transmute_material, "material") - } - })? - } - - let recipe_type: RecipeType = recipe_type - .ok_or_else(|| de::Error::missing_field("type"))? - .parse() - .unwrap(); - - let result = match recipe_type { - RecipeType::Crafting(CraftingType::Special(_)) - | RecipeType::Crafting(CraftingType::DecoratedPot) - | RecipeType::Smithing(_) => RecipeResult::Special, - _ => result.ok_or_else(|| de::Error::missing_field("result"))?, - }; - - match recipe_type { - RecipeType::Crafting(CraftingType::Shaped) => { - let mut rows = [[None; 3], [None; 3], [None; 3]]; - pattern - .ok_or_else(|| de::Error::missing_field("pattern"))? - .into_iter() - .map(|s| { - let mut chars = [None; 3]; - s.chars() - .enumerate() - .for_each(|(i, char)| chars[i] = Some(char)); - chars - }) - .enumerate() - .for_each(|(i, row)| rows[i] = row); - let keys = keys.ok_or_else(|| de::Error::missing_field("keys"))?; - Ok(Recipe::from(ShapedCrafting::new(keys, rows, result))) - } - RecipeType::Crafting(CraftingType::Shapeless) => { - let ingredients = - ingredients.ok_or_else(|| de::Error::missing_field("ingredients"))?; - Ok(Recipe::from(ShapelessCrafting::new(ingredients.0, result))) - } - RecipeType::Crafting(CraftingType::Special(_)) => Ok(Recipe::from(Test { - recipe_type, - result, - })), - RecipeType::Crafting(CraftingType::DecoratedPot) => Ok(Recipe::from(Test { - recipe_type, - result, - })), - RecipeType::Crafting(CraftingType::Transmute) => { - let _input = - transmute_input.ok_or_else(|| de::Error::missing_field("input"))?; - // Maybe also has material - Ok(Recipe::from(Test { - recipe_type, - result, - })) - } - RecipeType::Smithing(_) => Ok(Recipe::from(Test { - recipe_type, - result: RecipeResult::Special, - })), - _ => Ok(Recipe::from(Test { - recipe_type, - result, - })), - } - } - } - - const FIELDS: &[&str] = &[ - "type", - "category", - "group", - "key", - "pattern", - "result", - "ingredients", - "ingredient", - "addition", - "base", - "template", - "cookingtime", - "experience", - "show_notification", - "input", - "material", - ]; - - deserializer.deserialize_struct("Recipe", FIELDS, RecipeVisitor) - } -} - -#[inline(always)] -fn visit_option<'de, T: Deserialize<'de>, Map: MapAccess<'de>>( - map: &mut Map, - option: &mut Option, - field: &'static str, -) -> Result<(), Map::Error> { - match option { - Some(_) => Err(::Error::duplicate_field(field)), - None => { - *option = Some(map.next_value()?); - Ok(()) - } - } -} - -pub struct Recipe { - pub recipe_type: RecipeType, - pattern: Vec<[[Option; 3]; 3]>, - result: RecipeResult, -} - -impl Recipe { - pub fn pattern(&self) -> &[[[Option; 3]; 3]] { - &self.pattern - } - - pub fn result(&self) -> &RecipeResult { - &self.result - } - - pub fn implemented(&self) -> bool { - match self.recipe_type { - RecipeType::Crafting(crafting_type) => { - matches!( - crafting_type, - CraftingType::Shapeless | CraftingType::Shaped - ) - } - _ => false, - } - } -} - -struct Test { - recipe_type: RecipeType, - result: RecipeResult, -} -impl RecipeTrait for Test { - fn recipe_type(&self) -> RecipeType { - self.recipe_type - } - - fn pattern(&self) -> Vec<[[Option; 3]; 3]> { - vec![[const { [const { None }; 3] }; 3]] - } - - fn result(self) -> RecipeResult { - self.result - } -} -impl From for Recipe { - fn from(recipe_type: T) -> Self { - recipe_type.to_recipe() - } -} - -pub trait RecipeTrait: Sized { - fn recipe_type(&self) -> RecipeType; - - fn pattern(&self) -> Vec<[[Option; 3]; 3]>; - - fn result(self) -> RecipeResult; - - fn to_recipe(self) -> Recipe { - Recipe { - recipe_type: self.recipe_type(), - pattern: self.pattern().into_iter().map(flatten_3x3).collect(), - result: self.result(), - } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum CraftingType { - Shapeless, - Shaped, - Special(SpecialCraftingType), - DecoratedPot, - Transmute, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SpecialCraftingType { - BookCloning, - RepairItem, - ArmorDye, - Firework(FireworkCrafting), - SuspiciousStew, - ShulkerboxColoring, - Map(MapCrafting), - BannerDuplicate, - ShieldDecoration, - TippedArrow, -} -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum FireworkCrafting { - Rocket, - Star, - StarFade, -} -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum MapCrafting { - Extending, - Cloning, -} - -#[derive(Debug, Clone, Copy, PartialEq)] -pub enum SmithingType { - Normal, - Trim, - Transform, -} diff --git a/pumpkin-registry/src/recipe/recipe_formats.rs b/pumpkin-registry/src/recipe/recipe_formats.rs deleted file mode 100644 index 1576b26cb..000000000 --- a/pumpkin-registry/src/recipe/recipe_formats.rs +++ /dev/null @@ -1,92 +0,0 @@ -use pumpkin_util::registry::RegistryEntryList; - -use super::super::recipe::RecipeType; -use super::read::{CraftingType, RecipeKeys, RecipeResult, RecipeTrait}; -pub struct ShapedCrafting { - keys: RecipeKeys, - pattern: [[Option; 3]; 3], - output: RecipeResult, -} -impl RecipeKeys { - fn pattern_to_thing( - &self, - pattern: [[Option; 3]; 3], - ) -> [[Option; 3]; 3] { - pattern - .map(|row| row.map(|maybe_char| maybe_char.and_then(|char| self.0.get(&char).cloned()))) - } -} -impl ShapedCrafting { - pub fn new(keys: RecipeKeys, pattern: [[Option; 3]; 3], output: RecipeResult) -> Self { - Self { - keys, - pattern, - output, - } - } -} - -impl RecipeTrait for ShapedCrafting { - fn recipe_type(&self) -> RecipeType { - RecipeType::Crafting(CraftingType::Shaped) - } - - fn pattern(&self) -> Vec<[[Option; 3]; 3]> { - vec![self.keys.pattern_to_thing(self.pattern)] - } - - fn result(self) -> RecipeResult { - self.output - } -} - -pub struct ShapelessCrafting { - ingredients: Vec, - output: RecipeResult, -} - -impl ShapelessCrafting { - pub(crate) fn new(ingredients: Vec, output: RecipeResult) -> Self { - Self { - ingredients, - output, - } - } -} - -impl RecipeTrait for ShapelessCrafting { - fn recipe_type(&self) -> RecipeType { - RecipeType::Crafting(CraftingType::Shapeless) - } - - // Iterating over all permutations is cheaper than resolving and iterating over all tags when trying to check if a recipe - // is correct. Otherwise, we would have to backtrack and check for each item in the recipe input, which tags they are inside, - // and then sort those permutations. - fn pattern(&self) -> Vec<[[std::option::Option; 3]; 3]> { - vec![ - self.ingredients.clone(), //.permutations(self.ingredients.len()) - ] - .into_iter() - .map(|thing| { - let mut v1 = [const { None }; 3]; - let mut v2 = [const { None }; 3]; - let mut v3 = [const { None }; 3]; - for (i, thing) in thing.into_iter().enumerate() { - if i < 3 { - v1[i] = Some(thing) - } else if i < 6 { - v2[i - 3] = Some(thing) - } else { - v3[i - 6] = Some(thing) - } - } - - [v1, v2, v3] - }) - .collect() - } - - fn result(self) -> RecipeResult { - self.output - } -} diff --git a/pumpkin-world/src/item/mod.rs b/pumpkin-world/src/item/mod.rs index afb4465bc..010c2a948 100644 --- a/pumpkin-world/src/item/mod.rs +++ b/pumpkin-world/src/item/mod.rs @@ -28,10 +28,56 @@ impl PartialEq for ItemStack { } impl ItemStack { + pub const EMPTY: ItemStack = ItemStack { + item_count: 0, + item: Item::AIR, + }; + pub fn new(item_count: u8, item: Item) -> Self { Self { item_count, item } } + pub fn get_max_stack_size(&self) -> u8 { + self.item.components.max_stack_size + } + + pub fn get_item(&self) -> &Item { + if self.is_empty() { + &Item::AIR + } else { + &self.item + } + } + + pub fn is_empty(&self) -> bool { + self.item_count == 0 || self.item.id == Item::AIR.id + } + + pub fn split(&mut self, amount: u8) -> Self { + let min = amount.min(self.item_count); + let stack = self.copy_with_count(min); + self.decrement(min); + stack + } + + pub fn copy_with_count(&self, count: u8) -> Self { + let mut stack = self.clone(); + stack.item_count = count; + stack + } + + pub fn decrement(&mut self, amount: u8) { + self.item_count = self.item_count.saturating_sub(amount); + } + + pub fn increment(&mut self, amount: u8) { + self.item_count = self.item_count.saturating_add(amount); + } + + pub fn are_items_and_components_equal(&self, other: &Self) -> bool { + self.item == other.item //TODO: && self.item.components == other.item.components + } + /// Determines the mining speed for a block based on tool rules. /// Direct matches return immediately, tagged blocks are checked separately. /// If no match is found, returns the tool's default mining speed or `1.0`. diff --git a/pumpkin/src/entity/item.rs b/pumpkin/src/entity/item.rs index 2c5ee49c8..ca812d908 100644 --- a/pumpkin/src/entity/item.rs +++ b/pumpkin/src/entity/item.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use pumpkin_data::{damage::DamageType, item::Item}; use pumpkin_protocol::{ client::play::{CTakeItemEntity, MetaDataType, Metadata}, - codec::slot::Slot, + codec::item_stack_seralizer::ItemStackSerializer, }; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::item::ItemStack; @@ -16,11 +16,10 @@ use super::{Entity, EntityBase, living::LivingEntity, player::Player}; pub struct ItemEntity { entity: Entity, - item: Item, item_age: AtomicU32, // These cannot be atomic values because we mutate their state based on what they are; we run // into the ABA problem - item_count: Mutex, + item_stack: Mutex, pickup_delay: Mutex, } @@ -36,16 +35,21 @@ impl ItemEntity { entity.yaw.store(rand::random::() * 360.0); Self { entity, - item: Item::from_id(item_id).expect("We passed a bad item id into ItemEntity"), + item_stack: Mutex::new(ItemStack::new( + count as u8, + Item::from_id(item_id).expect("We passed a bad item id into ItemEntity"), + )), item_age: AtomicU32::new(0), - item_count: Mutex::new(count), pickup_delay: Mutex::new(10), // Vanilla pickup delay is 10 ticks } } pub async fn send_meta_packet(&self) { - let slot = Slot::new(self.item.id, *self.item_count.lock().await); self.entity - .send_meta_data(&[Metadata::new(8, MetaDataType::ItemStack, &slot)]) + .send_meta_data(&[Metadata::new( + 8, + MetaDataType::ItemStack, + &ItemStackSerializer::from(self.item_stack.lock().await.clone()), + )]) .await; } } @@ -81,10 +85,11 @@ impl EntityBase for ItemEntity { let mut total_pick_up = 0; let mut slot_updates = Vec::new(); let remove_entity = { - let mut stack_size = self.item_count.lock().await; - let max_stack = self.item.components.max_stack_size; - while *stack_size > 0 { - if let Some(slot) = inv.get_pickup_item_slot(self.item.id) { + let item_stack = self.item_stack.lock().await.clone(); + let mut stack_size = item_stack.item_count; + let max_stack = item_stack.item.components.max_stack_size; + while stack_size > 0 { + if let Some(slot) = inv.get_pickup_item_slot(item_stack.item.id) { // Fill the inventory while there are items in the stack and space in the inventory let maybe_stack = inv .get_slot(slot) @@ -96,7 +101,7 @@ impl EntityBase for ItemEntity { // This is bounded to `u8::MAX` let amount_to_fill = u32::from(max_stack - existing_stack.item_count); // This is also bounded to `u8::MAX` since `amount_to_fill` is max `u8::MAX` - let amount_to_add = amount_to_fill.min(*stack_size); + let amount_to_add = amount_to_fill.min(u32::from(stack_size)); // Therefore this is safe // Update referenced stack so next call to `get_pickup_item_slot` is @@ -105,7 +110,7 @@ impl EntityBase for ItemEntity { total_pick_up += amount_to_add; debug_assert!(amount_to_add > 0); - *stack_size -= amount_to_add; + stack_size = stack_size.saturating_sub(amount_to_add as u8); slot_updates.push((slot, existing_stack.clone())); } else { @@ -114,20 +119,13 @@ impl EntityBase for ItemEntity { // This is bounded to `u8::MAX` let amount_to_fill = u32::from(max_stack); // This is also bounded to `u8::MAX` since `amount_to_fill` is max `u8::MAX` - let amount_to_add = amount_to_fill.min(*stack_size); + let amount_to_add = amount_to_fill.min(u32::from(stack_size)); total_pick_up += amount_to_add; debug_assert!(amount_to_add > 0); - *stack_size -= amount_to_add; + stack_size = stack_size.saturating_sub(amount_to_add as u8); - // Therefore this is safe - let item_stack = ItemStack::new(amount_to_add as u8, self.item.clone()); - - // Update referenced stack so next call to `get_pickup_item_slot` is - // correct - *maybe_stack = Some(item_stack.clone()); - - slot_updates.push((slot, item_stack)); + slot_updates.push((slot, self.item_stack.lock().await.clone())); } } else { // We can't pick anything else up @@ -135,7 +133,7 @@ impl EntityBase for ItemEntity { } } - *stack_size == 0 + stack_size == 0 }; if total_pick_up > 0 { diff --git a/pumpkin/src/entity/living.rs b/pumpkin/src/entity/living.rs index 5ffb63ecf..e3fec7cf8 100644 --- a/pumpkin/src/entity/living.rs +++ b/pumpkin/src/entity/living.rs @@ -12,7 +12,7 @@ use pumpkin_protocol::client::play::{CHurtAnimation, CTakeItemEntity}; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::{ client::play::{CDamageEvent, CSetEquipment, EquipmentSlot, MetaDataType, Metadata}, - codec::slot::Slot, + codec::item_stack_seralizer::ItemStackSerializer, }; use pumpkin_util::math::vector3::Vector3; use pumpkin_world::item::ItemStack; @@ -55,9 +55,9 @@ impl LivingEntity { } pub async fn send_equipment_changes(&self, equipment: &[(EquipmentSlot, ItemStack)]) { - let equipment: Vec<(EquipmentSlot, Slot)> = equipment + let equipment: Vec<(EquipmentSlot, ItemStackSerializer)> = equipment .iter() - .map(|(slot, stack)| (*slot, Slot::from(stack))) + .map(|(slot, stack)| (*slot, ItemStackSerializer::from(stack.clone()))) .collect(); self.entity .world diff --git a/pumpkin/src/net/container.rs b/pumpkin/src/net/container.rs index e34f7161e..fe2c0b48f 100644 --- a/pumpkin/src/net/container.rs +++ b/pumpkin/src/net/container.rs @@ -13,7 +13,7 @@ use pumpkin_inventory::{InventoryError, OptionallyCombinedContainer, container_c use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; -use pumpkin_protocol::codec::slot::Slot; +use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::SClickContainer; use pumpkin_util::text::TextComponent; @@ -61,16 +61,17 @@ impl Player { let container = OptionallyCombinedContainer::new(&mut inventory, container); - let slots: Vec = container + let slots: Vec = container .all_slots_ref() .into_iter() - .map(Slot::from) + .map(|i| ItemStackSerializer::from(i.unwrap_or(&ItemStack::EMPTY).clone())) .collect(); let carried_item = self.carried_item.lock().await; - let carried_item = carried_item - .as_ref() - .map_or_else(Slot::empty, std::convert::Into::into); + let carried_item = carried_item.as_ref().map_or_else( + || ItemStackSerializer::from(ItemStack::EMPTY.clone()), + |item| ItemStackSerializer::from(item.clone()), + ); inventory.increment_state_id(); let packet = CSetContainerContent::new( @@ -192,7 +193,7 @@ impl Player { let combined_container = OptionallyCombinedContainer::new(&mut inventory, Some(&mut opened_container)); if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) { - let slot = Slot::from(slot); + let slot = ItemStackSerializer::from(slot.cloned()); drop(opened_container); self.send_container_changes(server, slot_index, slot) .await?; @@ -211,7 +212,7 @@ impl Player { ) -> Result<(), InventoryError> { // TODO: this will not update hotbar when server admin is peeking // TODO: check and iterate over all players in player inventory - let slot = Slot::from(item_stack); + let slot = ItemStackSerializer::from(item_stack.cloned()); *state_id += 1; let packet = CSetContainerSlot::new(0, *state_id as i32, slot_index, &slot); self.client.enqueue_packet(&packet).await; @@ -624,7 +625,7 @@ impl Player { &self, server: &Server, slot_index: usize, - slot: Slot, + slot: ItemStackSerializer<'_>, ) -> Result<(), InventoryError> { for player in self.get_current_players_in_container(server).await { let mut inventory = player.inventory().lock().await; diff --git a/pumpkin/src/net/packet/play.rs b/pumpkin/src/net/packet/play.rs index 68ac5b343..9ea18edfd 100644 --- a/pumpkin/src/net/packet/play.rs +++ b/pumpkin/src/net/packet/play.rs @@ -38,7 +38,7 @@ use pumpkin_protocol::client::play::{ CBlockEntityData, CBlockUpdate, COpenSignEditor, CPlayerInfoUpdate, CPlayerPosition, CSetContainerSlot, CSetHeldItem, CSystemChatMessage, EquipmentSlot, InitChat, PlayerAction, }; -use pumpkin_protocol::codec::slot::Slot; +use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::codec::var_int::VarInt; use pumpkin_protocol::server::play::{ SChunkBatch, SCookieResponse as SPCookieResponse, SPlayerSession, SUpdateSign, @@ -554,7 +554,7 @@ impl Player { stack: ItemStack, ) { inventory.increment_state_id(); - let slot_data = Slot::from(&stack); + let slot_data = ItemStackSerializer::from(stack.clone()); if let Err(err) = inventory.set_slot(slot, Some(stack), false) { log::error!("Pick item set slot error: {err}"); } else { @@ -1511,13 +1511,13 @@ impl Player { } let valid_slot = packet.slot >= 0 && packet.slot as usize <= SLOT_OFFHAND; // TODO: Handle error - let item_stack = packet.clicked_item.to_stack().unwrap(); + let item_stack = packet.clicked_item.to_stack(); if valid_slot { self.inventory() .lock() .await - .set_slot(packet.slot as usize, item_stack, true)?; - } else if let Some(item_stack) = item_stack { + .set_slot(packet.slot as usize, Some(item_stack), true)?; + } else { // Item drop self.drop_item(item_stack.item.id, u32::from(item_stack.item_count)) .await;