From f19eee2d70bef285dbcaef55c7adfdde260c97fd Mon Sep 17 00:00:00 2001 From: Alexander Medvedev Date: Thu, 4 Jun 2026 20:54:48 +0200 Subject: [PATCH] fix: some bedrock fixes also added join and leave translated messages --- .../src/bedrock/client/player_list.rs | 5 +- .../src/bedrock/packet_decoder.rs | 2 +- .../src/bedrock/packet_encoder.rs | 4 +- pumpkin-protocol/src/bedrock/server/emote.rs | 17 +++ .../src/bedrock/server/emote_list.rs | 64 ++++++++++ pumpkin-protocol/src/bedrock/server/mod.rs | 2 + pumpkin-protocol/src/bedrock/server/text.rs | 14 +++ pumpkin-util/src/text/mod.rs | 30 +++++ pumpkin/src/entity/mod.rs | 52 +++++++++ pumpkin/src/entity/player.rs | 44 ++++--- pumpkin/src/net/bedrock/mod.rs | 109 +++++++++++------- pumpkin/src/net/bedrock/play.rs | 36 ++++++ pumpkin/src/net/java/mod.rs | 4 +- .../loader/wasm/wasm_host/wit/v0_1/world.rs | 3 +- pumpkin/src/world/mod.rs | 72 ++++++------ 15 files changed, 357 insertions(+), 101 deletions(-) create mode 100644 pumpkin-protocol/src/bedrock/server/emote.rs create mode 100644 pumpkin-protocol/src/bedrock/server/emote_list.rs diff --git a/pumpkin-protocol/src/bedrock/client/player_list.rs b/pumpkin-protocol/src/bedrock/client/player_list.rs index 3d656e07c..809b467b1 100644 --- a/pumpkin-protocol/src/bedrock/client/player_list.rs +++ b/pumpkin-protocol/src/bedrock/client/player_list.rs @@ -106,11 +106,12 @@ impl Skin { Self { skin_id: "Standard_Custom".to_string(), play_fab_id: String::new(), - resource_patch: r#"{"geometry":{"default":"geometry.humanoid.custom"}}"#.into(), + resource_patch: r#"{"geometry":{"default":"geometry.humanoid"}}"#.into(), image_width: 64, image_height: 64, // 64 * 64 * 4 = 16384 bytes of raw RGBA data - skin_data: vec![0; 16384], + // Fill with 255 so the skin is visible (solid white) instead of invisible (transparent) + skin_data: vec![255; 16384], animations: Vec::new(), cape_width: 0, cape_height: 0, diff --git a/pumpkin-protocol/src/bedrock/packet_decoder.rs b/pumpkin-protocol/src/bedrock/packet_decoder.rs index a4877fccb..0cf0f4b51 100644 --- a/pumpkin-protocol/src/bedrock/packet_decoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_decoder.rs @@ -211,7 +211,7 @@ mod tests { const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1; let payload = vec![0x2a; PAYLOAD_LEN]; let mut wire_buf = Vec::new(); - let mut network_encoder = UDPNetworkEncoder::new(); + let network_encoder = UDPNetworkEncoder::new(); network_encoder .write_game_packet( 0x01, diff --git a/pumpkin-protocol/src/bedrock/packet_encoder.rs b/pumpkin-protocol/src/bedrock/packet_encoder.rs index bae3af02c..d35312037 100644 --- a/pumpkin-protocol/src/bedrock/packet_encoder.rs +++ b/pumpkin-protocol/src/bedrock/packet_encoder.rs @@ -111,7 +111,7 @@ impl UDPNetworkEncoder { } pub fn write_game_packet( - &mut self, + &self, packet_id: u16, sub_client_sender: SubClient, sub_client_target: SubClient, @@ -161,7 +161,7 @@ impl UDPNetworkEncoder { } pub async fn write_packet( - &mut self, + &self, packet_data: &[u8], addr: SocketAddr, socket: &UdpSocket, diff --git a/pumpkin-protocol/src/bedrock/server/emote.rs b/pumpkin-protocol/src/bedrock/server/emote.rs new file mode 100644 index 000000000..e1899fc8a --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/emote.rs @@ -0,0 +1,17 @@ +use crate::codec::var_ulong::VarULong; +use crate::serial::{PacketRead, PacketWrite}; +use pumpkin_macros::packet; + +pub const EMOTE_FLAG_SERVER_SIDE: u8 = 1 << 0; +pub const EMOTE_FLAG_MUTE_CHAT: u8 = 1 << 1; + +#[derive(PacketRead, PacketWrite, Debug)] +#[packet(138)] +pub struct SEmote { + pub runtime_entity_id: VarULong, + pub emote_id: String, + pub emote_length: u32, + pub xuid: String, + pub platform_id: String, + pub flags: u8, +} diff --git a/pumpkin-protocol/src/bedrock/server/emote_list.rs b/pumpkin-protocol/src/bedrock/server/emote_list.rs new file mode 100644 index 000000000..a51c4f202 --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/emote_list.rs @@ -0,0 +1,64 @@ +use std::io::{Error, Read, Write}; +use uuid::Uuid; + +use crate::{ + codec::{var_uint::VarUInt, var_ulong::VarULong}, + serial::{PacketRead, PacketWrite}, +}; +use pumpkin_macros::packet; + +#[derive(Debug)] +#[packet(152)] +pub struct SEmoteList { + pub runtime_entity_id: VarULong, + pub emote_pieces: Vec, +} + +impl PacketRead for SEmoteList { + fn read(reader: &mut R) -> Result { + let runtime_entity_id = VarULong::read(reader)?; + let len = VarUInt::read(reader)?.0 as usize; + let mut emote_pieces = Vec::with_capacity(len); + for _ in 0..len { + emote_pieces.push(Uuid::read(reader)?); + } + Ok(Self { + runtime_entity_id, + emote_pieces, + }) + } +} + +impl PacketWrite for SEmoteList { + fn write(&self, writer: &mut W) -> Result<(), Error> { + self.runtime_entity_id.write(writer)?; + VarUInt(self.emote_pieces.len() as u32).write(writer)?; + for piece in &self.emote_pieces { + piece.write(writer)?; + } + Ok(()) + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::io::Cursor; + + #[test] + fn emote_list_serialization() { + let packet = SEmoteList { + runtime_entity_id: VarULong(123), + emote_pieces: vec![Uuid::new_v4(), Uuid::new_v4()], + }; + + let mut buf = Vec::new(); + packet.write(&mut buf).unwrap(); + + let mut reader = Cursor::new(buf); + let decoded = SEmoteList::read(&mut reader).unwrap(); + + assert_eq!(packet.runtime_entity_id.0, decoded.runtime_entity_id.0); + assert_eq!(packet.emote_pieces, decoded.emote_pieces); + } +} diff --git a/pumpkin-protocol/src/bedrock/server/mod.rs b/pumpkin-protocol/src/bedrock/server/mod.rs index f74173988..f090dafe7 100644 --- a/pumpkin-protocol/src/bedrock/server/mod.rs +++ b/pumpkin-protocol/src/bedrock/server/mod.rs @@ -3,6 +3,8 @@ pub mod animate; pub mod client_cache_status; pub mod command_request; pub mod container_close; +pub mod emote; +pub mod emote_list; pub mod interaction; pub mod inventory_transaction; pub mod loading_screen; diff --git a/pumpkin-protocol/src/bedrock/server/text.rs b/pumpkin-protocol/src/bedrock/server/text.rs index 0c0eeb7b6..302668dcb 100644 --- a/pumpkin-protocol/src/bedrock/server/text.rs +++ b/pumpkin-protocol/src/bedrock/server/text.rs @@ -69,6 +69,20 @@ impl SText { } } + #[must_use] + pub const fn json(message: String) -> Self { + Self { + needs_translation: false, + r#type: TextPacketType::Json, + source_name: String::new(), + message, + parameters: Vec::new(), + xuid: String::new(), + platform_chat_id: String::new(), + filtered_message: None, + } + } + const fn get_category(&self) -> u8 { match self.r#type { TextPacketType::Raw diff --git a/pumpkin-util/src/text/mod.rs b/pumpkin-util/src/text/mod.rs index 9e6987f99..9b47bb8fe 100644 --- a/pumpkin-util/src/text/mod.rs +++ b/pumpkin-util/src/text/mod.rs @@ -150,6 +150,36 @@ impl TextComponentBase { text } + /// Converts this component into a raw Bedrock string, specifically for translation parameters. + /// Translations are emitted as `%translation.key` so Bedrock evaluates them natively. + #[must_use] + pub fn to_bedrock_string(&self) -> String { + let mut text = String::new(); + + match &*self.content { + TextContent::Text { text: t } => text.push_str(t), + TextContent::Translate { + translate, + bedrock_translate, + with: _, + } => { + let key = bedrock_translate.as_deref().unwrap_or(translate.as_ref()); + let _ = write!(text, "%{key}"); + } + TextContent::EntityNames { selector, .. } => text.push_str(selector), + TextContent::Keybind { keybind } => text.push_str(keybind), + TextContent::Custom { key, .. } => { + let _ = write!(text, "%{key}"); + } + } + + for child in &self.extra { + text.push_str(&child.to_bedrock_string()); + } + + text + } + #[must_use] pub fn to_bedrock_legacy(&self, locale: Locale) -> String { let mut text = String::new(); diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index aa9112b55..3783db8f3 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -674,6 +674,58 @@ impl Entity { self.world.store(world); } + pub fn bedrock_metadata(&self) -> EntityMetadata { + if self.bedrock_flags.load(Ordering::Relaxed) == 0 { + self.bedrock_flags.fetch_or( + (1i64 << entity_data_flag::HAS_GRAVITY) + | (1i64 << entity_data_flag::CLIMB) + | (1i64 << entity_data_flag::HAS_COLLISION) + | (1i64 << entity_data_flag::BREATHING), + Ordering::Relaxed, + ); + } + + let mut metadata = EntityMetadata::new(); + metadata.set( + entity_data_key::WIDTH, + MetadataValue::Float(self.entity_type.dimension[0]), + ); + metadata.set( + entity_data_key::HEIGHT, + MetadataValue::Float(self.entity_type.dimension[1]), + ); + metadata.set(entity_data_key::SCALE, MetadataValue::Float(1.0)); + metadata.set( + entity_data_key::FLAGS, + MetadataValue::Long(self.bedrock_flags.load(Ordering::Relaxed)), + ); + metadata.set( + entity_data_key::FLAGS_TWO, + MetadataValue::Long(self.bedrock_flags_two.load(Ordering::Relaxed)), + ); + + if let Some(name) = &**self.custom_name.load() { + metadata.set( + entity_data_key::NAME, + MetadataValue::String(name.clone().get_text()), + ); + if self.custom_name_visible.load(Ordering::Relaxed) { + metadata.set_flag( + entity_data_key::FLAGS, + entity_data_flag::SHOW_NAME as u8, + true, + ); + metadata.set_flag( + entity_data_key::FLAGS, + entity_data_flag::ALWAYS_SHOW_NAME as u8, + true, + ); + } + } + + metadata + } + /// Sets the entity's age in ticks. /// Negative values indicate that the entity is a baby. pub fn set_age(&self, age: i32) { diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index c2ff0fd9b..d5f4259c2 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -20,7 +20,6 @@ use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk; use pumpkin_protocol::bedrock::client::play_status::CPlayStatus; use pumpkin_protocol::bedrock::client::set_time::CSetTime; use pumpkin_protocol::bedrock::client::update_abilities::{Ability, CUpdateAbilities}; -use pumpkin_protocol::bedrock::frame_set::FrameSet; use pumpkin_protocol::bedrock::server::text::SText; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_util::translation::Locale; @@ -181,6 +180,11 @@ impl ChunkManager { &self.world } + #[must_use] + pub fn sent_chunks_count(&self) -> usize { + self.chunk_sent.len() + } + fn should_enqueue_chunk(&mut self, position: Vector2, chunk: &SyncChunk) -> bool { self.chunk_sent .insert(position, Arc::downgrade(chunk)) @@ -1805,10 +1809,10 @@ impl Player { } } - let chunk_of_chunks = { + let (chunk_of_chunks, total_sent_chunks) = { let mut chunk_manager = self.chunk_manager.lock().await; chunk_manager.pull_new_chunks(); - if let ClientPlatform::Java(_) = self.client { + let chunks = if let ClientPlatform::Java(_) = self.client { // Java clients can only send a limited amount of chunks per tick. // If we have sent too many chunks without receiving an ack, we stop sending chunks. chunk_manager @@ -1816,7 +1820,8 @@ impl Player { .then(|| chunk_manager.next_chunk()) } else { Some(chunk_manager.next_chunk()) - } + }; + (chunks, chunk_manager.sent_chunks_count()) }; if let Some(chunk_of_chunks) = chunk_of_chunks { @@ -1837,7 +1842,7 @@ impl Player { ClientPlatform::Bedrock(bedrock_client) => { for chunk in chunk_of_chunks { bedrock_client - .send_game_packet(&CLevelChunk { + .enqueue_packet(&CLevelChunk { dimension: 0, cache_enabled: false, chunk: &chunk, @@ -1845,13 +1850,10 @@ impl Player { .await; } - if !self.bedrock_spawned.load(Ordering::Relaxed) && chunk_count > 4 { - let mut frame_set = FrameSet::default(); - + if !self.bedrock_spawned.load(Ordering::Relaxed) && total_sent_chunks > 4 { bedrock_client - .write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set) + .enqueue_packet(&CPlayStatus::PlayerSpawn) .await; - bedrock_client.send_frame_set(frame_set, 0x84).await; self.bedrock_spawned.store(true, Ordering::Relaxed); } } @@ -2866,11 +2868,23 @@ impl Player { .await; } ClientPlatform::Bedrock(client) => { - client - .send_game_packet(&SText::system_message(text.clone().0.to_bedrock_legacy( - Locale::from_str(&self.config.load().locale).unwrap_or(Locale::EnUs), - ))) - .await; + let locale = Locale::from_str(&self.config.load().locale).unwrap_or(Locale::EnUs); + let packet = match &*text.0.content { + pumpkin_util::text::TextContent::Translate { + translate, + bedrock_translate, + with, + } => { + let key = bedrock_translate.as_deref().unwrap_or(translate.as_ref()); + let parameters = with + .iter() + .map(pumpkin_util::text::TextComponentBase::to_bedrock_string) + .collect(); + SText::translation(key.to_string(), parameters) + } + _ => SText::system_message(text.0.to_bedrock_legacy(locale)), + }; + client.enqueue_packet(&packet).await; } } } diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index f9abc4e67..4d55208f1 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -31,6 +31,7 @@ use pumpkin_protocol::{ client_cache_status::SClientCacheStatus, command_request::SCommandRequest, container_close::SContainerClose, + emote::SEmote, interaction::SInteraction, inventory_transaction::SInventoryTransaction, loading_screen::SLoadingScreen, @@ -59,9 +60,10 @@ use std::net::SocketAddr; use tokio::{ net::UdpSocket, sync::mpsc::{Receiver, Sender}, - sync::{Mutex, oneshot}, + sync::{Mutex, RwLock, oneshot}, task::JoinHandle, }; + use tokio_util::{sync::CancellationToken, task::TaskTracker}; pub mod connection; @@ -118,7 +120,7 @@ pub struct BedrockClient { outgoing_packet_priority_recv: Mutex>>, /// The packet encoder for outgoing packets. - network_writer: Arc>, + network_writer: Arc>, /// The packet decoder for incoming packets. network_reader: Mutex, @@ -154,9 +156,9 @@ impl BedrockClient { address: SocketAddr, be_clients: Arc>>>, ) -> Self { - let (send, recv) = tokio::sync::mpsc::channel(128); - let (priority_send, priority_recv) = tokio::sync::mpsc::channel(128); - let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(128); + let (send, recv) = tokio::sync::mpsc::channel(4096); + let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); + let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096); Self { socket, player: Mutex::new(None), @@ -164,7 +166,7 @@ impl BedrockClient { version: AtomicCell::new(BedrockMinecraftVersion::Unknown), client_data: ArcSwap::new(Arc::new(None)), be_clients, - network_writer: Arc::new(Mutex::new(UDPNetworkEncoder::new())), + network_writer: Arc::new(RwLock::new(UDPNetworkEncoder::new())), network_reader: Mutex::new(UDPNetworkDecoder::new()), tasks: TaskTracker::new(), outgoing_packet_queue_send: send, @@ -220,16 +222,19 @@ impl BedrockClient { let mut interval = tokio::time::interval(std::time::Duration::from_millis(100)); while !client.close_token.is_cancelled() { - let recv_result = tokio::select! { + let packet = tokio::select! { biased; - () = client.close_token.cancelled() => None, - res = priority_packet_receiver.recv() => res, + () = client.close_token.cancelled() => break, + res = priority_packet_receiver.recv() => match res { + Some(p) => p, + None => break, + }, _ = interval.tick() => { // Check for timeout (10 seconds) if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) { debug!("Bedrock client {} timed out", client.address); client.close().await; - return; + break; } // Flush ACKs @@ -244,31 +249,35 @@ impl BedrockClient { let now = std::time::Instant::now(); let mut resend = Vec::new(); { - let unacked = client.unacked_outgoing_frames.lock().await; - for (seq, (id, data, timestamp)) in unacked.iter() { + let mut unacked = client.unacked_outgoing_frames.lock().await; + for (seq, (id, data, timestamp)) in unacked.iter_mut() { if now.duration_since(*timestamp) > std::time::Duration::from_secs(1) { resend.push((*seq, *id, data.clone())); + // Update timestamp + *timestamp = now; + // Limit resends per tick to avoid starvation + if resend.len() >= 50 { + break; + } } } } - for (seq, id, data) in resend { - debug!("Resending reliable sequence {} (ID: {})", seq, id); - // Update timestamp - if let Some(entry) = client.unacked_outgoing_frames.lock().await.get_mut(&seq) { - entry.2 = now; - } - if let Err(err) = client.network_writer.lock().await.write_packet(&data, client.address, &client.socket).await { - warn!("Failed to resend packet for sequence {}: {}", seq, err); - } + if !resend.is_empty() { + let encoder = client.network_writer.read().await; + for (seq, id, data) in resend { + debug!("Resending reliable sequence {} (ID: {})", seq, id); + if let Err(err) = encoder.write_packet(&data, client.address, &client.socket).await { + warn!("Failed to resend packet for sequence {}: {}", seq, err); + } + } } continue; } - res = packet_receiver.recv() => res, - }; - - let Some(packet) = recv_result else { - break; + res = packet_receiver.recv() => match res { + Some(p) => p, + None => break, + }, }; client @@ -301,7 +310,7 @@ impl BedrockClient { .set_compression(compression.threshold as usize); self.network_writer - .lock() + .write() .await .set_compression((compression.threshold as usize, compression.level)); } @@ -329,7 +338,7 @@ impl BedrockClient { } { - let Ok(mut network_writer) = self.network_writer.try_lock() else { + let Ok(network_writer) = self.network_writer.try_read() else { debug!("Failed to lock network writer for try_enqueue_packet"); return; }; @@ -406,7 +415,7 @@ impl BedrockClient { let mut packet_payload = Vec::new(); packet.write_packet(&mut packet_payload)?; - let mut encoder = self.network_writer.lock().await; + let encoder = self.network_writer.read().await; encoder.write_game_packet( P::PACKET_ID as u16, SubClient::Main, @@ -500,7 +509,7 @@ impl BedrockClient { } for i in 0..count { - let end = if i + 1 == count { + let end = if i + 1 == count && !packet_buf.len().is_multiple_of(MTU) { packet_buf.len() % MTU } else { MTU @@ -558,7 +567,7 @@ impl BedrockClient { if let Err(err) = self .network_writer - .lock() + .read() .await .write_packet(&frame_set_buf, self.address, &self.socket) .await @@ -593,7 +602,7 @@ impl BedrockClient { if let Err(err) = self .network_writer - .lock() + .read() .await .write_packet(&packet_buf, self.address, &self.socket) .await @@ -639,17 +648,25 @@ impl BedrockClient { async fn handle_nack(&self, nack: &Acknowledge) { debug!("Received NACK for sequences: {:?}", nack.sequences); - let unacked = self.unacked_outgoing_frames.lock().await; - for seq in &nack.sequences { - if let Some((_id, data, _timestamp)) = unacked.get(seq) - && let Err(err) = self - .network_writer - .lock() - .await - .write_packet(data, self.address, &self.socket) - .await + let mut resend_data = Vec::new(); + { + let unacked = self.unacked_outgoing_frames.lock().await; + for seq in &nack.sequences { + if let Some((_id, data, _timestamp)) = unacked.get(seq) { + resend_data.push(data.clone()); + } + } + } + + for data in resend_data { + if let Err(err) = self + .network_writer + .read() + .await + .write_packet(&data, self.address, &self.socket) + .await { - warn!("Failed to resend packet for sequence {}: {}", seq, err); + warn!("Failed to resend packet from NACK: {}", err); } } } @@ -857,7 +874,7 @@ impl BedrockClient { match self.handle_login(packet, server).await { Ok(result) => return result, Err(err) => { - warn!("Bedrock login failed: {err}"); + self.kick(DisconnectReason::Unknown, err.to_string()).await; return PacketHandlerResult::Stop; } } @@ -941,6 +958,12 @@ impl BedrockClient { SAnimate::PACKET_ID => { self.handle_animate(player, server, &SAnimate::read(reader)?).await; } + SEmote::PACKET_ID => { + self.handle_emote(player, server, SEmote::read(reader)?).await; + } + // SEmoteList::PACKET_ID => { + // self.handle_emote_list(player, server, SEmoteList::read(reader)?); + // } pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => { self.handle_modal_form_response( player, diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index c3e97130e..9217fecb4 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -11,6 +11,7 @@ use pumpkin_protocol::{ animate::{AnimateAction, SAnimate}, command_request::SCommandRequest, container_close::SContainerClose, + emote::SEmote, interaction::{Action, SInteraction}, inventory_transaction::{SInventoryTransaction, TransactionData}, player_action::{Action as PlayerAction, SPlayerAction}, @@ -338,6 +339,41 @@ impl BedrockClient { } } + pub async fn handle_emote(&self, player: &Arc, _server: &Server, packet: SEmote) { + if !player.has_client_loaded() { + return; + } + + let entity = &player.living_entity.entity; + let world = entity.world.load(); + + let mut broadcast_packet = packet; + broadcast_packet.flags |= pumpkin_protocol::bedrock::server::emote::EMOTE_FLAG_SERVER_SIDE; + + world + .broadcast_packet_except_editioned( + &[player.gameprofile.id], + &CEntityAnimation::new( + VarInt(entity.entity_id), + Animation::SwingMainArm, // Fallback for Java? Or just ignore + ), + &broadcast_packet, + ) + .await; + } + + // pub fn handle_emote_list( + // &self, + // player: &Arc, + // _server: &Server, + // packet: &SEmoteList, + // ) { + // debug!( + // "Player {} sent emote list: {:?}", + // player.gameprofile.name, packet.emote_pieces + // ); + // } + pub async fn handle_inventory_action( &self, player: &Arc, diff --git a/pumpkin/src/net/java/mod.rs b/pumpkin/src/net/java/mod.rs index fcc9de3fb..4708b1b9b 100644 --- a/pumpkin/src/net/java/mod.rs +++ b/pumpkin/src/net/java/mod.rs @@ -142,8 +142,8 @@ impl JavaClient { #[must_use] pub fn new(tcp_stream: TcpStream, address: SocketAddr, id: u64) -> Self { let (read, write) = tcp_stream.into_split(); - let (send, recv) = tokio::sync::mpsc::channel(128); - let (priority_send, priority_recv) = tokio::sync::mpsc::channel(128); + let (send, recv) = tokio::sync::mpsc::channel(4096); + let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096); Self { id, gameprofile: Mutex::new(None), diff --git a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs index 7af16f9a2..e045d4c47 100644 --- a/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs +++ b/pumpkin/src/plugin/loader/wasm/wasm_host/wit/v0_1/world.rs @@ -305,7 +305,8 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState { let msg = self.get_text_provider(&message)?; self.get_world_res(&world)? .provider - .broadcast_system_message(&msg, overlay); + .broadcast_system_message(&msg, overlay) + .await; Ok(()) } diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index 27bbf57a3..1b55324a1 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -69,10 +69,7 @@ use pumpkin_data::{BlockDirection, BlockState, translation}; use pumpkin_inventory::crafting::recipe_provider::RecipeProvider; use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; -use pumpkin_protocol::bedrock::client::set_actor_data::{ - CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag, - entity_data_key, -}; +use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData}; use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData}; use pumpkin_protocol::bedrock::frame_set::FrameSet; use pumpkin_protocol::java::client::play::{ @@ -527,9 +524,32 @@ impl World { } } - pub fn broadcast_system_message(&self, message: &TextComponent, overlay: bool) { - let packet = CSystemChatMessage::new(message, overlay); - self.broadcast_packet_all(&packet); + pub async fn broadcast_system_message(&self, message: &TextComponent, overlay: bool) { + let je_packet = CSystemChatMessage::new(message, overlay); + let be_packet = Self::component_to_bedrock_text(message); + self.broadcast_editioned(&je_packet, &be_packet).await; + } + + fn component_to_bedrock_text(message: &TextComponent) -> SText { + match &*message.0.content { + pumpkin_util::text::TextContent::Translate { + translate, + bedrock_translate, + with, + } => { + let key = bedrock_translate.as_deref().unwrap_or(translate.as_ref()); + let parameters = with + .iter() + .map(pumpkin_util::text::TextComponentBase::to_bedrock_string) + .collect(); + SText::translation(key.to_string(), parameters) + } + _ => SText::system_message( + message + .0 + .to_bedrock_legacy(pumpkin_util::translation::Locale::EnUs), + ), + } } pub async fn broadcast_message( @@ -565,7 +585,7 @@ impl World { Self::broadcast_java_grouped(je_packet, je_recipients_by_version); for recipient in be_recipients { - recipient.send_game_packet(be_packet).await; + recipient.enqueue_packet(be_packet).await; } } @@ -1838,28 +1858,8 @@ impl World { abilities.set_for_gamemode(player.gamemode.load()); }; - let mut metadata = EntityMetadata::new(); - metadata.set(entity_data_key::WIDTH, MetadataValue::Float(0.6)); - metadata.set(entity_data_key::HEIGHT, MetadataValue::Float(1.8)); - - // This is super important, otherwise the client will float by default let entity = &player.living_entity.entity; - entity.bedrock_flags.fetch_or( - (1i64 << entity_data_flag::HAS_GRAVITY) - | (1i64 << entity_data_flag::CLIMB) - | (1i64 << entity_data_flag::HAS_COLLISION) - | (1i64 << entity_data_flag::BREATHING), - Ordering::Relaxed, - ); - - metadata.set( - entity_data_key::FLAGS, - MetadataValue::Long(entity.bedrock_flags.load(Ordering::Relaxed)), - ); - metadata.set( - entity_data_key::FLAGS_TWO, - MetadataValue::Long(entity.bedrock_flags_two.load(Ordering::Relaxed)), - ); + let metadata = entity.bedrock_metadata(); let actor_data = CSetActorData { actor_runtime_id: VarULong(runtime_id), @@ -2008,7 +2008,7 @@ impl World { GameMode::Adventure => 2, GameMode::Spectator => 6, }), - metadata: EntityMetadata::default(), + metadata: entity.bedrock_metadata(), properties: EntityProperties::default(), ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { entity_unique_id: runtime_id as i64, @@ -2088,7 +2088,7 @@ impl World { GameMode::Adventure => 2, GameMode::Spectator => 6, }), - metadata: EntityMetadata::default(), + metadata: ex_entity.bedrock_metadata(), properties: EntityProperties::default(), ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { entity_unique_id: existing_player.entity_id() as i64, @@ -2131,7 +2131,8 @@ impl World { let event = server.plugin_manager.fire(event).await; if !event.cancelled { - self.broadcast_system_message(&event.join_message, false); + self.broadcast_system_message(&event.join_message, false) + .await; info!("{}", event.join_message.to_pretty_console()); } } @@ -2385,7 +2386,7 @@ impl World { GameMode::Adventure => 2, GameMode::Spectator => 6, }), - metadata: EntityMetadata::default(), + metadata: player.get_entity().bedrock_metadata(), properties: EntityProperties::default(), ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { entity_unique_id: entity_id as i64, @@ -2493,7 +2494,7 @@ impl World { GameMode::Adventure => 2, GameMode::Spectator => 6, }), - metadata: EntityMetadata::default(), + metadata: entity.bedrock_metadata(), properties: EntityProperties::default(), ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { entity_unique_id: existing_player.entity_id() as i64, @@ -2722,7 +2723,8 @@ impl World { let event = server.plugin_manager.fire(event).await; if !event.cancelled { - self.broadcast_system_message(&event.join_message, false); + self.broadcast_system_message(&event.join_message, false) + .await; // TODO: Switch to structured logging, e.g. info!(player = %name, "connected") info!("{}", event.join_message.to_pretty_console()); }