diff --git a/pumpkin-inventory/src/lib.rs b/pumpkin-inventory/src/lib.rs index 1ab7cba1c..044980cc4 100644 --- a/pumpkin-inventory/src/lib.rs +++ b/pumpkin-inventory/src/lib.rs @@ -3,7 +3,7 @@ use num_derive::ToPrimitive; pub mod player; /// https://wiki.vg/Inventory -#[derive(Debug, ToPrimitive)] +#[derive(Debug, ToPrimitive, Clone)] pub enum WindowType { // not used Generic9x1, @@ -41,3 +41,13 @@ pub enum WindowType { CartographyTable, Stonecutter, } + +impl WindowType { + pub const fn default_title(&self) -> &'static str { + // TODO: Add titles here: + /*match self { + _ => "WINDOW TITLE", + }*/ + "WINDOW TITLE" + } +} diff --git a/pumpkin-inventory/src/player.rs b/pumpkin-inventory/src/player.rs index f7147f181..b67af9c6b 100644 --- a/pumpkin-inventory/src/player.rs +++ b/pumpkin-inventory/src/player.rs @@ -95,4 +95,13 @@ impl PlayerInventory { debug_assert!((0..9).contains(&self.selected)); self.items[self.selected + 36 - 9].as_ref() } + + 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())); + slots.extend(self.items.iter().map(|c| c.as_ref())); + slots.push(self.offhand.as_ref()); + slots + } } diff --git a/pumpkin-protocol/src/client/play/c_set_container_content.rs b/pumpkin-protocol/src/client/play/c_set_container_content.rs new file mode 100644 index 000000000..791ee1db5 --- /dev/null +++ b/pumpkin-protocol/src/client/play/c_set_container_content.rs @@ -0,0 +1,26 @@ +use crate::slot::Slot; +use crate::VarInt; +use pumpkin_macros::packet; +use serde::Serialize; + +#[derive(Serialize)] +#[packet(0x13)] +pub struct CSetContainerContent<'a> { + window_id: u8, + state_id: VarInt, + count: VarInt, + slot_data: &'a [Slot], + carried_item: &'a Slot, +} + +impl<'a> CSetContainerContent<'a> { + pub fn new(window_id: u8, state_id: VarInt, slots: &'a [Slot], carried_item: &'a Slot) -> Self { + Self { + window_id, + state_id, + count: slots.len().into(), + slot_data: slots, + carried_item, + } + } +} diff --git a/pumpkin-protocol/src/client/play/mod.rs b/pumpkin-protocol/src/client/play/mod.rs index 2ff20e4c0..df63734c9 100644 --- a/pumpkin-protocol/src/client/play/mod.rs +++ b/pumpkin-protocol/src/client/play/mod.rs @@ -22,6 +22,7 @@ mod c_player_chat_message; mod c_player_info_update; mod c_player_remove; mod c_remove_entities; +mod c_set_container_content; mod c_set_held_item; mod c_set_title; mod c_spawn_player; @@ -58,6 +59,7 @@ pub use c_player_chat_message::*; 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_held_item::*; pub use c_set_title::*; pub use c_spawn_player::*; diff --git a/pumpkin-protocol/src/slot.rs b/pumpkin-protocol/src/slot.rs index 6685ec207..053813fc7 100644 --- a/pumpkin-protocol/src/slot.rs +++ b/pumpkin-protocol/src/slot.rs @@ -1,8 +1,9 @@ use crate::VarInt; use pumpkin_world::item::Item; +use serde::ser::SerializeSeq; use serde::{ de::{self, SeqAccess}, - Deserialize, + Deserialize, Serialize, Serializer, }; #[derive(Debug, Clone)] @@ -76,6 +77,58 @@ impl<'de> Deserialize<'de> for Slot { } } +impl Serialize for Slot { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + if self.item_count == 0.into() { + let mut s = serializer.serialize_seq(Some(1))?; + s.serialize_element(&self.item_count)?; + s.end() + } else { + match (&self.num_components_to_add, &self.num_components_to_remove) { + (Some(to_add), Some(to_remove)) => { + let mut s = serializer.serialize_seq(Some(6))?; + s.serialize_element(&self.item_count)?; + s.serialize_element(self.item_id.as_ref().unwrap())?; + s.serialize_element(to_add)?; + s.serialize_element(to_remove)?; + s.serialize_element(self.components_to_add.as_ref().unwrap())?; + s.serialize_element(self.components_to_remove.as_ref().unwrap())?; + s.end() + } + (None, Some(to_remove)) => { + let mut s = serializer.serialize_seq(Some(5))?; + s.serialize_element(&self.item_count)?; + s.serialize_element(self.item_id.as_ref().unwrap())?; + s.serialize_element(&VarInt(0))?; + s.serialize_element(to_remove)?; + s.serialize_element(self.components_to_remove.as_ref().unwrap())?; + s.end() + } + (Some(to_add), None) => { + let mut s = serializer.serialize_seq(Some(5))?; + s.serialize_element(&self.item_count)?; + s.serialize_element(self.item_id.as_ref().unwrap())?; + s.serialize_element(to_add)?; + s.serialize_element(&VarInt(0))?; + s.serialize_element(self.components_to_add.as_ref().unwrap())?; + s.end() + } + (None, None) => { + let mut s = serializer.serialize_seq(Some(4))?; + s.serialize_element(&self.item_count)?; + s.serialize_element(&self.item_id.as_ref().unwrap())?; + s.serialize_element(&VarInt(0))?; + s.serialize_element(&VarInt(0))?; + s.end() + } + } + } + } +} + impl Slot { pub fn to_item(self) -> Option { let item_id = self.item_id?.0.try_into().unwrap(); @@ -84,12 +137,29 @@ impl Slot { item_count: self.item_count.0.try_into().unwrap(), }) } + + pub const fn empty() -> Self { + Slot { + item_count: VarInt(0), + item_id: None, + num_components_to_add: None, + num_components_to_remove: None, + components_to_add: None, + components_to_remove: None, + } + } } -impl From for Item { - fn from(slot: Slot) -> Self { - Item { - item_count: slot.item_count.0.try_into().unwrap(), - item_id: slot.item_id.unwrap().0.try_into().unwrap(), + +impl From<&Item> for Slot { + fn from(item: &Item) -> Self { + Slot { + item_count: item.item_count.into(), + item_id: Some(item.item_id.into()), + // TODO: add these + num_components_to_add: None, + num_components_to_remove: None, + components_to_add: None, + components_to_remove: None, } } } diff --git a/pumpkin/src/client/mod.rs b/pumpkin/src/client/mod.rs index 30121afdc..0e1083a3e 100644 --- a/pumpkin/src/client/mod.rs +++ b/pumpkin/src/client/mod.rs @@ -19,7 +19,10 @@ use pumpkin_protocol::{ client::{ config::CConfigDisconnect, login::CLoginDisconnect, - play::{CGameEvent, CPlayDisconnect, CSyncPlayerPostion, CSystemChatMessge}, + play::{ + CGameEvent, CPlayDisconnect, CSetContainerContent, CSyncPlayerPostion, + CSystemChatMessge, + }, }, packet_decoder::PacketDecoder, packet_encoder::PacketEncoder, @@ -38,6 +41,10 @@ use pumpkin_protocol::{ ClientPacket, ConnectionState, PacketError, RawPacket, ServerPacket, }; +use pumpkin_inventory::WindowType; +use pumpkin_protocol::client::play::COpenScreen; +use pumpkin_protocol::slot::Slot; +use pumpkin_world::item::Item; use std::io::Read; use thiserror::Error; @@ -175,6 +182,73 @@ impl Client { self.send_packet(&CGameEvent::new(3, gamemode.to_f32().unwrap())); } + pub fn open_container( + &mut self, + window_type: WindowType, + minecraft_menu_id: &str, + window_title: Option<&str>, + items: Option>>, + carried_item: Option<&Item>, + ) { + let menu_protocol_id = (*pumpkin_world::global_registry::REGISTRY + .get("minecraft:menu") + .unwrap() + .entries + .get(minecraft_menu_id) + .expect("Should be a valid menu id") + .get("protocol_id") + .unwrap()) + .into(); + let title = TextComponent::text(window_title.unwrap_or(window_type.default_title())); + self.send_packet(&COpenScreen::new( + (window_type.clone() as u8 + 1).into(), + menu_protocol_id, + title, + )); + self.set_container_content(window_type, items, carried_item); + } + + pub fn set_container_content<'a>( + &mut self, + window_type: WindowType, + items: Option>>, + carried_item: Option<&'a Item>, + ) { + let player = self.player.as_ref().unwrap(); + + let slots: Vec = { + if let Some(mut items) = items { + items.extend(player.inventory.slots()); + items + } else { + player.inventory.slots() + } + .into_iter() + .map(|item| { + if let Some(item) = item { + Slot::from(item) + } else { + Slot::empty() + } + }) + .collect() + }; + + let carried_item = { + if let Some(item) = carried_item { + item.into() + } else { + Slot::empty() + } + }; + self.send_packet(&CSetContainerContent::new( + window_type as u8 + 1, + 0.into(), + &slots, + &carried_item, + )); + } + pub async fn process_packets(&mut self, server: &mut Server) { let mut i = 0; while i < self.client_packets_queue.len() {