diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 044980cc4..dadb10cc6 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -1,9 +1,10 @@ -use num_derive::ToPrimitive; +use num_derive::{FromPrimitive, ToPrimitive}; pub mod player; +pub mod window_property; /// https://wiki.vg/Inventory -#[derive(Debug, ToPrimitive, Clone)] +#[derive(Debug, ToPrimitive, FromPrimitive, Clone)] pub enum WindowType { // not used Generic9x1, diff --git a/pumpkin-inventory/src/window_property.rs b/pumpkin-inventory/src/window_property.rs new file mode 100644 index 000000000..136d68312 --- /dev/null +++ b/pumpkin-inventory/src/window_property.rs @@ -0,0 +1,89 @@ +use num_derive::ToPrimitive; +use num_traits::ToPrimitive; + +pub trait WindowPropertyTrait { + fn to_id(self) -> i16; +} + +impl WindowPropertyTrait for T { + fn to_id(self) -> i16 { + self.to_i16().unwrap() + } +} + +pub struct WindowProperty { + window_property: T, + value: i16, +} + +impl WindowProperty { + pub fn new(window_property: T, value: i16) -> Self { + Self { + window_property, + value, + } + } + + pub fn into_tuple(self) -> (i16, i16) { + (self.window_property.to_id(), self.value) + } +} +#[derive(ToPrimitive)] +pub enum Furnace { + FireIcon, + MaximumFuelBurnTime, + ProgressArrow, + MaximumProgress, +} + +pub enum EnchantmentTable { + LevelRequirement { slot: u8 }, + EnchantmentSeed, + EnchantmentId { slot: u8 }, + EnchantmentLevel { slot: u8 }, +} + +impl WindowPropertyTrait for EnchantmentTable { + fn to_id(self) -> i16 { + use EnchantmentTable::*; + + (match self { + LevelRequirement { slot } => slot, + EnchantmentSeed => 3, + EnchantmentId { slot } => 4 + slot, + EnchantmentLevel { slot } => 7 + slot, + }) as i16 + } +} +#[derive(ToPrimitive)] +pub enum Beacon { + PowerLevel, + FirstPotionEffect, + SecondPotionEffect, +} + +#[derive(ToPrimitive)] +pub enum Anvil { + RepairCost, +} + +#[derive(ToPrimitive)] +pub enum BrewingStand { + BrewTime, + FuelTime, +} + +#[derive(ToPrimitive)] +pub enum Stonecutter { + SelectedRecipe, +} + +#[derive(ToPrimitive)] +pub enum Loom { + SelectedPattern, +} + +#[derive(ToPrimitive)] +pub enum Lectern { + PageNumber, +} diff --git a/pumpkin-protocol/src/client/play/c_close_container.rs b/pumpkin-protocol/src/client/play/c_close_container.rs new file mode 100644 index 000000000..b8350265b --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_close_container.rs @@ -0,0 +1,14 @@ +use pumpkin_macros::packet; +use serde::Serialize; + +#[derive(Serialize)] +#[packet(0x12)] +pub struct CCloseContainer { + window_id: u8, +} + +impl CCloseContainer { + pub const fn new(window_id: u8) -> Self { + Self { window_id } + } +} diff --git a/pumpkin-protocol/src/client/play/c_set_container_property.rs b/pumpkin-protocol/src/client/play/c_set_container_property.rs new file mode 100644 index 000000000..1fee26ef5 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_container_property.rs @@ -0,0 +1,19 @@ +use pumpkin_macros::packet; +use serde::Serialize; +#[derive(Serialize)] +#[packet(0x14)] +pub struct CSetContainerProperty { + window_id: u8, + property: i16, + value: i16, +} + +impl CSetContainerProperty { + pub const fn new(window_id: u8, property: i16, value: i16) -> Self { + Self { + window_id, + property, + value, + } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index d5064aae4..c64720948 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -5,6 +5,7 @@ mod c_block_update; mod c_center_chunk; mod c_change_difficulty; mod c_chunk_data; +mod c_close_container; mod c_disguised_chat_message; mod c_entity_animation; mod c_entity_metadata; @@ -23,6 +24,7 @@ mod c_player_info_update; mod c_player_remove; mod c_remove_entities; mod c_set_container_content; +mod c_set_container_property; mod c_set_container_slot; mod c_set_held_item; mod c_set_title; @@ -44,6 +46,7 @@ pub use c_block_update::*; pub use c_center_chunk::*; pub use c_change_difficulty::*; pub use c_chunk_data::*; +pub use c_close_container::*; pub use c_disguised_chat_message::*; pub use c_entity_animation::*; pub use c_entity_metadata::*; @@ -62,6 +65,7 @@ pub use c_player_info_update::*; pub use c_player_remove::*; pub use c_remove_entities::*; pub use c_set_container_content::*; +pub use c_set_container_property::*; pub use c_set_container_slot::*; pub use c_set_held_item::*; pub use c_set_title::*; diff --git a/pumpkin-protocol/src/server/play/mod.rs b/pumpkin-protocol/src/server/play/mod.rs index c29a11775..2cccb9c2e 100644 --- a/pumpkin-protocol/src/server/play/mod.rs +++ b/pumpkin-protocol/src/server/play/mod.rs @@ -1,6 +1,7 @@ mod s_chat_command; mod s_chat_message; mod s_client_information; +mod s_close_container; mod s_confirm_teleport; mod s_interact; mod s_ping_request; @@ -17,6 +18,7 @@ mod s_use_item_on; pub use s_chat_command::*; pub use s_chat_message::*; pub use s_client_information::*; +pub use s_close_container::*; pub use s_confirm_teleport::*; pub use s_interact::*; pub use s_ping_request::*; diff --git a/pumpkin-protocol/src/server/play/s_close_container.rs b/pumpkin-protocol/src/server/play/s_close_container.rs new file mode 100644 index 000000000..564151168 --- /dev/null +++ b/pumpkin-protocol/src/server/play/s_close_container.rs @@ -0,0 +1,8 @@ +use pumpkin_macros::packet; +use serde::Deserialize; + +#[derive(Deserialize)] +#[packet(0x0F)] +pub struct SCloseContainer { + pub window_id: u8, +} diff --git a/pumpkin/src/client/container.rs b/pumpkin/src/client/container.rs index df188212f..fc9cf1059 100644 --- a/pumpkin/src/client/container.rs +++ b/pumpkin/src/client/container.rs @@ -1,6 +1,9 @@ use pumpkin_core::text::TextComponent; +use pumpkin_inventory::window_property::{WindowProperty, WindowPropertyTrait}; use pumpkin_inventory::WindowType; -use pumpkin_protocol::client::play::{COpenScreen, CSetContainerContent, CSetContainerSlot}; +use pumpkin_protocol::client::play::{ + CCloseContainer, COpenScreen, CSetContainerContent, CSetContainerProperty, CSetContainerSlot, +}; use pumpkin_protocol::slot::Slot; use pumpkin_world::item::Item; @@ -82,4 +85,20 @@ impl Player { &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, window_type: WindowType) { + self.client + .send_packet(&CCloseContainer::new(window_type as u8)) + } + + 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)); + } } diff --git a/pumpkin/src/client/player_packet.rs b/pumpkin/src/client/player_packet.rs index 993b20f56..b27af67cd 100644 --- a/pumpkin/src/client/player_packet.rs +++ b/pumpkin/src/client/player_packet.rs @@ -9,6 +9,8 @@ use crate::{ use num_traits::FromPrimitive; use pumpkin_core::text::TextComponent; use pumpkin_entity::EntityId; +use pumpkin_inventory::WindowType; +use pumpkin_protocol::server::play::SCloseContainer; use pumpkin_protocol::{ client::play::{ Animation, CAcknowledgeBlockChange, CBlockUpdate, CEntityAnimation, CEntityVelocity, @@ -406,4 +408,15 @@ impl Player { self.inventory .set_slot(packet.slot as usize, packet.clicked_item.to_item(), false); } + + // 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) { + // window_id 0 represents both 9x1 Generic AND inventory here + let Some(_window_type) = WindowType::from_u8(packet.window_id) else { + self.kick(TextComponent::text("Invalid window ID")); + return; + }; + } }