From e0a20c1194ede743966bac97a36ac0b4469d08e4 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 12:58:12 +0200 Subject: [PATCH] Add all functionality of mode zero for packet SClickContainer --- pumpkin-inventory/src/container_click.rs | 9 +- pumpkin-inventory/src/lib.rs | 127 +++++++++++++++--- pumpkin-inventory/src/player.rs | 60 +++++++-- .../src/server/play/s_click_container.rs | 18 ++- pumpkin-protocol/src/slot.rs | 14 +- pumpkin-world/src/item/item_categories.rs | 4 +- pumpkin-world/src/item/mod.rs | 6 +- pumpkin/src/client/container.rs | 56 ++++++-- pumpkin/src/entity/player.rs | 10 +- 9 files changed, 240 insertions(+), 64 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 0267c8874..c84315195 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,5 +1,5 @@ use crate::WindowType; -use pumpkin_world::item::Item; +use pumpkin_world::item::ItemStack; pub struct ClickMode { pub slot: Slot, @@ -11,7 +11,7 @@ pub struct Click { pub state_id: u32, pub window_type: WindowType, pub changed_items: Vec, - pub carried_item: Option, + pub carried_item: Option, } impl ClickMode { @@ -166,11 +166,10 @@ pub enum ClickType { }, DoubleClick, } -#[derive(Debug)] +#[derive(Debug, PartialEq, Eq)] pub enum MouseClick { Left, Right, - Middle, } pub enum KeyClick { @@ -202,5 +201,5 @@ pub enum MouseDragState { pub enum ItemChange { Remove { slot: usize }, - Add { slot: usize, item: Item }, + Add { slot: usize, item: ItemStack }, } diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 45084c55e..f11b55b51 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,11 +1,13 @@ +use crate::container_click::MouseClick; use num_derive::{FromPrimitive, ToPrimitive}; +use pumpkin_world::item::ItemStack; pub mod container_click; pub mod player; pub mod window_property; /// https://wiki.vg/Inventory -#[derive(Debug, ToPrimitive, FromPrimitive, Clone)] +#[derive(Debug, ToPrimitive, FromPrimitive, Clone, Eq, PartialEq)] pub enum WindowType { // not used Generic9x1, @@ -55,25 +57,120 @@ impl WindowType { } pub trait Container { - const SLOTS: usize; + fn window_type(&self) -> &WindowType; - fn item_mut(&mut self, slot: usize) -> &mut Item; + fn handle_item_change( + &mut self, + carried_item: &mut Option, + slot: usize, + mouse_click: MouseClick, + ); +} + +pub fn handle_item_take( + carried_item: &mut Option, + item_slot: &mut Option, + mouse_click: MouseClick, +) { + let Some(item) = item_slot else { + return; + }; + let mut new_item = *item; + + match mouse_click { + MouseClick::Left => { + *item_slot = None; + } + MouseClick::Right => { + let half = item.item_count / 2; + item.item_count -= half; + new_item.item_count = half; + } + } + *carried_item = Some(new_item); +} +pub fn handle_item_change( + carried_slot: &mut Option, + current_slot: &mut Option, + mouse_click: MouseClick, +) { + match (current_slot.as_mut(), carried_slot.as_mut()) { + // Swap or combine current and carried + (Some(current), Some(carried)) => { + if current.item_id == carried.item_id { + combine_stacks(carried_slot, current, mouse_click); + } else if mouse_click == MouseClick::Left { + let carried = *carried; + *carried_slot = Some(current.to_owned()); + *current_slot = Some(carried.to_owned()); + } + } + // Put held stack into empty slot + (None, Some(carried)) => match mouse_click { + MouseClick::Left => { + *current_slot = Some(carried.to_owned()); + *carried_slot = None; + } + MouseClick::Right => { + carried.item_count -= 1; + let mut new = *carried; + new.item_count = 1; + *current_slot = Some(new); + } + }, + // Take stack into carried + (Some(_current), None) => handle_item_take(carried_slot, current_slot, mouse_click), + (None, None) => (), + } +} + +pub fn combine_stacks( + carried_slot: &mut Option, + slot: &mut ItemStack, + mouse_click: MouseClick, +) { + let Some(carried_item) = carried_slot else { + return; + }; + + let carried_change = match mouse_click { + MouseClick::Left => carried_item.item_count, + MouseClick::Right => 1, + }; + + // TODO: Check for item stack max size here + if slot.item_count + carried_change <= 64 { + slot.item_count += carried_change; + carried_item.item_count -= carried_change; + if carried_item.item_count == 0 { + *carried_slot = None; + } + } else { + let left_over = slot.item_count + carried_change - 64; + slot.item_count = 64; + carried_item.item_count = left_over; + } } pub struct ContainerStruct { - slots: [Option; SLOTS], - state_id: usize, - open_by: Option>, + slots: [Option; SLOTS], + _state_id: usize, + _open_by: Option>, + window_type: WindowType, } -impl ContainerStruct { - pub fn take_item(&mut self, slot: usize) -> Item { - assert!(slot < T); - let Some(item) = self.slots[slot] else { - panic!() - }; - self.slots[slot] = None; - self.state_id += 1; - item +impl Container for ContainerStruct { + fn window_type(&self) -> &WindowType { + &self.window_type + } + + fn handle_item_change( + &mut self, + carried_slot: &mut Option, + slot: usize, + mouse_click: MouseClick, + ) { + let current_slot = &mut self.slots[slot]; + handle_item_change(carried_slot, current_slot, mouse_click) } } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index fd92220a9..c6cdd0fa7 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -1,13 +1,16 @@ -use pumpkin_world::item::Item; +use crate::container_click::MouseClick; +use crate::{handle_item_change, Container, WindowType}; +use pumpkin_world::item::ItemStack; #[allow(dead_code)] +#[derive(Debug)] pub struct PlayerInventory { // Main Inventory + Hotbar - crafting: [Option; 4], - crafting_output: Option, - items: [Option; 36], - armor: [Option; 4], - offhand: Option, + crafting: [Option; 4], + crafting_output: Option, + items: [Option; 36], + armor: [Option; 4], + offhand: Option, // current selected slot in hotbar selected: usize, } @@ -42,7 +45,7 @@ impl PlayerInventory { /// ## Item allowed override /// An override, which when enabled, makes it so that invalid items, can be placed in slots they normally can't. /// Useful functionality for plugins in the future. - pub fn set_slot(&mut self, slot: usize, item: Option, item_allowed_override: bool) { + pub fn set_slot(&mut self, slot: usize, item: Option, item_allowed_override: bool) { match slot { 0 => { // TODO: Add crafting check here @@ -85,18 +88,30 @@ impl PlayerInventory { _ => unreachable!(), } } - + pub fn get_slot(&mut self, slot: usize) -> &mut Option { + match slot { + 0 => { + // TODO: Add crafting check here + &mut self.crafting_output + } + 1..=4 => &mut self.crafting[slot - 1], + 5..=8 => &mut self.armor[slot - 5], + 9..=44 => &mut self.items[slot - 9], + 45 => &mut self.offhand, + _ => unreachable!(), + } + } pub fn set_selected(&mut self, slot: usize) { assert!((0..9).contains(&slot)); self.selected = slot; } - pub fn held_item(&self) -> Option<&Item> { + pub fn held_item(&self) -> Option<&ItemStack> { debug_assert!((0..9).contains(&self.selected)); self.items[self.selected + 36 - 9].as_ref() } - pub fn slots(&self) -> Vec> { + pub fn slots(&self) -> Vec> { let mut slots = vec![self.crafting_output.as_ref()]; slots.extend(self.crafting.iter().map(|c| c.as_ref())); slots.extend(self.armor.iter().map(|c| c.as_ref())); @@ -104,4 +119,29 @@ impl PlayerInventory { slots.push(self.offhand.as_ref()); slots } + + pub fn slots_mut(&mut self) -> Vec<&mut Option> { + let mut slots = vec![&mut self.crafting_output]; + slots.extend(self.crafting.iter_mut()); + slots.extend(self.armor.iter_mut()); + slots.extend(self.items.iter_mut()); + slots.push(&mut self.offhand); + slots + } +} + +impl Container for PlayerInventory { + fn window_type(&self) -> &WindowType { + &WindowType::Generic9x1 + } + + fn handle_item_change( + &mut self, + carried_slot: &mut Option, + slot: usize, + mouse_click: MouseClick, + ) { + let item_slot = self.get_slot(slot); + handle_item_change(carried_slot, item_slot, mouse_click) + } } diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs index d1a169830..fd5e56aac 100644 --- a/pumpkin-protocol/src/server/play/s_click_container.rs +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -4,6 +4,7 @@ use pumpkin_macros::packet; use serde::de::SeqAccess; use serde::{de, Deserialize}; +#[derive(Debug)] #[packet(0x0E)] pub struct SClickContainer { pub window_id: u8, @@ -52,12 +53,17 @@ impl<'de> Deserialize<'de> for SClickContainer { let length_of_array = seq .next_element::()? .ok_or(de::Error::custom("Failed to decode VarInt"))?; - let array_of_changed_slots = if length_of_array.0 != 0 { - seq.next_element::>()? - .ok_or(de::Error::custom("Unable to parse changed slots list"))? - } else { - vec![] - }; + let mut array_of_changed_slots = vec![]; + for _ in 0..length_of_array.0 { + let slot_number = seq + .next_element::()? + .ok_or(de::Error::custom("Unable to parse slot"))?; + let slot = seq + .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::()? .ok_or(de::Error::custom("Failed to decode carried item"))?; diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/slot.rs index 2ccab79b9..1b2727ba3 100644 --- a/pumpkin-protocol/src/slot.rs +++ b/pumpkin-protocol/src/slot.rs @@ -1,5 +1,5 @@ use crate::VarInt; -use pumpkin_world::item::Item; +use pumpkin_world::item::ItemStack; use serde::ser::SerializeSeq; use serde::{ de::{self, SeqAccess}, @@ -130,9 +130,9 @@ impl Serialize for Slot { } impl Slot { - pub fn to_item(self) -> Option { + pub fn to_item(self) -> Option { let item_id = self.item_id?.0.try_into().unwrap(); - Some(Item { + Some(ItemStack { item_id, item_count: self.item_count.0.try_into().unwrap(), }) @@ -150,8 +150,8 @@ impl Slot { } } -impl From<&Item> for Slot { - fn from(item: &Item) -> Self { +impl From<&ItemStack> for Slot { + fn from(item: &ItemStack) -> Self { Slot { item_count: item.item_count.into(), item_id: Some(item.item_id.into()), @@ -164,8 +164,8 @@ impl From<&Item> for Slot { } } -impl From> for Slot { - fn from(item: Option<&Item>) -> Self { +impl From> for Slot { + fn from(item: Option<&ItemStack>) -> Self { item.map(Slot::from).unwrap_or(Slot::empty()) } } diff --git a/pumpkin-world/src/item/item_categories.rs b/pumpkin-world/src/item/item_categories.rs index 6543f348a..c0f8967bc 100644 --- a/pumpkin-world/src/item/item_categories.rs +++ b/pumpkin-world/src/item/item_categories.rs @@ -1,6 +1,6 @@ -use crate::item::Item; +use crate::item::ItemStack; -impl Item { +impl ItemStack { pub fn is_helmet(&self) -> bool { [ // Leather diff --git a/pumpkin-world/src/item/mod.rs b/pumpkin-world/src/item/mod.rs index 65c9dbfe8..5f5a588d3 100644 --- a/pumpkin-world/src/item/mod.rs +++ b/pumpkin-world/src/item/mod.rs @@ -11,9 +11,9 @@ pub enum Rarity { Epic, } -#[derive(Clone, Copy)] -pub struct Item { - pub item_count: u32, +#[derive(Clone, Copy, Debug)] +pub struct ItemStack { + pub item_count: u8, // This ID is the numerical protocol ID, not the usual minecraft::block ID. pub item_id: u32, // TODO: Add Item Components diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index d54844b52..f3310dfd0 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,17 +1,17 @@ +use num_traits::FromPrimitive; use pumpkin_core::text::TextComponent; +use pumpkin_inventory::container_click; use pumpkin_inventory::container_click::MouseClick; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; -use pumpkin_inventory::WindowType; -use pumpkin_inventory::{container_click, ContainerStruct}; +use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; -use pumpkin_world::item::Item; +use pumpkin_world::item::ItemStack; use crate::entity::player::Player; -use crate::server::Server; impl Player { pub fn open_container( @@ -19,8 +19,8 @@ impl Player { window_type: WindowType, minecraft_menu_id: &str, window_title: Option<&str>, - items: Option>>, - carried_item: Option<&Item>, + items: Option>>, + carried_item: Option<&ItemStack>, ) { let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") @@ -43,8 +43,8 @@ impl Player { pub fn set_container_content<'a>( &mut self, window_type: WindowType, - items: Option>>, - carried_item: Option<&'a Item>, + items: Option>>, + carried_item: Option<&'a ItemStack>, ) { let slots: Vec = { if let Some(mut items) = items { @@ -80,7 +80,7 @@ impl Player { &mut self, window_type: WindowType, slot: usize, - item: Option<&Item>, + item: Option<&ItemStack>, ) { self.client.send_packet(&CSetContainerSlot::new( window_type as i8, @@ -136,13 +136,45 @@ impl Player { }; match click.mode.click_type { - ClickType::MouseClick(mouse_click) => self.mouse_click(mouse_click), + ClickType::MouseClick(mouse_click) => { + self.mouse_click(mouse_click, click.window_type, click.mode.slot) + } _ => todo!(), } } - pub fn mouse_click(&mut self, mouse_click: MouseClick) { - dbg!(mouse_click); + pub fn mouse_click( + &mut self, + mouse_click: MouseClick, + window_type: WindowType, + slot: container_click::Slot, + ) { + let Some(_) = &self.open_container else { + // Inventory + if window_type == WindowType::Generic9x1 { + match slot { + container_click::Slot::Normal(slot) => { + self.inventory + .handle_item_change(&mut self.carried_item, slot, mouse_click) + } + container_click::Slot::OutsideInventory => (), + }; + + dbg!(&self.carried_item); + let filled_inventory_slots = self + .inventory + .slots() + .into_iter() + .enumerate() + .filter_map(|(slot, item)| item.map(|item| (slot, item.item_count))) + .collect::>(); + dbg!(filled_inventory_slots); + + return; + } else { + return; + } + }; } } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index cb5ecf52a..bf53dcd50 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,10 +1,12 @@ use std::str::FromStr; +use crate::{client::Client, server::Server}; use num_derive::{FromPrimitive, ToPrimitive}; use num_traits::ToPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_entity::{entity_type::EntityType, Entity, EntityId}; use pumpkin_inventory::player::PlayerInventory; +use pumpkin_inventory::Container; use pumpkin_protocol::{ bytebuf::packet_id::Packet, client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage}, @@ -16,11 +18,10 @@ use pumpkin_protocol::{ }, ConnectionState, RawPacket, ServerPacket, VarInt, }; +use pumpkin_world::item::ItemStack; use pumpkin_world::vector3::Vector3; use serde::{Deserialize, Serialize}; -use crate::{client::Client, server::Server}; - pub struct Player { pub client: Client, pub entity: Entity, @@ -31,8 +32,8 @@ pub struct Player { pub food: i32, pub food_saturation: f32, pub inventory: PlayerInventory, - pub open_container: Option<()>, - + pub open_container: Option>, + pub carried_item: Option, // Client side value, Should be not trusted pub on_ground: bool, @@ -66,6 +67,7 @@ impl Player { velocity: Vector3::new(0.0, 0.0, 0.0), inventory: PlayerInventory::new(), open_container: None, + carried_item: None, gamemode, } }