From 507b5213402f49f506a91533e51cab7d9ed39f1d Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 24 Aug 2024 19:15:03 +0200 Subject: [PATCH 01/36] add packet SClickContainer --- pumpkin-protocol/src/server/play/mod.rs | 1 + .../src/server/play/s_click_container.rs | 17 +++++++++++++++++ 2 files changed, 18 insertions(+) create mode 100644 pumpkin-protocol/src/server/play/s_click_container.rs diff --git a/pumpkin-protocol/src/server/play/mod.rs b/pumpkin-protocol/src/server/play/mod.rs index 2cccb9c2e..dbe32cc89 100644 --- a/pumpkin-protocol/src/server/play/mod.rs +++ b/pumpkin-protocol/src/server/play/mod.rs @@ -1,5 +1,6 @@ mod s_chat_command; mod s_chat_message; +mod s_click_container; mod s_client_information; mod s_close_container; mod s_confirm_teleport; diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs new file mode 100644 index 000000000..fe5f747a5 --- /dev/null +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -0,0 +1,17 @@ +use crate::slot::Slot; +use crate::VarInt; +use pumpkin_macros::packet; +use serde::Deserialize; + +#[derive(Deserialize)] +#[packet(0x0E)] +pub struct SClickContainer { + window_id: u8, + state_id: VarInt, + slot: Slot, + button: i8, + mode: VarInt, + length_of_array: VarInt, + array_of_changed_slots: Vec<(i16, Slot)>, + carried_item: Slot, +} From 83231f4271104421b9cac5239ff6eec6fd3a630d Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 24 Aug 2024 19:15:26 +0200 Subject: [PATCH 02/36] add container click abstraction --- pumpkin-inventory/src/container_click.rs | 190 +++++++++++++++++++++++ pumpkin-inventory/src/lib.rs | 1 + 2 files changed, 191 insertions(+) create mode 100644 pumpkin-inventory/src/container_click.rs diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs new file mode 100644 index 000000000..34c1264fb --- /dev/null +++ b/pumpkin-inventory/src/container_click.rs @@ -0,0 +1,190 @@ +pub struct ClickMode { + slot: Slot, + click_type: ClickType, +} + +impl ClickMode { + fn new(mode: u8, button: i8, slot: i16, total_slots: usize) -> Self { + assert!(slot < total_slots.try_into().unwrap()); + match mode { + 0 => Self::new_normal_click(button, slot), + // Both buttons do the same here, so we omit it + 1 => Self::new_shift_click(slot), + 2 => Self::new_key_click(button, slot), + 3 => Self { + click_type: ClickType::CreativePickItem, + slot: Slot::Normal(slot.try_into().unwrap()), + }, + 4 => Self::new_drop_item(button, slot), + 5 => Self::new_drag_item(button, slot), + 6 => Self { + click_type: ClickType::DoubleClick, + slot: Slot::Normal(slot.try_into().unwrap()), + }, + // TODO: Error handling + _ => unreachable!(), + } + } + + fn new_normal_click(button: i8, slot: i16) -> Self { + let slot = match slot { + -999 => Slot::OutsideInventory, + _ => { + // TODO: Error here + Slot::Normal(slot.try_into().unwrap()) + } + }; + let button = match button { + 0 => Click::LeftClick, + 1 => Click::RightClick, + // TODO: Error here + _ => unreachable!(), + }; + Self { + click_type: ClickType::NormalClick(button), + slot, + } + } + + fn new_shift_click(slot: i16) -> Self { + Self { + // TODO: Error handle this + slot: Slot::Normal(slot.try_into().unwrap()), + click_type: ClickType::ShiftClick, + } + } + + fn new_key_click(button: i8, slot: i16) -> Self { + let key = match button { + 0..9 => KeyClick::Slot(button.try_into().unwrap()), + 40 => KeyClick::Offhand, + // TODO: Error handling here + _ => unreachable!(), + }; + + Self { + click_type: ClickType::KeyClick(key), + slot: Slot::Normal(slot.try_into().unwrap()), + } + } + + fn new_drop_item(button: i8, slot: i16) -> Self { + let drop_type = match button { + 0 => DropType::SingleItem, + 1 => DropType::FullStack, + // TODO: Error handling + _ => unreachable!(), + }; + Self { + click_type: ClickType::DropType(drop_type), + slot: Slot::Normal(slot.try_into().unwrap()), + } + } + + fn new_drag_item(button: i8, slot: i16) -> Self { + let (mouse_type, state, slot) = match button { + 0 => ( + MouseDragType::LeftMouse, + MouseDragState::Start, + Slot::OutsideInventory, + ), + 1 => ( + MouseDragType::LeftMouse, + MouseDragState::AddSlot, + Slot::Normal(slot.try_into().unwrap()), + ), + 2 => ( + MouseDragType::LeftMouse, + MouseDragState::End, + Slot::OutsideInventory, + ), + + 4 => ( + MouseDragType::RightMouse, + MouseDragState::Start, + Slot::OutsideInventory, + ), + 5 => ( + MouseDragType::RightMouse, + MouseDragState::AddSlot, + Slot::Normal(slot.try_into().unwrap()), + ), + 6 => ( + MouseDragType::RightMouse, + MouseDragState::End, + Slot::OutsideInventory, + ), + + // ONLY FOR CREATIVE + 8 => ( + MouseDragType::MiddleMouse, + MouseDragState::Start, + Slot::OutsideInventory, + ), + 9 => ( + MouseDragType::MiddleMouse, + MouseDragState::AddSlot, + Slot::Normal(slot.try_into().unwrap()), + ), + 10 => ( + MouseDragType::MiddleMouse, + MouseDragState::End, + Slot::OutsideInventory, + ), + // TODO: Error handling + _ => unreachable!(), + }; + Self { + click_type: ClickType::MouseDrag { + drag_state: state, + drag_type: mouse_type, + }, + slot, + } + } +} + +enum ClickType { + NormalClick(Click), + ShiftClick, + KeyClick(KeyClick), + CreativePickItem, + DropType(DropType), + MouseDrag { + drag_state: MouseDragState, + drag_type: MouseDragType, + }, + DoubleClick, +} +enum Click { + LeftClick, + RightClick, + MiddleClick, +} + +enum KeyClick { + Slot(u8), + Offhand, +} + +enum Slot { + Normal(usize), + OutsideInventory, +} + +enum DropType { + SingleItem, + FullStack, +} + +enum MouseDragType { + LeftMouse, + RightMouse, + MiddleMouse, +} + +enum MouseDragState { + Start, + AddSlot, + End, +} diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index dadb10cc6..3baf5f9fd 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,5 +1,6 @@ use num_derive::{FromPrimitive, ToPrimitive}; +mod container_click; pub mod player; pub mod window_property; From c408244b37d9911eaa97187c33a684c1a1414882 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 24 Aug 2024 20:05:49 +0200 Subject: [PATCH 03/36] pass CI --- pumpkin-inventory/src/container_click.rs | 36 ++++++++++--------- .../src/server/play/s_click_container.rs | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 34c1264fb..53103e3ce 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,3 +1,5 @@ +#![allow(dead_code, unused)] + pub struct ClickMode { slot: Slot, click_type: ClickType, @@ -35,8 +37,8 @@ impl ClickMode { } }; let button = match button { - 0 => Click::LeftClick, - 1 => Click::RightClick, + 0 => Click::Left, + 1 => Click::Right, // TODO: Error here _ => unreachable!(), }; @@ -84,50 +86,50 @@ impl ClickMode { fn new_drag_item(button: i8, slot: i16) -> Self { let (mouse_type, state, slot) = match button { 0 => ( - MouseDragType::LeftMouse, + MouseDragType::Left, MouseDragState::Start, Slot::OutsideInventory, ), 1 => ( - MouseDragType::LeftMouse, + MouseDragType::Left, MouseDragState::AddSlot, Slot::Normal(slot.try_into().unwrap()), ), 2 => ( - MouseDragType::LeftMouse, + MouseDragType::Left, MouseDragState::End, Slot::OutsideInventory, ), 4 => ( - MouseDragType::RightMouse, + MouseDragType::Right, MouseDragState::Start, Slot::OutsideInventory, ), 5 => ( - MouseDragType::RightMouse, + MouseDragType::Right, MouseDragState::AddSlot, Slot::Normal(slot.try_into().unwrap()), ), 6 => ( - MouseDragType::RightMouse, + MouseDragType::Right, MouseDragState::End, Slot::OutsideInventory, ), // ONLY FOR CREATIVE 8 => ( - MouseDragType::MiddleMouse, + MouseDragType::Middle, MouseDragState::Start, Slot::OutsideInventory, ), 9 => ( - MouseDragType::MiddleMouse, + MouseDragType::Middle, MouseDragState::AddSlot, Slot::Normal(slot.try_into().unwrap()), ), 10 => ( - MouseDragType::MiddleMouse, + MouseDragType::Middle, MouseDragState::End, Slot::OutsideInventory, ), @@ -157,9 +159,9 @@ enum ClickType { DoubleClick, } enum Click { - LeftClick, - RightClick, - MiddleClick, + Left, + Right, + Middle, } enum KeyClick { @@ -178,9 +180,9 @@ enum DropType { } enum MouseDragType { - LeftMouse, - RightMouse, - MiddleMouse, + Left, + Right, + Middle, } enum MouseDragState { diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs index fe5f747a5..62377baad 100644 --- a/pumpkin-protocol/src/server/play/s_click_container.rs +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -2,7 +2,7 @@ use crate::slot::Slot; use crate::VarInt; use pumpkin_macros::packet; use serde::Deserialize; - +#[allow(dead_code)] #[derive(Deserialize)] #[packet(0x0E)] pub struct SClickContainer { From c968131e04e66bd47c8e7172616ab6e88206c34f Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sun, 25 Aug 2024 11:47:53 +0200 Subject: [PATCH 04/36] make packet public and more abstraction thing --- pumpkin-inventory/src/container_click.rs | 37 +++++++++++++++--------- pumpkin-inventory/src/lib.rs | 2 +- pumpkin-protocol/src/server/play/mod.rs | 1 + pumpkin/src/client/container.rs | 3 ++ 4 files changed, 28 insertions(+), 15 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 53103e3ce..bcc4647ca 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,8 +1,17 @@ -#![allow(dead_code, unused)] +use crate::WindowType; +use pumpkin_world::item::Item; pub struct ClickMode { - slot: Slot, - click_type: ClickType, + pub slot: Slot, + pub click_type: ClickType, +} + +pub struct Click { + pub mode: ClickMode, + pub state_id: u32, + pub window_type: WindowType, + pub changed_items: Vec<(usize, Item)>, + pub carried_item: Option, } impl ClickMode { @@ -37,13 +46,13 @@ impl ClickMode { } }; let button = match button { - 0 => Click::Left, - 1 => Click::Right, + 0 => MouseClick::Left, + 1 => MouseClick::Right, // TODO: Error here _ => unreachable!(), }; Self { - click_type: ClickType::NormalClick(button), + click_type: ClickType::MouseClick(button), slot, } } @@ -146,8 +155,8 @@ impl ClickMode { } } -enum ClickType { - NormalClick(Click), +pub enum ClickType { + MouseClick(MouseClick), ShiftClick, KeyClick(KeyClick), CreativePickItem, @@ -158,34 +167,34 @@ enum ClickType { }, DoubleClick, } -enum Click { +pub enum MouseClick { Left, Right, Middle, } -enum KeyClick { +pub enum KeyClick { Slot(u8), Offhand, } -enum Slot { +pub enum Slot { Normal(usize), OutsideInventory, } -enum DropType { +pub enum DropType { SingleItem, FullStack, } -enum MouseDragType { +pub enum MouseDragType { Left, Right, Middle, } -enum MouseDragState { +pub enum MouseDragState { Start, AddSlot, End, diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 3baf5f9fd..3c6bc7fc6 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,6 +1,6 @@ use num_derive::{FromPrimitive, ToPrimitive}; -mod container_click; +pub mod container_click; pub mod player; pub mod window_property; diff --git a/pumpkin-protocol/src/server/play/mod.rs b/pumpkin-protocol/src/server/play/mod.rs index dbe32cc89..3fda899a0 100644 --- a/pumpkin-protocol/src/server/play/mod.rs +++ b/pumpkin-protocol/src/server/play/mod.rs @@ -18,6 +18,7 @@ mod s_use_item_on; pub use s_chat_command::*; pub use s_chat_message::*; +pub use s_click_container::*; pub use s_client_information::*; pub use s_close_container::*; pub use s_confirm_teleport::*; diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index fc9cf1059..db27221e4 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,9 +1,12 @@ use pumpkin_core::text::TextComponent; +use pumpkin_inventory::container_click; +use pumpkin_inventory::container_click::KeyClick; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::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; From 7b05ded0b3cc91e7028a7b41e6c898e74998894a Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 08:46:57 +0200 Subject: [PATCH 05/36] fix deserialization of SClickContainer --- .../src/server/play/s_click_container.rs | 85 ++++++++++++++++--- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/pumpkin-protocol/src/server/play/s_click_container.rs b/pumpkin-protocol/src/server/play/s_click_container.rs index 62377baad..d1a169830 100644 --- a/pumpkin-protocol/src/server/play/s_click_container.rs +++ b/pumpkin-protocol/src/server/play/s_click_container.rs @@ -1,17 +1,80 @@ use crate::slot::Slot; use crate::VarInt; use pumpkin_macros::packet; -use serde::Deserialize; -#[allow(dead_code)] -#[derive(Deserialize)] +use serde::de::SeqAccess; +use serde::{de, Deserialize}; + #[packet(0x0E)] pub struct SClickContainer { - window_id: u8, - state_id: VarInt, - slot: Slot, - button: i8, - mode: VarInt, - length_of_array: VarInt, - array_of_changed_slots: Vec<(i16, Slot)>, - carried_item: Slot, + pub window_id: u8, + pub state_id: VarInt, + pub slot: i16, + pub button: i8, + pub mode: VarInt, + pub length_of_array: VarInt, + pub array_of_changed_slots: Vec<(i16, Slot)>, + pub carried_item: Slot, +} + +impl<'de> Deserialize<'de> for SClickContainer { + fn deserialize(deserializer: D) -> Result + where + D: de::Deserializer<'de>, + { + struct Visitor; + impl<'de> de::Visitor<'de> for Visitor { + type Value = SClickContainer; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a valid VarInt encoded in a byte sequence") + } + + fn visit_seq(self, mut seq: A) -> Result + where + A: SeqAccess<'de>, + { + let window_id = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode u8"))?; + let state_id = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?; + + let slot = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode i16"))?; + let button = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode i8"))?; + let mode = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode VarInt"))?; + 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 carried_item = seq + .next_element::()? + .ok_or(de::Error::custom("Failed to decode carried item"))?; + + Ok(SClickContainer { + window_id, + state_id, + slot, + button, + mode, + length_of_array, + array_of_changed_slots, + carried_item, + }) + } + } + + deserializer.deserialize_seq(Visitor) + } } From b90fd4c62d4e1eab3343517dbf38b7021dc71023 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 08:48:35 +0200 Subject: [PATCH 06/36] add debug of mouse_click --- pumpkin-inventory/src/container_click.rs | 11 ++++-- pumpkin-inventory/src/lib.rs | 24 ++++++++++++ pumpkin/src/client/container.rs | 50 +++++++++++++++++++++++- pumpkin/src/entity/player.rs | 12 ++++-- 4 files changed, 89 insertions(+), 8 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index bcc4647ca..0267c8874 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -10,13 +10,12 @@ pub struct Click { pub mode: ClickMode, pub state_id: u32, pub window_type: WindowType, - pub changed_items: Vec<(usize, Item)>, + pub changed_items: Vec, pub carried_item: Option, } impl ClickMode { - fn new(mode: u8, button: i8, slot: i16, total_slots: usize) -> Self { - assert!(slot < total_slots.try_into().unwrap()); + pub fn new(mode: u8, button: i8, slot: i16) -> Self { match mode { 0 => Self::new_normal_click(button, slot), // Both buttons do the same here, so we omit it @@ -167,6 +166,7 @@ pub enum ClickType { }, DoubleClick, } +#[derive(Debug)] pub enum MouseClick { Left, Right, @@ -199,3 +199,8 @@ pub enum MouseDragState { AddSlot, End, } + +pub enum ItemChange { + Remove { slot: usize }, + Add { slot: usize, item: Item }, +} diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 3c6bc7fc6..45084c55e 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -53,3 +53,27 @@ impl WindowType { "WINDOW TITLE" } } + +pub trait Container { + const SLOTS: usize; + + fn item_mut(&mut self, slot: usize) -> &mut Item; +} + +pub struct ContainerStruct { + slots: [Option; SLOTS], + state_id: usize, + open_by: Option>, +} + +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 + } +} diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index db27221e4..d54844b52 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,8 +1,8 @@ use pumpkin_core::text::TextComponent; -use pumpkin_inventory::container_click; -use pumpkin_inventory::container_click::KeyClick; +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_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; @@ -11,6 +11,7 @@ use pumpkin_protocol::slot::Slot; use pumpkin_world::item::Item; use crate::entity::player::Player; +use crate::server::Server; impl Player { pub fn open_container( @@ -104,4 +105,49 @@ impl Player { self.client .send_packet(&CSetContainerProperty::new(window_type as u8, id, value)); } + + pub fn handle_click_container(&mut self, packet: SClickContainer) { + use container_click::*; + let click = Click { + state_id: packet.state_id.0.try_into().unwrap(), + changed_items: packet + .array_of_changed_slots + .into_iter() + .map(|(slot, item)| { + let slot = slot.try_into().unwrap(); + if let Some(item) = item.to_item() { + ItemChange::Add { slot, item } + } else { + ItemChange::Remove { slot } + } + }) + .collect::>(), + window_type: WindowType::from_u8(packet.window_id).unwrap(), + carried_item: packet.carried_item.to_item(), + mode: ClickMode::new( + packet + .mode + .0 + .try_into() + .expect("Mode can only be between 0-6"), + packet.button, + packet.slot, + ), + }; + + match click.mode.click_type { + ClickType::MouseClick(mouse_click) => self.mouse_click(mouse_click), + _ => todo!(), + } + } + + pub fn mouse_click(&mut self, mouse_click: MouseClick) { + dbg!(mouse_click); + } } + +/*impl ContainerStruct { + pub fn opened_by_players(&mut self, server: Server) -> Vec<&Player> { + + } +}*/ diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 935ba7cd4..cb5ecf52a 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -9,9 +9,10 @@ use pumpkin_protocol::{ bytebuf::packet_id::Packet, client::play::{CGameEvent, CPlayDisconnect, CSyncPlayerPosition, CSystemChatMessage}, server::play::{ - SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, - SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, - SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm, SUseItemOn, + SChatCommand, SChatMessage, SClickContainer, SClientInformationPlay, SConfirmTeleport, + SInteract, SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, + SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSwingArm, + SUseItemOn, }, ConnectionState, RawPacket, ServerPacket, VarInt, }; @@ -30,6 +31,7 @@ pub struct Player { pub food: i32, pub food_saturation: f32, pub inventory: PlayerInventory, + pub open_container: Option<()>, // Client side value, Should be not trusted pub on_ground: bool, @@ -63,6 +65,7 @@ impl Player { current_block_destroy_stage: 0, velocity: Vector3::new(0.0, 0.0, 0.0), inventory: PlayerInventory::new(), + open_container: None, gamemode, } } @@ -195,6 +198,9 @@ impl Player { SPlayPingRequest::PACKET_ID => { self.handle_play_ping_request(server, SPlayPingRequest::read(bytebuf).unwrap()) } + SClickContainer::PACKET_ID => { + self.handle_click_container(SClickContainer::read(bytebuf).unwrap()) + } _ => log::error!("Failed to handle player packet id {:#04x}", packet.id.0), } } From e0a20c1194ede743966bac97a36ac0b4469d08e4 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 12:58:12 +0200 Subject: [PATCH 07/36] 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, } } From 10e0600df7eca7a9a9a8c91f42ff83041002705f Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 13:02:13 +0200 Subject: [PATCH 08/36] remove debug --- pumpkin/src/client/container.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index f3310dfd0..61747cac5 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -160,16 +160,6 @@ impl Player { 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; From b027c8830128833257c53cd0118304ab6343ac28 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 26 Aug 2024 14:46:16 +0200 Subject: [PATCH 09/36] add shift click to packet SClickContainer --- pumpkin/src/client/container.rs | 62 +++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 61747cac5..3bbb7cd25 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -139,8 +139,18 @@ impl Player { ClickType::MouseClick(mouse_click) => { self.mouse_click(mouse_click, click.window_type, click.mode.slot) } + ClickType::ShiftClick => self.shift_mouse_click(click.window_type, click.mode.slot), _ => todo!(), } + 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); } pub fn mouse_click( @@ -166,6 +176,58 @@ impl Player { } }; } + + pub fn shift_mouse_click(&mut self, 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) => { + if let Some(item_in_pressed_slot) = self.inventory.slots()[slot] { + let mut slots = self.inventory.slots().into_iter().enumerate(); + // Hotbar + let slots = if slot > 35 { + slots + .skip(9) + .find(|(_, slot)| { + slot.is_none_or(|item| { + item.item_id == item_in_pressed_slot.item_id + }) + }) + .map(|(slot_num, _)| slot_num) + } else { + slots + .skip(36) + .rev() + .find(|(_, slot)| { + slot.is_none_or(|item| { + item.item_id == item_in_pressed_slot.item_id + }) + }) + .map(|(slot_num, _)| slot_num) + }; + if let Some(slot) = slots { + let mut item_slot = + self.inventory.slots()[slot].map(|i| i.to_owned()); + + self.inventory.handle_item_change( + &mut item_slot, + slot, + MouseClick::Left, + ); + *self.inventory.slots_mut()[slot] = item_slot; + } + } + } + container_click::Slot::OutsideInventory => (), + }; + + return; + } else { + return; + } + }; + } } /*impl ContainerStruct { From 5e466a18985ef958b5313d27e92976f1b3332c09 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 27 Aug 2024 09:23:58 +0200 Subject: [PATCH 10/36] simplify shift mouse click function --- pumpkin/src/client/container.rs | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 3bbb7cd25..db894d31d 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -3,6 +3,7 @@ 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::{container_click, handle_item_change}; use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, @@ -184,26 +185,22 @@ impl Player { match slot { container_click::Slot::Normal(slot) => { if let Some(item_in_pressed_slot) = self.inventory.slots()[slot] { - let mut slots = self.inventory.slots().into_iter().enumerate(); + let slots = self.inventory.slots().into_iter().enumerate(); // Hotbar + let find_condition = |(_, slot): &(usize, Option<&ItemStack>)| { + slot.is_none_or(|item| item.item_id == item_in_pressed_slot.item_id) + }; + let slots = if slot > 35 { slots .skip(9) - .find(|(_, slot)| { - slot.is_none_or(|item| { - item.item_id == item_in_pressed_slot.item_id - }) - }) + .find(find_condition) .map(|(slot_num, _)| slot_num) } else { slots .skip(36) .rev() - .find(|(_, slot)| { - slot.is_none_or(|item| { - item.item_id == item_in_pressed_slot.item_id - }) - }) + .find(find_condition) .map(|(slot_num, _)| slot_num) }; if let Some(slot) = slots { From e0157663ede63eb000395bc7492773bab7b965d9 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 27 Aug 2024 09:24:17 +0200 Subject: [PATCH 11/36] add number button keyclick to packet SClickContainer --- pumpkin/src/client/container.rs | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index db894d31d..8fef720d7 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,7 +1,6 @@ use num_traits::FromPrimitive; use pumpkin_core::text::TextComponent; -use pumpkin_inventory::container_click; -use pumpkin_inventory::container_click::MouseClick; +use pumpkin_inventory::container_click::{KeyClick, MouseClick}; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::{container_click, handle_item_change}; use pumpkin_inventory::{Container, WindowType}; @@ -141,6 +140,14 @@ impl Player { self.mouse_click(mouse_click, click.window_type, click.mode.slot) } ClickType::ShiftClick => self.shift_mouse_click(click.window_type, click.mode.slot), + ClickType::KeyClick(key_click) => match click.mode.slot { + container_click::Slot::Normal(slot) => { + self.number_button_pressed(click.window_type, key_click, slot) + } + container_click::Slot::OutsideInventory => { + unimplemented!("This is not a valid state") + } + }, _ => todo!(), } dbg!(&self.carried_item); @@ -225,6 +232,26 @@ impl Player { } }; } + + pub fn number_button_pressed( + &mut self, + window_type: WindowType, + key_click: KeyClick, + slot: usize, + ) { + if window_type == WindowType::Generic9x1 { + let changing_slot = match key_click { + KeyClick::Slot(slot) => slot, + KeyClick::Offhand => 45, + }; + let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); + let item_slot = self.inventory.get_slot(slot); + if item_slot.is_some() { + handle_item_change(&mut changing_item_slot, item_slot, MouseClick::Left); + *self.inventory.get_slot(changing_slot as usize) = changing_item_slot + } + } + } } /*impl ContainerStruct { From 0f9ac2259c83be782d904a804b4a265664f15057 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 27 Aug 2024 10:30:33 +0200 Subject: [PATCH 12/36] add creative pick item to packet SClickContainer --- pumpkin-inventory/src/lib.rs | 6 ++++++ pumpkin-inventory/src/player.rs | 4 ++++ pumpkin/src/client/container.rs | 30 ++++++++++++++++++++++++++++-- 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index f11b55b51..0e224f180 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -65,6 +65,8 @@ pub trait Container { slot: usize, mouse_click: MouseClick, ); + + fn all_slots(&mut self) -> Vec<&mut Option>; } pub fn handle_item_take( @@ -173,4 +175,8 @@ impl Container for ContainerStruct { let current_slot = &mut self.slots[slot]; handle_item_change(carried_slot, current_slot, mouse_click) } + + fn all_slots(&mut self) -> Vec<&mut Option> { + self.slots.iter_mut().collect() + } } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index c6cdd0fa7..4da9ac73c 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -144,4 +144,8 @@ impl Container for PlayerInventory { let item_slot = self.get_slot(slot); handle_item_change(carried_slot, item_slot, mouse_click) } + + fn all_slots(&mut self) -> Vec<&mut Option> { + self.slots_mut() + } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 8fef720d7..9bdb7d0b6 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -148,7 +148,18 @@ impl Player { unimplemented!("This is not a valid state") } }, - _ => todo!(), + ClickType::CreativePickItem => { + if let container_click::Slot::Normal(slot) = click.mode.slot { + self.creative_pick_item(slot) + } + } + ClickType::DoubleClick => {} + ClickType::MouseDrag { + drag_state, + drag_type, + } => { + todo!() + } } dbg!(&self.carried_item); let filled_inventory_slots = self @@ -225,7 +236,6 @@ impl Player { } container_click::Slot::OutsideInventory => (), }; - return; } else { return; @@ -252,6 +262,22 @@ impl Player { } } } + + pub fn creative_pick_item(&mut self, slot: usize) { + let mut container = { + match &mut self.open_container { + Some(container) => { + let mut out = container.all_slots(); + out.extend(self.inventory.slots_mut()); + out + } + None => self.inventory.slots_mut(), + } + }; + if let Some(Some(item)) = container.get_mut(slot) { + self.carried_item = Some(item.to_owned()) + } + } } /*impl ContainerStruct { From d7e059cb861ca3af4a8f408e78ab6a3abc468d08 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Wed, 28 Aug 2024 09:58:35 +0200 Subject: [PATCH 13/36] refactor to make SClickContainer work in containers other than inventory --- pumpkin-inventory/src/lib.rs | 45 +++++++++ pumpkin/src/client/container.rs | 171 ++++++++++++++++---------------- 2 files changed, 130 insertions(+), 86 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 0e224f180..799e68a9f 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,4 +1,5 @@ use crate::container_click::MouseClick; +use crate::player::PlayerInventory; use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; @@ -180,3 +181,47 @@ impl Container for ContainerStruct { self.slots.iter_mut().collect() } } + +pub struct OptionallyCombinedContainer<'a> { + container: Option<&'a mut Box>, + inventory: &'a mut PlayerInventory, +} +impl<'a> OptionallyCombinedContainer<'a> { + pub fn new( + player_inventory: &'a mut PlayerInventory, + container: Option<&'a mut Box>, + ) -> Self { + Self { + inventory: player_inventory, + container, + } + } +} + +impl<'a> Container for OptionallyCombinedContainer<'a> { + fn window_type(&self) -> &WindowType { + if let Some(container) = &self.container { + container.window_type() + } else { + &WindowType::Generic9x1 + } + } + + fn all_slots(&mut self) -> Vec<&mut Option> { + let mut slots = if let Some(container) = &mut self.container { + container.all_slots() + } else { + vec![] + }; + slots.extend(self.inventory.all_slots()); + slots + } + + fn handle_item_change( + &mut self, + carried_item: &mut Option, + slot: usize, + mouse_click: MouseClick, + ) { + } +} diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 9bdb7d0b6..a32d9b26f 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,8 +1,11 @@ use num_traits::FromPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_inventory::container_click::{KeyClick, MouseClick}; +use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; -use pumpkin_inventory::{container_click, handle_item_change}; +use pumpkin_inventory::{ + container_click, handle_item_change, ContainerStruct, OptionallyCombinedContainer, +}; use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, @@ -160,8 +163,10 @@ impl Player { } => { todo!() } + ClickType::DropType(drop_type) => { + todo!() + } } - dbg!(&self.carried_item); let filled_inventory_slots = self .inventory .slots() @@ -169,115 +174,102 @@ impl Player { .enumerate() .filter_map(|(slot, item)| item.map(|item| (slot, item.item_count))) .collect::>(); - dbg!(filled_inventory_slots); } - pub fn mouse_click( + 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 => (), - }; + let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + if container.window_type() != &window_type { + return; + } - return; - } else { - return; + match slot { + container_click::Slot::Normal(slot) => { + container.handle_item_change(&mut self.carried_item, slot, mouse_click) } + container_click::Slot::OutsideInventory => (), }; } - pub fn shift_mouse_click(&mut self, 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) => { - if let Some(item_in_pressed_slot) = self.inventory.slots()[slot] { - let slots = self.inventory.slots().into_iter().enumerate(); - // Hotbar - let find_condition = |(_, slot): &(usize, Option<&ItemStack>)| { - slot.is_none_or(|item| item.item_id == item_in_pressed_slot.item_id) - }; + fn shift_mouse_click(&mut self, window_type: WindowType, slot: container_click::Slot) { + let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + if container.window_type() != &window_type { + return; + } + + match slot { + container_click::Slot::Normal(slot) => { + if let Some(item_in_pressed_slot) = self.inventory.slots()[slot] { + let slots = self.inventory.slots().into_iter().enumerate(); + // Hotbar + let find_condition = |(_, slot): &(usize, Option<&ItemStack>)| { + slot.is_none_or(|item| item.item_id == item_in_pressed_slot.item_id) + }; - let slots = if slot > 35 { - slots - .skip(9) - .find(find_condition) - .map(|(slot_num, _)| slot_num) - } else { - slots - .skip(36) - .rev() - .find(find_condition) - .map(|(slot_num, _)| slot_num) - }; - if let Some(slot) = slots { - let mut item_slot = - self.inventory.slots()[slot].map(|i| i.to_owned()); + let slots = if slot > 35 { + slots + .skip(9) + .find(find_condition) + .map(|(slot_num, _)| slot_num) + } else { + slots + .skip(36) + .rev() + .find(find_condition) + .map(|(slot_num, _)| slot_num) + }; + if let Some(slot) = slots { + let mut item_slot = self.inventory.slots()[slot].map(|i| i.to_owned()); - self.inventory.handle_item_change( - &mut item_slot, - slot, - MouseClick::Left, - ); - *self.inventory.slots_mut()[slot] = item_slot; - } - } + self.inventory + .handle_item_change(&mut item_slot, slot, MouseClick::Left); + *self.inventory.slots_mut()[slot] = item_slot; } - container_click::Slot::OutsideInventory => (), - }; - return; - } else { - return; + } } + container_click::Slot::OutsideInventory => (), }; } - pub fn number_button_pressed( - &mut self, - window_type: WindowType, - key_click: KeyClick, - slot: usize, - ) { - if window_type == WindowType::Generic9x1 { - let changing_slot = match key_click { - KeyClick::Slot(slot) => slot, - KeyClick::Offhand => 45, - }; - let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); - let item_slot = self.inventory.get_slot(slot); - if item_slot.is_some() { - handle_item_change(&mut changing_item_slot, item_slot, MouseClick::Left); - *self.inventory.get_slot(changing_slot as usize) = changing_item_slot - } + fn number_button_pressed(&mut self, window_type: WindowType, key_click: KeyClick, slot: usize) { + let changing_slot = match key_click { + KeyClick::Slot(slot) => slot, + KeyClick::Offhand => 45, + }; + let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); + let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + if container.window_type() != &window_type { + return; + } + + let item_slot = &mut container.all_slots()[slot]; + if item_slot.is_some() { + handle_item_change(&mut changing_item_slot, item_slot, MouseClick::Left); + *self.inventory.get_slot(changing_slot as usize) = changing_item_slot } } - pub fn creative_pick_item(&mut self, slot: usize) { - let mut container = { - match &mut self.open_container { - Some(container) => { - let mut out = container.all_slots(); - out.extend(self.inventory.slots_mut()); - out - } - None => self.inventory.slots_mut(), - } - }; - if let Some(Some(item)) = container.get_mut(slot) { + fn creative_pick_item(&mut self, slot: usize) { + let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + + if let Some(Some(item)) = container.all_slots().get_mut(slot) { self.carried_item = Some(item.to_owned()) } } + + fn double_click(&mut self, slot: usize) {} + + fn get_container_type(&mut self) -> &WindowType { + match &self.open_container { + Some(container) => container.window_type(), + // This is the one the inventory uses for some stupid reason + None => &WindowType::Generic9x1, + } + } } /*impl ContainerStruct { @@ -285,3 +277,10 @@ impl Player { } }*/ + +fn get_full_container<'a>( + inventory: &'a mut PlayerInventory, + open_container: Option<&'a mut Box>, +) -> OptionallyCombinedContainer<'a> { + OptionallyCombinedContainer::new(inventory, open_container) +} From 53c3a013c20e3af0d19a51bcc0c6e99bad393b7a Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 11:41:13 +0200 Subject: [PATCH 14/36] add OpenedContainer to Server we will store all opened containers there until closed by all players, and when closed we will save contents to the block in the world. We should implement Drop for OpenedContainer to achieve this. --- pumpkin-inventory/src/lib.rs | 33 +++++++--- pumpkin-inventory/src/open_container.rs | 46 ++++++++++++++ pumpkin-inventory/src/player.rs | 4 ++ pumpkin/src/client/container.rs | 84 +++++++++++++------------ pumpkin/src/entity/player.rs | 24 +++---- pumpkin/src/server.rs | 20 ++++-- 6 files changed, 145 insertions(+), 66 deletions(-) create mode 100644 pumpkin-inventory/src/open_container.rs diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 799e68a9f..3f854db60 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -4,11 +4,14 @@ use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; pub mod container_click; +mod open_container; pub mod player; pub mod window_property; +pub use open_container::OpenContainer; + /// https://wiki.vg/Inventory -#[derive(Debug, ToPrimitive, FromPrimitive, Clone, Eq, PartialEq)] +#[derive(Debug, ToPrimitive, FromPrimitive, Clone, Copy, Eq, PartialEq)] pub enum WindowType { // not used Generic9x1, @@ -56,8 +59,8 @@ impl WindowType { "WINDOW TITLE" } } - -pub trait Container { +// Container needs Sync + Send to be able to be in async Server +pub trait Container: Sync + Send { fn window_type(&self) -> &WindowType; fn handle_item_change( @@ -65,9 +68,13 @@ pub trait Container { carried_item: &mut Option, slot: usize, mouse_click: MouseClick, - ); + ) { + handle_item_take(carried_item, self.all_slots()[slot], mouse_click) + } fn all_slots(&mut self) -> Vec<&mut Option>; + + fn all_slots_ref(&self) -> Vec>; } pub fn handle_item_take( @@ -180,6 +187,10 @@ impl Container for ContainerStruct { fn all_slots(&mut self) -> Vec<&mut Option> { self.slots.iter_mut().collect() } + + fn all_slots_ref(&self) -> Vec> { + self.slots.iter().map(|slot| slot.as_ref()).collect() + } } pub struct OptionallyCombinedContainer<'a> { @@ -217,11 +228,13 @@ impl<'a> Container for OptionallyCombinedContainer<'a> { slots } - fn handle_item_change( - &mut self, - carried_item: &mut Option, - slot: usize, - mouse_click: MouseClick, - ) { + fn all_slots_ref(&self) -> Vec> { + let mut slots = if let Some(container) = &self.container { + container.all_slots_ref() + } else { + vec![] + }; + slots.extend(self.inventory.all_slots_ref()); + slots } } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs new file mode 100644 index 000000000..35a24b9a5 --- /dev/null +++ b/pumpkin-inventory/src/open_container.rs @@ -0,0 +1,46 @@ +use crate::container_click::MouseClick; +use crate::{handle_item_change, Container, WindowType}; +use pumpkin_world::item::ItemStack; +use std::sync::{Mutex, MutexGuard}; + +pub struct OpenContainer { + players: Vec, + container: Mutex>, +} + +impl OpenContainer { + pub fn try_open(&self, player_id: i32) -> Option>> { + if !self.players.contains(&player_id) { + return None; + } + Some(self.container.lock().unwrap()) + } + + pub fn empty(player_id: i32) -> Self { + Self { + players: vec![player_id], + container: Mutex::new(Box::new(Chest::new())), + } + } +} + +struct Chest([Option; 27]); + +impl Chest { + pub fn new() -> Self { + Self([None; 27]) + } +} +impl Container for Chest { + fn window_type(&self) -> &WindowType { + &WindowType::Generic9x3 + } + + fn all_slots(&mut self) -> Vec<&mut Option> { + self.0.iter_mut().collect() + } + + fn all_slots_ref(&self) -> Vec> { + self.0.iter().map(|slot| slot.as_ref()).collect() + } +} diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 4da9ac73c..ca7150920 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -148,4 +148,8 @@ impl Container for PlayerInventory { fn all_slots(&mut self) -> Vec<&mut Option> { self.slots_mut() } + + fn all_slots_ref(&self) -> Vec> { + self.slots() + } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index a32d9b26f..02cb84893 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -15,11 +15,12 @@ use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; use crate::entity::player::Player; +use crate::server::Server; impl Player { pub fn open_container( &mut self, - window_type: WindowType, + window_type: &WindowType, minecraft_menu_id: &str, window_title: Option<&str>, items: Option>>, @@ -45,7 +46,7 @@ impl Player { pub fn set_container_content<'a>( &mut self, - window_type: WindowType, + window_type: &WindowType, items: Option>>, carried_item: Option<&'a ItemStack>, ) { @@ -75,7 +76,7 @@ impl Player { } }; let packet = - CSetContainerContent::new(window_type as u8 + 1, 0.into(), &slots, &carried_item); + CSetContainerContent::new(*window_type as u8 + 1, 0.into(), &slots, &carried_item); self.client.send_packet(&packet); } @@ -109,8 +110,15 @@ impl Player { .send_packet(&CSetContainerProperty::new(window_type as u8, id, value)); } - pub fn handle_click_container(&mut self, packet: SClickContainer) { + pub fn handle_click_container(&mut self, server: &mut Server, packet: SClickContainer) { use container_click::*; + let mut opened_container = if let Some(id) = self.open_container { + server.try_get_container(self.entity_id(), self.open_container.unwrap()) + } else { + None + }; + let opened_container = opened_container.as_deref_mut(); + let click = Click { state_id: packet.state_id.0.try_into().unwrap(), changed_items: packet @@ -137,15 +145,19 @@ impl Player { packet.slot, ), }; - match click.mode.click_type { - ClickType::MouseClick(mouse_click) => { - self.mouse_click(mouse_click, click.window_type, click.mode.slot) + ClickType::MouseClick(mouse_click) => self.mouse_click( + opened_container, + mouse_click, + click.window_type, + click.mode.slot, + ), + ClickType::ShiftClick => { + self.shift_mouse_click(opened_container, click.window_type, click.mode.slot) } - ClickType::ShiftClick => self.shift_mouse_click(click.window_type, click.mode.slot), ClickType::KeyClick(key_click) => match click.mode.slot { container_click::Slot::Normal(slot) => { - self.number_button_pressed(click.window_type, key_click, slot) + self.number_button_pressed(opened_container, click.window_type, key_click, slot) } container_click::Slot::OutsideInventory => { unimplemented!("This is not a valid state") @@ -153,7 +165,7 @@ impl Player { }, ClickType::CreativePickItem => { if let container_click::Slot::Normal(slot) = click.mode.slot { - self.creative_pick_item(slot) + self.creative_pick_item(opened_container, slot) } } ClickType::DoubleClick => {} @@ -178,11 +190,12 @@ impl Player { fn mouse_click( &mut self, + opened_container: Option<&mut Box>, mouse_click: MouseClick, window_type: WindowType, slot: container_click::Slot, ) { - let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if container.window_type() != &window_type { return; } @@ -195,8 +208,13 @@ impl Player { }; } - fn shift_mouse_click(&mut self, window_type: WindowType, slot: container_click::Slot) { - let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + fn shift_mouse_click( + &mut self, + opened_container: Option<&mut Box>, + window_type: WindowType, + slot: container_click::Slot, + ) { + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if container.window_type() != &window_type { return; } @@ -235,13 +253,19 @@ impl Player { }; } - fn number_button_pressed(&mut self, window_type: WindowType, key_click: KeyClick, slot: usize) { + fn number_button_pressed( + &mut self, + opened_container: Option<&mut Box>, + window_type: WindowType, + key_click: KeyClick, + slot: usize, + ) { let changing_slot = match key_click { KeyClick::Slot(slot) => slot, KeyClick::Offhand => 45, }; let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); - let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if container.window_type() != &window_type { return; } @@ -253,34 +277,16 @@ impl Player { } } - fn creative_pick_item(&mut self, slot: usize) { - let mut container = get_full_container(&mut self.inventory, self.open_container.as_mut()); - + fn creative_pick_item( + &mut self, + mut opened_container: Option<&mut Box>, + slot: usize, + ) { + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if let Some(Some(item)) = container.all_slots().get_mut(slot) { self.carried_item = Some(item.to_owned()) } } fn double_click(&mut self, slot: usize) {} - - fn get_container_type(&mut self) -> &WindowType { - match &self.open_container { - Some(container) => container.window_type(), - // This is the one the inventory uses for some stupid reason - None => &WindowType::Generic9x1, - } - } -} - -/*impl ContainerStruct { - pub fn opened_by_players(&mut self, server: Server) -> Vec<&Player> { - - } -}*/ - -fn get_full_container<'a>( - inventory: &'a mut PlayerInventory, - open_container: Option<&'a mut Box>, -) -> OptionallyCombinedContainer<'a> { - OptionallyCombinedContainer::new(inventory, open_container) } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 9fd2e0dad..b734ff3a8 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -15,10 +15,10 @@ use pumpkin_protocol::{ }, position::WorldPosition, server::play::{ - SChatCommand, SChatMessage, SClientInformationPlay, SConfirmTeleport, SInteract, - SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, SPlayerPositionRotation, - SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, SSwingArm, SUseItem, - SUseItemOn, + SChatCommand, SChatMessage, SClickContainer, SClientInformationPlay, SConfirmTeleport, + SInteract, SPlayPingRequest, SPlayerAction, SPlayerCommand, SPlayerPosition, + SPlayerPositionRotation, SPlayerRotation, SSetCreativeSlot, SSetHeldItem, SSetPlayerGround, + SSwingArm, SUseItem, SUseItemOn, }, ConnectionState, RawPacket, ServerPacket, VarInt, }; @@ -26,11 +26,7 @@ use pumpkin_world::item::ItemStack; use pumpkin_world::vector3::Vector3; use serde::{Deserialize, Serialize}; -use crate::{ - client::{authentication::GameProfile, Client}, - server::Server, - util::boundingbox::BoundingBox, -}; +use crate::{client::authentication::GameProfile, util::boundingbox::BoundingBox}; pub struct PlayerAbilities { pub invulnerable: bool, @@ -65,7 +61,7 @@ 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, /// send `send_abilties_update` when changed @@ -406,9 +402,13 @@ impl Player { Ok(()) } SClickContainer::PACKET_ID => { - self.handle_click_container(SClickContainer::read(bytebuf).unwrap()) + self.handle_click_container(server, SClickContainer::read(bytebuf)?); + Ok(()) + } + _ => { + log::error!("Failed to handle player packet id {:#04x}", packet.id.0); + Ok(()) } - _ => log::error!("Failed to handle player packet id {:#04x}", packet.id.0), } } } diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index c673cbc9c..6d5b008f8 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -29,16 +29,16 @@ use pumpkin_protocol::{ }; use pumpkin_world::{dimension::Dimension, radial_chunk_iterator::RadialIterator, World}; -use pumpkin_registry::Registry; -use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; -use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; - use crate::{ client::Client, config::{AdvancedConfiguration, BasicConfiguration}, entity::player::{GameMode, Player}, }; +use pumpkin_inventory::{Container, OpenContainer}; +use pumpkin_registry::Registry; +use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; +use serde::{Deserialize, Serialize}; +use tokio::sync::mpsc; pub const CURRENT_MC_VERSION: &str = "1.21.1"; @@ -63,6 +63,7 @@ pub struct Server { // TODO: place this into every world pub current_players: HashMap, Arc>>, + pub open_containers: HashMap, entity_id: AtomicI32, pub base_config: BasicConfiguration, @@ -120,6 +121,7 @@ impl Server { status_response_json, public_key_der, current_players: HashMap::new(), + open_containers: HashMap::new(), base_config: config.0, auth_client, advanced_config: config.1, @@ -315,6 +317,14 @@ impl Server { None } + pub fn try_get_container( + &self, + player_id: EntityId, + container_id: u64, + ) -> Option>> { + self.open_containers.get(&container_id)?.try_open(player_id) + } + /// Sends a Packet to all Players pub fn broadcast_packet

