fix: bedrock edition and port it to 1.21.30

This commit is contained in:
Alexander Medvedev 2026-01-23 15:17:56 +01:00
parent 9c70f09360
commit d81acb0ab1
12 changed files with 235 additions and 193 deletions

View File

@ -29,7 +29,7 @@ impl PacketWrite for CreativeContent<'_> {
} }
#[repr(i32)] #[repr(i32)]
#[expect(unused)] #[allow(unused)]
enum CreativeCategory { enum CreativeCategory {
Construction = 1, Construction = 1,
Nature = 2, Nature = 2,

View File

@ -4,14 +4,14 @@ use pumpkin_macros::packet;
use pumpkin_util::math::{position::BlockPos, vector2::Vector2}; use pumpkin_util::math::{position::BlockPos, vector2::Vector2};
use crate::{ use crate::{
codec::{bedrock_block_pos::NetworkPos, var_int::VarInt, var_uint::VarUInt}, codec::{var_int::VarInt, var_uint::VarUInt},
serial::PacketWrite, serial::PacketWrite,
}; };
#[packet(121)] #[packet(121)]
pub struct CNetworkChunkPublisherUpdate { pub struct CNetworkChunkPublisherUpdate {
// https://mojang.github.io/bedrock-protocol-docs/html/NetworkChunkPublisherUpdatePacket.html // https://mojang.github.io/bedrock-protocol-docs/html/NetworkChunkPublisherUpdatePacket.html
pub pos_for_view: NetworkPos, pub pos_for_view: BlockPos,
// Is in blocks, not chunks! // Is in blocks, not chunks!
pub new_radius: VarUInt, pub new_radius: VarUInt,
// TODO // TODO
@ -21,8 +21,8 @@ pub struct CNetworkChunkPublisherUpdate {
impl CNetworkChunkPublisherUpdate { impl CNetworkChunkPublisherUpdate {
pub fn new(pos_for_view: BlockPos, new_radius: u32) -> Self { pub fn new(pos_for_view: BlockPos, new_radius: u32) -> Self {
Self { Self {
pos_for_view: NetworkPos(pos_for_view), pos_for_view,
new_radius: VarUInt(new_radius), new_radius: VarUInt(new_radius << 4),
server_build_chunk_list: Vec::new(), server_build_chunk_list: Vec::new(),
} }
} }
@ -30,7 +30,7 @@ impl CNetworkChunkPublisherUpdate {
impl PacketWrite for CNetworkChunkPublisherUpdate { impl PacketWrite for CNetworkChunkPublisherUpdate {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> { fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.pos_for_view.write_signed(writer)?; self.pos_for_view.write(writer)?;
self.new_radius.write(writer)?; self.new_radius.write(writer)?;
(self.server_build_chunk_list.len() as u32).write(writer)?; (self.server_build_chunk_list.len() as u32).write(writer)?;

View File

@ -6,10 +6,10 @@ use crate::{
#[derive(PacketWrite)] #[derive(PacketWrite)]
#[packet(7)] #[packet(7)]
/// https://mojang.github.io/bedrock-protocol-docs/html/ResourcePackStackPacket.html
pub struct CResourcePackStackPacket { pub struct CResourcePackStackPacket {
// https://mojang.github.io/bedrock-protocol-docs/html/ResourcePackStackPacket.html
resource_pack_required: bool, resource_pack_required: bool,
addons_list_size: VarUInt, texture_pack_list_size: VarUInt,
game_version: String, game_version: String,
experiments: Experiments, experiments: Experiments,
/// When connecting to an Editor world, include the vanilla editor packs in the stack /// When connecting to an Editor world, include the vanilla editor packs in the stack
@ -19,14 +19,14 @@ pub struct CResourcePackStackPacket {
impl CResourcePackStackPacket { impl CResourcePackStackPacket {
pub fn new( pub fn new(
resource_pack_required: bool, resource_pack_required: bool,
addons_list_size: VarUInt, texture_pack_list_size: VarUInt,
game_version: String, game_version: String,
experiments: Experiments, experiments: Experiments,
include_editor_packs: bool, include_editor_packs: bool,
) -> Self { ) -> Self {
Self { Self {
resource_pack_required, resource_pack_required,
addons_list_size, texture_pack_list_size,
game_version, game_version,
experiments, experiments,
include_editor_packs, include_editor_packs,

View File

@ -1,10 +1,9 @@
use std::io::{Error, Write};
use crate::serial::PacketWrite;
use pumpkin_macros::packet; use pumpkin_macros::packet;
use crate::serial::PacketWrite;
#[derive(PacketWrite)]
#[packet(6)] #[packet(6)]
/// https://mojang.github.io/bedrock-protocol-docs/html/ResourcePacksInfoPacket.html
pub struct CResourcePacksInfo { pub struct CResourcePacksInfo {
resource_pack_required: bool, resource_pack_required: bool,
has_addon_packs: bool, has_addon_packs: bool,
@ -12,36 +11,7 @@ pub struct CResourcePacksInfo {
is_vibrant_visuals_force_disabled: bool, is_vibrant_visuals_force_disabled: bool,
world_template_id: uuid::Uuid, world_template_id: uuid::Uuid,
world_template_version: String, world_template_version: String,
resource_packs_size: u16, resource_packs_size: u16, // TODO: Add more
resource_packs: Vec<ResourcePack>,
}
impl PacketWrite for CResourcePacksInfo {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.resource_pack_required.write(writer)?;
self.has_addon_packs.write(writer)?;
self.has_scripts.write(writer)?;
self.is_vibrant_visuals_force_disabled.write(writer)?;
self.world_template_id.write(writer)?;
self.world_template_version.write(writer)?;
self.resource_packs_size.write(writer)?;
self.resource_packs.write(writer)?;
Ok(())
}
}
#[derive(PacketWrite)]
pub struct ResourcePack {
pack_id: uuid::Uuid,
version: String,
size: u64,
content_key: String,
subpack_name: String,
content_identity: String,
has_scripts: bool,
is_addon_pack: bool,
is_raytracing_capable: bool,
cdn_url: String,
} }
impl CResourcePacksInfo { impl CResourcePacksInfo {
@ -52,7 +22,6 @@ impl CResourcePacksInfo {
is_vibrant_visuals_force_disabled: bool, is_vibrant_visuals_force_disabled: bool,
world_template_id: uuid::Uuid, world_template_id: uuid::Uuid,
world_template_version: String, world_template_version: String,
resource_packs: Vec<ResourcePack>,
) -> Self { ) -> Self {
Self { Self {
resource_pack_required, resource_pack_required,
@ -61,8 +30,8 @@ impl CResourcePacksInfo {
is_vibrant_visuals_force_disabled, is_vibrant_visuals_force_disabled,
world_template_id, world_template_id,
world_template_version, world_template_version,
resource_packs_size: resource_packs.len() as u16, // TODO
resource_packs, resource_packs_size: 0,
} }
} }
} }

View File

@ -34,7 +34,6 @@ pub struct CStartGame {
pub current_level_time: u64, pub current_level_time: u64,
pub enchantment_seed: VarInt, pub enchantment_seed: VarInt,
pub block_properties_size: VarUInt, pub block_properties_size: VarUInt,
//pub block_properties: [GG; 2], //pub block_properties: [GG; 2],
pub multiplayer_correlation_id: String, pub multiplayer_correlation_id: String,
@ -51,8 +50,7 @@ pub struct CStartGame {
pub enable_clientside_generation: bool, pub enable_clientside_generation: bool,
pub blocknetwork_ids_are_hashed: bool, pub blocknetwork_ids_are_hashed: bool,
// NetworkPermissions pub server_auth_sounds: bool,
pub disable_client_sounds: bool,
} }
#[derive(PacketWrite)] #[derive(PacketWrite)]
@ -115,7 +113,6 @@ pub struct LevelSettings {
pub new_nether: bool, pub new_nether: bool,
pub edu_shared_uri_button_name: String, pub edu_shared_uri_button_name: String,
pub edu_shared_uri_link_uri: String, pub edu_shared_uri_link_uri: String,
pub override_force_experimental_gameplay_has_value: bool, pub override_force_experimental_gameplay_has_value: bool,
pub chat_restriction_level: i8, pub chat_restriction_level: i8,
pub disable_player_interactions: bool, pub disable_player_interactions: bool,

View File

@ -134,7 +134,7 @@ impl UDPNetworkDecoder {
err => PacketDecodeError::MalformedLength(err.to_string()), err => PacketDecodeError::MalformedLength(err.to_string()),
})?; })?;
let packet_len = packet_len.0 as usize; let packet_len = packet_len.0 as u64;
let var_header = VarUInt::decode(&mut reader)?; let var_header = VarUInt::decode(&mut reader)?;
@ -157,7 +157,7 @@ impl UDPNetworkDecoder {
let gamepacket_id = (header & 0x3FF) as u16; // 0x3FF is 10 bits set to 1 let gamepacket_id = (header & 0x3FF) as u16; // 0x3FF is 10 bits set to 1
let payload = reader let payload = reader
.read_boxed_slice(packet_len - var_header.written_size()) .read_boxed_slice(packet_len as usize - var_header.written_size())
.map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?; .map_err(|err| PacketDecodeError::FailedDecompression(err.to_string()))?;
Ok(RawPacket { Ok(RawPacket {

View File

@ -1,97 +1,157 @@
use std::io::{Error, Read, Write};
use pumpkin_macros::packet;
use crate::{ use crate::{
codec::var_uint::VarUInt, codec::var_uint::VarUInt,
serial::{PacketRead, PacketWrite}, serial::{PacketRead, PacketWrite},
}; };
use pumpkin_macros::packet;
use std::io::{Error, ErrorKind, Read, Write};
#[derive(Debug)] #[derive(Debug)]
#[packet(9)] #[packet(9)]
pub struct SText { pub struct SText {
// https://mojang.github.io/bedrock-protocol-docs/html/TextPacket.html pub needs_translation: bool,
pub r#type: TextPacketType, pub r#type: TextPacketType,
pub localize: bool, pub source_name: String,
pub player_name: String,
pub message: String, pub message: String,
pub parameters: Vec<String>, pub parameters: Vec<String>,
pub sender_xuid: String, pub xuid: String,
pub platform_id: String, pub platform_chat_id: String,
pub filtered_message: String, pub filtered_message: Option<String>,
} }
impl SText { impl SText {
pub fn new(message: String, player_name: String) -> Self { pub fn new(message: String, source_name: String) -> Self {
Self { Self {
needs_translation: false,
r#type: TextPacketType::Chat, r#type: TextPacketType::Chat,
localize: false, source_name,
player_name, message: if message.is_empty() {
message, " ".to_string()
} else {
message
},
parameters: Vec::new(), parameters: Vec::new(),
sender_xuid: String::new(), xuid: String::new(),
platform_id: String::new(), platform_chat_id: String::new(),
filtered_message: String::new(), filtered_message: None,
} }
} }
pub fn system_message(message: String) -> Self { pub fn system_message(message: String) -> Self {
Self { Self {
r#type: TextPacketType::SystemMessage, needs_translation: false,
localize: false, r#type: TextPacketType::System,
player_name: String::new(), source_name: String::new(),
message, message: if message.is_empty() {
" ".to_string()
} else {
message
},
parameters: Vec::new(), parameters: Vec::new(),
sender_xuid: String::new(), xuid: String::new(),
platform_id: String::new(), platform_chat_id: String::new(),
filtered_message: String::new(), filtered_message: None,
}
}
fn get_dummy_strings(category: u8) -> &'static [&'static str] {
match category {
0 => &[
"raw",
"tip",
"systemMessage",
"textObjectWhisper",
"textObjectAnnouncement",
"textObject",
],
1 => &["chat", "whisper", "announcement"],
2 => &["translate", "popup", "jukeboxPopup"],
_ => &[],
}
}
fn get_category(&self) -> u8 {
match self.r#type {
TextPacketType::Raw
| TextPacketType::Tip
| TextPacketType::System
| TextPacketType::JsonWhisper
| TextPacketType::JsonAnnouncement
| TextPacketType::Json => 0, // CATEGORY_MESSAGE_ONLY
TextPacketType::Chat | TextPacketType::Whisper | TextPacketType::Announcement => 1, // CATEGORY_AUTHORED_MESSAGE
TextPacketType::Translation | TextPacketType::Popup | TextPacketType::JukeboxPopup => 2, // CATEGORY_MESSAGE_WITH_PARAMETERS
} }
} }
} }
impl PacketRead for SText { impl PacketRead for SText {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> { fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let r#type = TextPacketType::read(reader)?; let needs_translation = bool::read(reader)?;
let localize = bool::read(reader)?;
let mut player_name = String::new(); let category = u8::read(reader)?;
let message;
let expected_dummies = Self::get_dummy_strings(category);
for &expected in expected_dummies {
let actual = String::read(reader)?;
if actual != expected {
return Err(Error::new(
ErrorKind::InvalidData,
format!(
"Dummy string mismatch: expected {}, got {}",
expected, actual
),
));
}
}
let r#type = TextPacketType::read(reader)?;
let mut source_name = String::new();
#[expect(unused)]
let mut message = String::new();
let mut parameters = Vec::new(); let mut parameters = Vec::new();
match r#type { match r#type {
TextPacketType::Chat | TextPacketType::Whisper | TextPacketType::Announcement => {
source_name = String::read(reader)?;
message = String::read(reader)?;
}
TextPacketType::Raw TextPacketType::Raw
| TextPacketType::Tip | TextPacketType::Tip
| TextPacketType::SystemMessage | TextPacketType::System
| TextPacketType::TextObjectWhisper | TextPacketType::JsonWhisper
| TextPacketType::TextObject | TextPacketType::Json
| TextPacketType::TextObjectAnnouncement => { | TextPacketType::JsonAnnouncement => {
message = String::read(reader)?; message = String::read(reader)?;
} }
TextPacketType::Chat | TextPacketType::Whisper | TextPacketType::Announcement => { TextPacketType::Translation | TextPacketType::Popup | TextPacketType::JukeboxPopup => {
player_name = String::read(reader)?;
message = String::read(reader)?;
}
TextPacketType::Translate | TextPacketType::Popup | TextPacketType::JukeboxPopup => {
message = String::read(reader)?; message = String::read(reader)?;
let count = VarUInt::read(reader)?.0 as usize; let count = VarUInt::read(reader)?.0 as usize;
parameters = Vec::with_capacity(count);
for _ in 0..count { for _ in 0..count {
parameters.push(String::read(reader)?); parameters.push(String::read(reader)?);
} }
} }
} }
let sender_xuid = String::read(reader)?;
let platform_id = String::read(reader)?; let xuid = String::read(reader)?;
let filtered_message = String::read(reader)?; let platform_chat_id = String::read(reader)?;
let has_filtered = bool::read(reader)?;
let filtered_message = if has_filtered {
Some(String::read(reader)?)
} else {
None
};
Ok(Self { Ok(Self {
needs_translation,
r#type, r#type,
localize, source_name,
player_name,
message, message,
parameters, parameters,
sender_xuid, xuid,
platform_id, platform_chat_id,
filtered_message, filtered_message,
}) })
} }
@ -99,22 +159,31 @@ impl PacketRead for SText {
impl PacketWrite for SText { impl PacketWrite for SText {
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> { fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
self.needs_translation.write(writer)?;
let category = self.get_category();
category.write(writer)?;
for &dummy in Self::get_dummy_strings(category) {
dummy.to_string().write(writer)?;
}
self.r#type.write(writer)?; self.r#type.write(writer)?;
self.localize.write(writer)?;
match self.r#type { match self.r#type {
TextPacketType::Chat | TextPacketType::Whisper | TextPacketType::Announcement => {
self.source_name.write(writer)?;
self.message.write(writer)?;
}
TextPacketType::Raw TextPacketType::Raw
| TextPacketType::Tip | TextPacketType::Tip
| TextPacketType::SystemMessage | TextPacketType::System
| TextPacketType::TextObjectWhisper | TextPacketType::JsonWhisper
| TextPacketType::TextObject | TextPacketType::Json
| TextPacketType::TextObjectAnnouncement => { | TextPacketType::JsonAnnouncement => {
self.message.write(writer)?; self.message.write(writer)?;
} }
TextPacketType::Chat | TextPacketType::Whisper | TextPacketType::Announcement => { TextPacketType::Translation | TextPacketType::Popup | TextPacketType::JukeboxPopup => {
self.player_name.write(writer)?;
self.message.write(writer)?;
}
TextPacketType::Translate | TextPacketType::Popup | TextPacketType::JukeboxPopup => {
self.message.write(writer)?; self.message.write(writer)?;
VarUInt(self.parameters.len() as u32).write(writer)?; VarUInt(self.parameters.len() as u32).write(writer)?;
for param in &self.parameters { for param in &self.parameters {
@ -122,47 +191,36 @@ impl PacketWrite for SText {
} }
} }
} }
self.sender_xuid.write(writer)?;
self.platform_id.write(writer)?; self.xuid.write(writer)?;
self.filtered_message.write(writer) self.platform_chat_id.write(writer)?;
if let Some(msg) = &self.filtered_message {
true.write(writer)?;
msg.write(writer)?;
} else {
false.write(writer)?;
}
Ok(())
} }
} }
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy, PartialEq)]
#[repr(u8)] #[repr(u8)]
pub enum TextPacketType { pub enum TextPacketType {
Raw = 0, Raw = 0,
Chat = 1, Chat = 1,
Translate = 2, Translation = 2,
Popup = 3, Popup = 3,
JukeboxPopup = 4, JukeboxPopup = 4,
Tip = 5, Tip = 5,
SystemMessage = 6, System = 6,
Whisper = 7, Whisper = 7,
Announcement = 8, Announcement = 8,
TextObjectWhisper = 9, JsonWhisper = 9,
TextObject = 10, Json = 10,
TextObjectAnnouncement = 11, JsonAnnouncement = 11,
}
impl PacketRead for TextPacketType {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
Ok(match u8::read(reader)? {
0 => Self::Raw,
1 => Self::Chat,
2 => Self::Translate,
3 => Self::Popup,
4 => Self::JukeboxPopup,
5 => Self::Tip,
6 => Self::SystemMessage,
7 => Self::Whisper,
8 => Self::Announcement,
9 => Self::TextObjectWhisper,
10 => Self::TextObject,
11 => Self::TextObjectAnnouncement,
_ => return Err(Error::new(std::io::ErrorKind::InvalidData, "")),
})
}
} }
impl PacketWrite for TextPacketType { impl PacketWrite for TextPacketType {
@ -170,3 +228,23 @@ impl PacketWrite for TextPacketType {
(*self as u8).write(writer) (*self as u8).write(writer)
} }
} }
impl PacketRead for TextPacketType {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
match u8::read(reader)? {
0 => Ok(Self::Raw),
1 => Ok(Self::Chat),
2 => Ok(Self::Translation),
3 => Ok(Self::Popup),
4 => Ok(Self::JukeboxPopup),
5 => Ok(Self::Tip),
6 => Ok(Self::System),
7 => Ok(Self::Whisper),
8 => Ok(Self::Announcement),
9 => Ok(Self::JsonWhisper),
10 => Ok(Self::Json),
11 => Ok(Self::JsonAnnouncement),
_ => Err(Error::new(ErrorKind::InvalidData, "Unknown Text Type")),
}
}
}

View File

@ -24,7 +24,7 @@ pub type BlockStateId = u16;
pub const CURRENT_MC_VERSION: &str = "1.21.11"; pub const CURRENT_MC_VERSION: &str = "1.21.11";
pub const LOWEST_SUPPRORTED_PROTOCOL_VERSION: u32 = 773; pub const LOWEST_SUPPRORTED_PROTOCOL_VERSION: u32 = 773;
pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.32"; pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.130";
pub const CURRENT_BEDROCK_MC_PROTOCOL: u32 = 898; pub const CURRENT_BEDROCK_MC_PROTOCOL: u32 = 898;
#[macro_export] #[macro_export]

View File

@ -1785,18 +1785,7 @@ impl Player {
} }
pub async fn send_system_message(&self, text: &TextComponent) { pub async fn send_system_message(&self, text: &TextComponent) {
match &self.client { self.send_system_message_raw(text, false).await;
ClientPlatform::Java(client) => {
client
.enqueue_packet(&CSystemChatMessage::new(text, false))
.await;
}
ClientPlatform::Bedrock(client) => {
client
.send_game_packet(&SText::system_message(text.clone().get_text()))
.await;
}
}
} }
pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) { pub async fn send_system_message_raw(&self, text: &TextComponent, overlay: bool) {
@ -2508,13 +2497,6 @@ impl EntityBase for Player {
if self.abilities.lock().await.invulnerable && damage_type != DamageType::GENERIC_KILL { if self.abilities.lock().await.invulnerable && damage_type != DamageType::GENERIC_KILL {
return false; return false;
} }
let dyn_self = self
.living_entity
.entity
.world
.get_entity_by_id(self.living_entity.entity.entity_id)
.await
.expect("Entity not found in world");
let result = self let result = self
.living_entity .living_entity
.damage_with_context(caller, amount, damage_type, position, source, cause) .damage_with_context(caller, amount, damage_type, position, source, cause)
@ -2523,8 +2505,7 @@ impl EntityBase for Player {
let health = self.living_entity.health.load(); let health = self.living_entity.health.load();
if health <= 0.0 { if health <= 0.0 {
let death_message = let death_message =
LivingEntity::get_death_message(&*dyn_self, damage_type, source, cause) LivingEntity::get_death_message(caller, damage_type, source, cause).await;
.await;
self.handle_killed(death_message).await; self.handle_killed(death_message).await;
} }
} }

View File

@ -49,14 +49,12 @@ struct CertificateChainPayload {
impl BedrockClient { impl BedrockClient {
pub async fn handle_request_network_settings(&self, _packet: SRequestNetworkSettings) { pub async fn handle_request_network_settings(&self, _packet: SRequestNetworkSettings) {
log::debug!("Bedrock: Sending Network settings");
self.send_game_packet(&CNetworkSettings::new(0, 0, false, 0, 0.0)) self.send_game_packet(&CNetworkSettings::new(0, 0, false, 0, 0.0))
.await; .await;
self.set_compression(CompressionInfo::default()).await; self.set_compression(CompressionInfo::default()).await;
} }
pub async fn handle_login(self: &Arc<Self>, packet: SLogin, server: &Server) -> Option<()> { pub async fn handle_login(self: &Arc<Self>, packet: SLogin, server: &Server) -> Option<()> {
log::debug!("Bedrock: handle login");
match self.try_handle_login(packet, server).await { match self.try_handle_login(packet, server).await {
Ok(()) => Some(()), Ok(()) => Some(()),
Err(error) => { Err(error) => {
@ -103,15 +101,7 @@ impl BedrockClient {
self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set) self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set)
.await; .await;
self.write_game_packet_to_set( self.write_game_packet_to_set(
&CResourcePacksInfo::new( &CResourcePacksInfo::new(false, false, false, false, Uuid::default(), String::new()),
false,
false,
false,
true,
uuid::Uuid::default(),
String::new(),
Vec::new(),
),
&mut frame_set, &mut frame_set,
) )
.await; .await;

View File

@ -484,7 +484,6 @@ impl BedrockClient {
packet: RawPacket, packet: RawPacket,
) -> Result<(), Error> { ) -> Result<(), Error> {
let payload = &mut Cursor::new(&packet.payload); let payload = &mut Cursor::new(&packet.payload);
log::error!("packet id {}", packet.id);
match packet.id { match packet.id {
SRequestNetworkSettings::PACKET_ID => { SRequestNetworkSettings::PACKET_ID => {
self.handle_request_network_settings(SRequestNetworkSettings::read(payload)?) self.handle_request_network_settings(SRequestNetworkSettings::read(payload)?)

View File

@ -1,3 +1,4 @@
use std::num::NonZeroU32;
use std::pin::Pin; use std::pin::Pin;
use std::sync::Weak; use std::sync::Weak;
use std::sync::atomic::Ordering::Relaxed; use std::sync::atomic::Ordering::Relaxed;
@ -56,7 +57,10 @@ use pumpkin_data::{
use pumpkin_data::{BlockDirection, BlockState}; use pumpkin_data::{BlockDirection, BlockState};
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::chunk_radius_update::CChunkRadiusUpdate;
use pumpkin_protocol::bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate;
use pumpkin_protocol::bedrock::client::start_game::CStartGame; use pumpkin_protocol::bedrock::client::start_game::CStartGame;
use pumpkin_protocol::bedrock::frame_set::FrameSet;
use pumpkin_protocol::java::client::play::CPlayerSpawnPosition; use pumpkin_protocol::java::client::play::CPlayerSpawnPosition;
use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata}; use pumpkin_protocol::java::client::play::{CSetEntityMetadata, Metadata};
use pumpkin_protocol::ser::serializer::Serializer; use pumpkin_protocol::ser::serializer::Serializer;
@ -1161,7 +1165,7 @@ impl World {
self.dimension.min_y self.dimension.min_y
} }
#[expect(clippy::too_many_lines)] #[allow(clippy::too_many_lines)]
pub async fn spawn_bedrock_player( pub async fn spawn_bedrock_player(
&self, &self,
base_config: &BasicConfiguration, base_config: &BasicConfiguration,
@ -1249,15 +1253,15 @@ impl World {
entity_id: VarLong(runtime_id as _), entity_id: VarLong(runtime_id as _),
runtime_entity_id: VarULong(runtime_id), runtime_entity_id: VarULong(runtime_id),
player_gamemode: player.gamemode.load(), player_gamemode: player.gamemode.load(),
position: Vector3::new(0.0, 100.0, 0.0), position: Vector3::new(0.0, 200.0, 0.0),
pitch: 0.0, pitch: 0.0,
yaw: 0.0, yaw: 0.0,
level_settings, level_settings,
level_id: String::new(), level_id: String::new(),
level_name: "World".to_string(), level_name: "Pumpkin world".to_string(),
premium_world_template_id: String::new(), premium_world_template_id: String::new(),
is_trial: false, is_trial: false,
rewind_history_size: VarInt(0), rewind_history_size: VarInt(40),
server_authoritative_block_breaking: false, server_authoritative_block_breaking: false,
current_level_time: self.level_time.lock().await.world_age as _, current_level_time: self.level_time.lock().await.world_age as _,
enchantment_seed: VarInt(0), enchantment_seed: VarInt(0),
@ -1278,10 +1282,15 @@ impl World {
// TODO The client needs extra biome data for this // TODO The client needs extra biome data for this
enable_clientside_generation: false, enable_clientside_generation: false,
blocknetwork_ids_are_hashed: false, blocknetwork_ids_are_hashed: false,
disable_client_sounds: false, server_auth_sounds: false,
}) })
.await; .await;
// chunker::update_position(&player).await; client
.send_game_packet(&CChunkRadiusUpdate {
chunk_radius: VarInt(player.config.read().await.view_distance.get().into()),
})
.await;
chunker::update_position(&player).await;
client client
.send_game_packet(&CreativeContent { .send_game_packet(&CreativeContent {
groups: &[Group { groups: &[Group {
@ -1293,29 +1302,48 @@ impl World {
}) })
.await; .await;
let mut frame_set = FrameSet::default();
client client
.send_game_packet(&CUpdateAttributes { .write_game_packet_to_set(
runtime_id: VarULong(runtime_id), &CNetworkChunkPublisherUpdate::new(
attributes: vec![Attribute { BlockPos::new(0, 200, 0),
min_value: 0.0, NonZeroU32::from(player.config.read().await.view_distance).into(),
max_value: f32::MAX, ),
current_value: 0.1, &mut frame_set,
default_min_value: 0.0, )
default_max_value: f32::MAX,
default_value: 0.1,
name: "minecraft:movement".to_string(),
modifiers_list_size: VarUInt(0),
}],
player_tick: VarULong(0),
})
.await; .await;
player.send_abilities_update().await;
client
.write_game_packet_to_set(
&CUpdateAttributes {
runtime_id: VarULong(runtime_id),
attributes: vec![Attribute {
min_value: 0.0,
max_value: f32::MAX,
current_value: 0.1,
default_min_value: 0.0,
default_max_value: f32::MAX,
default_value: 0.1,
name: "minecraft:movement".to_string(),
modifiers_list_size: VarUInt(0),
}],
player_tick: VarULong(0),
},
&mut frame_set,
)
.await;
client
.write_game_packet_to_set(&CPlayStatus::PlayerSpawn, &mut frame_set)
.await;
client.send_frame_set(frame_set, 0x84).await;
{ {
let mut abilities = player.abilities.lock().await; let mut abilities = player.abilities.lock().await;
abilities.set_for_gamemode(player.gamemode.load()); abilities.set_for_gamemode(player.gamemode.load());
}; };
client.send_game_packet(&CPlayStatus::PlayerSpawn).await; player.send_abilities_update().await;
} }
#[expect(clippy::too_many_lines)] #[expect(clippy::too_many_lines)]