fix: some bedrock fixes

also added join and leave translated messages
This commit is contained in:
Alexander Medvedev 2026-06-04 20:54:48 +02:00
parent a2f0122503
commit f19eee2d70
15 changed files with 357 additions and 101 deletions

View File

@ -106,11 +106,12 @@ impl Skin {
Self { Self {
skin_id: "Standard_Custom".to_string(), skin_id: "Standard_Custom".to_string(),
play_fab_id: String::new(), 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_width: 64,
image_height: 64, image_height: 64,
// 64 * 64 * 4 = 16384 bytes of raw RGBA data // 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(), animations: Vec::new(),
cape_width: 0, cape_width: 0,
cape_height: 0, cape_height: 0,

View File

@ -211,7 +211,7 @@ mod tests {
const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1; const PAYLOAD_LEN: usize = 2 * 1024 * 1024 + 1;
let payload = vec![0x2a; PAYLOAD_LEN]; let payload = vec![0x2a; PAYLOAD_LEN];
let mut wire_buf = Vec::new(); let mut wire_buf = Vec::new();
let mut network_encoder = UDPNetworkEncoder::new(); let network_encoder = UDPNetworkEncoder::new();
network_encoder network_encoder
.write_game_packet( .write_game_packet(
0x01, 0x01,

View File

@ -111,7 +111,7 @@ impl UDPNetworkEncoder {
} }
pub fn write_game_packet( pub fn write_game_packet(
&mut self, &self,
packet_id: u16, packet_id: u16,
sub_client_sender: SubClient, sub_client_sender: SubClient,
sub_client_target: SubClient, sub_client_target: SubClient,
@ -161,7 +161,7 @@ impl UDPNetworkEncoder {
} }
pub async fn write_packet( pub async fn write_packet(
&mut self, &self,
packet_data: &[u8], packet_data: &[u8],
addr: SocketAddr, addr: SocketAddr,
socket: &UdpSocket, socket: &UdpSocket,

View File

@ -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,
}

View File

@ -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<Uuid>,
}
impl PacketRead for SEmoteList {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
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<W: 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);
}
}

View File

@ -3,6 +3,8 @@ pub mod animate;
pub mod client_cache_status; pub mod client_cache_status;
pub mod command_request; pub mod command_request;
pub mod container_close; pub mod container_close;
pub mod emote;
pub mod emote_list;
pub mod interaction; pub mod interaction;
pub mod inventory_transaction; pub mod inventory_transaction;
pub mod loading_screen; pub mod loading_screen;

View File

@ -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 { const fn get_category(&self) -> u8 {
match self.r#type { match self.r#type {
TextPacketType::Raw TextPacketType::Raw

View File

@ -150,6 +150,36 @@ impl TextComponentBase {
text 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] #[must_use]
pub fn to_bedrock_legacy(&self, locale: Locale) -> String { pub fn to_bedrock_legacy(&self, locale: Locale) -> String {
let mut text = String::new(); let mut text = String::new();

View File

@ -674,6 +674,58 @@ impl Entity {
self.world.store(world); 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. /// Sets the entity's age in ticks.
/// Negative values indicate that the entity is a baby. /// Negative values indicate that the entity is a baby.
pub fn set_age(&self, age: i32) { pub fn set_age(&self, age: i32) {

View File

@ -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::play_status::CPlayStatus;
use pumpkin_protocol::bedrock::client::set_time::CSetTime; use pumpkin_protocol::bedrock::client::set_time::CSetTime;
use pumpkin_protocol::bedrock::client::update_abilities::{Ability, CUpdateAbilities}; 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::bedrock::server::text::SText;
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer; use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
use pumpkin_util::translation::Locale; use pumpkin_util::translation::Locale;
@ -181,6 +180,11 @@ impl ChunkManager {
&self.world &self.world
} }
#[must_use]
pub fn sent_chunks_count(&self) -> usize {
self.chunk_sent.len()
}
fn should_enqueue_chunk(&mut self, position: Vector2<i32>, chunk: &SyncChunk) -> bool { fn should_enqueue_chunk(&mut self, position: Vector2<i32>, chunk: &SyncChunk) -> bool {
self.chunk_sent self.chunk_sent
.insert(position, Arc::downgrade(chunk)) .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; let mut chunk_manager = self.chunk_manager.lock().await;
chunk_manager.pull_new_chunks(); 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. // 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. // If we have sent too many chunks without receiving an ack, we stop sending chunks.
chunk_manager chunk_manager
@ -1816,7 +1820,8 @@ impl Player {
.then(|| chunk_manager.next_chunk()) .then(|| chunk_manager.next_chunk())
} else { } else {
Some(chunk_manager.next_chunk()) Some(chunk_manager.next_chunk())
} };
(chunks, chunk_manager.sent_chunks_count())
}; };
if let Some(chunk_of_chunks) = chunk_of_chunks { if let Some(chunk_of_chunks) = chunk_of_chunks {
@ -1837,7 +1842,7 @@ impl Player {
ClientPlatform::Bedrock(bedrock_client) => { ClientPlatform::Bedrock(bedrock_client) => {
for chunk in chunk_of_chunks { for chunk in chunk_of_chunks {
bedrock_client bedrock_client
.send_game_packet(&CLevelChunk { .enqueue_packet(&CLevelChunk {
dimension: 0, dimension: 0,
cache_enabled: false, cache_enabled: false,
chunk: &chunk, chunk: &chunk,
@ -1845,13 +1850,10 @@ impl Player {
.await; .await;
} }
if !self.bedrock_spawned.load(Ordering::Relaxed) && chunk_count > 4 { if !self.bedrock_spawned.load(Ordering::Relaxed) && total_sent_chunks > 4 {
let mut frame_set = FrameSet::default();
bedrock_client bedrock_client
.write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set) .enqueue_packet(&CPlayStatus::PlayerSpawn)
.await; .await;
bedrock_client.send_frame_set(frame_set, 0x84).await;
self.bedrock_spawned.store(true, Ordering::Relaxed); self.bedrock_spawned.store(true, Ordering::Relaxed);
} }
} }
@ -2866,11 +2868,23 @@ impl Player {
.await; .await;
} }
ClientPlatform::Bedrock(client) => { ClientPlatform::Bedrock(client) => {
client let locale = Locale::from_str(&self.config.load().locale).unwrap_or(Locale::EnUs);
.send_game_packet(&SText::system_message(text.clone().0.to_bedrock_legacy( let packet = match &*text.0.content {
Locale::from_str(&self.config.load().locale).unwrap_or(Locale::EnUs), pumpkin_util::text::TextContent::Translate {
))) translate,
.await; 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;
} }
} }
} }

View File

@ -31,6 +31,7 @@ use pumpkin_protocol::{
client_cache_status::SClientCacheStatus, client_cache_status::SClientCacheStatus,
command_request::SCommandRequest, command_request::SCommandRequest,
container_close::SContainerClose, container_close::SContainerClose,
emote::SEmote,
interaction::SInteraction, interaction::SInteraction,
inventory_transaction::SInventoryTransaction, inventory_transaction::SInventoryTransaction,
loading_screen::SLoadingScreen, loading_screen::SLoadingScreen,
@ -59,9 +60,10 @@ use std::net::SocketAddr;
use tokio::{ use tokio::{
net::UdpSocket, net::UdpSocket,
sync::mpsc::{Receiver, Sender}, sync::mpsc::{Receiver, Sender},
sync::{Mutex, oneshot}, sync::{Mutex, RwLock, oneshot},
task::JoinHandle, task::JoinHandle,
}; };
use tokio_util::{sync::CancellationToken, task::TaskTracker}; use tokio_util::{sync::CancellationToken, task::TaskTracker};
pub mod connection; pub mod connection;
@ -118,7 +120,7 @@ pub struct BedrockClient {
outgoing_packet_priority_recv: Mutex<Option<Receiver<OutgoingPacket>>>, outgoing_packet_priority_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
/// The packet encoder for outgoing packets. /// The packet encoder for outgoing packets.
network_writer: Arc<Mutex<UDPNetworkEncoder>>, network_writer: Arc<RwLock<UDPNetworkEncoder>>,
/// The packet decoder for incoming packets. /// The packet decoder for incoming packets.
network_reader: Mutex<UDPNetworkDecoder>, network_reader: Mutex<UDPNetworkDecoder>,
@ -154,9 +156,9 @@ impl BedrockClient {
address: SocketAddr, address: SocketAddr,
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>, be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
) -> Self { ) -> Self {
let (send, recv) = tokio::sync::mpsc::channel(128); let (send, recv) = tokio::sync::mpsc::channel(4096);
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(128); let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(128); let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(4096);
Self { Self {
socket, socket,
player: Mutex::new(None), player: Mutex::new(None),
@ -164,7 +166,7 @@ impl BedrockClient {
version: AtomicCell::new(BedrockMinecraftVersion::Unknown), version: AtomicCell::new(BedrockMinecraftVersion::Unknown),
client_data: ArcSwap::new(Arc::new(None)), client_data: ArcSwap::new(Arc::new(None)),
be_clients, be_clients,
network_writer: Arc::new(Mutex::new(UDPNetworkEncoder::new())), network_writer: Arc::new(RwLock::new(UDPNetworkEncoder::new())),
network_reader: Mutex::new(UDPNetworkDecoder::new()), network_reader: Mutex::new(UDPNetworkDecoder::new()),
tasks: TaskTracker::new(), tasks: TaskTracker::new(),
outgoing_packet_queue_send: send, outgoing_packet_queue_send: send,
@ -220,16 +222,19 @@ impl BedrockClient {
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100)); let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
while !client.close_token.is_cancelled() { while !client.close_token.is_cancelled() {
let recv_result = tokio::select! { let packet = tokio::select! {
biased; biased;
() = client.close_token.cancelled() => None, () = client.close_token.cancelled() => break,
res = priority_packet_receiver.recv() => res, res = priority_packet_receiver.recv() => match res {
Some(p) => p,
None => break,
},
_ = interval.tick() => { _ = interval.tick() => {
// Check for timeout (10 seconds) // Check for timeout (10 seconds)
if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) { if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) {
debug!("Bedrock client {} timed out", client.address); debug!("Bedrock client {} timed out", client.address);
client.close().await; client.close().await;
return; break;
} }
// Flush ACKs // Flush ACKs
@ -244,31 +249,35 @@ impl BedrockClient {
let now = std::time::Instant::now(); let now = std::time::Instant::now();
let mut resend = Vec::new(); let mut resend = Vec::new();
{ {
let unacked = client.unacked_outgoing_frames.lock().await; let mut unacked = client.unacked_outgoing_frames.lock().await;
for (seq, (id, data, timestamp)) in unacked.iter() { for (seq, (id, data, timestamp)) in unacked.iter_mut() {
if now.duration_since(*timestamp) > std::time::Duration::from_secs(1) { if now.duration_since(*timestamp) > std::time::Duration::from_secs(1) {
resend.push((*seq, *id, data.clone())); 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 { if !resend.is_empty() {
debug!("Resending reliable sequence {} (ID: {})", seq, id); let encoder = client.network_writer.read().await;
// Update timestamp for (seq, id, data) in resend {
if let Some(entry) = client.unacked_outgoing_frames.lock().await.get_mut(&seq) { debug!("Resending reliable sequence {} (ID: {})", seq, id);
entry.2 = now; if let Err(err) = encoder.write_packet(&data, client.address, &client.socket).await {
} warn!("Failed to resend packet for sequence {}: {}", seq, err);
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); }
}
} }
continue; continue;
} }
res = packet_receiver.recv() => res, res = packet_receiver.recv() => match res {
}; Some(p) => p,
None => break,
let Some(packet) = recv_result else { },
break;
}; };
client client
@ -301,7 +310,7 @@ impl BedrockClient {
.set_compression(compression.threshold as usize); .set_compression(compression.threshold as usize);
self.network_writer self.network_writer
.lock() .write()
.await .await
.set_compression((compression.threshold as usize, compression.level)); .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"); debug!("Failed to lock network writer for try_enqueue_packet");
return; return;
}; };
@ -406,7 +415,7 @@ impl BedrockClient {
let mut packet_payload = Vec::new(); let mut packet_payload = Vec::new();
packet.write_packet(&mut packet_payload)?; 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( encoder.write_game_packet(
P::PACKET_ID as u16, P::PACKET_ID as u16,
SubClient::Main, SubClient::Main,
@ -500,7 +509,7 @@ impl BedrockClient {
} }
for i in 0..count { 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 packet_buf.len() % MTU
} else { } else {
MTU MTU
@ -558,7 +567,7 @@ impl BedrockClient {
if let Err(err) = self if let Err(err) = self
.network_writer .network_writer
.lock() .read()
.await .await
.write_packet(&frame_set_buf, self.address, &self.socket) .write_packet(&frame_set_buf, self.address, &self.socket)
.await .await
@ -593,7 +602,7 @@ impl BedrockClient {
if let Err(err) = self if let Err(err) = self
.network_writer .network_writer
.lock() .read()
.await .await
.write_packet(&packet_buf, self.address, &self.socket) .write_packet(&packet_buf, self.address, &self.socket)
.await .await
@ -639,17 +648,25 @@ impl BedrockClient {
async fn handle_nack(&self, nack: &Acknowledge) { async fn handle_nack(&self, nack: &Acknowledge) {
debug!("Received NACK for sequences: {:?}", nack.sequences); debug!("Received NACK for sequences: {:?}", nack.sequences);
let unacked = self.unacked_outgoing_frames.lock().await; let mut resend_data = Vec::new();
for seq in &nack.sequences { {
if let Some((_id, data, _timestamp)) = unacked.get(seq) let unacked = self.unacked_outgoing_frames.lock().await;
&& let Err(err) = self for seq in &nack.sequences {
.network_writer if let Some((_id, data, _timestamp)) = unacked.get(seq) {
.lock() resend_data.push(data.clone());
.await }
.write_packet(data, self.address, &self.socket) }
.await }
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 { match self.handle_login(packet, server).await {
Ok(result) => return result, Ok(result) => return result,
Err(err) => { Err(err) => {
warn!("Bedrock login failed: {err}"); self.kick(DisconnectReason::Unknown, err.to_string()).await;
return PacketHandlerResult::Stop; return PacketHandlerResult::Stop;
} }
} }
@ -941,6 +958,12 @@ impl BedrockClient {
SAnimate::PACKET_ID => { SAnimate::PACKET_ID => {
self.handle_animate(player, server, &SAnimate::read(reader)?).await; 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 => { pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => {
self.handle_modal_form_response( self.handle_modal_form_response(
player, player,

View File

@ -11,6 +11,7 @@ use pumpkin_protocol::{
animate::{AnimateAction, SAnimate}, animate::{AnimateAction, SAnimate},
command_request::SCommandRequest, command_request::SCommandRequest,
container_close::SContainerClose, container_close::SContainerClose,
emote::SEmote,
interaction::{Action, SInteraction}, interaction::{Action, SInteraction},
inventory_transaction::{SInventoryTransaction, TransactionData}, inventory_transaction::{SInventoryTransaction, TransactionData},
player_action::{Action as PlayerAction, SPlayerAction}, player_action::{Action as PlayerAction, SPlayerAction},
@ -338,6 +339,41 @@ impl BedrockClient {
} }
} }
pub async fn handle_emote(&self, player: &Arc<Player>, _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<Player>,
// _server: &Server,
// packet: &SEmoteList,
// ) {
// debug!(
// "Player {} sent emote list: {:?}",
// player.gameprofile.name, packet.emote_pieces
// );
// }
pub async fn handle_inventory_action( pub async fn handle_inventory_action(
&self, &self,
player: &Arc<Player>, player: &Arc<Player>,

View File

@ -142,8 +142,8 @@ impl JavaClient {
#[must_use] #[must_use]
pub fn new(tcp_stream: TcpStream, address: SocketAddr, id: u64) -> Self { pub fn new(tcp_stream: TcpStream, address: SocketAddr, id: u64) -> Self {
let (read, write) = tcp_stream.into_split(); let (read, write) = tcp_stream.into_split();
let (send, recv) = tokio::sync::mpsc::channel(128); let (send, recv) = tokio::sync::mpsc::channel(4096);
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(128); let (priority_send, priority_recv) = tokio::sync::mpsc::channel(4096);
Self { Self {
id, id,
gameprofile: Mutex::new(None), gameprofile: Mutex::new(None),

View File

@ -305,7 +305,8 @@ impl pumpkin::plugin::world::HostWorld for PluginHostState {
let msg = self.get_text_provider(&message)?; let msg = self.get_text_provider(&message)?;
self.get_world_res(&world)? self.get_world_res(&world)?
.provider .provider
.broadcast_system_message(&msg, overlay); .broadcast_system_message(&msg, overlay)
.await;
Ok(()) Ok(())
} }

View File

@ -69,10 +69,7 @@ use pumpkin_data::{BlockDirection, BlockState, translation};
use pumpkin_inventory::crafting::recipe_provider::RecipeProvider; use pumpkin_inventory::crafting::recipe_provider::RecipeProvider;
use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_inventory::screen_handler::InventoryPlayer;
use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed}; use pumpkin_nbt::{compound::NbtCompound, to_bytes_unnamed};
use pumpkin_protocol::bedrock::client::set_actor_data::{ use pumpkin_protocol::bedrock::client::set_actor_data::{CSetActorData, PropertySyncData};
CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag,
entity_data_key,
};
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData}; use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
use pumpkin_protocol::bedrock::frame_set::FrameSet; use pumpkin_protocol::bedrock::frame_set::FrameSet;
use pumpkin_protocol::java::client::play::{ use pumpkin_protocol::java::client::play::{
@ -527,9 +524,32 @@ impl World {
} }
} }
pub fn broadcast_system_message(&self, message: &TextComponent, overlay: bool) { pub async fn broadcast_system_message(&self, message: &TextComponent, overlay: bool) {
let packet = CSystemChatMessage::new(message, overlay); let je_packet = CSystemChatMessage::new(message, overlay);
self.broadcast_packet_all(&packet); 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( pub async fn broadcast_message(
@ -565,7 +585,7 @@ impl World {
Self::broadcast_java_grouped(je_packet, je_recipients_by_version); Self::broadcast_java_grouped(je_packet, je_recipients_by_version);
for recipient in be_recipients { 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()); 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; let entity = &player.living_entity.entity;
entity.bedrock_flags.fetch_or( let metadata = entity.bedrock_metadata();
(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 actor_data = CSetActorData { let actor_data = CSetActorData {
actor_runtime_id: VarULong(runtime_id), actor_runtime_id: VarULong(runtime_id),
@ -2008,7 +2008,7 @@ impl World {
GameMode::Adventure => 2, GameMode::Adventure => 2,
GameMode::Spectator => 6, GameMode::Spectator => 6,
}), }),
metadata: EntityMetadata::default(), metadata: entity.bedrock_metadata(),
properties: EntityProperties::default(), properties: EntityProperties::default(),
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
entity_unique_id: runtime_id as i64, entity_unique_id: runtime_id as i64,
@ -2088,7 +2088,7 @@ impl World {
GameMode::Adventure => 2, GameMode::Adventure => 2,
GameMode::Spectator => 6, GameMode::Spectator => 6,
}), }),
metadata: EntityMetadata::default(), metadata: ex_entity.bedrock_metadata(),
properties: EntityProperties::default(), properties: EntityProperties::default(),
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
entity_unique_id: existing_player.entity_id() as i64, entity_unique_id: existing_player.entity_id() as i64,
@ -2131,7 +2131,8 @@ impl World {
let event = server.plugin_manager.fire(event).await; let event = server.plugin_manager.fire(event).await;
if !event.cancelled { 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()); info!("{}", event.join_message.to_pretty_console());
} }
} }
@ -2385,7 +2386,7 @@ impl World {
GameMode::Adventure => 2, GameMode::Adventure => 2,
GameMode::Spectator => 6, GameMode::Spectator => 6,
}), }),
metadata: EntityMetadata::default(), metadata: player.get_entity().bedrock_metadata(),
properties: EntityProperties::default(), properties: EntityProperties::default(),
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
entity_unique_id: entity_id as i64, entity_unique_id: entity_id as i64,
@ -2493,7 +2494,7 @@ impl World {
GameMode::Adventure => 2, GameMode::Adventure => 2,
GameMode::Spectator => 6, GameMode::Spectator => 6,
}), }),
metadata: EntityMetadata::default(), metadata: entity.bedrock_metadata(),
properties: EntityProperties::default(), properties: EntityProperties::default(),
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData { ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
entity_unique_id: existing_player.entity_id() as i64, entity_unique_id: existing_player.entity_id() as i64,
@ -2722,7 +2723,8 @@ impl World {
let event = server.plugin_manager.fire(event).await; let event = server.plugin_manager.fire(event).await;
if !event.cancelled { 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") // TODO: Switch to structured logging, e.g. info!(player = %name, "connected")
info!("{}", event.join_message.to_pretty_console()); info!("{}", event.join_message.to_pretty_console());
} }