(&self, from: &mut Player, packet: &P) where From f34a483118979e900f5f8f4bdf5220e2d217c4de Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 11:44:47 +0200 Subject: [PATCH 15/36] fix lints --- pumpkin-inventory/src/open_container.rs | 3 +-- pumpkin/src/client/container.rs | 34 ++++++++++--------------- pumpkin/src/entity/player.rs | 1 - 3 files changed, 15 insertions(+), 23 deletions(-) diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 35a24b9a5..985d483a8 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,5 +1,4 @@ -use crate::container_click::MouseClick; -use crate::{handle_item_change, Container, WindowType}; +use crate::{Container, WindowType}; use pumpkin_world::item::ItemStack; use std::sync::{Mutex, MutexGuard}; diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 02cb84893..b03ae01de 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,11 +1,8 @@ use num_traits::FromPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_inventory::container_click::{KeyClick, MouseClick}; -use pumpkin_inventory::player::PlayerInventory; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; -use pumpkin_inventory::{ - container_click, handle_item_change, ContainerStruct, OptionallyCombinedContainer, -}; +use pumpkin_inventory::{container_click, handle_item_change, OptionallyCombinedContainer}; use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, @@ -37,7 +34,7 @@ impl Player { .into(); let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); self.client.send_packet(&COpenScreen::new( - (window_type.clone() as u8 + 1).into(), + (*window_type as u8 + 1).into(), menu_protocol_id, title, )); @@ -113,7 +110,7 @@ impl Player { pub fn handle_click_container(&mut self, server: &mut Server, packet: SClickContainer) { use container_click::*; let mut opened_container = if let Some(id) = self.open_container { - server.try_get_container(self.entity_id(), self.open_container.unwrap()) + server.try_get_container(self.entity_id(), id) } else { None }; @@ -168,24 +165,21 @@ impl Player { self.creative_pick_item(opened_container, slot) } } - ClickType::DoubleClick => {} + ClickType::DoubleClick => { + if let container_click::Slot::Normal(slot) = click.mode.slot { + self.double_click(slot) + } + } ClickType::MouseDrag { - drag_state, - drag_type, + drag_state: _, + drag_type: _, } => { todo!() } - ClickType::DropType(drop_type) => { + ClickType::DropType(_drop_type) => { todo!() } } - let filled_inventory_slots = self - .inventory - .slots() - .into_iter() - .enumerate() - .filter_map(|(slot, item)| item.map(|item| (slot, item.item_count))) - .collect::>(); } fn mouse_click( @@ -214,7 +208,7 @@ impl Player { window_type: WindowType, slot: container_click::Slot, ) { - let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if container.window_type() != &window_type { return; } @@ -279,7 +273,7 @@ impl Player { fn creative_pick_item( &mut self, - mut opened_container: Option<&mut Box>, + opened_container: Option<&mut Box>, slot: usize, ) { let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); @@ -288,5 +282,5 @@ impl Player { } } - fn double_click(&mut self, slot: usize) {} + fn double_click(&mut self, _slot: usize) {} } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index b734ff3a8..a0033c67f 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -6,7 +6,6 @@ use num_traits::ToPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_entity::{entity_type::EntityType, pose::EntityPose, Entity, EntityId}; use pumpkin_inventory::player::PlayerInventory; -use pumpkin_inventory::Container; use pumpkin_protocol::{ bytebuf::{packet_id::Packet, DeserializerError}, client::play::{ From 909943bb5d496c98cb273801c5932867dee1e021 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 11:57:13 +0200 Subject: [PATCH 16/36] add development command for opening a global enderchest --- pumpkin/src/commands/cmd_echest.rs | 31 ++++++++++++++++++++++++++++++ pumpkin/src/commands/mod.rs | 2 ++ 2 files changed, 33 insertions(+) create mode 100644 pumpkin/src/commands/cmd_echest.rs diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs new file mode 100644 index 000000000..83767abcc --- /dev/null +++ b/pumpkin/src/commands/cmd_echest.rs @@ -0,0 +1,31 @@ +use pumpkin_inventory::OpenContainer; + +use crate::commands::tree::CommandTree; + +const NAMES: [&str; 2] = ["echest", "enderchest"]; + +const DESCRIPTION: &str = + "Show your personal enderchest (this command is used for testing container behaviour)"; + +pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { + CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { + if let Some(player) = sender.as_mut_player() { + if let Some(container) = server + .open_containers + .entry(0) + .or_insert(OpenContainer::empty(player.entity_id())) + .try_open(player.entity_id()) + { + player.open_container( + container.window_type(), + "minecraft:generic_9x3", + Some("Ender Chest"), + Some(container.all_slots_ref()), + None, + ); + } + } + + Ok(()) + }) +} diff --git a/pumpkin/src/commands/mod.rs b/pumpkin/src/commands/mod.rs index 1321ec665..7d6f89a6b 100644 --- a/pumpkin/src/commands/mod.rs +++ b/pumpkin/src/commands/mod.rs @@ -6,6 +6,7 @@ use crate::commands::dispatcher::CommandDispatcher; use crate::entity::player::Player; use crate::server::Server; mod arg_player; +mod cmd_echest; mod cmd_gamemode; mod cmd_help; mod cmd_pumpkin; @@ -80,6 +81,7 @@ fn dispatcher_init<'a>() -> CommandDispatcher<'a> { dispatcher.register(cmd_gamemode::init_command_tree()); dispatcher.register(cmd_stop::init_command_tree()); dispatcher.register(cmd_help::init_command_tree()); + dispatcher.register(cmd_echest::init_command_tree()); dispatcher } From 1d065645e1f176f4fa03f09c2cacd36b16874c7c Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 13:24:58 +0200 Subject: [PATCH 17/36] add state id --- pumpkin-inventory/src/lib.rs | 46 ++++++++----------------- pumpkin-inventory/src/open_container.rs | 37 +++++++++++++++++--- pumpkin-inventory/src/player.rs | 9 ++++- pumpkin/src/client/container.rs | 41 ++++++++++++++-------- 4 files changed, 81 insertions(+), 52 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 3f854db60..a019c298c 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -75,6 +75,8 @@ pub trait Container: Sync + Send { fn all_slots(&mut self) -> Vec<&mut Option>; fn all_slots_ref(&self) -> Vec>; + + fn state_id(&mut self) -> i32; } pub fn handle_item_take( @@ -104,6 +106,12 @@ pub fn handle_item_change( current_slot: &mut Option, mouse_click: MouseClick, ) { + if let Some(carried) = carried_slot.as_ref() { + dbg!(carried); + } + if let Some(current) = current_slot.as_ref() { + dbg!(current); + } match (current_slot.as_mut(), carried_slot.as_mut()) { // Swap or combine current and carried (Some(current), Some(carried)) => { @@ -162,40 +170,10 @@ pub fn combine_stacks( } } -pub struct ContainerStruct { - slots: [Option; SLOTS], - _state_id: usize, - _open_by: Option>, - window_type: WindowType, -} - -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) - } - - fn all_slots(&mut self) -> Vec<&mut Option> { - self.slots.iter_mut().collect() - } - - fn all_slots_ref(&self) -> Vec> { - self.slots.iter().map(|slot| slot.as_ref()).collect() - } -} - pub struct OptionallyCombinedContainer<'a> { container: Option<&'a mut Box>, inventory: &'a mut PlayerInventory, + state_id: i32, } impl<'a> OptionallyCombinedContainer<'a> { pub fn new( @@ -205,6 +183,7 @@ impl<'a> OptionallyCombinedContainer<'a> { Self { inventory: player_inventory, container, + state_id: 0, } } } @@ -237,4 +216,9 @@ impl<'a> Container for OptionallyCombinedContainer<'a> { slots.extend(self.inventory.all_slots_ref()); slots } + + fn state_id(&mut self) -> i32 { + self.state_id += 1; + self.state_id - 1 + } } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 985d483a8..895f92f08 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -12,7 +12,23 @@ impl OpenContainer { if !self.players.contains(&player_id) { return None; } - Some(self.container.lock().unwrap()) + let container = self.container.lock().unwrap(); + container + .all_slots_ref() + .iter() + .enumerate() + .for_each(|(slot, item)| { + if let Some(item) = item { + dbg!(slot, item); + } + }); + Some(container) + } + + pub fn add_player(&mut self, player_id: i32) { + if !self.players.contains(&player_id) { + self.players.push(player_id); + } } pub fn empty(player_id: i32) -> Self { @@ -23,11 +39,17 @@ impl OpenContainer { } } -struct Chest([Option; 27]); +struct Chest { + slots: [Option; 27], + state_id: i32, +} impl Chest { pub fn new() -> Self { - Self([None; 27]) + Self { + slots: [None; 27], + state_id: 0, + } } } impl Container for Chest { @@ -36,10 +58,15 @@ impl Container for Chest { } fn all_slots(&mut self) -> Vec<&mut Option> { - self.0.iter_mut().collect() + self.slots.iter_mut().collect() } fn all_slots_ref(&self) -> Vec> { - self.0.iter().map(|slot| slot.as_ref()).collect() + self.slots.iter().map(|slot| slot.as_ref()).collect() + } + + fn state_id(&mut self) -> i32 { + self.state_id += 1; + self.state_id - 1 } } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index ca7150920..b84a4acc7 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -13,6 +13,7 @@ pub struct PlayerInventory { offhand: Option, // current selected slot in hotbar selected: usize, + state_id: i32, } impl Default for PlayerInventory { @@ -31,6 +32,7 @@ impl PlayerInventory { offhand: None, // TODO: What when player spawns in with an different index ? selected: 0, + state_id: 0, } } @@ -131,7 +133,7 @@ impl PlayerInventory { } impl Container for PlayerInventory { - fn window_type(&self) -> &WindowType { + fn window_type(&self) -> &'static WindowType { &WindowType::Generic9x1 } @@ -152,4 +154,9 @@ impl Container for PlayerInventory { fn all_slots_ref(&self) -> Vec> { self.slots() } + + fn state_id(&mut self) -> i32 { + self.state_id += 1; + self.state_id - 1 + } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index b03ae01de..f5f1edde7 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -22,6 +22,7 @@ impl Player { window_title: Option<&str>, items: Option>>, carried_item: Option<&ItemStack>, + state_id: i32, ) { let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") @@ -32,27 +33,30 @@ impl Player { .get("protocol_id") .unwrap()) .into(); + let window_type = container.window_type(); let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); self.client.send_packet(&COpenScreen::new( (*window_type as u8 + 1).into(), menu_protocol_id, title, )); - self.set_container_content(window_type, items, carried_item); + + self.set_container_content(Some(container), carried_item, state_id); } pub fn set_container_content<'a>( &mut self, - window_type: &WindowType, - items: Option>>, + container: Option<&Box>, carried_item: Option<&'a ItemStack>, + state_id: i32, ) { let slots: Vec = { + let items = container.map(|container| container.all_slots_ref()); if let Some(mut items) = items { - items.extend(self.inventory.slots()); + items.extend(self.inventory.all_slots_ref()); items } else { - self.inventory.slots() + self.inventory.all_slots_ref() } .into_iter() .map(|item| { @@ -72,8 +76,13 @@ impl Player { Slot::empty() } }; - let packet = - CSetContainerContent::new(*window_type as u8 + 1, 0.into(), &slots, &carried_item); + + let window_type = match container { + Some(container) => *container.window_type() as u8, + None => 0, + }; + + let packet = CSetContainerContent::new(window_type, state_id.into(), &slots, &carried_item); self.client.send_packet(&packet); } @@ -82,10 +91,11 @@ impl Player { window_type: WindowType, slot: usize, item: Option<&ItemStack>, + state_id: i32, ) { self.client.send_packet(&CSetContainerSlot::new( window_type as i8, - 0, + state_id, slot, &item.into(), )) @@ -130,7 +140,12 @@ impl Player { } }) .collect::>(), - window_type: WindowType::from_u8(packet.window_id).unwrap(), + window_type: WindowType::from_u8(if packet.window_id == 0 { + 0 + } else { + packet.window_id - 1 + }) + .unwrap(), carried_item: packet.carried_item.to_item(), mode: ClickMode::new( packet @@ -173,12 +188,8 @@ impl Player { ClickType::MouseDrag { drag_state: _, drag_type: _, - } => { - todo!() - } - ClickType::DropType(_drop_type) => { - todo!() - } + } => (), + ClickType::DropType(_drop_type) => (), } } From d213fad06774c5da5868514f087e0949635be89c Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 13:25:35 +0200 Subject: [PATCH 18/36] make window_type static in trait --- pumpkin-inventory/src/lib.rs | 4 ++-- pumpkin-inventory/src/open_container.rs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index a019c298c..ff6c2a429 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -61,7 +61,7 @@ impl WindowType { } // Container needs Sync + Send to be able to be in async Server pub trait Container: Sync + Send { - fn window_type(&self) -> &WindowType; + fn window_type(&self) -> &'static WindowType; fn handle_item_change( &mut self, @@ -189,7 +189,7 @@ impl<'a> OptionallyCombinedContainer<'a> { } impl<'a> Container for OptionallyCombinedContainer<'a> { - fn window_type(&self) -> &WindowType { + fn window_type(&self) -> &'static WindowType { if let Some(container) = &self.container { container.window_type() } else { diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 895f92f08..f5a361b2b 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -53,7 +53,7 @@ impl Chest { } } impl Container for Chest { - fn window_type(&self) -> &WindowType { + fn window_type(&self) -> &'static WindowType { &WindowType::Generic9x3 } From f465bc06d8c4441055449b613e3352e0316b8e24 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 13:26:07 +0200 Subject: [PATCH 19/36] change to use correct function --- pumpkin-inventory/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index ff6c2a429..144893f7b 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -69,7 +69,7 @@ pub trait Container: Sync + Send { slot: usize, mouse_click: MouseClick, ) { - handle_item_take(carried_item, self.all_slots()[slot], mouse_click) + handle_item_change(carried_item, self.all_slots()[slot], mouse_click) } fn all_slots(&mut self) -> Vec<&mut Option>; From 460d1506b780b7e71e2b7eb372be7d438527eba4 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 31 Aug 2024 13:26:16 +0200 Subject: [PATCH 20/36] various small changes --- pumpkin/src/client/container.rs | 3 +-- pumpkin/src/commands/cmd_echest.rs | 42 ++++++++++++++++++++---------- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index f5f1edde7..46320a61f 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -17,10 +17,9 @@ use crate::server::Server; impl Player { pub fn open_container( &mut self, - window_type: &WindowType, + container: &Box, minecraft_menu_id: &str, window_title: Option<&str>, - items: Option>>, carried_item: Option<&ItemStack>, state_id: i32, ) { diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index 83767abcc..8a06233f0 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -10,20 +10,34 @@ const DESCRIPTION: &str = pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { if let Some(player) = sender.as_mut_player() { - if let Some(container) = server - .open_containers - .entry(0) - .or_insert(OpenContainer::empty(player.entity_id())) - .try_open(player.entity_id()) - { - player.open_container( - container.window_type(), - "minecraft:generic_9x3", - Some("Ender Chest"), - Some(container.all_slots_ref()), - None, - ); - } + let entity_id = player.entity_id().clone(); + player.open_container = Some(0); + let mut container = match server.open_containers.get_mut(&0) { + Some(ender_chest) => { + if ender_chest.try_open(entity_id).is_none() { + ender_chest.add_player(entity_id); + } + ender_chest.try_open(entity_id).unwrap() + } + None => { + let open_container = OpenContainer::empty(entity_id); + server.open_containers.insert(0, open_container); + server + .open_containers + .get_mut(&0) + .unwrap() + .try_open(entity_id) + .unwrap() + } + }; + let state_id = container.state_id(); + player.open_container( + &container, + "minecraft:generic_9x3", + Some("Ender Chest"), + None, + state_id, + ); } Ok(()) From d1c4e5995534ddf1f06cebf6de853a8f369da7ab Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Mon, 2 Sep 2024 15:05:56 +0200 Subject: [PATCH 21/36] add working container state and change to fix with latest current branch --- Cargo.lock | 5 +- pumpkin-inventory/src/container_click.rs | 10 +- pumpkin-inventory/src/lib.rs | 82 +++++---- pumpkin-inventory/src/open_container.rs | 24 ++- pumpkin-inventory/src/player.rs | 21 ++- pumpkin/Cargo.toml | 1 + pumpkin/src/client/container.rs | 207 +++++++++++----------- pumpkin/src/client/player_packet.rs | 8 +- pumpkin/src/commands/cmd_echest.rs | 26 +-- pumpkin/src/entity/player.rs | 13 +- pumpkin/src/server.rs | 212 ++--------------------- 11 files changed, 243 insertions(+), 366 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 966efd56b..fd53bd141 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1901,6 +1901,7 @@ dependencies = [ "digest 0.11.0-pre.9", "hmac", "image", + "itertools 0.13.0", "log", "mio", "num-bigint", @@ -2806,9 +2807,9 @@ checksum = "1f3ccbac311fea05f86f61904b462b55fb3df8837a366dfc601a0161d0532f20" [[package]] name = "tokio" -version = "1.39.2" +version = "1.40.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "daa4fb1bc778bd6f04cbfc4bb2d06a7396a8f299dc33ea1900cedaa316f467b1" +checksum = "e2b070231665d27ad9ec9b8df639893f46727666c6767db40317fbe920a5d998" dependencies = [ "backtrace", "bytes", diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index c84315195..14a26c905 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,4 +1,3 @@ -use crate::WindowType; use pumpkin_world::item::ItemStack; pub struct ClickMode { @@ -8,8 +7,6 @@ pub struct ClickMode { pub struct Click { pub mode: ClickMode, - pub state_id: u32, - pub window_type: WindowType, pub changed_items: Vec, pub carried_item: Option, } @@ -25,7 +22,7 @@ impl ClickMode { click_type: ClickType::CreativePickItem, slot: Slot::Normal(slot.try_into().unwrap()), }, - 4 => Self::new_drop_item(button, slot), + 4 => Self::new_drop_item(button), 5 => Self::new_drag_item(button, slot), 6 => Self { click_type: ClickType::DoubleClick, @@ -78,7 +75,7 @@ impl ClickMode { } } - fn new_drop_item(button: i8, slot: i16) -> Self { + fn new_drop_item(button: i8) -> Self { let drop_type = match button { 0 => DropType::SingleItem, 1 => DropType::FullStack, @@ -87,7 +84,7 @@ impl ClickMode { }; Self { click_type: ClickType::DropType(drop_type), - slot: Slot::Normal(slot.try_into().unwrap()), + slot: Slot::OutsideInventory, } } @@ -108,7 +105,6 @@ impl ClickMode { MouseDragState::End, Slot::OutsideInventory, ), - 4 => ( MouseDragType::Right, MouseDragState::Start, diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 144893f7b..654d2352e 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -76,7 +76,28 @@ pub trait Container: Sync + Send { fn all_slots_ref(&self) -> Vec>; - fn state_id(&mut self) -> i32; + fn all_combinable_slots(&self) -> Vec> { + self.all_slots_ref() + } + + fn all_combinable_slots_mut(&mut self) -> Vec<&mut Option> { + self.all_slots() + } + + fn advance_state_id(&mut self) -> i32; + + fn reset_state_id(&mut self); + fn state_id(&self) -> i32; + + fn print_all_contents(&self) { + self.all_slots_ref() + .into_iter() + .enumerate() + .filter_map(|(slot, item)| item.map(|item| (slot, item))) + .for_each(|(slot, item)| { + dbg!(slot, item); + }); + } } pub fn handle_item_take( @@ -106,12 +127,6 @@ pub fn handle_item_change( current_slot: &mut Option, mouse_click: MouseClick, ) { - if let Some(carried) = carried_slot.as_ref() { - dbg!(carried); - } - if let Some(current) = current_slot.as_ref() { - dbg!(current); - } match (current_slot.as_mut(), carried_slot.as_mut()) { // Swap or combine current and carried (Some(current), Some(carried)) => { @@ -170,25 +185,23 @@ pub fn combine_stacks( } } -pub struct OptionallyCombinedContainer<'a> { +pub struct OptionallyCombinedContainer<'a, 'b> { container: Option<&'a mut Box>, - inventory: &'a mut PlayerInventory, - state_id: i32, + inventory: &'b mut PlayerInventory, } -impl<'a> OptionallyCombinedContainer<'a> { +impl<'a, 'b> OptionallyCombinedContainer<'a, 'b> { pub fn new( - player_inventory: &'a mut PlayerInventory, + player_inventory: &'b mut PlayerInventory, container: Option<&'a mut Box>, ) -> Self { Self { inventory: player_inventory, container, - state_id: 0, } } } -impl<'a> Container for OptionallyCombinedContainer<'a> { +impl<'a> Container for OptionallyCombinedContainer<'a, 'a> { fn window_type(&self) -> &'static WindowType { if let Some(container) = &self.container { container.window_type() @@ -198,27 +211,38 @@ impl<'a> Container for OptionallyCombinedContainer<'a> { } fn all_slots(&mut self) -> Vec<&mut Option> { - let mut slots = if let Some(container) = &mut self.container { - container.all_slots() - } else { - vec![] + let slots = match &mut self.container { + Some(container) => { + let mut slots = container.all_slots(); + slots.extend(self.inventory.all_combinable_slots_mut()); + slots + } + None => self.inventory.all_slots(), }; - slots.extend(self.inventory.all_slots()); + dbg!(slots.len()); slots } fn all_slots_ref(&self) -> Vec> { - let mut slots = if let Some(container) = &self.container { - container.all_slots_ref() - } else { - vec![] - }; - slots.extend(self.inventory.all_slots_ref()); - slots + match &self.container { + Some(container) => { + let mut slots = container.all_slots_ref(); + slots.extend(self.inventory.all_combinable_slots()); + slots + } + None => self.inventory.all_slots_ref(), + } + } + + fn advance_state_id(&mut self) -> i32 { + self.inventory.advance_state_id() + } + + fn reset_state_id(&mut self) { + self.inventory.reset_state_id() } - fn state_id(&mut self) -> i32 { - self.state_id += 1; - self.state_id - 1 + fn state_id(&self) -> i32 { + self.inventory.state_id() } } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index f5a361b2b..601262384 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -10,6 +10,7 @@ pub struct OpenContainer { impl OpenContainer { pub fn try_open(&self, player_id: i32) -> Option>> { if !self.players.contains(&player_id) { + dbg!("couldn't open container"); return None; } let container = self.container.lock().unwrap(); @@ -31,6 +32,18 @@ impl OpenContainer { } } + pub fn remove_player(&mut self, player_id: i32) { + if let Some(index) = self.players.iter().enumerate().find_map(|(index, id)| { + if *id == player_id { + Some(index) + } else { + None + } + }) { + self.players.remove(index); + } + } + pub fn empty(player_id: i32) -> Self { Self { players: vec![player_id], @@ -58,6 +71,7 @@ impl Container for Chest { } fn all_slots(&mut self) -> Vec<&mut Option> { + dbg!(self.slots.iter().len()); self.slots.iter_mut().collect() } @@ -65,8 +79,16 @@ impl Container for Chest { self.slots.iter().map(|slot| slot.as_ref()).collect() } - fn state_id(&mut self) -> i32 { + fn advance_state_id(&mut self) -> i32 { self.state_id += 1; self.state_id - 1 } + + fn reset_state_id(&mut self) { + self.state_id = 0; + } + + fn state_id(&self) -> i32 { + self.state_id + } } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index b84a4acc7..355fea173 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -14,6 +14,7 @@ pub struct PlayerInventory { // current selected slot in hotbar selected: usize, state_id: i32, + pub total_opened_containers: u8, } impl Default for PlayerInventory { @@ -33,6 +34,7 @@ impl PlayerInventory { // TODO: What when player spawns in with an different index ? selected: 0, state_id: 0, + total_opened_containers: 2, } } @@ -155,8 +157,23 @@ impl Container for PlayerInventory { self.slots() } - fn state_id(&mut self) -> i32 { + fn all_combinable_slots(&self) -> Vec> { + self.items.iter().map(|item| item.as_ref()).collect() + } + + fn all_combinable_slots_mut(&mut self) -> Vec<&mut Option> { + self.items.iter_mut().collect() + } + + fn advance_state_id(&mut self) -> i32 { self.state_id += 1; - self.state_id - 1 + self.state_id + } + + fn reset_state_id(&mut self) { + self.state_id = 0; + } + fn state_id(&self) -> i32 { + self.state_id } } diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index 3cdd834e3..bd0264416 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -64,3 +64,4 @@ crossbeam-channel = "0.5.13" uuid.workspace = true tokio.workspace = true rayon.workspace = true +itertools = "0.13.0" diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 46320a61f..dee0ae9ca 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,8 +1,8 @@ -use num_traits::FromPrimitive; +use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_inventory::container_click::{KeyClick, MouseClick}; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; -use pumpkin_inventory::{container_click, handle_item_change, OptionallyCombinedContainer}; +use pumpkin_inventory::{container_click, OptionallyCombinedContainer}; use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, @@ -10,6 +10,7 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; +use std::sync::MutexGuard; use crate::entity::player::Player; use crate::server::Server; @@ -17,12 +18,12 @@ use crate::server::Server; impl Player { pub fn open_container( &mut self, - container: &Box, + server: &mut Server, minecraft_menu_id: &str, window_title: Option<&str>, - carried_item: Option<&ItemStack>, - state_id: i32, ) { + let mut container = self.get_open_container(server); + let container = container.as_deref_mut(); let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") .unwrap() @@ -32,56 +33,48 @@ impl Player { .get("protocol_id") .unwrap()) .into(); - let window_type = container.window_type(); + let window_type = match &container { + Some(container) => container.window_type(), + None => &WindowType::Generic9x1, + } + .to_owned(); let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); + + self.inventory.reset_state_id(); + self.client.send_packet(&COpenScreen::new( - (*window_type as u8 + 1).into(), + self.inventory.total_opened_containers.into(), menu_protocol_id, title, )); - - self.set_container_content(Some(container), carried_item, state_id); + self.set_container_content(container); } - pub fn set_container_content<'a>( - &mut self, - container: Option<&Box>, - carried_item: Option<&'a ItemStack>, - state_id: i32, - ) { - let slots: Vec = { - let items = container.map(|container| container.all_slots_ref()); - if let Some(mut items) = items { - items.extend(self.inventory.all_slots_ref()); - items - } else { - self.inventory.all_slots_ref() - } + pub fn set_container_content(&mut self, container: Option<&mut Box>) { + let total_opened_containers = self.inventory.total_opened_containers; + let container = OptionallyCombinedContainer::new(&mut self.inventory, container); + + let slots = container + .all_slots_ref() .into_iter() - .map(|item| { - if let Some(item) = item { - Slot::from(item) - } else { - Slot::empty() - } - }) - .collect() - }; + .map(Slot::from) + .collect_vec(); let carried_item = { - if let Some(item) = carried_item { + if let Some(item) = self.carried_item.as_ref() { item.into() } else { Slot::empty() } }; - let window_type = match container { - Some(container) => *container.window_type() as u8, - None => 0, - }; - - let packet = CSetContainerContent::new(window_type, state_id.into(), &slots, &carried_item); + let packet = CSetContainerContent::new( + total_opened_containers, + self.inventory.state_id().into(), + &slots, + &carried_item, + ); + self.inventory.advance_state_id(); self.client.send_packet(&packet); } @@ -101,32 +94,54 @@ impl Player { } /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent - pub fn close_container(&mut self, window_type: WindowType) { - self.client - .send_packet(&CCloseContainer::new(window_type as u8)) + pub fn close_container(&mut self) { + self.inventory.total_opened_containers += 1; + self.client.send_packet(&CCloseContainer::new( + self.inventory.total_opened_containers, + )) } pub fn set_container_property( &mut self, - window_type: WindowType, window_property: WindowProperty, ) { let (id, value) = window_property.into_tuple(); - self.client - .send_packet(&CSetContainerProperty::new(window_type as u8, id, value)); + self.client.send_packet(&CSetContainerProperty::new( + self.inventory.total_opened_containers, + id, + value, + )); } pub fn handle_click_container(&mut self, server: &mut Server, packet: SClickContainer) { use container_click::*; - let mut opened_container = if let Some(id) = self.open_container { - server.try_get_container(self.entity_id(), id) + let mut opened_container = self.get_open_container(server); + let opened_container = opened_container.as_deref_mut(); + + let current_state_id = if let Some(container) = opened_container.as_ref() { + container.state_id() } else { - None + self.inventory.state_id() }; - let opened_container = opened_container.as_deref_mut(); + if current_state_id != packet.state_id.0 { + dbg!(current_state_id, packet.state_id.0); + //self.set_container_content(opened_container.as_deref_mut()); + //return; + } + if opened_container.is_some() { + if packet.window_id != self.inventory.total_opened_containers { + dbg!( + self.inventory.total_opened_containers, + opened_container.unwrap().window_type() + ); + return; + } + } else if packet.window_id != 0 { + dbg!("weird"); + return; + } let click = Click { - state_id: packet.state_id.0.try_into().unwrap(), changed_items: packet .array_of_changed_slots .into_iter() @@ -139,12 +154,6 @@ impl Player { } }) .collect::>(), - window_type: WindowType::from_u8(if packet.window_id == 0 { - 0 - } else { - packet.window_id - 1 - }) - .unwrap(), carried_item: packet.carried_item.to_item(), mode: ClickMode::new( packet @@ -157,18 +166,13 @@ impl Player { ), }; match click.mode.click_type { - ClickType::MouseClick(mouse_click) => self.mouse_click( - opened_container, - mouse_click, - click.window_type, - click.mode.slot, - ), - ClickType::ShiftClick => { - self.shift_mouse_click(opened_container, click.window_type, click.mode.slot) + ClickType::MouseClick(mouse_click) => { + self.mouse_click(opened_container, mouse_click, click.mode.slot) } + ClickType::ShiftClick => self.shift_mouse_click(opened_container, click.mode.slot), ClickType::KeyClick(key_click) => match click.mode.slot { container_click::Slot::Normal(slot) => { - self.number_button_pressed(opened_container, click.window_type, key_click, slot) + self.number_button_pressed(opened_container, key_click, slot) } container_click::Slot::OutsideInventory => { unimplemented!("This is not a valid state") @@ -189,20 +193,16 @@ impl Player { drag_type: _, } => (), ClickType::DropType(_drop_type) => (), - } + }; } fn mouse_click( &mut self, opened_container: Option<&mut Box>, mouse_click: MouseClick, - window_type: WindowType, slot: container_click::Slot, ) { let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); - if container.window_type() != &window_type { - return; - } match slot { container_click::Slot::Normal(slot) => { @@ -215,41 +215,42 @@ impl Player { fn shift_mouse_click( &mut self, opened_container: Option<&mut Box>, - window_type: WindowType, slot: container_click::Slot, ) { - let container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); - if container.window_type() != &window_type { - return; - } + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); match slot { container_click::Slot::Normal(slot) => { - if let Some(item_in_pressed_slot) = self.inventory.slots()[slot] { - let slots = self.inventory.slots().into_iter().enumerate(); + let all_slots = container.all_slots(); + if let Some(item_in_pressed_slot) = all_slots[slot].to_owned() { + let slots = all_slots.into_iter().enumerate(); // Hotbar - let find_condition = |(_, slot): &(usize, Option<&ItemStack>)| { - slot.is_none_or(|item| item.item_id == item_in_pressed_slot.item_id) + let find_condition = |(slot_number, slot): (usize, &mut Option)| { + // TODO: Check for max item count here + match slot { + Some(item) => { + if item.item_id == item_in_pressed_slot.item_id + && item.item_count != 64 + { + Some(slot_number) + } else { + None + } + } + None => Some(slot_number), + } }; let slots = if slot > 35 { - slots - .skip(9) - .find(find_condition) - .map(|(slot_num, _)| slot_num) + slots.skip(9).find_map(find_condition) } else { - slots - .skip(36) - .rev() - .find(find_condition) - .map(|(slot_num, _)| slot_num) + slots.skip(36).rev().find_map(find_condition) }; if let Some(slot) = slots { - let mut item_slot = self.inventory.slots()[slot].map(|i| i.to_owned()); + let mut item_slot = container.all_slots()[slot].map(|i| i.to_owned()); - self.inventory - .handle_item_change(&mut item_slot, slot, MouseClick::Left); - *self.inventory.slots_mut()[slot] = item_slot; + container.handle_item_change(&mut item_slot, slot, MouseClick::Left); + *container.all_slots()[slot] = item_slot; } } } @@ -260,7 +261,6 @@ impl Player { fn number_button_pressed( &mut self, opened_container: Option<&mut Box>, - window_type: WindowType, key_click: KeyClick, slot: usize, ) { @@ -270,15 +270,9 @@ impl Player { }; let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); - if container.window_type() != &window_type { - return; - } - let item_slot = &mut container.all_slots()[slot]; - if item_slot.is_some() { - handle_item_change(&mut changing_item_slot, item_slot, MouseClick::Left); - *self.inventory.get_slot(changing_slot as usize) = changing_item_slot - } + container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left); + *self.inventory.get_slot(changing_slot as usize) = changing_item_slot } fn creative_pick_item( @@ -293,4 +287,15 @@ impl Player { } fn double_click(&mut self, _slot: usize) {} + + pub fn get_open_container<'a>( + &self, + server: &'a Server, + ) -> Option>> { + if let Some(id) = self.open_container { + server.try_get_container(self.entity_id(), id) + } else { + None + } + } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 45fb667a5..76df0811d 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -537,8 +537,14 @@ impl Player { // TODO: // This function will in the future be used to keep track of if the client is in a valid state. // But this is not possible yet - pub fn handle_close_container(&mut self, _server: &mut Server, packet: SCloseContainer) { + pub fn handle_close_container(&mut self, server: &mut Server, packet: SCloseContainer) { // window_id 0 represents both 9x1 Generic AND inventory here + if let Some(id) = self.open_container { + if let Some(container) = server.open_containers.get_mut(&id) { + container.remove_player(self.entity_id()) + } + self.open_container = None; + } let Some(_window_type) = WindowType::from_u8(packet.window_id) else { self.kick(TextComponent::text("Invalid window ID")); return; diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index 8a06233f0..594015cb6 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -10,34 +10,18 @@ const DESCRIPTION: &str = pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { CommandTree::new(NAMES, DESCRIPTION).execute(&|sender, server, _| { if let Some(player) = sender.as_mut_player() { - let entity_id = player.entity_id().clone(); + let entity_id = player.entity_id(); player.open_container = Some(0); - let mut container = match server.open_containers.get_mut(&0) { + match server.open_containers.get_mut(&0) { Some(ender_chest) => { - if ender_chest.try_open(entity_id).is_none() { - ender_chest.add_player(entity_id); - } - ender_chest.try_open(entity_id).unwrap() + ender_chest.add_player(entity_id); } None => { let open_container = OpenContainer::empty(entity_id); server.open_containers.insert(0, open_container); - server - .open_containers - .get_mut(&0) - .unwrap() - .try_open(entity_id) - .unwrap() } - }; - let state_id = container.state_id(); - player.open_container( - &container, - "minecraft:generic_9x3", - Some("Ender Chest"), - None, - state_id, - ); + } + player.open_container(server, "minecraft:generic_9x3", Some("Ender Chest")); } Ok(()) diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index dec87a738..d2e1afe4d 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,8 +1,6 @@ use std::sync::Arc; - -use crate::{client::Client, server::Server}; -use num_derive::{FromPrimitive, ToPrimitive}; +use num_derive::FromPrimitive; use num_traits::ToPrimitive; use pumpkin_core::{ math::{boundingbox::BoundingBox, position::WorldPosition, vector3::Vector3}, @@ -26,16 +24,13 @@ use pumpkin_protocol::{ ConnectionState, RawPacket, ServerPacket, VarInt, }; +use pumpkin_protocol::server::play::SCloseContainer; use pumpkin_world::item::ItemStack; -use pumpkin_world::vector3::Vector3; -use serde::{Deserialize, Serialize}; - use crate::{ client::{authentication::GameProfile, Client, PlayerConfig}, server::Server, world::World, - util::boundingbox::BoundingBox }; pub struct PlayerAbilities { @@ -460,6 +455,10 @@ impl Player { self.handle_click_container(server, SClickContainer::read(bytebuf)?); Ok(()) } + SCloseContainer::PACKET_ID => { + self.handle_close_container(server, SCloseContainer::read(bytebuf)?); + Ok(()) + } _ => { log::error!("Failed to handle player packet id {:#04x}", packet.id.0); Ok(()) diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index 155acf4de..f83f3f421 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -1,13 +1,3 @@ -use std::{ - io::Cursor, - path::Path, - sync::{ - atomic::{AtomicI32, Ordering}, - Arc, Mutex, - }, - time::Duration, -}; - use base64::{engine::general_purpose, Engine}; use image::GenericImageView; use mio::Token; @@ -20,19 +10,21 @@ use pumpkin_protocol::{ CURRENT_MC_PROTOCOL, }; use pumpkin_world::dimension::Dimension; - -use crate::{ - client::Client, - config::{AdvancedConfiguration, BasicConfiguration}, - entity::player::{GameMode, Player}, - world::World, +use std::collections::HashMap; +use std::{ + io::Cursor, + path::Path, + sync::{ + atomic::{AtomicI32, Ordering}, + Arc, Mutex, MutexGuard, + }, + time::Duration, }; + +use crate::{client::Client, entity::player::Player, world::World}; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_registry::Registry; use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; -use serde::{Deserialize, Serialize}; -use tokio::sync::mpsc; - pub const CURRENT_MC_VERSION: &str = "1.21.1"; @@ -55,7 +47,6 @@ pub struct Server { /// Cache the registry so we don't have to parse it every time a player joins pub cached_registry: Vec, - pub current_players: HashMap, Arc>>, pub open_containers: HashMap, entity_id: AtomicI32, @@ -102,6 +93,7 @@ impl Server { Self { plugin_loader, cached_registry: Registry::get_static(), + open_containers: HashMap::new(), // 0 is invalid entity_id: 2.into(), worlds: vec![Arc::new(tokio::sync::Mutex::new(world))], @@ -111,8 +103,6 @@ impl Server { status_response, status_response_json, public_key_der, - open_containers: HashMap::new(), - base_config: config.0, auth_client, } } @@ -134,188 +124,20 @@ impl Server { client, world.clone(), entity_id, - self.base_config.hardcore, - &["minecraft:overworld"], - self.base_config.max_players.into(), - self.base_config.view_distance.into(), // TODO: view distance - self.base_config.simulation_distance.into(), // TODO: sim view dinstance - false, - false, - false, - 0.into(), - "minecraft:overworld", - 0, // seed - gamemode.to_u8().unwrap(), - self.base_config.default_gamemode.to_i8().unwrap(), - false, - false, - None, - 0.into(), - false, - )); - dbg!("sending abilities"); - // player abilities - player - .client - .send_packet(&CPlayerAbilities::new(0x02, 0.1, 0.1)); - - // teleport - let x = 10.0; - let y = 120.0; - let z = 10.0; - let yaw = 10.0; - let pitch = 10.0; - player.teleport(x, y, z, 10.0, 10.0); - let gameprofile = &player.gameprofile; - // first send info update to our new player, So he can see his Skin - // also send his info to everyone else - self.broadcast_packet( - player, - &CPlayerInfoUpdate::new( - 0x01 | 0x08, - &[pumpkin_protocol::client::play::Player { - uuid: gameprofile.id, - actions: vec![ - PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), - }, - PlayerAction::UpdateListed(true), - ], - }], - ), - ); - - // here we send all the infos of already joined players - let mut entries = Vec::new(); - for (_, playerr) in self - .current_players - .iter() - .filter(|c| c.0 != &player.client.token) - { - let playerr = playerr.as_ref().lock().unwrap(); - let gameprofile = &playerr.gameprofile; - entries.push(pumpkin_protocol::client::play::Player { - uuid: gameprofile.id, - actions: vec![ - PlayerAction::AddPlayer { - name: gameprofile.name.clone(), - properties: gameprofile.properties.clone(), - }, - PlayerAction::UpdateListed(true), - ], - }) - } - player - .client - .send_packet(&CPlayerInfoUpdate::new(0x01 | 0x08, &entries)); - - // Start waiting for level chunks - player.client.send_packet(&CGameEvent::new(13, 0.0)); - - let gameprofile = &player.gameprofile; - - // spawn player for every client - self.broadcast_packet_except( - &[&player.client.token], - // TODO: add velo - &CSpawnEntity::new( - entity_id.into(), - UUID(gameprofile.id), - (EntityType::Player as i32).into(), - x, - y, - z, - pitch, - yaw, - yaw, - 0.into(), - 0.0, - 0.0, - 0.0, - ), - ); - // spawn players for our client - let token = player.client.token.clone(); - for (_, existing_player) in self.current_players.iter().filter(|c| c.0 != &token) { - let existing_player = existing_player.as_ref().lock().unwrap(); - let entity = &existing_player.entity; - let gameprofile = &existing_player.gameprofile; - player.client.send_packet(&CSpawnEntity::new( - existing_player.entity_id().into(), - UUID(gameprofile.id), - EntityType::Player.to_i32().unwrap().into(), - entity.x, - entity.y, - entity.z, - entity.yaw, - entity.pitch, - entity.pitch, - 0.into(), - 0.0, - 0.0, - 0.0, - )) - } - // entity meta data - if let Some(config) = player.client.config.as_ref() { - self.broadcast_packet( - player, - &CSetEntityMetadata::new( - entity_id.into(), - Metadata::new(17, VarInt(0), config.skin_parts), - ), - ) - } - - self.spawn_test_chunk(player, self.base_config.view_distance as u32) - .await; - } - - /// TODO: This definitly should be in world - pub fn get_by_entityid(&self, from: &Player, id: EntityId) -> Option> { - for (_, player) in self - .current_players - .iter() - .filter(|c| c.0 != &from.client.token) - { - let player = player.lock().unwrap(); - if player.entity_id() == id { - return Some(player); - } - } - None + gamemode, + ))); + world.lock().await.add_player(token, player.clone()); + (player, world) } pub fn try_get_container( &self, player_id: EntityId, container_id: u64, - ) -> Option>> { + ) -> Option>> { self.open_containers.get(&container_id)?.try_open(player_id) } - /// Sends a Packet to all Players - pub fn broadcast_packet

(&self, from: &mut Player, packet: &P) - where - P: ClientPacket, - { - // we can't borrow twice at same time - from.client.send_packet(packet); - for (_, player) in self - .current_players - .iter() - .filter(|c| c.0 != &from.client.token) - { - let mut player = player.lock().unwrap(); - player.client.send_packet(packet); - } - gamemode, - ))); - world.lock().await.add_player(token, player.clone()); - (player, world) - } - /// Sends a Packet to all Players in all worlds pub fn broadcast_packet_all

(&self, expect: &[&Arc], packet: &P) where From 28292439de1330a8a8ccc131927fb229be631f8b Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Tue, 3 Sep 2024 08:36:08 +0200 Subject: [PATCH 22/36] refactor away unnecessary abstraction --- pumpkin-inventory/src/container_click.rs | 10 ++---- pumpkin/src/client/container.rs | 46 +++++++++--------------- 2 files changed, 18 insertions(+), 38 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 14a26c905..2e739d12a 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,17 +1,11 @@ use pumpkin_world::item::ItemStack; -pub struct ClickMode { +pub struct Click { pub slot: Slot, pub click_type: ClickType, } -pub struct Click { - pub mode: ClickMode, - pub changed_items: Vec, - pub carried_item: Option, -} - -impl ClickMode { +impl Click { pub fn new(mode: u8, button: i8, slot: i16) -> Self { match mode { 0 => Self::new_normal_click(button, slot), diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index dee0ae9ca..d681c9676 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -141,36 +141,22 @@ impl Player { dbg!("weird"); return; } - let click = Click { - changed_items: packet - .array_of_changed_slots - .into_iter() - .map(|(slot, item)| { - let slot = slot.try_into().unwrap(); - if let Some(item) = item.to_item() { - ItemChange::Add { slot, item } - } else { - ItemChange::Remove { slot } - } - }) - .collect::>(), - carried_item: packet.carried_item.to_item(), - mode: ClickMode::new( - packet - .mode - .0 - .try_into() - .expect("Mode can only be between 0-6"), - packet.button, - packet.slot, - ), - }; - match click.mode.click_type { + + let click = Click::new( + packet + .mode + .0 + .try_into() + .expect("Mode can only be between 0-6"), + packet.button, + packet.slot, + ); + match click.click_type { ClickType::MouseClick(mouse_click) => { - self.mouse_click(opened_container, mouse_click, click.mode.slot) + self.mouse_click(opened_container, mouse_click, click.slot) } - ClickType::ShiftClick => self.shift_mouse_click(opened_container, click.mode.slot), - ClickType::KeyClick(key_click) => match click.mode.slot { + ClickType::ShiftClick => self.shift_mouse_click(opened_container, click.slot), + ClickType::KeyClick(key_click) => match click.slot { container_click::Slot::Normal(slot) => { self.number_button_pressed(opened_container, key_click, slot) } @@ -179,12 +165,12 @@ impl Player { } }, ClickType::CreativePickItem => { - if let container_click::Slot::Normal(slot) = click.mode.slot { + if let container_click::Slot::Normal(slot) = click.slot { self.creative_pick_item(opened_container, slot) } } ClickType::DoubleClick => { - if let container_click::Slot::Normal(slot) = click.mode.slot { + if let container_click::Slot::Normal(slot) = click.slot { self.double_click(slot) } } From bb6677dd19163b73015a3bb3552af301457f2130 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 14:54:28 +0200 Subject: [PATCH 23/36] add drag and error handling for containers --- Cargo.lock | 2 + pumpkin-inventory/Cargo.toml | 3 +- pumpkin-inventory/src/container_click.rs | 73 ++-------- pumpkin-inventory/src/drag_handler.rs | 178 +++++++++++++++++++++++ pumpkin-inventory/src/error.rs | 33 +++++ pumpkin-inventory/src/lib.rs | 18 ++- pumpkin-inventory/src/open_container.rs | 16 +- pumpkin-inventory/src/player.rs | 25 ++-- pumpkin-world/src/item/mod.rs | 6 + pumpkin/src/client/container.rs | 137 +++++++++++------ pumpkin/src/server.rs | 10 +- 11 files changed, 367 insertions(+), 134 deletions(-) create mode 100644 pumpkin-inventory/src/drag_handler.rs create mode 100644 pumpkin-inventory/src/error.rs diff --git a/Cargo.lock b/Cargo.lock index fd53bd141..40d338ca4 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1967,9 +1967,11 @@ dependencies = [ name = "pumpkin-inventory" version = "0.1.0" dependencies = [ + "itertools 0.13.0", "num-derive", "num-traits", "pumpkin-world", + "thiserror", ] [[package]] diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index 83f80a194..ccbb61f69 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] num-traits = "0.2" num-derive = "0.4" - +thiserror = "1.0.63" +itertools = "0.13.0" # For items pumpkin-world = { path = "../pumpkin-world"} diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 2e739d12a..cd5559dcb 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -83,63 +83,21 @@ impl Click { } fn new_drag_item(button: i8, slot: i16) -> Self { - let (mouse_type, state, slot) = match button { - 0 => ( - MouseDragType::Left, - MouseDragState::Start, - Slot::OutsideInventory, - ), - 1 => ( - MouseDragType::Left, - MouseDragState::AddSlot, - Slot::Normal(slot.try_into().unwrap()), - ), - 2 => ( - MouseDragType::Left, - MouseDragState::End, - Slot::OutsideInventory, - ), - 4 => ( - MouseDragType::Right, - MouseDragState::Start, - Slot::OutsideInventory, - ), - 5 => ( - MouseDragType::Right, - MouseDragState::AddSlot, - Slot::Normal(slot.try_into().unwrap()), - ), - 6 => ( - MouseDragType::Right, - MouseDragState::End, - Slot::OutsideInventory, - ), - - // ONLY FOR CREATIVE - 8 => ( - MouseDragType::Middle, - MouseDragState::Start, - Slot::OutsideInventory, - ), - 9 => ( - MouseDragType::Middle, - MouseDragState::AddSlot, - Slot::Normal(slot.try_into().unwrap()), - ), - 10 => ( - MouseDragType::Middle, - MouseDragState::End, - Slot::OutsideInventory, - ), + let state = match button { + 0 => MouseDragState::Start(MouseDragType::Left), + 4 => MouseDragState::Start(MouseDragType::Right), + 8 => MouseDragState::Start(MouseDragType::Middle), + 1 | 5 | 9 => MouseDragState::AddSlot(slot.try_into().unwrap()), + 2 | 6 | 10 => MouseDragState::End, // TODO: Error handling _ => unreachable!(), }; Self { - click_type: ClickType::MouseDrag { - drag_state: state, - drag_type: mouse_type, + slot: match &state { + MouseDragState::AddSlot(slot) => Slot::Normal(*slot), + _ => Slot::OutsideInventory, }, - slot, + click_type: ClickType::MouseDrag { drag_state: state }, } } } @@ -150,10 +108,7 @@ pub enum ClickType { KeyClick(KeyClick), CreativePickItem, DropType(DropType), - MouseDrag { - drag_state: MouseDragState, - drag_type: MouseDragType, - }, + MouseDrag { drag_state: MouseDragState }, DoubleClick, } #[derive(Debug, PartialEq, Eq)] @@ -176,7 +131,7 @@ pub enum DropType { SingleItem, FullStack, } - +#[derive(Debug)] pub enum MouseDragType { Left, Right, @@ -184,8 +139,8 @@ pub enum MouseDragType { } pub enum MouseDragState { - Start, - AddSlot, + Start(MouseDragType), + AddSlot(usize), End, } diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs new file mode 100644 index 000000000..8ac7198fc --- /dev/null +++ b/pumpkin-inventory/src/drag_handler.rs @@ -0,0 +1,178 @@ +use crate::container_click::MouseDragType; +use crate::{Container, InventoryError}; +use itertools::Itertools; +use num_traits::Euclid; +use pumpkin_world::item::ItemStack; +use std::collections::HashMap; +use std::iter::{Enumerate, Filter, TakeWhile}; +use std::sync::{Arc, Mutex, RwLock}; +use std::vec::IntoIter; +#[derive(Debug, Default)] +pub struct DragHandler(RwLock>>>); + +impl DragHandler { + pub fn new() -> Self { + Self(RwLock::new(HashMap::new())) + } + pub fn new_drag( + &self, + container_id: u64, + player: i32, + drag_type: MouseDragType, + ) -> Result<(), InventoryError> { + let drag = Drag { + player, + drag_type, + slots: vec![], + }; + let mut drags = match self.0.write() { + Ok(drags) => drags, + Err(_) => Err(InventoryError::LockError)?, + }; + drags.insert(container_id, Arc::new(Mutex::new(drag))); + Ok(()) + } + + pub fn add_slot( + &self, + container_id: u64, + player: i32, + slot: usize, + ) -> Result<(), InventoryError> { + let drags = match self.0.read() { + Ok(drags) => drags, + Err(_) => Err(InventoryError::LockError)?, + }; + match drags.get(&container_id) { + Some(drag) => { + let mut drag = drag.lock().unwrap(); + if drag.player != player { + Err(InventoryError::MultiplePlayersDragging)? + } + if !drag.slots.contains(&slot) { + drag.slots.push(slot); + } + } + None => Err(InventoryError::OutOfOrderDragging)?, + } + Ok(()) + } + + pub fn apply_drag( + &self, + carried_item: &mut Option, + container: &mut T, + container_id: &u64, + player: i32, + ) -> Result<(), InventoryError> { + // Minecraft client does still send dragging packets when not carrying an item! + if carried_item.is_none() { + return Ok(()); + } + + let Ok(mut drags) = self.0.write() else { + Err(InventoryError::LockError)? + }; + let Some((_, drag)) = drags.remove_entry(container_id) else { + Err(InventoryError::OutOfOrderDragging)? + }; + let mut drag = drag.lock().unwrap(); + + if player != drag.player { + Err(InventoryError::MultiplePlayersDragging)? + } + let mut slots = container.all_slots(); + let slots_cloned = slots + .iter() + .map(|stack| stack.map(|item| item.to_owned())) + .collect_vec(); + match drag.drag_type { + // This is only valid in Creative GameMode. + // Checked in any function that uses this function. + MouseDragType::Middle => { + for slot in &drag.slots { + *slots[*slot] = carried_item.clone(); + } + } + MouseDragType::Right => { + let amount_of_items = carried_item.unwrap().item_count as usize; + let mut single_item = carried_item.clone().unwrap(); + single_item.item_count = 1; + let changing_slots = drag.changing_slots( + amount_of_items, + &slots_cloned, + carried_item.as_ref().unwrap(), + ); + let mut amount_removed = 0; + changing_slots.for_each(|slot| { + amount_removed += 1; + *slots[slot] = Some(single_item.clone()) + }); + + let mut remaining = carried_item.unwrap(); + if remaining.item_count == amount_removed { + *carried_item = None + } else { + remaining.item_count -= amount_removed; + *carried_item = Some(remaining) + } + } + MouseDragType::Left => { + let amount_of_items = carried_item.unwrap().item_count as usize; + // TODO: Handle dragging a stack with greater amount than item allows as max unstackable + // In that specific case, follow MouseDragType::Right behaviours instead! + + let changing_slots = drag.changing_slots( + amount_of_items, + &slots_cloned, + carried_item.as_ref().unwrap(), + ); + let amount_of_slots = changing_slots.clone().count(); + let (amount_per_slot, remainder) = + (carried_item.unwrap().item_count as usize).div_rem_euclid(&amount_of_slots); + let mut item_in_each_slot = carried_item.unwrap().clone(); + item_in_each_slot.item_count = amount_per_slot as u8; + changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot.clone())); + + if remainder > 0 { + let mut remaining = carried_item.unwrap().clone(); + remaining.item_count = remainder as u8; + *carried_item = Some(remaining) + } else { + *carried_item = None + } + } + } + Ok(()) + } +} +#[derive(Debug)] +struct Drag { + player: i32, + drag_type: MouseDragType, + slots: Vec, +} + +impl Drag { + fn changing_slots<'a>( + &'a self, + amount_of_items: usize, + slots: &'a [Option], + carried_item: &'a ItemStack, + ) -> impl Iterator + 'a + Clone { + self.slots + .iter() + .enumerate() + .take_while(move |(slot_number, _)| *slot_number <= amount_of_items) + .filter_map(move |(_, slot)| match &slots[*slot] { + Some(item_slot) => { + if *item_slot == *carried_item { + Some(*slot) + } else { + None + } + } + None => Some(*slot), + }) + } +} diff --git a/pumpkin-inventory/src/error.rs b/pumpkin-inventory/src/error.rs new file mode 100644 index 000000000..e74ece87e --- /dev/null +++ b/pumpkin-inventory/src/error.rs @@ -0,0 +1,33 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum InventoryError { + #[error("Unable to lock")] + LockError, + #[error("Invalid slot")] + InvalidSlot, + #[error("Player '{0}' tried to interact with a closed container")] + ClosedContainerInteract(i32), + #[error("Multiple players dragging in a container at once")] + MultiplePlayersDragging, + #[error("Out of order dragging")] + OutOfOrderDragging, + #[error("Invalid inventory packet")] + InvalidPacket, + #[error("Player does not have enough permissions")] + PermissionError, +} + +impl InventoryError { + pub fn should_kick(&self) -> bool { + match self { + InventoryError::InvalidSlot + | InventoryError::ClosedContainerInteract(..) + | InventoryError::InvalidPacket + | InventoryError::PermissionError => true, + InventoryError::LockError + | InventoryError::OutOfOrderDragging + | InventoryError::MultiplePlayersDragging => false, + } + } +} diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 654d2352e..e90884da8 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,13 +1,16 @@ -use crate::container_click::MouseClick; +use crate::container_click::{MouseClick, MouseDragState, MouseDragType}; use crate::player::PlayerInventory; use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; pub mod container_click; +pub mod drag_handler; +mod error; mod open_container; pub mod player; pub mod window_property; +pub use error::InventoryError; pub use open_container::OpenContainer; /// https://wiki.vg/Inventory @@ -68,8 +71,13 @@ pub trait Container: Sync + Send { carried_item: &mut Option, slot: usize, mouse_click: MouseClick, - ) { - handle_item_change(carried_item, self.all_slots()[slot], mouse_click) + ) -> Result<(), InventoryError> { + let mut all_slots = self.all_slots(); + if slot < all_slots.len() { + Err(InventoryError::InvalidSlot)? + } + handle_item_change(carried_item, all_slots[slot], mouse_click); + Ok(()) } fn all_slots(&mut self) -> Vec<&mut Option>; @@ -98,6 +106,10 @@ pub trait Container: Sync + Send { dbg!(slot, item); }); } + + fn internal_pumpkin_id(&self) -> u64 { + 0 + } } pub fn handle_item_take( diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 601262384..3789575c7 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,6 +1,7 @@ +use crate::drag_handler::DragHandler; use crate::{Container, WindowType}; use pumpkin_world::item::ItemStack; -use std::sync::{Mutex, MutexGuard}; +use std::sync::{Arc, Mutex}; pub struct OpenContainer { players: Vec, @@ -8,21 +9,12 @@ pub struct OpenContainer { } impl OpenContainer { - pub fn try_open(&self, player_id: i32) -> Option>> { + pub fn try_open(&self, player_id: i32) -> Option<&Mutex>> { if !self.players.contains(&player_id) { dbg!("couldn't open container"); return None; } - let container = self.container.lock().unwrap(); - container - .all_slots_ref() - .iter() - .enumerate() - .for_each(|(slot, item)| { - if let Some(item) = item { - dbg!(slot, item); - } - }); + let container = &self.container; Some(container) } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 355fea173..96e7a8907 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -1,5 +1,6 @@ use crate::container_click::MouseClick; -use crate::{handle_item_change, Container, WindowType}; +use crate::drag_handler::DragHandler; +use crate::{handle_item_change, Container, InventoryError, WindowType}; use pumpkin_world::item::ItemStack; #[allow(dead_code)] @@ -92,17 +93,17 @@ impl PlayerInventory { _ => unreachable!(), } } - pub fn get_slot(&mut self, slot: usize) -> &mut Option { + pub fn get_slot(&mut self, slot: usize) -> Result<&mut Option, InventoryError> { match slot { 0 => { // TODO: Add crafting check here - &mut self.crafting_output + Ok(&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!(), + 1..=4 => Ok(&mut self.crafting[slot - 1]), + 5..=8 => Ok(&mut self.armor[slot - 5]), + 9..=44 => Ok(&mut self.items[slot - 9]), + 45 => Ok(&mut self.offhand), + _ => Err(InventoryError::InvalidSlot), } } pub fn set_selected(&mut self, slot: usize) { @@ -144,9 +145,11 @@ impl Container for PlayerInventory { 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) + ) -> Result<(), InventoryError> { + let item_slot = self.get_slot(slot)?; + + handle_item_change(carried_slot, item_slot, mouse_click); + Ok(()) } fn all_slots(&mut self) -> Vec<&mut Option> { diff --git a/pumpkin-world/src/item/mod.rs b/pumpkin-world/src/item/mod.rs index 5f5a588d3..8625a9023 100644 --- a/pumpkin-world/src/item/mod.rs +++ b/pumpkin-world/src/item/mod.rs @@ -18,3 +18,9 @@ pub struct ItemStack { pub item_id: u32, // TODO: Add Item Components } + +impl PartialEq for ItemStack { + fn eq(&self, other: &Self) -> bool { + self.item_id == other.item_id + } +} diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index d681c9676..d34fa9982 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,8 +1,12 @@ +use crate::entity::player::Player; +use crate::server::Server; use itertools::Itertools; use pumpkin_core::text::TextComponent; -use pumpkin_inventory::container_click::{KeyClick, MouseClick}; +use pumpkin_core::GameMode; +use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState, MouseDragType}; +use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; -use pumpkin_inventory::{container_click, OptionallyCombinedContainer}; +use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, @@ -10,10 +14,7 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; -use std::sync::MutexGuard; - -use crate::entity::player::Player; -use crate::server::Server; +use std::sync::{Arc, Mutex, MutexGuard}; impl Player { pub fn open_container( @@ -22,8 +23,11 @@ impl Player { minecraft_menu_id: &str, window_title: Option<&str>, ) { - let mut container = self.get_open_container(server); - let container = container.as_deref_mut(); + self.inventory.reset_state_id(); + let total_opened_containers = self.inventory.total_opened_containers; + let mut container = self + .get_open_container(server) + .map(|container| container.lock().unwrap()); let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY .get("minecraft:menu") .unwrap() @@ -40,14 +44,12 @@ impl Player { .to_owned(); let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); - self.inventory.reset_state_id(); - self.client.send_packet(&COpenScreen::new( - self.inventory.total_opened_containers.into(), + total_opened_containers.into(), menu_protocol_id, title, )); - self.set_container_content(container); + self.set_container_content(container.as_deref_mut()); } pub fn set_container_content(&mut self, container: Option<&mut Box>) { @@ -113,33 +115,35 @@ impl Player { )); } - pub fn handle_click_container(&mut self, server: &mut Server, packet: SClickContainer) { + pub fn handle_click_container( + &mut self, + server: &mut Server, + packet: SClickContainer, + ) -> Result<(), InventoryError> { use container_click::*; - let mut opened_container = self.get_open_container(server); - let opened_container = opened_container.as_deref_mut(); + let mut opened_container = self + .get_open_container(server) + .map(|container| container.lock().unwrap()); + let mut opened_container = opened_container.as_deref_mut(); + let drag_handler = &server.drag_handler; let current_state_id = if let Some(container) = opened_container.as_ref() { container.state_id() } else { self.inventory.state_id() }; + // This is just checking for regular desync, client hasn't done anything malicious if current_state_id != packet.state_id.0 { - dbg!(current_state_id, packet.state_id.0); - //self.set_container_content(opened_container.as_deref_mut()); - //return; + self.set_container_content(opened_container.as_deref_mut()); + return Ok(()); } if opened_container.is_some() { if packet.window_id != self.inventory.total_opened_containers { - dbg!( - self.inventory.total_opened_containers, - opened_container.unwrap().window_type() - ); - return; + return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } } else if packet.window_id != 0 { - dbg!("weird"); - return; + return Err(InventoryError::ClosedContainerInteract(self.entity_id())); } let click = Click::new( @@ -160,26 +164,27 @@ impl Player { container_click::Slot::Normal(slot) => { self.number_button_pressed(opened_container, key_click, slot) } - container_click::Slot::OutsideInventory => { - unimplemented!("This is not a valid state") - } + container_click::Slot::OutsideInventory => Err(InventoryError::InvalidPacket), }, ClickType::CreativePickItem => { if let container_click::Slot::Normal(slot) = click.slot { self.creative_pick_item(opened_container, slot) + } else { + Err(InventoryError::InvalidPacket) } } ClickType::DoubleClick => { if let container_click::Slot::Normal(slot) = click.slot { self.double_click(slot) + } else { + Err(InventoryError::InvalidPacket) } } - ClickType::MouseDrag { - drag_state: _, - drag_type: _, - } => (), - ClickType::DropType(_drop_type) => (), - }; + ClickType::MouseDrag { drag_state } => { + self.mouse_drag(drag_handler, opened_container, drag_state) + } + ClickType::DropType(_drop_type) => todo!(), + } } fn mouse_click( @@ -187,22 +192,22 @@ impl Player { opened_container: Option<&mut Box>, mouse_click: MouseClick, slot: container_click::Slot, - ) { + ) -> Result<(), InventoryError> { let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); match slot { container_click::Slot::Normal(slot) => { container.handle_item_change(&mut self.carried_item, slot, mouse_click) } - container_click::Slot::OutsideInventory => (), - }; + container_click::Slot::OutsideInventory => todo!(), + } } fn shift_mouse_click( &mut self, opened_container: Option<&mut Box>, slot: container_click::Slot, - ) { + ) -> Result<(), InventoryError> { let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); match slot { @@ -235,13 +240,14 @@ impl Player { if let Some(slot) = slots { let mut item_slot = container.all_slots()[slot].map(|i| i.to_owned()); - container.handle_item_change(&mut item_slot, slot, MouseClick::Left); + container.handle_item_change(&mut item_slot, slot, MouseClick::Left)?; *container.all_slots()[slot] = item_slot; } } } container_click::Slot::OutsideInventory => (), }; + Ok(()) } fn number_button_pressed( @@ -249,35 +255,74 @@ impl Player { opened_container: Option<&mut Box>, key_click: KeyClick, slot: usize, - ) { + ) -> Result<(), InventoryError> { let changing_slot = match key_click { KeyClick::Slot(slot) => slot, KeyClick::Offhand => 45, }; - let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize).to_owned(); + let mut changing_item_slot = self.inventory.get_slot(changing_slot as usize)?.to_owned(); let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); - container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left); - *self.inventory.get_slot(changing_slot as usize) = changing_item_slot + container.handle_item_change(&mut changing_item_slot, slot, MouseClick::Left)?; + *self.inventory.get_slot(changing_slot as usize)? = changing_item_slot; + Ok(()) } fn creative_pick_item( &mut self, opened_container: Option<&mut Box>, slot: usize, - ) { + ) -> Result<(), InventoryError> { + if self.gamemode != GameMode::Creative { + return Err(InventoryError::PermissionError); + } let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); if let Some(Some(item)) = container.all_slots().get_mut(slot) { self.carried_item = Some(item.to_owned()) } + Ok(()) + } + + fn double_click(&mut self, _slot: usize) -> Result<(), InventoryError> { + Ok(()) } - fn double_click(&mut self, _slot: usize) {} + fn mouse_drag( + &mut self, + drag_handler: &DragHandler, + opened_container: Option<&mut Box>, + mouse_drag_state: MouseDragState, + ) -> Result<(), InventoryError> { + let player_id = self.entity_id(); + let container_id = opened_container + .as_ref() + .map(|container| container.internal_pumpkin_id()) + .unwrap_or(player_id as u64); + match mouse_drag_state { + MouseDragState::Start(drag_type) => { + drag_handler.new_drag(container_id, player_id, drag_type)? + } + MouseDragState::AddSlot(slot) => { + drag_handler.add_slot(container_id, player_id, slot)? + } + MouseDragState::End => { + let mut container = + OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + drag_handler.apply_drag( + &mut self.carried_item, + &mut container, + &container_id, + player_id, + )? + } + } + Ok(()) + } pub fn get_open_container<'a>( &self, server: &'a Server, - ) -> Option>> { + ) -> Option<&'a Mutex>> { if let Some(id) = self.open_container { server.try_get_container(self.entity_id(), id) } else { diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index f83f3f421..d36f05a47 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -22,6 +22,7 @@ use std::{ }; use crate::{client::Client, entity::player::Player, world::World}; +use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::{Container, OpenContainer}; use pumpkin_registry::Registry; use rsa::{traits::PublicKeyParts, RsaPrivateKey, RsaPublicKey}; @@ -48,6 +49,7 @@ pub struct Server { pub cached_registry: Vec, pub open_containers: HashMap, + pub drag_handler: DragHandler, entity_id: AtomicI32, /// Used for Authentication, None is Online mode is disabled @@ -94,6 +96,7 @@ impl Server { plugin_loader, cached_registry: Registry::get_static(), open_containers: HashMap::new(), + drag_handler: DragHandler::new(), // 0 is invalid entity_id: 2.into(), worlds: vec![Arc::new(tokio::sync::Mutex::new(world))], @@ -134,8 +137,11 @@ impl Server { &self, player_id: EntityId, container_id: u64, - ) -> Option>> { - self.open_containers.get(&container_id)?.try_open(player_id) + ) -> Option<&Mutex>> { + self.open_containers + .get(&container_id)? + .try_open(player_id) + .map(|container| container) } /// Sends a Packet to all Players in all worlds From 4551d0bd0d97b37630020fc687040d28ddc77cbd Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 14:57:11 +0200 Subject: [PATCH 24/36] clippy and format --- pumpkin-inventory/src/drag_handler.rs | 4 +--- pumpkin-inventory/src/lib.rs | 2 +- pumpkin-inventory/src/open_container.rs | 3 +-- pumpkin-inventory/src/player.rs | 1 - pumpkin/src/client/container.rs | 4 ++-- pumpkin/src/entity/player.rs | 3 ++- pumpkin/src/server.rs | 2 +- 7 files changed, 8 insertions(+), 11 deletions(-) diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index 8ac7198fc..aefb96cac 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -4,9 +4,7 @@ use itertools::Itertools; use num_traits::Euclid; use pumpkin_world::item::ItemStack; use std::collections::HashMap; -use std::iter::{Enumerate, Filter, TakeWhile}; use std::sync::{Arc, Mutex, RwLock}; -use std::vec::IntoIter; #[derive(Debug, Default)] pub struct DragHandler(RwLock>>>); @@ -76,7 +74,7 @@ impl DragHandler { let Some((_, drag)) = drags.remove_entry(container_id) else { Err(InventoryError::OutOfOrderDragging)? }; - let mut drag = drag.lock().unwrap(); + let drag = drag.lock().unwrap(); if player != drag.player { Err(InventoryError::MultiplePlayersDragging)? diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index e90884da8..738262c92 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,4 +1,4 @@ -use crate::container_click::{MouseClick, MouseDragState, MouseDragType}; +use crate::container_click::MouseClick; use crate::player::PlayerInventory; use num_derive::{FromPrimitive, ToPrimitive}; use pumpkin_world::item::ItemStack; diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 3789575c7..87a9e52fa 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -1,7 +1,6 @@ -use crate::drag_handler::DragHandler; use crate::{Container, WindowType}; use pumpkin_world::item::ItemStack; -use std::sync::{Arc, Mutex}; +use std::sync::Mutex; pub struct OpenContainer { players: Vec, diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index 96e7a8907..e37cbd9bb 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -1,5 +1,4 @@ use crate::container_click::MouseClick; -use crate::drag_handler::DragHandler; use crate::{handle_item_change, Container, InventoryError, WindowType}; use pumpkin_world::item::ItemStack; diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index d34fa9982..a51383ced 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -3,7 +3,7 @@ use crate::server::Server; use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; -use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState, MouseDragType}; +use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState}; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; @@ -14,7 +14,7 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; -use std::sync::{Arc, Mutex, MutexGuard}; +use std::sync::Mutex; impl Player { pub fn open_container( diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index d2e1afe4d..617eb8e03 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -452,7 +452,8 @@ impl Player { Ok(()) } SClickContainer::PACKET_ID => { - self.handle_click_container(server, SClickContainer::read(bytebuf)?); + self.handle_click_container(server, SClickContainer::read(bytebuf)?) + .unwrap(); Ok(()) } SCloseContainer::PACKET_ID => { diff --git a/pumpkin/src/server.rs b/pumpkin/src/server.rs index d36f05a47..4d908919e 100644 --- a/pumpkin/src/server.rs +++ b/pumpkin/src/server.rs @@ -16,7 +16,7 @@ use std::{ path::Path, sync::{ atomic::{AtomicI32, Ordering}, - Arc, Mutex, MutexGuard, + Arc, Mutex, }, time::Duration, }; From db70ea1cbfc961ca63f1b04fb0ab85ec0478b419 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 14:58:48 +0200 Subject: [PATCH 25/36] add checking gamemode for middleclick dragging --- pumpkin-inventory/src/container_click.rs | 2 +- pumpkin/src/client/container.rs | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index cd5559dcb..b9ad246d6 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -131,7 +131,7 @@ pub enum DropType { SingleItem, FullStack, } -#[derive(Debug)] +#[derive(Debug, PartialEq)] pub enum MouseDragType { Left, Right, diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index a51383ced..68f849c82 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -3,7 +3,7 @@ use crate::server::Server; use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; -use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState}; +use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState, MouseDragType}; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; @@ -300,11 +300,12 @@ impl Player { .unwrap_or(player_id as u64); match mouse_drag_state { MouseDragState::Start(drag_type) => { - drag_handler.new_drag(container_id, player_id, drag_type)? - } - MouseDragState::AddSlot(slot) => { - drag_handler.add_slot(container_id, player_id, slot)? + if drag_type == MouseDragType::Middle && self.gamemode != GameMode::Creative { + Err(InventoryError::PermissionError)? + } + drag_handler.new_drag(container_id, player_id, drag_type) } + MouseDragState::AddSlot(slot) => drag_handler.add_slot(container_id, player_id, slot), MouseDragState::End => { let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); @@ -313,10 +314,9 @@ impl Player { &mut container, &container_id, player_id, - )? + ) } } - Ok(()) } pub fn get_open_container<'a>( From 516ef3021f7c2b04843b312271e71be43591d7c0 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 16:12:50 +0200 Subject: [PATCH 26/36] add sending packets on container change --- pumpkin-inventory/src/container_click.rs | 2 +- pumpkin-inventory/src/lib.rs | 3 +- pumpkin-inventory/src/open_container.rs | 4 ++ pumpkin/src/client/container.rs | 64 +++++++++++++++++------- pumpkin/src/entity/player.rs | 1 + 5 files changed, 54 insertions(+), 20 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index b9ad246d6..779a85866 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -121,7 +121,7 @@ pub enum KeyClick { Slot(u8), Offhand, } - +#[derive(Copy, Clone)] pub enum Slot { Normal(usize), OutsideInventory, diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 738262c92..ac818851f 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -73,7 +73,7 @@ pub trait Container: Sync + Send { mouse_click: MouseClick, ) -> Result<(), InventoryError> { let mut all_slots = self.all_slots(); - if slot < all_slots.len() { + if slot > all_slots.len() || slot < 0 { Err(InventoryError::InvalidSlot)? } handle_item_change(carried_item, all_slots[slot], mouse_click); @@ -231,7 +231,6 @@ impl<'a> Container for OptionallyCombinedContainer<'a, 'a> { } None => self.inventory.all_slots(), }; - dbg!(slots.len()); slots } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 87a9e52fa..4591b48a2 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -41,6 +41,10 @@ impl OpenContainer { container: Mutex::new(Box::new(Chest::new())), } } + + pub fn all_player_ids(&self) -> Vec { + self.players.clone() + } } struct Chest { diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 68f849c82..96f071b5f 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -14,6 +14,7 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; +use std::ops::DerefMut; use std::sync::Mutex; impl Player { @@ -80,21 +81,6 @@ impl Player { self.client.send_packet(&packet); } - pub fn set_container_slot( - &mut self, - window_type: WindowType, - slot: usize, - item: Option<&ItemStack>, - state_id: i32, - ) { - self.client.send_packet(&CSetContainerSlot::new( - window_type as i8, - state_id, - slot, - &item.into(), - )) - } - /// The official Minecraft client is weird, and will always just close *any* window that is opened when this gets sent pub fn close_container(&mut self) { self.inventory.total_opened_containers += 1; @@ -115,7 +101,7 @@ impl Player { )); } - pub fn handle_click_container( + pub async fn handle_click_container( &mut self, server: &mut Server, packet: SClickContainer, @@ -155,6 +141,8 @@ impl Player { packet.button, packet.slot, ); + + let slot = click.slot.clone(); match click.click_type { ClickType::MouseClick(mouse_click) => { self.mouse_click(opened_container, mouse_click, click.slot) @@ -184,7 +172,11 @@ impl Player { self.mouse_drag(drag_handler, opened_container, drag_state) } ClickType::DropType(_drop_type) => todo!(), + }?; + if let container_click::Slot::Normal(slot) = click.slot { + self.send_container_changes(server, slot).await?; } + Ok(()) } fn mouse_click( @@ -239,7 +231,6 @@ impl Player { }; if let Some(slot) = slots { let mut item_slot = container.all_slots()[slot].map(|i| i.to_owned()); - container.handle_item_change(&mut item_slot, slot, MouseClick::Left)?; *container.all_slots()[slot] = item_slot; } @@ -319,6 +310,45 @@ impl Player { } } + async fn send_container_changes( + &mut self, + server: &Server, + slot_index: usize, + ) -> Result<(), InventoryError> { + // If we don't have a container open, then we don't need to share state + let Some(container) = self.get_open_container(server) else { + return Ok(()); + }; + let player_ids = server + .open_containers + .get(&self.open_container.unwrap()) + .unwrap() + .all_player_ids() + .into_iter() + .filter(|player_id| *player_id != self.entity_id()) + .collect_vec(); + let mut container = container.lock().or(Err(InventoryError::LockError))?; + + // TODO: Figure out better way to get only the players from player_ids + // Also refactor out a better method to get individual advanced state ids + for player in self.world.lock().await.current_players.values() { + let mut container = + OptionallyCombinedContainer::new(&mut self.inventory, Some(container.deref_mut())); + let state_id = container.advance_state_id(); + let all_slots = container.all_slots_ref(); + + let slot = Slot::from(all_slots[slot_index]); + let packet = + CSetContainerSlot::new(*container.window_type() as i8, state_id, slot_index, &slot); + let mut player = player.lock().or(Err(InventoryError::LockError))?; + if player_ids.contains(&player.entity_id()) { + player.client.send_packet(&packet) + } + } + + Ok(()) + } + pub fn get_open_container<'a>( &self, server: &'a Server, diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 617eb8e03..eca9aa6cb 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -453,6 +453,7 @@ impl Player { } SClickContainer::PACKET_ID => { self.handle_click_container(server, SClickContainer::read(bytebuf)?) + .await .unwrap(); Ok(()) } From 602c1c0859520eb50f8ba89c3b018e9bf2dd5e33 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 16:46:30 +0200 Subject: [PATCH 27/36] clippy + format --- pumpkin-inventory/src/drag_handler.rs | 12 ++++++------ pumpkin-inventory/src/lib.rs | 2 +- pumpkin/src/client/container.rs | 1 - pumpkin/src/server/mod.rs | 5 +---- 4 files changed, 8 insertions(+), 12 deletions(-) diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index aefb96cac..c34d423a8 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -89,12 +89,12 @@ impl DragHandler { // Checked in any function that uses this function. MouseDragType::Middle => { for slot in &drag.slots { - *slots[*slot] = carried_item.clone(); + *slots[*slot] = *carried_item; } } MouseDragType::Right => { let amount_of_items = carried_item.unwrap().item_count as usize; - let mut single_item = carried_item.clone().unwrap(); + let mut single_item = carried_item.unwrap(); single_item.item_count = 1; let changing_slots = drag.changing_slots( amount_of_items, @@ -104,7 +104,7 @@ impl DragHandler { let mut amount_removed = 0; changing_slots.for_each(|slot| { amount_removed += 1; - *slots[slot] = Some(single_item.clone()) + *slots[slot] = Some(single_item) }); let mut remaining = carried_item.unwrap(); @@ -128,12 +128,12 @@ impl DragHandler { let amount_of_slots = changing_slots.clone().count(); let (amount_per_slot, remainder) = (carried_item.unwrap().item_count as usize).div_rem_euclid(&amount_of_slots); - let mut item_in_each_slot = carried_item.unwrap().clone(); + let mut item_in_each_slot = carried_item.unwrap(); item_in_each_slot.item_count = amount_per_slot as u8; - changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot.clone())); + changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot)); if remainder > 0 { - let mut remaining = carried_item.unwrap().clone(); + let mut remaining = carried_item.unwrap(); remaining.item_count = remainder as u8; *carried_item = Some(remaining) } else { diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index ac818851f..dbbc4b0a1 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -73,7 +73,7 @@ pub trait Container: Sync + Send { mouse_click: MouseClick, ) -> Result<(), InventoryError> { let mut all_slots = self.all_slots(); - if slot > all_slots.len() || slot < 0 { + if slot > all_slots.len() { Err(InventoryError::InvalidSlot)? } handle_item_change(carried_item, all_slots[slot], mouse_click); diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 96f071b5f..6b8da77d8 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -142,7 +142,6 @@ impl Player { packet.slot, ); - let slot = click.slot.clone(); match click.click_type { ClickType::MouseClick(mouse_click) => { self.mouse_click(opened_container, mouse_click, click.slot) diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index d540dd40e..8be4c6175 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -138,10 +138,7 @@ impl Server { player_id: EntityId, container_id: u64, ) -> Option<&Mutex>> { - self.open_containers - .get(&container_id)? - .try_open(player_id) - .map(|container| container) + self.open_containers.get(&container_id)?.try_open(player_id) } /// Sends a Packet to all Players in all worlds From d3ec4961cf78eda28e14edd5c71e2a8937a8d094 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 17:16:46 +0200 Subject: [PATCH 28/36] try to combat deadlocks --- pumpkin/src/client/container.rs | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 6b8da77d8..a981db95d 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -330,7 +330,23 @@ impl Player { // TODO: Figure out better way to get only the players from player_ids // Also refactor out a better method to get individual advanced state ids - for player in self.world.lock().await.current_players.values() { + + let world = self.world.lock().await; + let players = world + .current_players + .iter() + .filter_map(|(token, player)| { + if player_ids.contains(&(token.0 as i32)) { + Some(player.clone()) + } else { + None + } + }) + .collect_vec(); + std::mem::drop(world); + for player in players { + dbg!("really shouldn't be here"); + let mut player = player.lock().unwrap(); let mut container = OptionallyCombinedContainer::new(&mut self.inventory, Some(container.deref_mut())); let state_id = container.advance_state_id(); @@ -339,11 +355,9 @@ impl Player { let slot = Slot::from(all_slots[slot_index]); let packet = CSetContainerSlot::new(*container.window_type() as i8, state_id, slot_index, &slot); - let mut player = player.lock().or(Err(InventoryError::LockError))?; - if player_ids.contains(&player.entity_id()) { - player.client.send_packet(&packet) - } + player.client.send_packet(&packet) } + dbg!("got here"); Ok(()) } From daaafeefea75268b4dd9c4404e1a797e13a9ff99 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 19:18:48 +0200 Subject: [PATCH 29/36] actually fixed deadlock --- pumpkin/src/client/container.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index a981db95d..3cb36a8ba 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -110,7 +110,6 @@ impl Player { let mut opened_container = self .get_open_container(server) .map(|container| container.lock().unwrap()); - let mut opened_container = opened_container.as_deref_mut(); let drag_handler = &server.drag_handler; let current_state_id = if let Some(container) = opened_container.as_ref() { @@ -144,18 +143,20 @@ impl Player { match click.click_type { ClickType::MouseClick(mouse_click) => { - self.mouse_click(opened_container, mouse_click, click.slot) + self.mouse_click(opened_container.as_deref_mut(), mouse_click, click.slot) + } + ClickType::ShiftClick => { + self.shift_mouse_click(opened_container.as_deref_mut(), click.slot) } - ClickType::ShiftClick => self.shift_mouse_click(opened_container, click.slot), ClickType::KeyClick(key_click) => match click.slot { container_click::Slot::Normal(slot) => { - self.number_button_pressed(opened_container, key_click, slot) + self.number_button_pressed(opened_container.as_deref_mut(), key_click, slot) } container_click::Slot::OutsideInventory => Err(InventoryError::InvalidPacket), }, ClickType::CreativePickItem => { if let container_click::Slot::Normal(slot) = click.slot { - self.creative_pick_item(opened_container, slot) + self.creative_pick_item(opened_container.as_deref_mut(), slot) } else { Err(InventoryError::InvalidPacket) } @@ -168,10 +169,11 @@ impl Player { } } ClickType::MouseDrag { drag_state } => { - self.mouse_drag(drag_handler, opened_container, drag_state) + self.mouse_drag(drag_handler, opened_container.as_deref_mut(), drag_state) } ClickType::DropType(_drop_type) => todo!(), }?; + std::mem::drop(opened_container); if let container_click::Slot::Normal(slot) = click.slot { self.send_container_changes(server, slot).await?; } @@ -357,7 +359,6 @@ impl Player { CSetContainerSlot::new(*container.window_type() as i8, state_id, slot_index, &slot); player.client.send_packet(&packet) } - dbg!("got here"); Ok(()) } From d07eec224585598fedcc24562e133e36f4261fcf Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Thu, 5 Sep 2024 23:34:11 +0200 Subject: [PATCH 30/36] fix so that it actually always updates both clients --- pumpkin-inventory/src/container_click.rs | 7 ++- pumpkin-inventory/src/open_container.rs | 1 - .../src/client/play/c_set_container_slot.rs | 8 ++-- pumpkin/src/client/container.rs | 43 +++++++++++++------ pumpkin/src/server/mod.rs | 1 + 5 files changed, 42 insertions(+), 18 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 779a85866..0c4897d30 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -32,7 +32,12 @@ impl Click { -999 => Slot::OutsideInventory, _ => { // TODO: Error here - Slot::Normal(slot.try_into().unwrap()) + let slot = slot.try_into(); + if let Ok(slot) = slot { + Slot::Normal(slot) + } else { + Slot::OutsideInventory + } } }; let button = match button { diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 4591b48a2..456f2490a 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -66,7 +66,6 @@ impl Container for Chest { } fn all_slots(&mut self) -> Vec<&mut Option> { - dbg!(self.slots.iter().len()); self.slots.iter_mut().collect() } diff --git a/pumpkin-protocol/src/client/play/c_set_container_slot.rs b/pumpkin-protocol/src/client/play/c_set_container_slot.rs index e07d07d82..8fcd0c8b1 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_slot.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_slot.rs @@ -4,15 +4,15 @@ use pumpkin_macros::packet; use serde::Serialize; #[derive(Serialize)] #[packet(0x15)] -pub struct CSetContainerSlot<'a> { +pub struct CSetContainerSlot { window_id: i8, state_id: VarInt, slot: i16, - slot_data: &'a Slot, + slot_data: Slot, } -impl<'a> CSetContainerSlot<'a> { - pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: &'a Slot) -> Self { +impl CSetContainerSlot { + pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: Slot) -> Self { Self { window_id, state_id: state_id.into(), diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 3cb36a8ba..b6df21528 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -119,6 +119,7 @@ impl Player { }; // This is just checking for regular desync, client hasn't done anything malicious if current_state_id != packet.state_id.0 { + dbg!(current_state_id, packet.state_id.0); self.set_container_content(opened_container.as_deref_mut()); return Ok(()); } @@ -328,36 +329,54 @@ impl Player { .into_iter() .filter(|player_id| *player_id != self.entity_id()) .collect_vec(); - let mut container = container.lock().or(Err(InventoryError::LockError))?; + dbg!(&player_ids); + let player_token = self.client.token; // TODO: Figure out better way to get only the players from player_ids // Also refactor out a better method to get individual advanced state ids let world = self.world.lock().await; + dbg!("here"); let players = world .current_players .iter() .filter_map(|(token, player)| { - if player_ids.contains(&(token.0 as i32)) { - Some(player.clone()) + dbg!(token); + if *token != player_token { + let entity_id = player.lock().unwrap().entity_id(); + if player_ids.contains(&entity_id) { + Some(player.clone()) + } else { + None + } } else { None } }) .collect_vec(); - std::mem::drop(world); + drop(world); for player in players { - dbg!("really shouldn't be here"); + dbg!("here"); let mut player = player.lock().unwrap(); - let mut container = - OptionallyCombinedContainer::new(&mut self.inventory, Some(container.deref_mut())); - let state_id = container.advance_state_id(); - let all_slots = container.all_slots_ref(); + dbg!(player.entity_id()); + let total_opened_containers = player.inventory.total_opened_containers; + let mut container = container.lock().or(Err(InventoryError::LockError))?; + let mut combined_container = OptionallyCombinedContainer::new( + &mut player.inventory, + Some(container.deref_mut()), + ); + let state_id = combined_container.advance_state_id(); + let all_slots = combined_container.all_slots_ref(); let slot = Slot::from(all_slots[slot_index]); - let packet = - CSetContainerSlot::new(*container.window_type() as i8, state_id, slot_index, &slot); - player.client.send_packet(&packet) + let packet = CSetContainerSlot::new( + dbg!(total_opened_containers) as i8, + state_id, + slot_index, + slot, + ); + player.client.send_packet(&packet); + drop(container) } Ok(()) diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 8be4c6175..e0ab0ed8e 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -123,6 +123,7 @@ impl Server { // Basicly the default world // TODO: select default from config let world = self.worlds[0].clone(); + let player = Arc::new(Mutex::new(Player::new( client, world.clone(), From 2acd1ed02a9dd869904c16369c1afa0cb4241390 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Fri, 6 Sep 2024 17:23:21 +0200 Subject: [PATCH 31/36] refactored state_id to PlayerInventory --- pumpkin-inventory/src/lib.rs | 21 ++---- pumpkin-inventory/src/open_container.rs | 27 ++------ pumpkin-inventory/src/player.rs | 15 +---- .../src/client/play/c_set_container_slot.rs | 8 +-- pumpkin/src/client/container.rs | 66 ++++++++----------- pumpkin/src/client/player_packet.rs | 1 + 6 files changed, 42 insertions(+), 96 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index dbbc4b0a1..0cf9d92f3 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -92,11 +92,6 @@ pub trait Container: Sync + Send { self.all_slots() } - fn advance_state_id(&mut self) -> i32; - - fn reset_state_id(&mut self); - fn state_id(&self) -> i32; - fn print_all_contents(&self) { self.all_slots_ref() .into_iter() @@ -211,6 +206,10 @@ impl<'a, 'b> OptionallyCombinedContainer<'a, 'b> { container, } } + /// Returns None if the slot is in the players inventory, Returns Some(Option<&ItemStack>) if it's inside of the container + pub fn get_slot_excluding_inventory(&self, slot: usize) -> Option> { + self.container.as_ref()?.all_slots_ref().get(slot).copied() + } } impl<'a> Container for OptionallyCombinedContainer<'a, 'a> { @@ -244,16 +243,4 @@ impl<'a> Container for OptionallyCombinedContainer<'a, 'a> { None => self.inventory.all_slots_ref(), } } - - fn advance_state_id(&mut self) -> i32 { - self.inventory.advance_state_id() - } - - fn reset_state_id(&mut self) { - self.inventory.reset_state_id() - } - - fn state_id(&self) -> i32 { - self.inventory.state_id() - } } diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 456f2490a..3ca446e25 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -47,17 +47,11 @@ impl OpenContainer { } } -struct Chest { - slots: [Option; 27], - state_id: i32, -} +struct Chest([Option; 27]); impl Chest { pub fn new() -> Self { - Self { - slots: [None; 27], - state_id: 0, - } + Self([None; 27]) } } impl Container for Chest { @@ -66,23 +60,10 @@ impl Container for Chest { } fn all_slots(&mut self) -> Vec<&mut Option> { - self.slots.iter_mut().collect() + self.0.iter_mut().collect() } fn all_slots_ref(&self) -> Vec> { - self.slots.iter().map(|slot| slot.as_ref()).collect() - } - - fn advance_state_id(&mut self) -> i32 { - self.state_id += 1; - self.state_id - 1 - } - - fn reset_state_id(&mut self) { - self.state_id = 0; - } - - fn state_id(&self) -> i32 { - self.state_id + self.0.iter().map(|slot| slot.as_ref()).collect() } } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index e37cbd9bb..a4cadd46a 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -13,7 +13,8 @@ pub struct PlayerInventory { offhand: Option, // current selected slot in hotbar selected: usize, - state_id: i32, + pub state_id: u32, + // Notchian server wraps this value at 100, we can just keep it as a u8 that automatically wraps pub total_opened_containers: u8, } @@ -166,16 +167,4 @@ impl Container for PlayerInventory { fn all_combinable_slots_mut(&mut self) -> Vec<&mut Option> { self.items.iter_mut().collect() } - - fn advance_state_id(&mut self) -> i32 { - self.state_id += 1; - self.state_id - } - - fn reset_state_id(&mut self) { - self.state_id = 0; - } - fn state_id(&self) -> i32 { - self.state_id - } } diff --git a/pumpkin-protocol/src/client/play/c_set_container_slot.rs b/pumpkin-protocol/src/client/play/c_set_container_slot.rs index 8fcd0c8b1..e07d07d82 100644 --- a/pumpkin-protocol/src/client/play/c_set_container_slot.rs +++ b/pumpkin-protocol/src/client/play/c_set_container_slot.rs @@ -4,15 +4,15 @@ use pumpkin_macros::packet; use serde::Serialize; #[derive(Serialize)] #[packet(0x15)] -pub struct CSetContainerSlot { +pub struct CSetContainerSlot<'a> { window_id: i8, state_id: VarInt, slot: i16, - slot_data: Slot, + slot_data: &'a Slot, } -impl CSetContainerSlot { - pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: Slot) -> Self { +impl<'a> CSetContainerSlot<'a> { + pub fn new(window_id: i8, state_id: i32, slot: usize, slot_data: &'a Slot) -> Self { Self { window_id, state_id: state_id.into(), diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index b6df21528..a02926b43 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -3,7 +3,9 @@ use crate::server::Server; use itertools::Itertools; use pumpkin_core::text::TextComponent; use pumpkin_core::GameMode; -use pumpkin_inventory::container_click::{KeyClick, MouseClick, MouseDragState, MouseDragType}; +use pumpkin_inventory::container_click::{ + Click, ClickType, KeyClick, MouseClick, MouseDragState, MouseDragType, +}; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; @@ -14,7 +16,6 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; -use std::ops::DerefMut; use std::sync::Mutex; impl Player { @@ -24,7 +25,7 @@ impl Player { minecraft_menu_id: &str, window_title: Option<&str>, ) { - self.inventory.reset_state_id(); + self.inventory.state_id = 0; let total_opened_containers = self.inventory.total_opened_containers; let mut container = self .get_open_container(server) @@ -70,14 +71,13 @@ impl Player { Slot::empty() } }; - + self.inventory.state_id += 1; let packet = CSetContainerContent::new( total_opened_containers, - self.inventory.state_id().into(), + (self.inventory.state_id as i32).into(), &slots, &carried_item, ); - self.inventory.advance_state_id(); self.client.send_packet(&packet); } @@ -106,20 +106,14 @@ impl Player { server: &mut Server, packet: SClickContainer, ) -> Result<(), InventoryError> { - use container_click::*; let mut opened_container = self .get_open_container(server) .map(|container| container.lock().unwrap()); let drag_handler = &server.drag_handler; - let current_state_id = if let Some(container) = opened_container.as_ref() { - container.state_id() - } else { - self.inventory.state_id() - }; + let state_id = self.inventory.state_id; // This is just checking for regular desync, client hasn't done anything malicious - if current_state_id != packet.state_id.0 { - dbg!(current_state_id, packet.state_id.0); + if state_id != packet.state_id.0 as u32 { self.set_container_content(opened_container.as_deref_mut()); return Ok(()); } @@ -174,9 +168,19 @@ impl Player { } ClickType::DropType(_drop_type) => todo!(), }?; - std::mem::drop(opened_container); - if let container_click::Slot::Normal(slot) = click.slot { - self.send_container_changes(server, slot).await?; + if let Some(mut opened_container) = opened_container { + if let container_click::Slot::Normal(slot_index) = click.slot { + let combined_container = OptionallyCombinedContainer::new( + &mut self.inventory, + Some(&mut opened_container), + ); + if let Some(slot) = combined_container.get_slot_excluding_inventory(slot_index) { + let slot = Slot::from(slot); + drop(opened_container); + self.send_container_changes(server, slot_index, slot) + .await?; + } + } } Ok(()) } @@ -193,7 +197,7 @@ impl Player { container_click::Slot::Normal(slot) => { container.handle_item_change(&mut self.carried_item, slot, mouse_click) } - container_click::Slot::OutsideInventory => todo!(), + container_click::Slot::OutsideInventory => Ok(()), } } @@ -316,11 +320,8 @@ impl Player { &mut self, server: &Server, slot_index: usize, + slot: Slot, ) -> Result<(), InventoryError> { - // If we don't have a container open, then we don't need to share state - let Some(container) = self.get_open_container(server) else { - return Ok(()); - }; let player_ids = server .open_containers .get(&self.open_container.unwrap()) @@ -329,19 +330,16 @@ impl Player { .into_iter() .filter(|player_id| *player_id != self.entity_id()) .collect_vec(); - dbg!(&player_ids); let player_token = self.client.token; // TODO: Figure out better way to get only the players from player_ids // Also refactor out a better method to get individual advanced state ids let world = self.world.lock().await; - dbg!("here"); let players = world .current_players .iter() .filter_map(|(token, player)| { - dbg!(token); if *token != player_token { let entity_id = player.lock().unwrap().entity_id(); if player_ids.contains(&entity_id) { @@ -356,27 +354,17 @@ impl Player { .collect_vec(); drop(world); for player in players { - dbg!("here"); let mut player = player.lock().unwrap(); - dbg!(player.entity_id()); let total_opened_containers = player.inventory.total_opened_containers; - let mut container = container.lock().or(Err(InventoryError::LockError))?; - let mut combined_container = OptionallyCombinedContainer::new( - &mut player.inventory, - Some(container.deref_mut()), - ); - let state_id = combined_container.advance_state_id(); - let all_slots = combined_container.all_slots_ref(); - let slot = Slot::from(all_slots[slot_index]); + player.inventory.state_id += 1; let packet = CSetContainerSlot::new( - dbg!(total_opened_containers) as i8, - state_id, + total_opened_containers as i8, + player.inventory.state_id as i32, slot_index, - slot, + &slot, ); player.client.send_packet(&packet); - drop(container) } Ok(()) diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index ef5604808..41262b733 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -533,6 +533,7 @@ impl Player { // But this is not possible yet pub fn handle_close_container(&mut self, server: &mut Server, packet: SCloseContainer) { // window_id 0 represents both 9x1 Generic AND inventory here + self.inventory.state_id = 0; if let Some(id) = self.open_container { if let Some(container) = server.open_containers.get_mut(&id) { container.remove_player(self.entity_id()) From 3b93892f269588870dc163b70812e16d8187f54e Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Fri, 6 Sep 2024 18:48:06 +0200 Subject: [PATCH 32/36] start trying to drag, it doesn't work though --- pumpkin-inventory/src/container_click.rs | 2 +- pumpkin-inventory/src/drag_handler.rs | 85 ++++++++++++------------ pumpkin/src/client/container.rs | 80 +++++++++++++++++++--- 3 files changed, 113 insertions(+), 54 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 0c4897d30..9ec1beda3 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -142,7 +142,7 @@ pub enum MouseDragType { Right, Middle, } - +#[derive(PartialEq)] pub enum MouseDragState { Start(MouseDragType), AddSlot(usize), diff --git a/pumpkin-inventory/src/drag_handler.rs b/pumpkin-inventory/src/drag_handler.rs index c34d423a8..11d8b2f82 100644 --- a/pumpkin-inventory/src/drag_handler.rs +++ b/pumpkin-inventory/src/drag_handler.rs @@ -58,13 +58,13 @@ impl DragHandler { pub fn apply_drag( &self, - carried_item: &mut Option, + maybe_carried_item: &mut Option, container: &mut T, container_id: &u64, player: i32, ) -> Result<(), InventoryError> { // Minecraft client does still send dragging packets when not carrying an item! - if carried_item.is_none() { + if maybe_carried_item.is_none() { return Ok(()); } @@ -84,60 +84,60 @@ impl DragHandler { .iter() .map(|stack| stack.map(|item| item.to_owned())) .collect_vec(); + let Some(carried_item) = maybe_carried_item else { + return Ok(()); + }; match drag.drag_type { // This is only valid in Creative GameMode. // Checked in any function that uses this function. MouseDragType::Middle => { for slot in &drag.slots { - *slots[*slot] = *carried_item; + *slots[*slot] = *maybe_carried_item; } } MouseDragType::Right => { - let amount_of_items = carried_item.unwrap().item_count as usize; - let mut single_item = carried_item.unwrap(); + let mut single_item = *carried_item; single_item.item_count = 1; - let changing_slots = drag.changing_slots( - amount_of_items, - &slots_cloned, - carried_item.as_ref().unwrap(), - ); - let mut amount_removed = 0; + + let changing_slots = + drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); changing_slots.for_each(|slot| { - amount_removed += 1; - *slots[slot] = Some(single_item) + if carried_item.item_count != 0 { + carried_item.item_count -= 1; + if let Some(stack) = &mut slots[slot] { + // TODO: Check for stack max here + if stack.item_count + 1 < 64 { + stack.item_count += 1; + } else { + carried_item.item_count += 1; + } + } else { + *slots[slot] = Some(single_item) + } + } }); - let mut remaining = carried_item.unwrap(); - if remaining.item_count == amount_removed { - *carried_item = None - } else { - remaining.item_count -= amount_removed; - *carried_item = Some(remaining) + if carried_item.item_count == 0 { + *maybe_carried_item = None } } MouseDragType::Left => { - let amount_of_items = carried_item.unwrap().item_count as usize; // TODO: Handle dragging a stack with greater amount than item allows as max unstackable // In that specific case, follow MouseDragType::Right behaviours instead! - let changing_slots = drag.changing_slots( - amount_of_items, - &slots_cloned, - carried_item.as_ref().unwrap(), - ); + let changing_slots = + drag.possibly_changing_slots(&slots_cloned, carried_item.item_id); let amount_of_slots = changing_slots.clone().count(); let (amount_per_slot, remainder) = - (carried_item.unwrap().item_count as usize).div_rem_euclid(&amount_of_slots); - let mut item_in_each_slot = carried_item.unwrap(); + (carried_item.item_count as usize).div_rem_euclid(&amount_of_slots); + let mut item_in_each_slot = *carried_item; item_in_each_slot.item_count = amount_per_slot as u8; changing_slots.for_each(|slot| *slots[slot] = Some(item_in_each_slot)); if remainder > 0 { - let mut remaining = carried_item.unwrap(); - remaining.item_count = remainder as u8; - *carried_item = Some(remaining) + carried_item.item_count = remainder as u8; } else { - *carried_item = None + *maybe_carried_item = None } } } @@ -152,25 +152,24 @@ struct Drag { } impl Drag { - fn changing_slots<'a>( + fn possibly_changing_slots<'a>( &'a self, - amount_of_items: usize, slots: &'a [Option], - carried_item: &'a ItemStack, + carried_item_id: u32, ) -> impl Iterator + 'a + Clone { - self.slots - .iter() - .enumerate() - .take_while(move |(slot_number, _)| *slot_number <= amount_of_items) - .filter_map(move |(_, slot)| match &slots[*slot] { + self.slots.iter().filter_map(move |slot_index| { + let slot = &slots[*slot_index]; + + match slot { Some(item_slot) => { - if *item_slot == *carried_item { - Some(*slot) + if item_slot.item_id == carried_item_id { + Some(*slot_index) } else { None } } - None => Some(*slot), - }) + None => Some(*slot_index), + } + }) } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index a02926b43..eadbbeac0 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -16,7 +16,7 @@ use pumpkin_protocol::client::play::{ use pumpkin_protocol::server::play::SClickContainer; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::ItemStack; -use std::sync::Mutex; +use std::sync::{Arc, Mutex}; impl Player { pub fn open_container( @@ -135,6 +135,7 @@ impl Player { packet.button, packet.slot, ); + let mut update_whole_container = false; match click.click_type { ClickType::MouseClick(mouse_click) => { @@ -157,19 +158,26 @@ impl Player { } } ClickType::DoubleClick => { + update_whole_container = true; if let container_click::Slot::Normal(slot) = click.slot { - self.double_click(slot) + self.double_click(opened_container.as_deref_mut(), slot) } else { Err(InventoryError::InvalidPacket) } } ClickType::MouseDrag { drag_state } => { + if drag_state == MouseDragState::End { + update_whole_container = true; + } self.mouse_drag(drag_handler, opened_container.as_deref_mut(), drag_state) } ClickType::DropType(_drop_type) => todo!(), }?; if let Some(mut opened_container) = opened_container { - if let container_click::Slot::Normal(slot_index) = click.slot { + if update_whole_container { + drop(opened_container); + self.send_whole_container_change(server).await?; + } else if let container_click::Slot::Normal(slot_index) = click.slot { let combined_container = OptionallyCombinedContainer::new( &mut self.inventory, Some(&mut opened_container), @@ -280,7 +288,40 @@ impl Player { Ok(()) } - fn double_click(&mut self, _slot: usize) -> Result<(), InventoryError> { + fn double_click( + &mut self, + opened_container: Option<&mut Box>, + slot: usize, + ) -> Result<(), InventoryError> { + let mut container = OptionallyCombinedContainer::new(&mut self.inventory, opened_container); + let mut slots = container.all_slots(); + + let Some(item) = slots.get_mut(slot) else { + return Ok(()); + }; + let Some(mut carried_item) = **item else { + return Ok(()); + }; + **item = None; + + for slot in slots.iter_mut().filter_map(|slot| slot.as_mut()) { + if slot.item_id == carried_item.item_id { + // TODO: Check for max stack size + if slot.item_count + carried_item.item_count <= 64 { + slot.item_count = 0; + carried_item.item_count = 64; + } else { + let to_remove = slot.item_count - (64 - carried_item.item_count); + slot.item_count -= to_remove; + carried_item.item_count += to_remove; + } + + if carried_item.item_count == 64 { + break; + } + } + } + self.carried_item = Some(carried_item); Ok(()) } @@ -316,12 +357,10 @@ impl Player { } } - async fn send_container_changes( + async fn get_current_players_in_container( &mut self, server: &Server, - slot_index: usize, - slot: Slot, - ) -> Result<(), InventoryError> { + ) -> Vec>> { let player_ids = server .open_containers .get(&self.open_container.unwrap()) @@ -352,8 +391,16 @@ impl Player { } }) .collect_vec(); - drop(world); - for player in players { + players + } + + async fn send_container_changes( + &mut self, + server: &Server, + slot_index: usize, + slot: Slot, + ) -> Result<(), InventoryError> { + for player in self.get_current_players_in_container(server).await { let mut player = player.lock().unwrap(); let total_opened_containers = player.inventory.total_opened_containers; @@ -366,7 +413,20 @@ impl Player { ); player.client.send_packet(&packet); } + Ok(()) + } + async fn send_whole_container_change(&mut self, server: &Server) -> Result<(), InventoryError> { + let players = self.get_current_players_in_container(server).await; + + for player in players { + let mut player = player.lock().unwrap(); + let mut container = player + .get_open_container(server) + .map(|container| container.lock().unwrap()); + player.set_container_content(container.as_deref_mut()); + drop(container) + } Ok(()) } From c6dbc3d00f637ab53ffe586a2d02b3136664ce86 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 7 Sep 2024 16:03:11 +0200 Subject: [PATCH 33/36] move around dependencies to match project spec --- pumpkin-inventory/Cargo.toml | 7 ++++--- pumpkin/Cargo.toml | 3 ++- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/pumpkin-inventory/Cargo.toml b/pumpkin-inventory/Cargo.toml index e30cbb273..3b6c8c972 100644 --- a/pumpkin-inventory/Cargo.toml +++ b/pumpkin-inventory/Cargo.toml @@ -4,9 +4,10 @@ version.workspace = true edition.workspace = true [dependencies] +# For items +pumpkin-world = { path = "../pumpkin-world"} + num-traits = "0.2" num-derive = "0.4" thiserror = "1.0.63" -itertools = "0.13.0" -# For items -pumpkin-world = { path = "../pumpkin-world"} \ No newline at end of file +itertools = "0.13.0" \ No newline at end of file diff --git a/pumpkin/Cargo.toml b/pumpkin/Cargo.toml index edb2209a7..178300ceb 100644 --- a/pumpkin/Cargo.toml +++ b/pumpkin/Cargo.toml @@ -18,6 +18,8 @@ pumpkin-entity = { path = "../pumpkin-entity"} pumpkin-protocol = { path = "../pumpkin-protocol"} pumpkin-registry = { path = "../pumpkin-registry"} +itertools = "0.13.0" + # config serde.workspace = true serde_json = "1.0" @@ -62,4 +64,3 @@ mio = { version = "1.0.2", features = ["os-poll", "net"]} uuid.workspace = true tokio.workspace = true rayon.workspace = true -itertools = "0.13.0" From 814b48a485852dbdcb4b125ecff6199279a7d1c8 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 7 Sep 2024 16:27:38 +0200 Subject: [PATCH 34/36] remove debug leftover --- pumpkin-inventory/src/lib.rs | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 0cf9d92f3..100790a76 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -92,16 +92,6 @@ pub trait Container: Sync + Send { self.all_slots() } - fn print_all_contents(&self) { - self.all_slots_ref() - .into_iter() - .enumerate() - .filter_map(|(slot, item)| item.map(|item| (slot, item))) - .for_each(|(slot, item)| { - dbg!(slot, item); - }); - } - fn internal_pumpkin_id(&self) -> u64 { 0 } From 3b6fc48a0f83c3347935d53a1778197f5a814160 Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 7 Sep 2024 16:31:24 +0200 Subject: [PATCH 35/36] add error handling to `Click` functions --- pumpkin-inventory/src/container_click.rs | 80 +++++++++++------------- pumpkin/src/client/container.rs | 2 +- 2 files changed, 37 insertions(+), 45 deletions(-) diff --git a/pumpkin-inventory/src/container_click.rs b/pumpkin-inventory/src/container_click.rs index 9ec1beda3..5d1039a9a 100644 --- a/pumpkin-inventory/src/container_click.rs +++ b/pumpkin-inventory/src/container_click.rs @@ -1,3 +1,4 @@ +use crate::InventoryError; use pumpkin_world::item::ItemStack; pub struct Click { @@ -6,104 +7,95 @@ pub struct Click { } impl Click { - pub fn new(mode: u8, button: i8, slot: i16) -> Self { + pub fn new(mode: u8, button: i8, slot: i16) -> Result { match mode { 0 => Self::new_normal_click(button, slot), // Both buttons do the same here, so we omit it 1 => Self::new_shift_click(slot), 2 => Self::new_key_click(button, slot), - 3 => Self { + 3 => Ok(Self { click_type: ClickType::CreativePickItem, - slot: Slot::Normal(slot.try_into().unwrap()), - }, + slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), + }), 4 => Self::new_drop_item(button), 5 => Self::new_drag_item(button, slot), - 6 => Self { + 6 => Ok(Self { click_type: ClickType::DoubleClick, - slot: Slot::Normal(slot.try_into().unwrap()), - }, - // TODO: Error handling - _ => unreachable!(), + slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), + }), + _ => Err(InventoryError::InvalidPacket), } } - fn new_normal_click(button: i8, slot: i16) -> Self { + fn new_normal_click(button: i8, slot: i16) -> Result { let slot = match slot { -999 => Slot::OutsideInventory, _ => { - // TODO: Error here - let slot = slot.try_into(); - if let Ok(slot) = slot { - Slot::Normal(slot) - } else { - Slot::OutsideInventory - } + let slot = slot.try_into().or(Err(InventoryError::InvalidSlot))?; + Slot::Normal(slot) } }; let button = match button { 0 => MouseClick::Left, 1 => MouseClick::Right, - // TODO: Error here - _ => unreachable!(), + _ => Err(InventoryError::InvalidPacket)?, }; - Self { + Ok(Self { click_type: ClickType::MouseClick(button), slot, - } + }) } - fn new_shift_click(slot: i16) -> Self { - Self { - // TODO: Error handle this - slot: Slot::Normal(slot.try_into().unwrap()), + fn new_shift_click(slot: i16) -> Result { + Ok(Self { + slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), click_type: ClickType::ShiftClick, - } + }) } - fn new_key_click(button: i8, slot: i16) -> Self { + fn new_key_click(button: i8, slot: i16) -> Result { let key = match button { - 0..9 => KeyClick::Slot(button.try_into().unwrap()), + 0..9 => KeyClick::Slot(button.try_into().or(Err(InventoryError::InvalidSlot))?), 40 => KeyClick::Offhand, - // TODO: Error handling here - _ => unreachable!(), + _ => Err(InventoryError::InvalidSlot)?, }; - Self { + Ok(Self { click_type: ClickType::KeyClick(key), - slot: Slot::Normal(slot.try_into().unwrap()), - } + slot: Slot::Normal(slot.try_into().or(Err(InventoryError::InvalidSlot))?), + }) } - fn new_drop_item(button: i8) -> Self { + fn new_drop_item(button: i8) -> Result { let drop_type = match button { 0 => DropType::SingleItem, 1 => DropType::FullStack, - // TODO: Error handling - _ => unreachable!(), + _ => Err(InventoryError::InvalidPacket)?, }; - Self { + Ok(Self { click_type: ClickType::DropType(drop_type), slot: Slot::OutsideInventory, - } + }) } - fn new_drag_item(button: i8, slot: i16) -> Self { + fn new_drag_item(button: i8, slot: i16) -> Result { let state = match button { 0 => MouseDragState::Start(MouseDragType::Left), 4 => MouseDragState::Start(MouseDragType::Right), 8 => MouseDragState::Start(MouseDragType::Middle), - 1 | 5 | 9 => MouseDragState::AddSlot(slot.try_into().unwrap()), + 1 | 5 | 9 => { + MouseDragState::AddSlot(slot.try_into().or(Err(InventoryError::InvalidSlot))?) + } 2 | 6 | 10 => MouseDragState::End, - // TODO: Error handling - _ => unreachable!(), + _ => Err(InventoryError::InvalidPacket)?, }; - Self { + Ok(Self { slot: match &state { MouseDragState::AddSlot(slot) => Slot::Normal(*slot), _ => Slot::OutsideInventory, }, click_type: ClickType::MouseDrag { drag_state: state }, - } + }) } } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index eadbbeac0..954b9ba44 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -134,7 +134,7 @@ impl Player { .expect("Mode can only be between 0-6"), packet.button, packet.slot, - ); + )?; let mut update_whole_container = false; match click.click_type { From 59a950c6063345747074f1de2a877cdd1f66ae9c Mon Sep 17 00:00:00 2001 From: Edvin Bryntesson Date: Sat, 7 Sep 2024 16:31:24 +0200 Subject: [PATCH 36/36] a bit of refactor, and actually enforce player inventory condition rules on handle_item_change --- pumpkin-inventory/src/lib.rs | 19 +++--- pumpkin-inventory/src/open_container.rs | 3 + pumpkin-inventory/src/player.rs | 90 +++++++++++++------------ pumpkin/src/client/container.rs | 20 ++---- pumpkin/src/client/player_packet.rs | 15 +++-- pumpkin/src/commands/cmd_echest.rs | 2 +- pumpkin/src/entity/player.rs | 3 +- 7 files changed, 79 insertions(+), 73 deletions(-) diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 100790a76..e1e554fae 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -52,20 +52,14 @@ pub enum WindowType { CartographyTable, Stonecutter, } +pub struct ContainerStruct([Option; SLOTS]); -impl WindowType { - pub const fn default_title(&self) -> &'static str { - // TODO: Add titles here: - /*match self { - _ => "WINDOW TITLE", - }*/ - "WINDOW TITLE" - } -} // Container needs Sync + Send to be able to be in async Server pub trait Container: Sync + Send { fn window_type(&self) -> &'static WindowType; + fn window_name(&self) -> &'static str; + fn handle_item_change( &mut self, carried_item: &mut Option, @@ -211,6 +205,13 @@ impl<'a> Container for OptionallyCombinedContainer<'a, 'a> { } } + fn window_name(&self) -> &'static str { + self.container + .as_ref() + .map(|container| container.window_name()) + .unwrap_or(self.inventory.window_name()) + } + fn all_slots(&mut self) -> Vec<&mut Option> { let slots = match &mut self.container { Some(container) => { diff --git a/pumpkin-inventory/src/open_container.rs b/pumpkin-inventory/src/open_container.rs index 3ca446e25..df052755f 100644 --- a/pumpkin-inventory/src/open_container.rs +++ b/pumpkin-inventory/src/open_container.rs @@ -59,6 +59,9 @@ impl Container for Chest { &WindowType::Generic9x3 } + fn window_name(&self) -> &'static str { + "Chest" + } fn all_slots(&mut self) -> Vec<&mut Option> { self.0.iter_mut().collect() } diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index a3611229c..d8e127b78 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -36,7 +36,6 @@ impl PlayerInventory { total_opened_containers: 2, } } - /// Set the contents of an item in a slot /// /// ## Slot @@ -48,48 +47,44 @@ 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) { - match slot { - 0 => { - // TODO: Add crafting check here - self.crafting_output = item - } - 1..=4 => self.crafting[slot - 1] = item, - 5..=8 => { - match item { - None => self.armor[slot - 5] = None, - Some(item) => { - // TODO: Replace asserts with error handling - match slot - 5 { - 0 => { - assert!(item.is_helmet() || item_allowed_override); - self.armor[0] = Some(item); - } - 1 => { - assert!(item.is_chestplate() || item_allowed_override); - self.armor[1] = Some(item) - } - 2 => { - assert!(item.is_leggings() || item_allowed_override); - self.armor[2] = Some(item); - } - 3 => { - assert!(item.is_boots() || item_allowed_override); - self.armor[3] = Some(item) - } - _ => unreachable!(), - } - } - } - } - 9..=44 => { - self.items[slot - 9] = item; + pub fn set_slot( + &mut self, + slot: usize, + item: Option, + item_allowed_override: bool, + ) -> Result<(), InventoryError> { + if item_allowed_override { + if !(0..=45).contains(&slot) { + Err(InventoryError::InvalidSlot)? } - 45 => { - self.offhand = item; + *self.all_slots()[slot] = item; + return Ok(()); + } + let slot_condition = self.slot_condition(slot)?; + if let Some(item) = item { + if slot_condition(&item) { + *self.all_slots()[slot] = Some(item); } - _ => unreachable!(), } + Ok(()) + } + #[allow(clippy::type_complexity)] + pub fn slot_condition( + &self, + slot: usize, + ) -> Result bool>, InventoryError> { + if !(0..=45).contains(&slot) { + return Err(InventoryError::InvalidSlot); + } + + Ok(Box::new(match slot { + 0..=4 | 9..=45 => |_| true, + 5 => |item: &ItemStack| item.is_helmet(), + 6 => |item: &ItemStack| item.is_chestplate(), + 7 => |item: &ItemStack| item.is_leggings(), + 8 => |item: &ItemStack| item.is_boots(), + _ => unreachable!(), + })) } pub fn get_slot(&mut self, slot: usize) -> Result<&mut Option, InventoryError> { match slot { @@ -138,15 +133,26 @@ impl Container for PlayerInventory { &WindowType::Generic9x1 } + fn window_name(&self) -> &'static str { + // We never send an OpenContainer with inventory, so it has no name. + "" + } + fn handle_item_change( &mut self, carried_slot: &mut Option, slot: usize, mouse_click: MouseClick, ) -> Result<(), InventoryError> { + let slot_condition = self.slot_condition(slot)?; let item_slot = self.get_slot(slot)?; - - handle_item_change(carried_slot, item_slot, mouse_click); + if let Some(item) = carried_slot { + if slot_condition(item) { + handle_item_change(carried_slot, item_slot, mouse_click); + } + } else { + handle_item_change(carried_slot, item_slot, mouse_click) + } Ok(()) } diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index 954b9ba44..69c1c5dd1 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -8,8 +8,8 @@ use pumpkin_inventory::container_click::{ }; use pumpkin_inventory::drag_handler::DragHandler; use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; +use pumpkin_inventory::Container; use pumpkin_inventory::{container_click, InventoryError, OptionallyCombinedContainer}; -use pumpkin_inventory::{Container, WindowType}; use pumpkin_protocol::client::play::{ CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, }; @@ -19,12 +19,7 @@ use pumpkin_world::item::ItemStack; use std::sync::{Arc, Mutex}; impl Player { - pub fn open_container( - &mut self, - server: &mut Server, - minecraft_menu_id: &str, - window_title: Option<&str>, - ) { + pub fn open_container(&mut self, server: &mut Server, minecraft_menu_id: &str) { self.inventory.state_id = 0; let total_opened_containers = self.inventory.total_opened_containers; let mut container = self @@ -39,12 +34,11 @@ impl Player { .get("protocol_id") .unwrap()) .into(); - let window_type = match &container { - Some(container) => container.window_type(), - None => &WindowType::Generic9x1, - } - .to_owned(); - let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); + let window_title = container + .as_ref() + .map(|container| container.window_name()) + .unwrap_or(self.inventory.window_name()); + let title = TextComponent::text(window_title); self.client.send_packet(&COpenScreen::new( total_opened_containers.into(), diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 41262b733..034e8d1b7 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -14,7 +14,7 @@ use pumpkin_core::{ GameMode, }; use pumpkin_entity::EntityId; -use pumpkin_inventory::WindowType; +use pumpkin_inventory::{InventoryError, WindowType}; use pumpkin_protocol::server::play::{SCloseContainer, SSetPlayerGround, SUseItem}; use pumpkin_protocol::{ client::play::{ @@ -517,15 +517,16 @@ impl Player { self.inventory.set_selected(slot as usize); } - pub fn handle_set_creative_slot(&mut self, _server: &mut Server, packet: SSetCreativeSlot) { + pub fn handle_set_creative_slot( + &mut self, + _server: &mut Server, + packet: SSetCreativeSlot, + ) -> Result<(), InventoryError> { if self.gamemode != GameMode::Creative { - self.kick(TextComponent::text( - "Invalid action, you can only do that if you are in creative", - )); - return; + return Err(InventoryError::PermissionError); } self.inventory - .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false); + .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false) } // TODO: diff --git a/pumpkin/src/commands/cmd_echest.rs b/pumpkin/src/commands/cmd_echest.rs index 594015cb6..d082ad1d3 100644 --- a/pumpkin/src/commands/cmd_echest.rs +++ b/pumpkin/src/commands/cmd_echest.rs @@ -21,7 +21,7 @@ pub(crate) fn init_command_tree<'a>() -> CommandTree<'a> { server.open_containers.insert(0, open_container); } } - player.open_container(server, "minecraft:generic_9x3", Some("Ender Chest")); + player.open_container(server, "minecraft:generic_9x3"); } Ok(()) diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 3c7b05ee3..73c4b6585 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -438,7 +438,8 @@ impl Player { Ok(()) } SSetCreativeSlot::PACKET_ID => { - self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf)?); + self.handle_set_creative_slot(server, SSetCreativeSlot::read(bytebuf)?) + .unwrap(); Ok(()) } SPlayPingRequest::PACKET_ID => {