diff --git a/Cargo.lock b/Cargo.lock index 9e6bbfe41..829888627 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3257,7 +3257,7 @@ dependencies = [ name = "pumpkin" version = "0.1.0-dev+26.2-26.40" dependencies = [ - "aes 0.9.1", + "aes 0.9.2", "arc-swap", "axum", "base64 0.23.1", @@ -3454,6 +3454,7 @@ dependencies = [ "thiserror 2.0.20", "tokio", "uuid", + "xxhash-rust", ] [[package]] diff --git a/crates/pumpkin-config/src/networking/bedrock.rs b/crates/pumpkin-config/src/networking/bedrock.rs index af0eec2cf..420da2e6c 100644 --- a/crates/pumpkin-config/src/networking/bedrock.rs +++ b/crates/pumpkin-config/src/networking/bedrock.rs @@ -82,6 +82,8 @@ pub struct BedrockConfig { pub authentication: BedrockAuthenticationConfig, /// Bedrock `NetherNet` transport settings. pub nethernet: NetherNetConfig, + /// Whether Bedrock client chunk blob caching is enabled. + pub chunk_caching: bool, } impl Default for BedrockConfig { @@ -98,6 +100,7 @@ impl Default for BedrockConfig { motd: "A blazingly fast Pumpkin server!".to_string(), authentication: BedrockAuthenticationConfig::default(), nethernet: NetherNetConfig::default(), + chunk_caching: true, } } } diff --git a/crates/pumpkin-protocol/Cargo.toml b/crates/pumpkin-protocol/Cargo.toml index b943aa550..33ecfae3b 100644 --- a/crates/pumpkin-protocol/Cargo.toml +++ b/crates/pumpkin-protocol/Cargo.toml @@ -34,6 +34,7 @@ async-compression = { workspace = true, features = ["tokio", "zlib", "deflate"] flate2.workspace = true bitflags.workspace = true +xxhash-rust.workspace = true [dev-dependencies] # Validate correctness diff --git a/crates/pumpkin-protocol/src/bedrock/client/client_cache_miss_response.rs b/crates/pumpkin-protocol/src/bedrock/client/client_cache_miss_response.rs new file mode 100644 index 000000000..c0a5aa7db --- /dev/null +++ b/crates/pumpkin-protocol/src/bedrock/client/client_cache_miss_response.rs @@ -0,0 +1,28 @@ +use std::io::{Error, Write}; + +use pumpkin_macros::packet; + +use crate::{codec::var_uint::VarUInt, serial::PacketWrite}; + +#[derive(Clone, Debug)] +pub struct CacheBlob { + pub hash: u64, + pub payload: Vec, +} + +#[packet(136)] +pub struct CClientCacheMissResponse<'a> { + pub blobs: &'a [CacheBlob], +} + +impl PacketWrite for CClientCacheMissResponse<'_> { + fn write(&self, writer: &mut W) -> Result<(), Error> { + VarUInt(self.blobs.len() as u32).write(writer)?; + for blob in self.blobs { + writer.write_all(&blob.hash.to_le_bytes())?; + VarUInt(blob.payload.len() as u32).write(writer)?; + writer.write_all(&blob.payload)?; + } + Ok(()) + } +} diff --git a/crates/pumpkin-protocol/src/bedrock/client/level_chunk.rs b/crates/pumpkin-protocol/src/bedrock/client/level_chunk.rs index a6401a19f..6ebabbf8e 100644 --- a/crates/pumpkin-protocol/src/bedrock/client/level_chunk.rs +++ b/crates/pumpkin-protocol/src/bedrock/client/level_chunk.rs @@ -1,4 +1,5 @@ use std::io::{Error, Write}; +use xxhash_rust::xxh64::xxh64; use pumpkin_macros::packet; use pumpkin_world::chunk::{ChunkData, palette::NetworkPalette}; @@ -7,6 +8,7 @@ use crate::{ codec::{var_int::VarInt, var_uint::VarUInt}, serial::PacketWrite, }; + const VERSION: u8 = 9; #[packet(58)] @@ -20,94 +22,137 @@ pub struct CLevelChunk<'a> { pub chunk: &'a ChunkData, } -impl PacketWrite for CLevelChunk<'_> { - fn write(&self, writer: &mut W) -> Result<(), Error> { - VarInt(self.chunk.x).write(writer)?; - VarInt(self.chunk.z).write(writer)?; +pub type ChunkBlob = (u64, Vec); +pub type EncodedChunk = (Vec, Vec); - VarInt(self.dimension).write(writer)?; - let sub_chunk_count = self.chunk.section.count as u32; - debug_assert_eq!(sub_chunk_count, 24); - VarUInt(sub_chunk_count).write(writer)?; +impl CLevelChunk<'_> { + pub fn encode_chunk( + chunk: &ChunkData, + dimension: i32, + cache_enabled: bool, + ) -> Result { + let mut writer = Vec::new(); + + VarInt(chunk.x).write(&mut writer)?; + VarInt(chunk.z).write(&mut writer)?; + + VarInt(dimension).write(&mut writer)?; + let sub_chunk_count = chunk.section.count as u32; + VarUInt(sub_chunk_count).write(&mut writer)?; // Optional sub-chunk request limit. Pumpkin sends complete chunks. - false.write(writer)?; - self.cache_enabled.write(writer)?; - // Blob IDs are present in 26.40 even when client caching is disabled. - VarUInt(0).write(writer)?; + false.write(&mut writer)?; + cache_enabled.write(&mut writer)?; - let mut chunk_data = Vec::new(); - let data_write = &mut chunk_data; + let mut blobs = Vec::new(); - let block_sections = self - .chunk + let block_sections = chunk .section .block_sections .read() .map_err(|_| Error::other("block_sections read lock poisoned"))?; - let min_y_section = (self.chunk.section.min_y >> 4) as i8; + let min_y_section = (chunk.section.min_y >> 4) as i8; + + let mut subchunk_bytes_list = Vec::with_capacity(block_sections.len()); for (i, block_palette) in block_sections.iter().enumerate() { + let mut subchunk_buf = Vec::new(); // Version 9: [version:byte][num_storages:byte][sub_chunk_index:byte] let y = (i as i8) + min_y_section; let num_storages = 1; - data_write.write_all(&[VERSION, num_storages, y as u8])?; + subchunk_buf.write_all(&[VERSION, num_storages, y as u8])?; let network_repr = block_palette.convert_be_network(); - (network_repr.bits_per_entry << 1 | 1).write(data_write)?; + (network_repr.bits_per_entry << 1 | 1).write(&mut subchunk_buf)?; for data in network_repr.packed_data { - data.write(data_write)?; + data.write(&mut subchunk_buf)?; } match network_repr.palette { NetworkPalette::Single(id) => { - VarInt(i32::from(id)).write(data_write)?; + VarInt(i32::from(id)).write(&mut subchunk_buf)?; } NetworkPalette::Indirect(palette) => { - VarInt(palette.len() as i32).write(data_write)?; + VarInt(palette.len() as i32).write(&mut subchunk_buf)?; for id in palette { - VarInt(i32::from(id)).write(data_write)?; + VarInt(i32::from(id)).write(&mut subchunk_buf)?; } } NetworkPalette::Direct => (), } + + subchunk_bytes_list.push(subchunk_buf); } - let biome_sections = self - .chunk + let biome_sections = chunk .section .biome_sections .read() .map_err(|_| Error::other("biome_sections read lock poisoned"))?; + let mut biome_buf = Vec::new(); for biome_palette in biome_sections.iter() { let network_repr = biome_palette.convert_be_network(); - (network_repr.bits_per_entry << 1 | 1).write(data_write)?; + (network_repr.bits_per_entry << 1 | 1).write(&mut biome_buf)?; for data in network_repr.packed_data { - data.write(data_write)?; + data.write(&mut biome_buf)?; } match network_repr.palette { NetworkPalette::Single(id) => { - VarInt(i32::from(id)).write(data_write)?; + VarInt(i32::from(id)).write(&mut biome_buf)?; } NetworkPalette::Indirect(palette) => { - VarInt(palette.len() as i32).write(data_write)?; + VarInt(palette.len() as i32).write(&mut biome_buf)?; for id in palette { - VarInt(i32::from(id)).write(data_write)?; + VarInt(i32::from(id)).write(&mut biome_buf)?; } } NetworkPalette::Direct => (), } } - data_write.write_all(&[0])?; + if cache_enabled { + for subchunk_buf in subchunk_bytes_list { + let hash = xxh64(&subchunk_buf, 0); + blobs.push((hash, subchunk_buf)); + } + let biome_hash = xxh64(&biome_buf, 0); + blobs.push((biome_hash, biome_buf)); - VarUInt(chunk_data.len() as u32).write(writer)?; - writer.write_all(&chunk_data) + VarUInt(blobs.len() as u32).write(&mut writer)?; + for (hash, _) in &blobs { + writer.write_all(&hash.to_le_bytes())?; + } + + // Chunk data payload when cache_enabled: only border block count byte (0). + VarUInt(1).write(&mut writer)?; + writer.write_all(&[0])?; + } else { + VarUInt(0).write(&mut writer)?; + + let mut chunk_data = Vec::new(); + for subchunk_buf in subchunk_bytes_list { + chunk_data.write_all(&subchunk_buf)?; + } + chunk_data.write_all(&biome_buf)?; + chunk_data.write_all(&[0])?; + + VarUInt(chunk_data.len() as u32).write(&mut writer)?; + writer.write_all(&chunk_data)?; + } + + Ok((writer, blobs)) + } +} + +impl PacketWrite for CLevelChunk<'_> { + fn write(&self, writer: &mut W) -> Result<(), Error> { + let (encoded, _) = Self::encode_chunk(self.chunk, self.dimension, self.cache_enabled)?; + writer.write_all(&encoded) } } diff --git a/crates/pumpkin-protocol/src/bedrock/client/mod.rs b/crates/pumpkin-protocol/src/bedrock/client/mod.rs index 18785ccba..5013a0d84 100644 --- a/crates/pumpkin-protocol/src/bedrock/client/mod.rs +++ b/crates/pumpkin-protocol/src/bedrock/client/mod.rs @@ -5,6 +5,7 @@ pub mod available_commands; pub mod boss_event; pub mod change_dimension; pub mod chunk_radius_update; +pub mod client_cache_miss_response; pub mod common; pub mod container_open; pub mod correct_player_move; @@ -57,6 +58,7 @@ pub use available_commands::*; pub use boss_event::*; pub use change_dimension::*; pub use chunk_radius_update::*; +pub use client_cache_miss_response::*; pub use common::*; pub use container_open::*; pub use correct_player_move::*; diff --git a/crates/pumpkin-protocol/src/bedrock/server/client_cache_blob_status.rs b/crates/pumpkin-protocol/src/bedrock/server/client_cache_blob_status.rs new file mode 100644 index 000000000..32d00b417 --- /dev/null +++ b/crates/pumpkin-protocol/src/bedrock/server/client_cache_blob_status.rs @@ -0,0 +1,36 @@ +use std::io::{Error, Read}; + +use pumpkin_macros::packet; + +use crate::{codec::var_uint::VarUInt, serial::PacketRead}; + +#[packet(135)] +pub struct SClientCacheBlobStatus { + pub miss_hashes: Vec, + pub hit_hashes: Vec, +} + +impl PacketRead for SClientCacheBlobStatus { + fn read(reader: &mut R) -> Result { + let miss_count = VarUInt::read(reader)?.0 as usize; + let mut miss_hashes = Vec::with_capacity(miss_count); + for _ in 0..miss_count { + let mut bytes = [0u8; 8]; + reader.read_exact(&mut bytes)?; + miss_hashes.push(u64::from_le_bytes(bytes)); + } + + let hit_count = VarUInt::read(reader)?.0 as usize; + let mut hit_hashes = Vec::with_capacity(hit_count); + for _ in 0..hit_count { + let mut bytes = [0u8; 8]; + reader.read_exact(&mut bytes)?; + hit_hashes.push(u64::from_le_bytes(bytes)); + } + + Ok(Self { + miss_hashes, + hit_hashes, + }) + } +} diff --git a/crates/pumpkin-protocol/src/bedrock/server/mod.rs b/crates/pumpkin-protocol/src/bedrock/server/mod.rs index ab9fa52e3..7f6b960e7 100644 --- a/crates/pumpkin-protocol/src/bedrock/server/mod.rs +++ b/crates/pumpkin-protocol/src/bedrock/server/mod.rs @@ -1,6 +1,7 @@ pub mod actor_event; pub mod animate; pub mod block_pick_request; +pub mod client_cache_blob_status; pub mod client_cache_status; pub mod command_request; pub mod container_close; @@ -28,6 +29,7 @@ pub mod text; pub use actor_event::*; pub use animate::*; pub use block_pick_request::*; +pub use client_cache_blob_status::*; pub use client_cache_status::*; pub use command_request::*; pub use container_close::*; diff --git a/crates/pumpkin/src/block/blocks/nether_portal.rs b/crates/pumpkin/src/block/blocks/nether_portal.rs index 4f1739eff..440733eb6 100644 --- a/crates/pumpkin/src/block/blocks/nether_portal.rs +++ b/crates/pumpkin/src/block/blocks/nether_portal.rs @@ -81,7 +81,7 @@ impl BlockBehaviour for NetherPortalBlock { return; } - tracing::info!( + tracing::debug!( "Nether portal collision at {:?}, targeting world {:?}", args.position, target_world.dimension.minecraft_name diff --git a/crates/pumpkin/src/entity/player.rs b/crates/pumpkin/src/entity/player.rs index 4510737d0..18cafe78d 100644 --- a/crates/pumpkin/src/entity/player.rs +++ b/crates/pumpkin/src/entity/player.rs @@ -2180,6 +2180,7 @@ impl Player { .enqueue_packet(&CPlayStatus::PlayerSpawn) .await; self.bedrock_spawned.store(true, Ordering::Relaxed); + self.set_client_loaded(true); } } self.tick_counter.fetch_add(1, Ordering::Relaxed); @@ -2839,6 +2840,7 @@ impl Player { } /// Teleports the player to a different world or dimension with an optional position, yaw, and pitch. + #[expect(clippy::too_many_lines)] pub async fn teleport_world( self: &Arc, new_world: Arc, @@ -2938,6 +2940,7 @@ impl Player { false, ); bedrock.enqueue_packet(&change_dim_packet).await; + self.bedrock_spawned.store(false, Ordering::Relaxed); } } diff --git a/crates/pumpkin/src/entity/player/advancement.rs b/crates/pumpkin/src/entity/player/advancement.rs index 9aec6686f..2a13164e3 100644 --- a/crates/pumpkin/src/entity/player/advancement.rs +++ b/crates/pumpkin/src/entity/player/advancement.rs @@ -9,7 +9,10 @@ use pumpkin_data::advancement_data::{ AdvancementNode, AdvancementProgressData, AdvancementRequirement, AdvancementReward, Criteria, }; use pumpkin_data::{ADVANCEMENT_TREE, Advancement, translation}; -use pumpkin_protocol::java::client::play::{CSelectAdvancementsTab, CUpdateAdvancements}; +use pumpkin_protocol::bedrock::server::text::SText; +use pumpkin_protocol::java::client::play::{ + CSelectAdvancementsTab, CSystemChatMessage, CUpdateAdvancements, +}; use pumpkin_util::identifier::Identifier; use pumpkin_util::text::TextComponent; use serde::ser::SerializeMap; @@ -441,15 +444,25 @@ impl PlayerAdvancement { .show_advancement_messages { tokio::spawn(async move { - let component = TextComponent::translate_cross( + let player_name = player.get_display_name().await; + let je_component = TextComponent::translate( display.frame_type.get_translation(), - translation::bedrock::CHAT_TYPE_ACHIEVEMENT, - [player.get_display_name().await, advancement.name()], + [player_name.clone(), advancement.name()], ); + let je_packet = CSystemChatMessage::new(&je_component, false); + + let be_packet = SText::translation( + translation::bedrock::CHAT_TYPE_ACHIEVEMENT.to_string(), + vec![ + player_name.0.to_bedrock_string(), + display.get_title().0.to_bedrock_string(), + ], + ); + player .world() - .broadcast_system_message(&component, false) - .await; //send translate component for the event + .broadcast_editioned(&je_packet, &be_packet) + .await; }); } } diff --git a/crates/pumpkin/src/net/bedrock/mod.rs b/crates/pumpkin/src/net/bedrock/mod.rs index 6753e8058..697fae5d0 100644 --- a/crates/pumpkin/src/net/bedrock/mod.rs +++ b/crates/pumpkin/src/net/bedrock/mod.rs @@ -8,7 +8,7 @@ use std::{ net::SocketAddr, sync::{ Arc, - atomic::{AtomicBool, AtomicU32}, + atomic::{AtomicBool, AtomicU32, Ordering}, }, }; @@ -20,11 +20,16 @@ use pumpkin_protocol::{ BClientPacket, PacketDecodeError, RawPacket, bedrock::{ BEDROCK_GAME_PACKET, SubClient, - client::{disconnect_player::CDisconnectPlayer, level_chunk::CLevelChunk}, + client::{ + client_cache_miss_response::{CClientCacheMissResponse, CacheBlob}, + disconnect_player::CDisconnectPlayer, + level_chunk::CLevelChunk, + }, packet_decoder::BedrockBatchDecoder, packet_encoder::BedrockBatchEncoder, server::{ animate::SAnimate, block_pick_request::SBlockPickRequest, + client_cache_blob_status::SClientCacheBlobStatus, client_cache_status::SClientCacheStatus, command_request::SCommandRequest, container_close::SContainerClose, emote::SEmote, emote_list::SEmoteList, interaction::SInteraction, inventory_transaction::SInventoryTransaction, @@ -111,6 +116,8 @@ pub struct BedrockClient { /// The next form ID to use for custom forms. pub next_form_id: AtomicU32, pub inventory_opened: AtomicBool, + pub client_cache_supported: AtomicBool, + pub blob_cache: Mutex>>, /// An notifier that is triggered when this client is closed. close_token: CancellationToken, last_seen: Arc>, @@ -146,6 +153,8 @@ impl BedrockClient { outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)), next_form_id: AtomicU32::new(0), inventory_opened: AtomicBool::new(false), + client_cache_supported: AtomicBool::new(false), + blob_cache: Mutex::new(HashMap::new()), close_token: CancellationToken::new(), last_seen: Arc::new(AtomicCell::new(std::time::Instant::now())), incoming_game_packet_send: incoming_send, @@ -288,30 +297,45 @@ impl BedrockClient { return; } + let bedrock_dimension = + if player.world().dimension == pumpkin_data::dimension::Dimension::THE_NETHER { + 1 + } else if player.world().dimension == pumpkin_data::dimension::Dimension::THE_END { + 2 + } else { + 0 + }; + + let cache_enabled = server.advanced_config.networking.bedrock.chunk_caching + && self.client_cache_supported.load(Ordering::Relaxed); + let mut serialize_tasks = Vec::with_capacity(valid_chunks.len()); for chunk in valid_chunks { serialize_tasks.push(tokio::task::spawn_blocking(move || { - let mut packet_payload = Vec::new(); - let packet = CLevelChunk { - dimension: 0, - cache_enabled: false, - chunk: &chunk, - }; - packet - .write_packet(&mut packet_payload) - .map(|()| packet_payload) + CLevelChunk::encode_chunk(&chunk, bedrock_dimension, cache_enabled) })); } let mut encoded_payloads = Vec::with_capacity(serialize_tasks.len()); + let mut new_blobs = Vec::new(); for task in serialize_tasks { match task.await { - Ok(Ok(payload)) => encoded_payloads.push(payload), + Ok(Ok((payload, blobs))) => { + encoded_payloads.push(payload); + new_blobs.extend(blobs); + } Ok(Err(e)) => error!("Failed to serialize Bedrock chunk: {:?}", e), Err(e) => error!("Join error in Bedrock chunk serialization: {:?}", e), } } + if !new_blobs.is_empty() { + let mut cache = self.blob_cache.lock().await; + for (hash, payload) in new_blobs { + cache.insert(hash, payload); + } + } + let mut packets_to_enqueue = Vec::with_capacity(encoded_payloads.len()); { let encoder = self.network_writer.read().await; @@ -635,7 +659,13 @@ impl BedrockClient { let reader = &mut &payload[..]; match packet.id { SClientCacheStatus::PACKET_ID => { - // TODO + let packet = SClientCacheStatus::read(reader)?; + self.client_cache_supported + .store(packet.cache_supported, Ordering::Relaxed); + } + SClientCacheBlobStatus::PACKET_ID => { + self.handle_client_cache_blob_status(SClientCacheBlobStatus::read(reader)?) + .await; } SResourcePackResponse::PACKET_ID => { self.handle_resource_pack_response(SResourcePackResponse::read(reader)?, server) @@ -736,6 +766,30 @@ impl BedrockClient { Ok(()) } + pub async fn handle_client_cache_blob_status(&self, packet: SClientCacheBlobStatus) { + if packet.miss_hashes.is_empty() { + return; + } + let cache = self.blob_cache.lock().await; + let mut missing_blobs = Vec::with_capacity(packet.miss_hashes.len()); + for hash in packet.miss_hashes { + if let Some(payload) = cache.get(&hash) { + missing_blobs.push(CacheBlob { + hash, + payload: payload.clone(), + }); + } else { + warn!("Client requested missing blob {hash:#x} not found in server cache"); + } + } + if !missing_blobs.is_empty() { + self.send_game_packet(&CClientCacheMissResponse { + blobs: &missing_blobs, + }) + .await; + } + } + pub async fn await_close_interrupt(&self) { self.close_token.cancelled().await; } diff --git a/crates/pumpkin/src/net/bedrock/play.rs b/crates/pumpkin/src/net/bedrock/play.rs index 95bf64cc4..c071d1da3 100644 --- a/crates/pumpkin/src/net/bedrock/play.rs +++ b/crates/pumpkin/src/net/bedrock/play.rs @@ -72,35 +72,15 @@ use tracing::{debug, info}; const MIN_PREDICTED_BREAK_PROGRESS: f32 = 0.65; -fn descriptor_to_stack(desc: &NetworkItemDescriptor, is_creative: bool) -> ItemStack { +fn descriptor_to_stack(desc: &NetworkItemDescriptor) -> ItemStack { if desc.id.0 == 0 || desc.stack_size == 0 { ItemStack::EMPTY.clone() } else { - let mut mapped_item = None; - - if is_creative { - let index = (desc.id.0.saturating_sub(1)) as usize; - if index < pumpkin_data::bedrock_creative::CREATIVE_ENTRIES.len() { - let entry = pumpkin_data::bedrock_creative::CREATIVE_ENTRIES[index]; - if let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( - entry.item_id, - entry.item_aux_value, - ) { - mapped_item = Some(mapping.java_item); - } - } - } - - if mapped_item.is_none() - && let Some(mapping) = pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( - desc.id.0 as i16, - desc.aux_value.0, - ) - { - mapped_item = Some(mapping.java_item); - } - - mapped_item.map_or_else( + pumpkin_data::item::JavaToBedrockItemMapping::from_bedrock( + desc.id.0 as i16, + desc.aux_value.0, + ) + .map_or_else( || { tracing::warn!( "Failed to map bedrock item id {} and data {} to Java item", @@ -109,7 +89,7 @@ fn descriptor_to_stack(desc: &NetworkItemDescriptor, is_creative: bool) -> ItemS ); ItemStack::EMPTY.clone() }, - |item| ItemStack::new(desc.stack_size as u8, item), + |mapping| ItemStack::new(desc.stack_size as u8, mapping.java_item), ) } } @@ -506,7 +486,7 @@ impl BedrockClient { player: &Arc, packet: SInventoryTransaction, ) { - tracing::info!("handle_inventory_action: packet={:?}", packet); + tracing::debug!("handle_inventory_action: packet={:?}", packet); let mut inventory_updated = false; let mut updates = Vec::new(); let result = 0u8; @@ -558,13 +538,12 @@ impl BedrockClient { player_screen_handler.send_content_updates().await; } - let is_creative = player.gamemode.load() == GameMode::Creative; for action in &packet.actions { use pumpkin_protocol::bedrock::server::inventory_transaction::InventoryActionSource; let source_type = InventoryActionSource::from(action.source_type); if source_type == InventoryActionSource::World { - let old_stack = descriptor_to_stack(&action.old_item, is_creative); - let new_stack = descriptor_to_stack(&action.new_item, is_creative); + let old_stack = descriptor_to_stack(&action.old_item); + let new_stack = descriptor_to_stack(&action.new_item); if old_stack.is_empty() && !new_stack.is_empty() { player.drop_item(new_stack).await; } @@ -572,7 +551,7 @@ impl BedrockClient { if let Some(screen_slot) = map_bedrock_slot_to_screen_handler(window_id, action.inventory_slot) { - let item_stack = descriptor_to_stack(&action.new_item, is_creative); + let item_stack = descriptor_to_stack(&action.new_item); let mut player_screen_handler = player.player_screen_handler.lock().await; @@ -666,8 +645,7 @@ impl BedrockClient { if data.action_type.0 == 0 { // Click block - let is_creative = player.gamemode.load() == GameMode::Creative; - let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative); + let client_stack = descriptor_to_stack(&data.item_in_hand); let mut held_item = player.inventory().held_item().await; if !client_stack.is_empty() { @@ -763,8 +741,7 @@ impl BedrockClient { } } else if data.action_type.0 == 1 { // Click air / Use item - let is_creative = player.gamemode.load() == GameMode::Creative; - let client_stack = descriptor_to_stack(&data.item_in_hand, is_creative); + let client_stack = descriptor_to_stack(&data.item_in_hand); let mut held = player.inventory.held_item().await; if !client_stack.is_empty() @@ -1363,7 +1340,6 @@ impl BedrockClient { let mut result = 0u8; // 0 = Success, 1 = Error for action in request.actions { - tracing::info!("Processing ItemStackRequestAction: {:?}", action); match action { ItemStackRequestAction::CraftCreative { creative_item_id,