From b08be9e158c132e37024ae4318ffaced4b860c25 Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Sun, 7 Jun 2026 21:12:16 +0200 Subject: [PATCH] feat(bedrock): add container open --- pumpkin-protocol/src/bedrock/network_item.rs | 36 +++++++- .../bedrock/server/inventory_transaction.rs | 85 ++++++++++++++---- pumpkin/src/entity/player.rs | 90 +++++++++++++++---- pumpkin/src/net/bedrock/play.rs | 58 +++++++++++- 4 files changed, 230 insertions(+), 39 deletions(-) diff --git a/pumpkin-protocol/src/bedrock/network_item.rs b/pumpkin-protocol/src/bedrock/network_item.rs index 7663226ca..479551b29 100644 --- a/pumpkin-protocol/src/bedrock/network_item.rs +++ b/pumpkin-protocol/src/bedrock/network_item.rs @@ -1,4 +1,4 @@ -use std::io::{Error, Write}; +use std::io::{Error, Read, Write}; use std::num::NonZeroI32; use pumpkin_data::item::{BedrockItem, JavaToBedrockItemMapping}; @@ -7,10 +7,10 @@ use pumpkin_nbt::Nbt; use crate::{ codec::{var_int::VarInt, var_uint::VarUInt}, - serial::PacketWrite, + serial::{PacketRead, PacketWrite}, }; -#[derive(Default, Clone)] +#[derive(Default, Clone, Debug)] pub struct NetworkItemDescriptor { // I hate mojang // https://mojang.github.io/bedrock-protocol-docs/html/NetworkItemInstanceDescriptor.html @@ -33,6 +33,36 @@ impl PacketWrite for NetworkItemDescriptor { } } +impl PacketRead for NetworkItemDescriptor { + fn read(buf: &mut R) -> Result { + let id = VarInt::read(buf)?; + if id.0 == 0 { + return Ok(Self::default()); + } + let stack_size = u16::read(buf)?; + let aux_value = VarUInt::read(buf)?; + + let has_net_id = bool::read(buf)?; + if has_net_id { + let _net_id = VarInt::read(buf)?; + } + + let block_runtime_id = VarInt::read(buf)?; + + let user_data_len = VarUInt::read(buf)?.0; + let mut user_data = vec![0u8; user_data_len as usize]; + buf.read_exact(&mut user_data)?; + + Ok(Self { + id, + stack_size, + aux_value, + block_runtime_id, + ..Default::default() + }) + } +} + impl NetworkItemDescriptor { #[allow(clippy::option_option)] fn write_with_net_id( diff --git a/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs b/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs index 671d04bac..a2eed8d4e 100644 --- a/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs +++ b/pumpkin-protocol/src/bedrock/server/inventory_transaction.rs @@ -3,10 +3,12 @@ use std::io::{Error, ErrorKind, Read}; use pumpkin_macros::packet; use pumpkin_util::math::position::BlockPos; +use crate::bedrock::network_item::NetworkItemDescriptor; use crate::{ codec::{var_int::VarInt, var_uint::VarUInt, var_ulong::VarULong}, serial::PacketRead, }; +use pumpkin_util::math::vector3::Vector3; pub const WINDOW_ID_INVENTORY: i32 = 0; pub const WINDOW_ID_OFF_HAND: i32 = 119; @@ -55,9 +57,8 @@ pub struct InventoryAction { pub window_id: Option, pub source_flags: Option, pub inventory_slot: u32, - // TODO - pub old_item: (), - pub new_item: (), + pub old_item: NetworkItemDescriptor, + pub new_item: NetworkItemDescriptor, } impl PacketRead for InventoryAction { @@ -79,16 +80,16 @@ impl PacketRead for InventoryAction { let inventory_slot = VarULong::read(buf)?.0 as u32; - // let old_item = ItemStack::read(buf)?; - // let new_item = ItemStack::read(buf)?; + let old_item = NetworkItemDescriptor::read(buf)?; + let new_item = NetworkItemDescriptor::read(buf)?; Ok(Self { source_type, window_id, source_flags, inventory_slot, - old_item: (), - new_item: (), + old_item, + new_item, }) } } @@ -99,29 +100,79 @@ pub struct NormalTransactionData; #[derive(Debug, PacketRead)] pub struct MismatchTransactionData; -#[derive(Debug, PacketRead)] +#[derive(Debug)] pub struct UseItemTransactionData { - pub action_type: VarULong, - pub trigger_type: VarULong, + pub action_type: VarUInt, + pub trigger_type: VarUInt, pub block_position: BlockPos, pub block_face: VarInt, pub hot_bar_slot: VarInt, - // TODO + pub item_in_hand: NetworkItemDescriptor, + pub player_position: Vector3, + pub click_position: Vector3, + pub block_runtime_id: VarUInt, + pub client_prediction: VarUInt, + pub client_cooldown_state: u8, } -#[derive(Debug, PacketRead)] +impl PacketRead for UseItemTransactionData { + fn read(buf: &mut R) -> Result { + Ok(Self { + action_type: VarUInt::read(buf)?, + trigger_type: VarUInt::read(buf)?, + block_position: BlockPos::read(buf)?, + block_face: VarInt::read(buf)?, + hot_bar_slot: VarInt::read(buf)?, + item_in_hand: NetworkItemDescriptor::read(buf)?, + player_position: Vector3::read(buf)?, + click_position: Vector3::read(buf)?, + block_runtime_id: VarUInt::read(buf)?, + client_prediction: VarUInt::read(buf)?, + client_cooldown_state: u8::read(buf)?, + }) + } +} + +#[derive(Debug)] pub struct UseItemOnEntityTransactionData { pub target_entity_runtime_id: VarULong, - pub action_type: VarULong, + pub action_type: VarUInt, pub hot_bar_slot: VarInt, - // TODO + pub item_in_hand: NetworkItemDescriptor, + pub player_position: Vector3, + pub click_position: Vector3, } -#[derive(Debug, PacketRead)] +impl PacketRead for UseItemOnEntityTransactionData { + fn read(buf: &mut R) -> Result { + Ok(Self { + target_entity_runtime_id: VarULong::read(buf)?, + action_type: VarUInt::read(buf)?, + hot_bar_slot: VarInt::read(buf)?, + item_in_hand: NetworkItemDescriptor::read(buf)?, + player_position: Vector3::read(buf)?, + click_position: Vector3::read(buf)?, + }) + } +} + +#[derive(Debug)] pub struct ReleaseItemTransactionData { - pub action_type: VarULong, + pub action_type: VarUInt, pub hot_bar_slot: VarInt, - // TODO + pub item_in_hand: NetworkItemDescriptor, + pub head_position: Vector3, +} + +impl PacketRead for ReleaseItemTransactionData { + fn read(buf: &mut R) -> Result { + Ok(Self { + action_type: VarUInt::read(buf)?, + hot_bar_slot: VarInt::read(buf)?, + item_in_hand: NetworkItemDescriptor::read(buf)?, + head_position: Vector3::read(buf)?, + }) + } } #[derive(Debug)] diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 1ac065250..aded149e2 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -40,7 +40,7 @@ use pumpkin_data::item_stack::ItemStack; use pumpkin_data::particle::Particle; use pumpkin_data::sound::{Sound, SoundCategory}; use pumpkin_data::tag::Taggable; -use pumpkin_data::{Block, BlockState, Enchantment, tag, translation}; +use pumpkin_data::{Block, BlockState, Enchantment, screen::WindowType, tag, translation}; use pumpkin_inventory::player::{ player_inventory::PlayerInventory, player_screen_handler::PlayerScreenHandler, }; @@ -54,7 +54,9 @@ use pumpkin_nbt::compound::NbtCompound; use pumpkin_nbt::tag::NbtTag; use pumpkin_protocol::IdOr; use pumpkin_protocol::SoundEvent; +use pumpkin_protocol::bedrock::client::container_open::CContainerOpen; use pumpkin_protocol::codec::var_int::VarInt; +use pumpkin_protocol::codec::var_long::VarLong; use pumpkin_protocol::java::client::play::{ Animation, CAcknowledgeBlockChange, CActionBar, CChangeDifficulty, CCloseContainer, CCombatDeath, CCustomPayload, CDisguisedChatMessage, CEntityAnimation, CEntityPositionSync, @@ -3326,16 +3328,44 @@ impl Player { .await { let screen_handler_temp = screen_handler.lock().await; + let sync_id = screen_handler_temp.sync_id(); + let window_type = screen_handler_temp + .window_type() + .expect("Can't open PlayerScreenHandler"); + + let display_name = screen_handler_factory.get_display_name(); + let java_packet = + COpenScreen::new(sync_id.into(), (window_type as i32).into(), &display_name); + + let bedrock_window_type = match window_type { + WindowType::Crafting => 1, + WindowType::Furnace => 2, + WindowType::Enchantment => 3, + WindowType::BrewingStand => 4, + WindowType::Anvil => 5, + WindowType::Hopper => 8, + WindowType::Beacon => 13, + WindowType::BlastFurnace => 27, + WindowType::Smoker => 28, + WindowType::Stonecutter => 29, + WindowType::CartographyTable => 30, + WindowType::Grindstone => 26, + WindowType::Loom => 24, + WindowType::Smithing => 34, + _ => 0, + }; + + let bedrock_packet = CContainerOpen { + container_id: sync_id, + container_type: bedrock_window_type, + position: block_pos.unwrap_or(BlockPos::ZERO), + target_entity_id: VarLong(-1), + }; + self.client - .enqueue_packet(&COpenScreen::new( - screen_handler_temp.sync_id().into(), - (screen_handler_temp - .window_type() - .expect("Can't open PlayerScreenHandler") as i32) - .into(), - &screen_handler_factory.get_display_name(), - )) + .enqueue_packet_editioned(&java_packet, &bedrock_packet) .await; + drop(screen_handler_temp); self.on_screen_handler_opened(screen_handler.clone()).await; *self.current_screen_handler.lock().await = screen_handler; @@ -3366,16 +3396,42 @@ impl Player { } let screen_handler_temp = screen_handler.lock().await; + let sync_id = screen_handler_temp.sync_id(); + let window_type = screen_handler_temp + .window_type() + .expect("Can't open PlayerScreenHandler"); + + let java_packet = COpenScreen::new(sync_id.into(), (window_type as i32).into(), &title); + + let bedrock_window_type = match window_type { + WindowType::Crafting => 1, + WindowType::Furnace => 2, + WindowType::Enchantment => 3, + WindowType::BrewingStand => 4, + WindowType::Anvil => 5, + WindowType::Hopper => 8, + WindowType::Beacon => 13, + WindowType::BlastFurnace => 27, + WindowType::Smoker => 28, + WindowType::Stonecutter => 29, + WindowType::CartographyTable => 30, + WindowType::Grindstone => 26, + WindowType::Loom => 24, + WindowType::Smithing => 34, + _ => 0, + }; + + let bedrock_packet = CContainerOpen { + container_id: sync_id, + container_type: bedrock_window_type, + position: BlockPos::ZERO, + target_entity_id: VarLong(-1), + }; + self.client - .enqueue_packet(&COpenScreen::new( - screen_handler_temp.sync_id().into(), - (screen_handler_temp - .window_type() - .expect("Can't open PlayerScreenHandler") as i32) - .into(), - &title, - )) + .enqueue_packet_editioned(&java_packet, &bedrock_packet) .await; + drop(screen_handler_temp); self.on_screen_handler_opened(screen_handler.clone()).await; *self.current_screen_handler.lock().await = screen_handler; diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index 0dcfbf329..a326786f0 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -29,6 +29,7 @@ use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent}; use pumpkin_world::world::BlockFlags; use crate::{ + block::{BlockHitResult, registry::BlockActionResult}, entity::{EntityBase, player::Player}, net::{DisconnectReason, bedrock::BedrockClient}, plugin::player::{ @@ -38,6 +39,7 @@ use crate::{ server::{Server, seasonal_events}, world::chunker::{self}, }; +use pumpkin_data::BlockDirection; use tracing::{debug, info}; impl BedrockClient { @@ -386,8 +388,60 @@ impl BedrockClient { TransactionData::Mismatch(_data) => { // TODO } - TransactionData::UseItem(_data) => { - // TODO + TransactionData::UseItem(data) => { + let face = match data.block_face.0 { + 0 => BlockDirection::Down, + 2 => BlockDirection::North, + 3 => BlockDirection::South, + 4 => BlockDirection::West, + 5 => BlockDirection::East, + _ => BlockDirection::Up, + }; + let world = player.world(); + let block = world.get_block(&data.block_position); + let server = world.server.upgrade().expect("Server is gone"); + + if data.action_type.0 == 0 { + // Click block + let held_item = player.inventory.held_item(); + + let result = server + .block_registry + .use_with_item( + block, + player, + &data.block_position, + &BlockHitResult { + face: &face, + cursor_pos: &data.click_position, + }, + &held_item, + &server, + &world, + ) + .await; + + if result.consumes_action() { + return; + } + + if matches!(result, BlockActionResult::PassToDefaultBlockAction) { + server + .block_registry + .on_use( + block, + player, + &data.block_position, + &BlockHitResult { + face: &face, + cursor_pos: &data.click_position, + }, + &server, + &world, + ) + .await; + } + } } TransactionData::UseItemOnEntity(data) => { let target_runtime_id = data.target_entity_runtime_id.0 as i32;