From 0bc7405f120a484593b09ec9187c9545b4178551 Mon Sep 17 00:00:00 2001 From: unschlagbar <153451111+unschlagbar@users.noreply.github.com> Date: Sat, 30 Aug 2025 09:27:03 +0200 Subject: [PATCH] fix world y offset & bedrock update (#1121) --- assets/bedrock_block_states.nbt | Bin 2088511 -> 2091687 bytes pumpkin-data/build/block.rs | 3 +- .../src/bedrock/client/correct_player_move.rs | 15 ++ .../src/bedrock/client/level_chunk.rs | 11 +- pumpkin-protocol/src/bedrock/client/mod.rs | 3 + .../src/bedrock/client/set_actor_motion.rs | 12 ++ .../src/bedrock/client/start_game.rs | 21 ++- .../src/bedrock/client/update_abilities.rs | 50 +++++ pumpkin-protocol/src/bedrock/frame_set.rs | 23 +++ pumpkin-protocol/src/bedrock/mod.rs | 1 + .../src/bedrock/server/command_request.rs | 19 ++ .../src/bedrock/server/interaction.rs | 21 ++- .../src/bedrock/server/loading_screen.rs | 19 ++ pumpkin-protocol/src/bedrock/server/mod.rs | 2 + .../src/bedrock/server/player_auth_input.rs | 18 +- .../bedrock/server/request_chunk_radius.rs | 1 + pumpkin-protocol/src/bedrock/server/text.rs | 13 ++ pumpkin-protocol/src/codec/bitset.rs | 54 ++++++ pumpkin-protocol/src/codec/mod.rs | 1 + pumpkin-protocol/src/codec/var_ulong.rs | 2 +- pumpkin-protocol/src/serial/deserializer.rs | 37 +++- pumpkin-protocol/src/serial/serializer.rs | 31 ++- pumpkin-util/src/math/mod.rs | 4 +- pumpkin-util/src/math/position.rs | 25 ++- pumpkin-world/src/chunk/format/linear.rs | 6 + pumpkin-world/src/chunk/format/mod.rs | 73 +++----- pumpkin-world/src/chunk/mod.rs | 15 +- pumpkin-world/src/chunk/palette.rs | 27 ++- .../src/cylindrical_chunk_iterator.rs | 14 +- pumpkin-world/src/generation/generator/mod.rs | 20 +- pumpkin-world/src/generation/proto_chunk.rs | 9 +- pumpkin-world/src/lib.rs | 2 +- pumpkin/src/block/blocks/signs.rs | 9 +- pumpkin/src/entity/mod.rs | 4 +- pumpkin/src/entity/player.rs | 149 +++++++++++---- pumpkin/src/net/bedrock/login.rs | 53 +++--- pumpkin/src/net/bedrock/mod.rs | 167 ++++++++--------- pumpkin/src/net/bedrock/play.rs | 176 +++++++++++++++--- pumpkin/src/net/bedrock/unconnected.rs | 2 +- pumpkin/src/server/mod.rs | 2 +- pumpkin/src/world/chunker.rs | 43 +---- pumpkin/src/world/mod.rs | 128 ++++++------- pumpkin/src/world/natural_spawner.rs | 14 +- pumpkin/src/world/time.rs | 2 +- 44 files changed, 882 insertions(+), 419 deletions(-) create mode 100644 pumpkin-protocol/src/bedrock/client/correct_player_move.rs create mode 100644 pumpkin-protocol/src/bedrock/client/set_actor_motion.rs create mode 100644 pumpkin-protocol/src/bedrock/client/update_abilities.rs create mode 100644 pumpkin-protocol/src/bedrock/server/command_request.rs create mode 100644 pumpkin-protocol/src/bedrock/server/loading_screen.rs create mode 100644 pumpkin-protocol/src/codec/bitset.rs diff --git a/assets/bedrock_block_states.nbt b/assets/bedrock_block_states.nbt index ce674cc5cb3c992684545b702ee497d60d27c16b..46350120065d81d42ac20714e510defa4899411a 100644 GIT binary patch delta 894 zcmZvaKS&!<9LF)KiS50Y+%x8n%^7byXv}5{LLtGyJc@%9+C>n>EsL2P>|ja>H%KsW zke{WCoAjuXDUX1ybO>~)B7#}EWb5D(2-2Y-{&E-0J>u{gzTfwK@AtiT@9cE<=z6za zQ6Ohi1rEQlK<#gc!Nxl}2c;{jjrwUg{1AJ@=Z{3KTznBa`Ia%dI0>TvBN)q16Z_W=r@GKk@__M*>I?nLwV3 zmU#BP3VN0hI4Sr8?!f9!%z)#9KiHSfu%YB~*qH>Kmp;X;a79!&DSw4}KFXl;JFE`- z%iiEVr5qoL{GkS@J;I^*lc}(_sl?&(P9<>pj~GyD%r1Fk^%s2$#ar@Tbb4g(h0_Qw z4jA=R=(l|E1{EZa%iA!570m4vMM6jzQN?zOmG`Wb*94Z&L!sgMlI0#RSzJ=?#dSaW z6O{w1N}z^>#sBd*0dM;PUdHVUc$xOEo?h@thNqo#Hxm#u z12GE_vjQ<25VHd@2M}{^=iJS;cf#}yMoiq(t!=~vr~7Q-({3;J<=$TG%j3Cny1+jk zKqx zSjXf8=ZvPW{{z&z;RjFjv<-Y-A*wmYZ^wsucH za2e>(1)Bxc+iN!q0HY<>hJDIwB0jB0ww(#9}}!zFq5xM8D~{bEauKfqRdI(+ diff --git a/pumpkin-data/build/block.rs b/pumpkin-data/build/block.rs index 12500d606..5353f0029 100644 --- a/pumpkin-data/build/block.rs +++ b/pumpkin-data/build/block.rs @@ -841,8 +841,7 @@ pub(crate) fn build() -> TokenStream { let start_id = id as u16 + i as u16; block_state_to_bedrock.push((state.id, start_id)) } else { - let start_id = id as u16 + state_count as u16 - 1; - block_state_to_bedrock.push((state.id, start_id)) + block_state_to_bedrock.push((state.id, id as u16)) } } //else { diff --git a/pumpkin-protocol/src/bedrock/client/correct_player_move.rs b/pumpkin-protocol/src/bedrock/client/correct_player_move.rs new file mode 100644 index 000000000..452b4f9bf --- /dev/null +++ b/pumpkin-protocol/src/bedrock/client/correct_player_move.rs @@ -0,0 +1,15 @@ +use pumpkin_macros::packet; +use pumpkin_util::math::vector3::Vector3; + +use crate::{codec::var_ulong::VarULong, serial::PacketWrite}; + +#[derive(PacketWrite)] +#[packet(161)] +pub struct CCorrectPlayerMove { + // https://mojang.github.io/bedrock-protocol-docs/html/CorrectPlayerMovePredictionPacket.html + pub prediction_type: u8, + pub pos: Vector3, + pub pos_delta: Vector3, + pub on_ground: bool, + pub tick: VarULong, +} diff --git a/pumpkin-protocol/src/bedrock/client/level_chunk.rs b/pumpkin-protocol/src/bedrock/client/level_chunk.rs index f941de319..7b4a46eb6 100644 --- a/pumpkin-protocol/src/bedrock/client/level_chunk.rs +++ b/pumpkin-protocol/src/bedrock/client/level_chunk.rs @@ -27,22 +27,26 @@ impl<'a> PacketWrite for CLevelChunk<'a> { VarInt(self.dimension).write(writer)?; let sub_chunk_count = self.chunk.section.sections.len() as u32; + assert_eq!(sub_chunk_count, 24); VarUInt(sub_chunk_count).write(writer)?; self.cache_enabled.write(writer)?; let mut chunk_data = Vec::new(); let data_write = &mut chunk_data; + let min_y = (self.chunk.section.min_y >> 4) as i8; + // Blocks for (i, sub_chunk) in self.chunk.section.sections.iter().enumerate() { // Version 9 // [version:byte][num_storages:byte][sub_chunk_index:byte][block storage1]...[blockStorageN] + let y = i as i8 + min_y; let num_storages = 1; - data_write.write_all(&[VERSION, num_storages, ((i as i8) - 4) as u8])?; + data_write.write_all(&[VERSION, num_storages, y as _])?; let network_repr = sub_chunk.block_states.convert_be_network(); (network_repr.bits_per_entry << 1 | 1).write(data_write)?; - for data in network_repr.packed_data.iter() { + for data in network_repr.packed_data { data.write(data_write)?; } @@ -63,7 +67,8 @@ impl<'a> PacketWrite for CLevelChunk<'a> { // Biomes for i in 0..sub_chunk_count { let num_storages = 1; - data_write.write_all(&[VERSION, num_storages, ((i as i8) - 4) as u8])?; + let y = i as i8 + min_y; + data_write.write_all(&[VERSION, num_storages, y as _])?; for _ in 0..num_storages { 1u8.write(data_write)?; diff --git a/pumpkin-protocol/src/bedrock/client/mod.rs b/pumpkin-protocol/src/bedrock/client/mod.rs index 79a08d0e9..6f6547f3c 100644 --- a/pumpkin-protocol/src/bedrock/client/mod.rs +++ b/pumpkin-protocol/src/bedrock/client/mod.rs @@ -1,5 +1,6 @@ pub mod chunk_radius_update; pub mod container_open; +pub mod correct_player_move; pub mod creative_content; pub mod disconnect_player; pub mod gamerules_changed; @@ -14,7 +15,9 @@ pub mod player_hotbar; pub mod raknet; pub mod resource_pack_stack; pub mod resource_packs_info; +pub mod set_actor_motion; pub mod set_player_gamemode; pub mod set_time; pub mod start_game; +pub mod update_abilities; pub mod update_artributes; diff --git a/pumpkin-protocol/src/bedrock/client/set_actor_motion.rs b/pumpkin-protocol/src/bedrock/client/set_actor_motion.rs new file mode 100644 index 000000000..eed5ec727 --- /dev/null +++ b/pumpkin-protocol/src/bedrock/client/set_actor_motion.rs @@ -0,0 +1,12 @@ +use pumpkin_macros::packet; +use pumpkin_util::math::vector3::Vector3; + +use crate::{codec::var_ulong::VarULong, serial::PacketWrite}; + +#[derive(PacketWrite)] +#[packet(40)] +pub struct CSetActorMotion { + pub target_runtime_id: VarULong, + pub motion: Vector3, + pub tick: VarULong, +} diff --git a/pumpkin-protocol/src/bedrock/client/start_game.rs b/pumpkin-protocol/src/bedrock/client/start_game.rs index 3abe0ecc8..5b81d208a 100644 --- a/pumpkin-protocol/src/bedrock/client/start_game.rs +++ b/pumpkin-protocol/src/bedrock/client/start_game.rs @@ -1,3 +1,5 @@ +use std::io::{Error, Write}; + use crate::{ bedrock::client::gamerules_changed::GameRules, codec::{ @@ -7,7 +9,7 @@ use crate::{ serial::PacketWrite, }; use pumpkin_macros::packet; -use pumpkin_util::math::vector3::Vector3; +use pumpkin_util::{GameMode, math::vector3::Vector3}; use uuid::Uuid; #[derive(PacketWrite)] @@ -16,7 +18,7 @@ pub struct CStartGame { // https://mojang.github.io/bedrock-protocol-docs/html/StartGamePacket.html pub entity_id: VarLong, pub runtime_entity_id: VarULong, - pub player_gamemode: VarInt, + pub player_gamemode: GameMode, pub position: Vector3, pub pitch: f32, pub yaw: f32, @@ -48,6 +50,7 @@ pub struct CStartGame { pub enable_clientside_generation: bool, pub blocknetwork_ids_are_hashed: bool, + pub tick_death_system_enabled: bool, pub server_auth_sounds: bool, } @@ -64,7 +67,7 @@ pub struct LevelSettings { // Level Settings pub generator_type: VarInt, - pub world_gamemode: VarInt, + pub world_gamemode: GameMode, pub hardcore: bool, pub difficulty: VarInt, pub spawn_position: NetworkPos, @@ -81,8 +84,8 @@ pub struct LevelSettings { pub has_confirmed_platform_locked_content: bool, pub was_multiplayer_intended: bool, pub was_lan_broadcasting_intended: bool, - pub xbox_live_broadcast_setting: VarInt, - pub platform_broadcast_setting: VarInt, + pub xbox_live_broadcast_setting: GamePublishSetting, + pub platform_broadcast_setting: GamePublishSetting, pub commands_enabled: bool, pub is_texture_packs_required: bool, @@ -127,7 +130,7 @@ pub struct Experiments { pub experiments_ever_toggled: bool, } -#[repr(i32)] +#[derive(Clone, Copy)] pub enum GamePublishSetting { NoMultiPlay = 0, InviteOnly = 1, @@ -136,6 +139,12 @@ pub enum GamePublishSetting { Public = 4, } +impl PacketWrite for GamePublishSetting { + fn write(&self, writer: &mut W) -> Result<(), Error> { + VarInt(*self as i32).write(writer) + } +} + #[derive(PacketWrite)] pub struct GG { pub name: String, diff --git a/pumpkin-protocol/src/bedrock/client/update_abilities.rs b/pumpkin-protocol/src/bedrock/client/update_abilities.rs new file mode 100644 index 000000000..10e3e4116 --- /dev/null +++ b/pumpkin-protocol/src/bedrock/client/update_abilities.rs @@ -0,0 +1,50 @@ +use pumpkin_macros::packet; + +use crate::serial::PacketWrite; + +#[packet(187)] +#[derive(PacketWrite)] +pub struct CUpdateAbilities { + // https://mojang.github.io/bedrock-protocol-docs/html/UpdateAbilitiesPacket.html + // https://mojang.github.io/bedrock-protocol-docs/html/SerializedAbilitiesData.html + pub target_player_raw_id: i64, + pub player_permission: u8, + pub command_permission: u8, + pub layers: Vec, +} + +#[derive(PacketWrite)] +pub struct AbilityLayer { + // https://mojang.github.io/bedrock-protocol-docs/html/SerializedAbilitiesData__SerializedLayer.html + pub serialized_layer: u16, + pub abilities_set: u32, + pub ability_value: u32, + pub fly_speed: f32, + pub vertical_fly_speed: f32, + pub walk_speed: f32, +} + +#[repr(u32)] +pub enum Ability { + Build = 0, + Mine = 1, + DoorsAndSwitches = 2, + OpenContainers = 3, + AttackPlayers = 4, + AttackMobs = 5, + OperatorCommands = 6, + Teleport = 7, + Invulnerable = 8, + Flying = 9, + MayFly = 10, + Instabuild = 11, + Lightning = 12, + FlySpeed = 13, + WalkSpeed = 14, + Muted = 15, + WorldBuilder = 16, + NoClip = 17, + PrivilegedBuilder = 18, + VerticalFlySpeed = 19, + AbilityCount = 20, +} diff --git a/pumpkin-protocol/src/bedrock/frame_set.rs b/pumpkin-protocol/src/bedrock/frame_set.rs index 95c0c6cd9..e64ee0170 100644 --- a/pumpkin-protocol/src/bedrock/frame_set.rs +++ b/pumpkin-protocol/src/bedrock/frame_set.rs @@ -27,6 +27,15 @@ impl FrameSet { } } +impl Default for FrameSet { + fn default() -> Self { + Self { + sequence: u24(0), + frames: Vec::default(), + } + } +} + #[derive(Default)] pub struct Frame { pub reliability: RakReliability, @@ -42,6 +51,20 @@ pub struct Frame { } impl Frame { + pub fn new_unreliable(payload: Vec) -> Self { + Self { + reliability: RakReliability::Unreliable, + payload, + reliable_number: 0, + sequence_index: 0, + order_index: 0, + order_channel: 0, + split_size: 0, + split_id: 0, + split_index: 0, + } + } + pub fn read(reader: &mut R) -> Result, Error> { let mut frames = Vec::new(); diff --git a/pumpkin-protocol/src/bedrock/mod.rs b/pumpkin-protocol/src/bedrock/mod.rs index 9b54e585f..bc0ac30dc 100644 --- a/pumpkin-protocol/src/bedrock/mod.rs +++ b/pumpkin-protocol/src/bedrock/mod.rs @@ -7,6 +7,7 @@ pub mod packet_encoder; pub mod server; pub const UDP_HEADER_SIZE: u16 = 28; +pub const MTU: usize = 1400; pub const RAKNET_MAGIC: [u8; 16] = [ 0x00, 0xff, 0xff, 0x0, 0xfe, 0xfe, 0xfe, 0xfe, 0xfd, 0xfd, 0xfd, 0xfd, 0x12, 0x34, 0x56, 0x78, diff --git a/pumpkin-protocol/src/bedrock/server/command_request.rs b/pumpkin-protocol/src/bedrock/server/command_request.rs new file mode 100644 index 000000000..00456324c --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/command_request.rs @@ -0,0 +1,19 @@ +use pumpkin_macros::packet; +use uuid::Uuid; + +use crate::{ + codec::{var_int::VarInt, var_uint::VarUInt}, + serial::PacketRead, +}; + +#[derive(Debug, PacketRead)] +#[packet(77)] +pub struct SCommandRequest { + // https://mojang.github.io/bedrock-protocol-docs/html/CommandRequestPacket.html + pub command: String, + pub command_type: VarUInt, + pub command_uuid: Uuid, + pub request_id: String, + pub is_internal_source: bool, + pub version: VarInt, +} diff --git a/pumpkin-protocol/src/bedrock/server/interaction.rs b/pumpkin-protocol/src/bedrock/server/interaction.rs index 4456355e3..062d4815e 100644 --- a/pumpkin-protocol/src/bedrock/server/interaction.rs +++ b/pumpkin-protocol/src/bedrock/server/interaction.rs @@ -1,15 +1,34 @@ use std::io::{Error, Read}; use pumpkin_macros::packet; +use pumpkin_util::math::vector3::Vector3; use crate::{codec::var_ulong::VarULong, serial::PacketRead}; -#[derive(Debug, PacketRead)] +#[derive(Debug)] #[packet(33)] pub struct SInteraction { // https://mojang.github.io/bedrock-protocol-docs/html/InteractPacket.html pub action: Action, pub target_runtime_id: VarULong, + pub position: Vector3, +} + +impl PacketRead for SInteraction { + fn read(reader: &mut R) -> Result { + let action = Action::read(reader)?; + let target_runtime_id = VarULong::read(reader)?; + let mut position = Vector3::default(); + + if matches!(action, Action::InteractUpdate | Action::StopRiding) { + position = Vector3::read(reader)?; + } + Ok(Self { + action, + target_runtime_id, + position, + }) + } } #[derive(Debug)] diff --git a/pumpkin-protocol/src/bedrock/server/loading_screen.rs b/pumpkin-protocol/src/bedrock/server/loading_screen.rs new file mode 100644 index 000000000..db2b810ea --- /dev/null +++ b/pumpkin-protocol/src/bedrock/server/loading_screen.rs @@ -0,0 +1,19 @@ +use pumpkin_macros::packet; + +use crate::{codec::var_int::VarInt, serial::PacketRead}; + +#[derive(PacketRead)] +#[packet(312)] +pub struct SLoadingScreen { + // https://mojang.github.io/bedrock-protocol-docs/html/ServerboundLoadingScreenPacket.html + // Loading Screen Packet Type + // 0: Inavil, 1: Start, 2: End + status: VarInt, + _id: Option, +} + +impl SLoadingScreen { + pub fn is_loading_done(&self) -> bool { + self.status.0 == 2 + } +} diff --git a/pumpkin-protocol/src/bedrock/server/mod.rs b/pumpkin-protocol/src/bedrock/server/mod.rs index b136f01cf..a50a94549 100644 --- a/pumpkin-protocol/src/bedrock/server/mod.rs +++ b/pumpkin-protocol/src/bedrock/server/mod.rs @@ -1,6 +1,8 @@ pub mod client_cache_status; +pub mod command_request; pub mod container_close; pub mod interaction; +pub mod loading_screen; pub mod login; pub mod player_auth_input; pub mod raknet; diff --git a/pumpkin-protocol/src/bedrock/server/player_auth_input.rs b/pumpkin-protocol/src/bedrock/server/player_auth_input.rs index 8e0a0f1f6..1d30d839c 100644 --- a/pumpkin-protocol/src/bedrock/server/player_auth_input.rs +++ b/pumpkin-protocol/src/bedrock/server/player_auth_input.rs @@ -2,7 +2,7 @@ use pumpkin_macros::packet; use pumpkin_util::math::{vector2::Vector2, vector3::Vector3}; use crate::{ - codec::{var_uint::VarUInt, var_ulong::VarULong}, + codec::{bitset::Bitset, var_uint::VarUInt, var_ulong::VarULong}, serial::PacketRead, }; @@ -15,12 +15,7 @@ pub struct SPlayerAuthInput { pub position: Vector3, pub move_vec: Vector2, pub head_rotation: f32, - pub bit1: u8, - pub bit2: u8, - pub bit3: u8, - pub bit4: u8, - pub bit5: u8, - pub bit6: u8, + pub input_data: Bitset<65>, pub input_mode: VarUInt, pub play_mode: VarUInt, pub new_interaction_model: VarUInt, @@ -29,9 +24,10 @@ pub struct SPlayerAuthInput { pub pos_delta: Vector3, pub analog_move: Vector2, pub camera_orientation: Vector3, - //pub raw_move_vec: Vector2, + pub raw_move_vec: Vector2, } +#[derive(Clone, Copy)] pub enum InputData { // https://mojang.github.io/bedrock-protocol-docs/html/enums.html#PlayerAuthInputPacket::InputData Ascend = 0, @@ -101,6 +97,12 @@ pub enum InputData { InputNum = 65, } +impl From for usize { + fn from(value: InputData) -> Self { + value as Self + } +} + pub enum InputMode { Undefined = 0, Mouse = 1, diff --git a/pumpkin-protocol/src/bedrock/server/request_chunk_radius.rs b/pumpkin-protocol/src/bedrock/server/request_chunk_radius.rs index 733e0d264..d2c084c30 100644 --- a/pumpkin-protocol/src/bedrock/server/request_chunk_radius.rs +++ b/pumpkin-protocol/src/bedrock/server/request_chunk_radius.rs @@ -5,6 +5,7 @@ use crate::{codec::var_int::VarInt, serial::PacketRead}; #[derive(PacketRead, Debug)] #[packet(69)] pub struct SRequestChunkRadius { + // https://mojang.github.io/bedrock-protocol-docs/html/RequestChunkRadiusPacket.html pub chunk_radius: VarInt, pub max_radius: u8, } diff --git a/pumpkin-protocol/src/bedrock/server/text.rs b/pumpkin-protocol/src/bedrock/server/text.rs index 5e53b4445..fcb2ab456 100644 --- a/pumpkin-protocol/src/bedrock/server/text.rs +++ b/pumpkin-protocol/src/bedrock/server/text.rs @@ -34,6 +34,19 @@ impl SText { filtered_message: String::new(), } } + + pub fn system_message(message: String) -> Self { + Self { + r#type: TextPacketType::SystemMessage, + localize: false, + player_name: String::new(), + message, + parameters: Vec::new(), + sender_xuid: String::new(), + platform_id: String::new(), + filtered_message: String::new(), + } + } } impl PacketRead for SText { diff --git a/pumpkin-protocol/src/codec/bitset.rs b/pumpkin-protocol/src/codec/bitset.rs new file mode 100644 index 000000000..71dba33bd --- /dev/null +++ b/pumpkin-protocol/src/codec/bitset.rs @@ -0,0 +1,54 @@ +use std::io::{Error, ErrorKind, Read}; + +use crate::serial::PacketRead; + +#[derive(Debug)] +pub struct Bitset { + pub bits: u128, +} + +impl Bitset { + pub fn get>(&self, index: T) -> bool { + let index: usize = index.into(); + if index > N { + panic!("") + } + (self.bits & (1 << index)) != 0 + } + + pub fn set>(&mut self, index: T, value: bool) { + let index: usize = index.into(); + if index > N { + panic!("") + } + if value { + self.bits |= 1 << index; + } else { + self.bits &= !(1 << index); + } + } +} + +impl Default for Bitset { + fn default() -> Self { + if N > 80 { + panic!() + } + Self { bits: 0 } + } +} + +impl PacketRead for Bitset { + fn read(reader: &mut R) -> Result { + let mut bitset = Bitset::::default(); + + for i in 0..N.div_ceil(8) { + let byte = u8::read(reader)?; + bitset.bits |= (u128::from(byte) & 0x7F) << (i * 7); + if byte & 0x80 == 0 { + return Ok(bitset); + } + } + Err(Error::new(ErrorKind::InvalidData, "")) + } +} diff --git a/pumpkin-protocol/src/codec/mod.rs b/pumpkin-protocol/src/codec/mod.rs index d33ee903f..6fb9eb9a6 100644 --- a/pumpkin-protocol/src/codec/mod.rs +++ b/pumpkin-protocol/src/codec/mod.rs @@ -1,5 +1,6 @@ pub mod bedrock_block_pos; pub mod bit_set; +pub mod bitset; pub mod data_component; pub mod item_stack_seralizer; pub mod little_endian; diff --git a/pumpkin-protocol/src/codec/var_ulong.rs b/pumpkin-protocol/src/codec/var_ulong.rs index 008167769..db04df9f0 100644 --- a/pumpkin-protocol/src/codec/var_ulong.rs +++ b/pumpkin-protocol/src/codec/var_ulong.rs @@ -20,7 +20,7 @@ pub type VarULongType = u64; /** * A variable-length long type used by the Minecraft network protocol. */ -#[derive(Debug, Clone, PartialEq, Eq)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub struct VarULong(pub VarULongType); impl VarULong { diff --git a/pumpkin-protocol/src/serial/deserializer.rs b/pumpkin-protocol/src/serial/deserializer.rs index 74893b9c9..c087334e9 100644 --- a/pumpkin-protocol/src/serial/deserializer.rs +++ b/pumpkin-protocol/src/serial/deserializer.rs @@ -4,6 +4,7 @@ use std::{ }; use pumpkin_util::math::{vector2::Vector2, vector3::Vector3}; +use uuid::Uuid; use crate::{codec::var_uint::VarUInt, serial::PacketRead}; @@ -175,23 +176,45 @@ impl PacketRead for SocketAddr { fn read(reader: &mut R) -> Result { match u8::read(reader)? { 4 => { - let mut ip = [0; 4]; - reader.read_exact(&mut ip)?; + let ip = u32::read_be(reader)?; let port = u16::read_be(reader)?; Ok(SocketAddr::V4(SocketAddrV4::new(Ipv4Addr::from(ip), port))) } 6 => { + // Addr family + u16::read(reader)?; + let port = u16::read_be(reader)?; + let flowinfo = u32::read_be(reader)?; + let mut ip = [0; 16]; reader.read_exact(&mut ip)?; - let port = u16::read_be(reader)?; + let ip = Ipv6Addr::from(ip); + + let scope_id = u32::read_be(reader)?; + Ok(SocketAddr::V6(SocketAddrV6::new( - Ipv6Addr::from(ip), - port, - 0, // flowinfo - 0, // scope_id + ip, port, flowinfo, scope_id, ))) } _ => Err(Error::other("Invalid socket address version")), } } } + +impl PacketRead for Uuid { + fn read(reader: &mut R) -> Result { + let mut bytes = [0; 16]; + reader.read_exact(&mut bytes)?; + Ok(Uuid::from_bytes(bytes)) + } +} + +impl PacketRead for Option { + fn read(reader: &mut R) -> Result { + Ok(if bool::read(reader)? { + Some(T::read(reader)?) + } else { + None + }) + } +} diff --git a/pumpkin-protocol/src/serial/serializer.rs b/pumpkin-protocol/src/serial/serializer.rs index 47bc0706a..c88e6becd 100644 --- a/pumpkin-protocol/src/serial/serializer.rs +++ b/pumpkin-protocol/src/serial/serializer.rs @@ -3,7 +3,10 @@ use std::{ net::SocketAddr, }; -use pumpkin_util::math::{position::BlockPos, vector3::Vector3}; +use pumpkin_util::{ + GameMode, + math::{position::BlockPos, vector3::Vector3}, +}; use crate::{ codec::{var_int::VarInt, var_uint::VarUInt}, @@ -152,16 +155,34 @@ impl PacketWrite for Option { impl PacketWrite for SocketAddr { fn write(&self, writer: &mut W) -> Result<(), Error> { match self { + // version, addr, port SocketAddr::V4(addr) => { - writer.write_all(&[4])?; + 4u8.write(writer)?; writer.write_all(&addr.ip().octets())?; + addr.port().write_be(writer) } + // version, addr_family, port, flow_info, addr, scope_id SocketAddr::V6(addr) => { - writer.write_all(&[6])?; + 6u8.write(writer)?; + 10u16.write(writer)?; + addr.port().write_be(writer)?; + addr.flowinfo().write_be(writer)?; writer.write_all(&addr.ip().octets())?; + addr.scope_id().write_be(writer) } - }; + } + } +} - writer.write_all(&self.port().to_be_bytes()) +impl PacketWrite for GameMode { + fn write(&self, writer: &mut W) -> Result<(), Error> { + VarInt(match self { + Self::Survival => 0, + Self::Creative => 1, + Self::Adventure => 2, + // I have no idea why + Self::Spectator => 6, + }) + .write(writer) } } diff --git a/pumpkin-util/src/math/mod.rs b/pumpkin-util/src/math/mod.rs index 9cc41fc0f..6d1b7fa25 100644 --- a/pumpkin-util/src/math/mod.rs +++ b/pumpkin-util/src/math/mod.rs @@ -119,9 +119,9 @@ pub fn lerp_progress(value: T, start: T, end: T) -> T { } pub fn clamped_lerp(start: f64, end: f64, delta: f64) -> f64 { - if delta < 0f64 { + if delta < 0.0 { start - } else if delta > 1f64 { + } else if delta > 1.0 { end } else { lerp(delta, start, end) diff --git a/pumpkin-util/src/math/position.rs b/pumpkin-util/src/math/position.rs index 1cc8e4c07..f44d9bffa 100644 --- a/pumpkin-util/src/math/position.rs +++ b/pumpkin-util/src/math/position.rs @@ -228,19 +228,18 @@ impl BlockPos { pub fn chunk_and_chunk_relative_position(&self) -> (Vector2, Vector3) { let (z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); let (x_chunk, x_rem) = self.0.x.div_rem_euclid(&16); - let chunk_coordinate = Vector2 { - x: x_chunk, - y: z_chunk, - }; - - // Since we divide by 16, remnant can never exceed u8 - let relative = Vector3 { - x: x_rem, - z: z_rem, - - y: self.0.y, - }; - (chunk_coordinate, relative) + ( + Vector2 { + x: x_chunk, + y: z_chunk, + }, + // Since we divide by 16, remnant can never exceed u8 + Vector3 { + x: x_rem, + z: z_rem, + y: self.0.y, + }, + ) } pub fn section_relative_position(&self) -> Vector3 { let (_z_chunk, z_rem) = self.0.z.div_rem_euclid(&16); diff --git a/pumpkin-world/src/chunk/format/linear.rs b/pumpkin-world/src/chunk/format/linear.rs index 68d90c3a2..b200c02ac 100644 --- a/pumpkin-world/src/chunk/format/linear.rs +++ b/pumpkin-world/src/chunk/format/linear.rs @@ -464,6 +464,8 @@ mod tests { } } + let section_count = chunks[0].1.read().await.section.sections.len(); + for i in 0..5 { println!("Iteration {}", i + 1); // Mark the chunks as dirty so we save them again @@ -509,6 +511,10 @@ mod tests { let chunk = chunk.read().await; for read_chunk in read_chunks.iter() { let read_chunk = read_chunk.read().await; + + // Before this commit the chunks got an extra section after saving and reading + // so we prevent that from happening in the future :) + assert_eq!(read_chunk.section.sections.len(), section_count); if read_chunk.position == chunk.position { let original = chunk.section.dump_blocks(); let read = read_chunk.section.dump_blocks(); diff --git a/pumpkin-world/src/chunk/format/mod.rs b/pumpkin-world/src/chunk/format/mod.rs index 387307024..0807bbbf1 100644 --- a/pumpkin-world/src/chunk/format/mod.rs +++ b/pumpkin-world/src/chunk/format/mod.rs @@ -134,37 +134,31 @@ impl ChunkData { } let light_engine = ChunkLight { - block_light: (0..chunk_data.sections.len() + 2) - .map(|index| { - chunk_data - .sections - .iter() - .find(|section| { - section.y as i32 == index as i32 + chunk_data.min_y_section - 1 - }) - .and_then(|section| section.block_light.clone()) + block_light: chunk_data + .sections + .iter() + .map(|x| { + x.block_light + .clone() .map(LightContainer::new) .unwrap_or_default() }) .collect(), - sky_light: (0..chunk_data.sections.len() + 2) - .map(|index| { - chunk_data - .sections - .iter() - .find(|section| { - section.y as i32 == index as i32 + chunk_data.min_y_section - 1 - }) - .and_then(|section| section.sky_light.clone()) + sky_light: chunk_data + .sections + .iter() + .map(|x| { + x.sky_light + .clone() .map(LightContainer::new) .unwrap_or_default() }) .collect(), }; + let sub_chunks = chunk_data .sections .into_iter() - .filter(|section| section.y >= chunk_data.min_y_section as i8) .map(|section| SubChunk { block_states: section .block_states @@ -202,30 +196,23 @@ impl ChunkData { } async fn internal_to_bytes(&self) -> Result { - let sections: Vec<_> = (0..self.section.sections.len() + 2) - .map(|i| { - let has_blocks = i >= 1 && i - 1 < self.section.sections.len(); - let section = has_blocks.then(|| &self.section.sections[i - 1]); - - ChunkSectionNBT { - y: (i as i8) - 1i8 + section_coords::block_to_section(self.section.min_y) as i8, - block_states: section.map(|section| section.block_states.to_disk_nbt()), - biomes: section.map(|section| section.biomes.to_disk_nbt()), - block_light: match self.light_engine.block_light[i].clone() { - LightContainer::Empty(_) => None, - LightContainer::Full(data) => Some(data), - }, - sky_light: match self.light_engine.sky_light[i].clone() { - LightContainer::Empty(_) => None, - LightContainer::Full(data) => Some(data), - }, - } - }) - .filter(|nbt| { - nbt.block_states.is_some() - || nbt.biomes.is_some() - || nbt.block_light.is_some() - || nbt.sky_light.is_some() + let sections: Vec<_> = self + .section + .sections + .iter() + .enumerate() + .map(|(i, section)| ChunkSectionNBT { + y: (i as i8) + section_coords::block_to_section(self.section.min_y) as i8, + block_states: Some(section.block_states.to_disk_nbt()), + biomes: Some(section.biomes.to_disk_nbt()), + block_light: match self.light_engine.block_light[i].clone() { + LightContainer::Empty(_) => None, + LightContainer::Full(data) => Some(data), + }, + sky_light: match self.light_engine.sky_light[i].clone() { + LightContainer::Empty(_) => None, + LightContainer::Full(data) => Some(data), + }, }) .collect(); diff --git a/pumpkin-world/src/chunk/mod.rs b/pumpkin-world/src/chunk/mod.rs index 25c77b4af..aec846653 100644 --- a/pumpkin-world/src/chunk/mod.rs +++ b/pumpkin-world/src/chunk/mod.rs @@ -547,15 +547,12 @@ impl ChunkData { } pub fn get_highest_non_empty_subchunk(&self) -> usize { - let idx = self - .section - .sections - .iter() - .rev() - .position(|sub_chunk| sub_chunk.block_states.non_air_block_count() != 0) - .map(|idx| (self.section.sections.len() - idx).saturating_sub(1)); - - idx.unwrap_or_default() + for (i, sub_chunk) in self.section.sections.iter().enumerate().rev() { + if sub_chunk.block_states.non_air_block_count() != 0 { + return i; + } + } + 0 } } diff --git a/pumpkin-world/src/chunk/palette.rs b/pumpkin-world/src/chunk/palette.rs index 57ceee133..d69e48857 100644 --- a/pumpkin-world/src/chunk/palette.rs +++ b/pumpkin-world/src/chunk/palette.rs @@ -427,18 +427,25 @@ impl BlockPalette { let mut current_word: u32 = 0; let mut current_index_in_word = 0; - for key in data.cube.as_flattened().as_flattened().iter() { - let key_index = key_to_index_map.get(key).unwrap(); - debug_assert!((1 << bits_per_entry) > *key_index); + for x in 0..16 { + for y in 0..16 { + for z in 0..16 { + // Java has it in y, z, x order, so we need to convert it back to x, y, z + // Please test your code on bedrock before merging + let key = data.get(x, z, y); + let key_index = key_to_index_map.get(&key).unwrap(); + debug_assert!((1 << bits_per_entry) > *key_index); - current_word |= - (*key_index as u32) << (bits_per_entry as u32 * current_index_in_word); - current_index_in_word += 1; + current_word |= (*key_index as u32) + << (bits_per_entry as u32 * current_index_in_word); + current_index_in_word += 1; - if current_index_in_word == blocks_per_word as u32 { - packed_data.push(current_word); - current_word = 0; - current_index_in_word = 0; + if current_index_in_word == blocks_per_word as u32 { + packed_data.push(current_word); + current_word = 0; + current_index_in_word = 0; + } + } } } diff --git a/pumpkin-world/src/cylindrical_chunk_iterator.rs b/pumpkin-world/src/cylindrical_chunk_iterator.rs index 66fd6def6..2d209f1a0 100644 --- a/pumpkin-world/src/cylindrical_chunk_iterator.rs +++ b/pumpkin-world/src/cylindrical_chunk_iterator.rs @@ -19,20 +19,20 @@ impl Cylindrical { pub fn for_each_changed_chunk( old_cylindrical: Cylindrical, new_cylindrical: Cylindrical, - mut newly_included: impl FnMut(Vector2), - mut just_removed: impl FnMut(Vector2), + newly_included: &mut Vec>, + just_removed: &mut Vec>, ) { for new_cylindrical_chunk in new_cylindrical.all_chunks_within() { if !old_cylindrical.is_within_distance(new_cylindrical_chunk.x, new_cylindrical_chunk.y) { - newly_included(new_cylindrical_chunk); + newly_included.push(new_cylindrical_chunk); } } for old_cylindrical_chunk in old_cylindrical.all_chunks_within() { if !new_cylindrical.is_within_distance(old_cylindrical_chunk.x, old_cylindrical_chunk.y) { - just_removed(old_cylindrical_chunk); + just_removed.push(old_cylindrical_chunk); } } } @@ -54,6 +54,9 @@ impl Cylindrical { } pub fn is_within_distance(&self, x: i32, z: i32) -> bool { + if self.view_distance.get() == 1 { + return false; + } let rel_x = ((x - self.center.x).abs() as i64 - 2).max(0); let rel_z = ((z - self.center.y).abs() as i64 - 2).max(0); @@ -64,6 +67,9 @@ impl Cylindrical { /// Returns an iterator of all chunks within this cylinder pub fn all_chunks_within(&self) -> Vec> { + if self.view_distance.get() == 1 { + return Vec::new(); + } // I came up with this values by testing // for view distances 2-32 it usually gives 5 - 20 chunks more than needed if the player is on ground // this looks scary but this few calculations are definitely faster than ~5 reallocations diff --git a/pumpkin-world/src/generation/generator/mod.rs b/pumpkin-world/src/generation/generator/mod.rs index 7ae4c6c9c..a0f1b761e 100644 --- a/pumpkin-world/src/generation/generator/mod.rs +++ b/pumpkin-world/src/generation/generator/mod.rs @@ -13,6 +13,7 @@ use super::{ }; use crate::chunk::format::LightContainer; use crate::generation::proto_chunk::TerrainCache; +use crate::generation::section_coords; use crate::level::Level; use crate::world::BlockRegistryExt; use crate::{chunk::ChunkLight, dimension::Dimension}; @@ -83,11 +84,7 @@ impl WorldGenerator for VanillaGenerator { ) -> ChunkData { let generation_settings = gen_settings_from_dimension(&self.dimension); - let height: usize = match self.dimension { - Dimension::Overworld => 384, - Dimension::Nether | Dimension::End => 256, - }; - let sub_chunks = height / BlockPalette::SIZE; + let sub_chunks = generation_settings.shape.height as usize / BlockPalette::SIZE; let sections = (0..sub_chunks).map(|_| SubChunk::default()).collect(); let mut sections = ChunkSections::new(sections, generation_settings.shape.min_y as i32); @@ -122,26 +119,25 @@ impl WorldGenerator for VanillaGenerator { } } for y in 0..generation_settings.shape.height { - let relative_y = (y as i32 - sections.min_y) as usize; - let section_index = relative_y / BlockPalette::SIZE; + let relative_y = y as usize; + let section_index = section_coords::block_to_section(relative_y); let relative_y = relative_y % BlockPalette::SIZE; if let Some(section) = sections.sections.get_mut(section_index) { for z in 0..BlockPalette::SIZE { for x in 0..BlockPalette::SIZE { - let absolute_y = generation_settings.shape.min_y as i32 + y as i32; let block = proto_chunk - .get_block_state(&Vector3::new(x as i32, absolute_y, z as i32)); - section.block_states.set(x, relative_y, z, block.0); + .get_block_state_raw(&Vector3::new(x as i32, y as i32, z as i32)); + section.block_states.set(x, relative_y, z, block); } } } } ChunkData { light_engine: ChunkLight { - sky_light: (0..sections.sections.len() + 2) + sky_light: (0..sections.sections.len()) .map(|_| LightContainer::new_filled(15)) .collect(), - block_light: (0..sections.sections.len() + 2) + block_light: (0..sections.sections.len()) .map(|_| LightContainer::new_empty(15)) .collect(), }, diff --git a/pumpkin-world/src/generation/proto_chunk.rs b/pumpkin-world/src/generation/proto_chunk.rs index aa8122fa4..237f7fc05 100644 --- a/pumpkin-world/src/generation/proto_chunk.rs +++ b/pumpkin-world/src/generation/proto_chunk.rs @@ -378,6 +378,12 @@ impl<'a> ProtoChunk<'a> { state.is_air() } + #[inline] + pub fn get_block_state_raw(&self, local_pos: &Vector3) -> u16 { + let index = self.local_pos_to_block_index(local_pos); + self.flat_block_map[index] + } + #[inline] pub fn get_block_state(&self, local_pos: &Vector3) -> RawBlockState { let local_pos = Vector3::new( @@ -388,8 +394,7 @@ impl<'a> ProtoChunk<'a> { if local_pos.y < 0 || local_pos.y >= self.height() as i32 { return RawBlockState(Block::VOID_AIR.default_state.id); } - let index = self.local_pos_to_block_index(&local_pos); - RawBlockState(self.flat_block_map[index]) + RawBlockState(self.get_block_state_raw(&local_pos)) } pub fn set_block_state(&mut self, pos: &Vector3, block_state: &BlockState) { diff --git a/pumpkin-world/src/lib.rs b/pumpkin-world/src/lib.rs index c39b0e36c..680eefc89 100644 --- a/pumpkin-world/src/lib.rs +++ b/pumpkin-world/src/lib.rs @@ -23,7 +23,7 @@ pub type BlockId = u16; pub type BlockStateId = u16; pub const CURRENT_MC_VERSION: &str = "1.21.8"; -pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.94"; +pub const CURRENT_BEDROCK_MC_VERSION: &str = "1.21.100"; #[macro_export] macro_rules! global_path { diff --git a/pumpkin/src/block/blocks/signs.rs b/pumpkin/src/block/blocks/signs.rs index 30fd24f9c..a8672e880 100644 --- a/pumpkin/src/block/blocks/signs.rs +++ b/pumpkin/src/block/blocks/signs.rs @@ -29,6 +29,7 @@ use crate::item::items::dye::DyeItem; use crate::item::items::glowing_ink_sac::GlowingInkSacItem; use crate::item::items::honeycomb::HoneyCombItem; use crate::item::items::ink_sac::InkSacItem; +use crate::net::ClientPlatform; use crate::world::World; type SignProperties = pumpkin_data::block_properties::OakSignLikeProperties; @@ -55,7 +56,7 @@ impl BlockBehaviour for SignBlock { } async fn player_placed(&self, args: PlayerPlacedArgs<'_>) { - match args.player.client.as_ref() { + match &args.player.client { crate::net::ClientPlatform::Java(java) => { java.send_sign_packet(*args.position, true).await; } @@ -100,12 +101,12 @@ impl BlockBehaviour for SignBlock { let is_facing_front_text = is_facing_front_text(args.world, args.position, args.block, args.player).await; - match args.player.client.as_ref() { - crate::net::ClientPlatform::Java(java) => { + match &args.player.client { + ClientPlatform::Java(java) => { java.send_sign_packet(*args.position, is_facing_front_text) .await; } - crate::net::ClientPlatform::Bedrock(_bedrock) => todo!(), + ClientPlatform::Bedrock(_bedrock) => todo!(), } BlockActionResult::SuccessServer diff --git a/pumpkin/src/entity/mod.rs b/pumpkin/src/entity/mod.rs index b393216cf..9225887e9 100644 --- a/pumpkin/src/entity/mod.rs +++ b/pumpkin/src/entity/mod.rs @@ -1412,7 +1412,7 @@ impl Entity { } pub async fn set_sneaking(&self, sneaking: bool) { - assert!(self.sneaking.load(Relaxed) != sneaking); + //assert!(self.sneaking.load(Relaxed) != sneaking); self.sneaking.store(sneaking, Relaxed); self.set_flag(Flag::Sneaking, sneaking).await; if sneaking { @@ -1554,7 +1554,7 @@ impl Entity { } pub async fn set_sprinting(&self, sprinting: bool) { - assert!(self.sprinting.load(Relaxed) != sprinting); + //assert!(self.sprinting.load(Relaxed) != sprinting); self.sprinting.store(sprinting, Relaxed); self.set_flag(Flag::Sprinting, sprinting).await; } diff --git a/pumpkin/src/entity/player.rs b/pumpkin/src/entity/player.rs index 876478ab8..872b2bec5 100644 --- a/pumpkin/src/entity/player.rs +++ b/pumpkin/src/entity/player.rs @@ -1,4 +1,3 @@ -use core::f32; use std::collections::VecDeque; use std::f64::consts::TAU; use std::num::NonZeroU8; @@ -12,6 +11,10 @@ use crossbeam::atomic::AtomicCell; use log::warn; use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk; use pumpkin_protocol::bedrock::client::set_time::CSetTime; +use pumpkin_protocol::bedrock::client::update_abilities::{ + Ability, AbilityLayer, CUpdateAbilities, +}; +use pumpkin_protocol::bedrock::server::text::SText; use pumpkin_world::chunk::{ChunkData, ChunkEntityData}; use pumpkin_world::inventory::Inventory; use tokio::sync::{Mutex, RwLock}; @@ -200,7 +203,7 @@ pub struct Player { /// The player's game profile information, including their username and UUID. pub gameprofile: GameProfile, /// The client connection associated with the player. - pub client: Arc, + pub client: ClientPlatform, /// The player's inventory. pub inventory: Arc, /// The player's configuration settings. Changes when the player changes their settings. @@ -278,7 +281,7 @@ pub struct Player { impl Player { pub async fn new( - client: Arc, + client: ClientPlatform, gameprofile: GameProfile, config: PlayerConfig, world: Arc, @@ -303,7 +306,7 @@ impl Player { let living_entity = LivingEntity::new(Entity::new( player_uuid, world, - Vector3::new(0.0, 0.0, 0.0), + Vector3::new(0.0, 100.0, 0.0), &EntityType::PLAYER, matches!(gamemode, GameMode::Creative | GameMode::Spectator), )); @@ -341,11 +344,12 @@ impl Player { // TODO: Send the CPlayerSpawnPosition packet when the client connects with proper values respawn_point: AtomicCell::new(None), sleeping_since: AtomicCell::new(None), - // We want this to be an impossible watched section so that `player_chunker::update_position` + // We want this to be an impossible watched section so that `chunker::update_position` // will mark chunks as watched for a new join rather than a respawn. // (We left shift by one so we can search around that chunk) watched_section: AtomicCell::new(Cylindrical::new( - Vector2::new(i32::MAX >> 1, i32::MAX >> 1), + Vector2::new(0, 0), + // Since 1 is not possible in vanilla it is used as uninit NonZeroU8::new(1).unwrap(), )), wait_for_keep_alive: AtomicBool::new(false), @@ -758,7 +762,7 @@ impl Player { let chunk_of_chunks = { let mut chunk_manager = self.chunk_manager.lock().await; - if let ClientPlatform::Java(_) = self.client.as_ref() { + 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 @@ -771,7 +775,7 @@ impl Player { if let Some(chunk_of_chunks) = chunk_of_chunks { let chunk_count = chunk_of_chunks.len(); - match self.client.as_ref() { + match &self.client { ClientPlatform::Java(java_client) => { java_client.send_packet_now(&CChunkBatchStart).await; for chunk in chunk_of_chunks { @@ -845,7 +849,7 @@ impl Player { // TODO This should only be handled by the ClientPlatform let now = Instant::now(); if now.duration_since(self.last_keep_alive_time.load()) >= Duration::from_secs(15) { - if matches!(self.client.as_ref(), ClientPlatform::Bedrock(_)) { + if matches!(self.client, ClientPlatform::Bedrock(_)) { return; } // We never got a response from the last keep alive we sent. @@ -962,28 +966,77 @@ impl Player { /// Updates the current abilities the player has. pub async fn send_abilities_update(&self) { - let mut b = 0i8; - let abilities = &self.abilities.lock().await; + match &self.client { + ClientPlatform::Java(java) => { + let mut b = 0; + let abilities = &self.abilities.lock().await; - if abilities.invulnerable { - b |= 1; + if abilities.invulnerable { + b |= 1; + } + if abilities.flying { + b |= 2; + } + if abilities.allow_flying { + b |= 4; + } + if abilities.creative { + b |= 8; + } + java.enqueue_packet(&CPlayerAbilities::new( + b, + abilities.fly_speed, + abilities.walk_speed, + )) + .await; + } + ClientPlatform::Bedrock(bedrock) => { + let mut ability_value = 0; + let abilities = &self.abilities.lock().await; + + if abilities.invulnerable { + ability_value |= 1 << Ability::Invulnerable as u32; + } + + if abilities.flying { + ability_value |= 1 << Ability::Flying as u32; + } + + if abilities.allow_flying { + ability_value |= 1 << Ability::MayFly as u32; + } + + if abilities.creative { + ability_value |= 1 << Ability::OperatorCommands as u32; + ability_value |= 1 << Ability::Teleport as u32; + ability_value |= 1 << Ability::Invulnerable as u32; + } + + // Todo: Integrate this into the system + ability_value |= 1 << Ability::AttackMobs as u32; + ability_value |= 1 << Ability::AttackPlayers as u32; + ability_value |= 1 << Ability::Build as u32; + ability_value |= 1 << Ability::DoorsAndSwitches as u32; + ability_value |= 1 << Ability::Instabuild as u32; + ability_value |= 1 << Ability::Mine as u32; + + let packet = CUpdateAbilities { + target_player_raw_id: self.entity_id().into(), + player_permission: 2, + command_permission: 4, + layers: vec![AbilityLayer { + serialized_layer: 1, + abilities_set: (1 << Ability::AbilityCount as u32) - 1, + ability_value, + fly_speed: 0.05, + vertical_fly_speed: 1.0, + walk_speed: 0.1, + }], + }; + + bedrock.send_game_packet(&packet).await; + } } - if abilities.flying { - b |= 2; - } - if abilities.allow_flying { - b |= 4; - } - if abilities.creative { - b |= 8; - } - self.client - .enqueue_packet(&CPlayerAbilities::new( - b, - abilities.fly_speed, - abilities.walk_speed, - )) - .await; } /// Updates the client of the player's current permission level. @@ -1026,7 +1079,7 @@ impl Player { /// Sends the world time to only this player. pub async fn send_time(&self, world: &World) { let l_world = world.level_time.lock().await; - match self.client.as_ref() { + match &self.client { ClientPlatform::Java(java_client) => { java_client .enqueue_packet(&CUpdateTime::new( @@ -1058,7 +1111,7 @@ impl Player { } self.watched_section.store(Cylindrical::new( - Vector2::new(i32::MAX >> 1, i32::MAX >> 1), + Vector2::new(0, 0), NonZeroU8::new(1).unwrap(), )); } @@ -1489,13 +1542,33 @@ impl Player { } pub async fn send_system_message(&self, text: &TextComponent) { - self.send_system_message_raw(text, false).await; + match &self.client { + 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) { - self.client - .enqueue_packet(&CSystemChatMessage::new(text, overlay)) - .await; + match &self.client { + ClientPlatform::Java(client) => { + client + .enqueue_packet(&CSystemChatMessage::new(text, overlay)) + .await; + } + ClientPlatform::Bedrock(client) => { + client + .send_game_packet(&SText::system_message(text.clone().get_text())) + .await; + } + } } pub async fn tick_experience(&self) { @@ -1873,9 +1946,11 @@ impl Player { .await; } } +} - pub async fn reset_state(&self) { - self.living_entity.reset_state().await; +impl PartialEq for Player { + fn eq(&self, other: &Self) -> bool { + self.gameprofile.id == other.gameprofile.id } } diff --git a/pumpkin/src/net/bedrock/login.rs b/pumpkin/src/net/bedrock/login.rs index 3cef37818..df3eed5d7 100644 --- a/pumpkin/src/net/bedrock/login.rs +++ b/pumpkin/src/net/bedrock/login.rs @@ -9,6 +9,7 @@ use pumpkin_protocol::{ resource_pack_stack::CResourcePackStackPacket, resource_packs_info::CResourcePacksInfo, start_game::Experiments, }, + frame_set::FrameSet, server::{login::SLogin, request_network_settings::SRequestNetworkSettings}, }, codec::var_uint::VarUInt, @@ -78,30 +79,40 @@ impl BedrockClient { // String::from_utf8_unchecked(general_purpose::URL_SAFE_NO_PAD.decode(raw_token[1]).unwrap()) //}; - // TODO: Batch these - self.send_game_packet(&CPlayStatus::LoginSuccess).await; - self.send_game_packet(&CResourcePacksInfo::new( - false, - false, - false, - false, - uuid::Uuid::default(), - String::new(), - )) + let mut frame_set = FrameSet::default(); + + self.write_game_packet_to_set(&CPlayStatus::LoginSuccess, &mut frame_set) + .await; + self.write_game_packet_to_set( + &CResourcePacksInfo::new( + false, + false, + false, + false, + uuid::Uuid::default(), + String::new(), + ), + &mut frame_set, + ) .await; - self.send_game_packet(&CResourcePackStackPacket::new( - false, - VarUInt(0), - VarUInt(0), - CURRENT_BEDROCK_MC_VERSION.to_string(), - Experiments { - names_size: 0, - experiments_ever_toggled: false, - }, - false, - )) + self.write_game_packet_to_set( + &CResourcePackStackPacket::new( + false, + VarUInt(0), + VarUInt(0), + CURRENT_BEDROCK_MC_VERSION.to_string(), + Experiments { + names_size: 0, + experiments_ever_toggled: false, + }, + false, + ), + &mut frame_set, + ) .await; + self.send_frame_set(frame_set, 0x84).await; + if let Some((player, world)) = server .add_player( ClientPlatform::Bedrock(self.clone()), diff --git a/pumpkin/src/net/bedrock/mod.rs b/pumpkin/src/net/bedrock/mod.rs index 89c689eb5..f6b6c6360 100644 --- a/pumpkin/src/net/bedrock/mod.rs +++ b/pumpkin/src/net/bedrock/mod.rs @@ -13,7 +13,7 @@ use pumpkin_config::networking::compression::CompressionInfo; use pumpkin_protocol::{ BClientPacket, PacketDecodeError, RawPacket, bedrock::{ - RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RAKNET_VALID, RakReliability, SubClient, + MTU, RAKNET_ACK, RAKNET_GAME_PACKET, RAKNET_NACK, RakReliability, SubClient, ack::Ack, client::disconnect_player::CDisconnectPlayer, frame_set::{Frame, FrameSet}, @@ -21,8 +21,10 @@ use pumpkin_protocol::{ packet_encoder::UDPNetworkEncoder, server::{ client_cache_status::SClientCacheStatus, + command_request::SCommandRequest, container_close::SContainerClose, interaction::SInteraction, + loading_screen::SLoadingScreen, login::SLogin, player_auth_input::SPlayerAuthInput, raknet::{ @@ -77,7 +79,7 @@ pub struct BedrockClient { /// The packet decoder for incoming packets. network_reader: Mutex, - use_frame_sets: AtomicBool, + _use_frame_sets: AtomicBool, output_sequence_number: AtomicU32, output_reliable_number: AtomicU32, output_split_number: AtomicU16, @@ -113,7 +115,7 @@ impl BedrockClient { tasks: TaskTracker::new(), outgoing_packet_queue_send: send, outgoing_packet_queue_recv: Some(recv), - use_frame_sets: AtomicBool::new(false), + _use_frame_sets: AtomicBool::new(false), output_sequence_number: AtomicU32::new(0), output_reliable_number: AtomicU32::new(0), output_split_number: AtomicU16::new(0), @@ -168,7 +170,7 @@ impl BedrockClient { }); } - pub async fn process_packet(self: &Arc, server: &Server, packet: Cursor>) { + pub async fn process_packet(self: &Arc, server: &Arc, packet: Cursor>) { let packet = self.get_packet_payload(packet).await; if let Some(packet) = packet && let Err(error) = self.handle_packet_payload(server, packet).await @@ -192,7 +194,6 @@ impl BedrockClient { .set_compression((compression.threshold as usize, compression.level)); } - #[allow(clippy::unused_async)] pub async fn kick(&self, reason: DisconnectReason, message: String) { self.send_game_packet(&CDisconnectPlayer::new(reason as i32, message)) .await; @@ -244,31 +245,6 @@ impl BedrockClient { .await } - pub async fn send_raknet_packet_now(&self, packet: &P) { - let mut packet_buf = Vec::new(); - let writer = &mut packet_buf; - Self::write_raw_packet(packet, writer).unwrap(); - - if !self.use_frame_sets.load(Ordering::Relaxed) { - // Sent the packet directly - if let Err(err) = self - .network_writer - .lock() - .await - .write_packet(&packet_buf, self.address, &self.socket) - .await - { - // It is expected that the packet will fail if we are closed - if !self.closed.load(Ordering::Relaxed) { - log::warn!("Failed to send packet to client: {err}"); - // We now need to close the connection to the client since the stream is in an - // unknown state - self.close().await; - } - } - } - } - pub async fn send_offline_packet( packet: &P, addr: SocketAddr, @@ -290,6 +266,17 @@ impl BedrockClient { .await; } + pub async fn write_game_packet_to_set( + &self, + packet: &P, + frame_set: &mut FrameSet, + ) { + let mut payload = Vec::new(); + self.write_game_packet(packet, &mut payload).await.unwrap(); + + frame_set.frames.push(Frame::new_unreliable(payload)); + } + pub async fn send_framed_packet( &self, packet: &P, @@ -309,10 +296,10 @@ impl BedrockClient { let mut split_id = 0; let mut order_index = 0; - let count = if packet_buf.len() > 1340 { + let count = if packet_buf.len() > MTU { reliability = RakReliability::ReliableOrdered; split_id = self.output_split_number.fetch_add(1, Ordering::Relaxed); - split_size = (packet_buf.len() as u32).div_ceil(1340); + split_size = packet_buf.len().div_ceil(MTU) as u32; split_size as usize } else { 1 @@ -324,14 +311,14 @@ impl BedrockClient { for i in 0..count { let end = if i + 1 == count { - packet_buf.len() % 1340 + packet_buf.len() % MTU } else { - 1340 + MTU }; - let chunk = &packet_buf[i * 1340..i * 1340 + end]; + let chunk = &packet_buf[i * MTU..i * MTU + end]; let mut frame_set = FrameSet { - sequence: u24(self.output_sequence_number.fetch_add(1, Ordering::Relaxed)), + sequence: u24(0), frames: Vec::with_capacity(1), }; @@ -357,25 +344,30 @@ impl BedrockClient { frame_set.frames.push(frame); - let mut frame_set_buf = Vec::new(); - frame_set - .write_packet_data(&mut frame_set_buf, if i == 0 { 0x84 } else { 0x8c }) - .unwrap(); + let id = if i == 0 { 0x84 } else { 0x8c }; + self.send_frame_set(frame_set, id).await; + } + } - if let Err(err) = self - .network_writer - .lock() - .await - .write_packet(&frame_set_buf, self.address, &self.socket) - .await - { - // It is expected that the packet will fail if we are closed - if !self.closed.load(Ordering::Relaxed) { - log::warn!("Failed to send packet to client: {err}"); - // We now need to close the connection to the client since the stream is in an - // unknown state - self.close().await; - } + pub async fn send_frame_set(&self, mut frame_set: FrameSet, id: u8) { + frame_set.sequence = u24(self.output_sequence_number.fetch_add(1, Ordering::Relaxed)); + let mut frame_set_buf = Vec::new(); + frame_set.write_packet_data(&mut frame_set_buf, id).unwrap(); + + // I dont know if thats the right place to make encryption & decoding + if let Err(err) = self + .network_writer + .lock() + .await + .write_packet(&frame_set_buf, self.address, &self.socket) + .await + { + // It is expected that the packet will fail if we are closed + if !self.closed.load(Ordering::Relaxed) { + log::warn!("Failed to send packet to client: {err}"); + // We now need to close the connection to the client since the stream is in an + // unknown state + self.closed.store(true, Ordering::Relaxed); } } } @@ -403,44 +395,29 @@ impl BedrockClient { .write_packet(&packet_buf, self.address, &self.socket) .await { - // It is expected that the packet will fail if we are closed - if !self.closed.load(Ordering::Relaxed) { - log::warn!("Failed to send packet to client: {err}"); - // We now need to close the connection to the client since the stream is in an - // unknown state - self.close().await; - } + log::warn!("Failed to send packet to client: {err}"); + self.close().await; } } pub async fn handle_packet_payload( self: &Arc, - server: &Server, + server: &Arc, packet: Bytes, ) -> Result<(), Error> { - let payload = &mut Cursor::new(packet); + let reader = &mut Cursor::new(packet); - let id = u8::read(payload)?; - - let is_valid = id & RAKNET_VALID == RAKNET_VALID; - if !is_valid { - // Offline packets just have Packet ID + Payload - return Err(Error::other("not valid online packet")); - } - self.use_frame_sets.store(true, Ordering::Relaxed); - - match id { + match u8::read(reader)? { RAKNET_ACK => { - Self::handle_ack(&Ack::read(payload)?); + Self::handle_ack(&Ack::read(reader)?); } RAKNET_NACK => { dbg!("received nack, client is missing packets"); } 0x80..0x8d => { - self.handle_frame_set(server, FrameSet::read(payload)?) - .await; + self.handle_frame_set(server, FrameSet::read(reader)?).await; } - _ => { + id => { log::warn!("Bedrock: Received unknown packet header {id}"); } } @@ -449,7 +426,7 @@ impl BedrockClient { fn handle_ack(_ack: &Ack) {} - async fn handle_frame_set(self: &Arc, server: &Server, frame_set: FrameSet) { + async fn handle_frame_set(self: &Arc, server: &Arc, frame_set: FrameSet) { // TODO: Send all ACKs in short intervals in batches self.send_ack(&Ack::new(vec![frame_set.sequence.0])).await; // TODO @@ -460,7 +437,7 @@ impl BedrockClient { async fn handle_frame( self: &Arc, - server: &Server, + server: &Arc, mut frame: Frame, ) -> Result<(), Error> { if frame.split_size > 0 { @@ -509,7 +486,7 @@ impl BedrockClient { async fn handle_game_packet( self: &Arc, - server: &Server, + server: &Arc, packet: RawPacket, ) -> Result<(), Error> { let payload = &mut Cursor::new(&packet.payload); @@ -536,33 +513,42 @@ impl BedrockClient { pub async fn handle_play_packet( &self, player: &Arc, - _server: &Server, + server: &Arc, packet: RawPacket, ) { - let payload = &mut &packet.payload[..]; + let reader = &mut &packet.payload[..]; match packet.id { SPlayerAuthInput::PACKET_ID => { - if let Ok(input_packet) = SPlayerAuthInput::read(payload) { + if let Ok(input_packet) = SPlayerAuthInput::read(reader) { self.player_pos_update(player, input_packet).await; } } + SLoadingScreen::PACKET_ID => { + if SLoadingScreen::read(reader).unwrap().is_loading_done() { + player.set_client_loaded(true); + } + } SRequestChunkRadius::PACKET_ID => { self.handle_request_chunk_radius( player, - SRequestChunkRadius::read(payload).unwrap(), + SRequestChunkRadius::read(reader).unwrap(), ) .await; } SInteraction::PACKET_ID => { - self.handle_interaction(player, SInteraction::read(payload).unwrap()) + self.handle_interaction(player, SInteraction::read(reader).unwrap()) .await; } SContainerClose::PACKET_ID => { - self.handle_container_close(player, SContainerClose::read(payload).unwrap()) + self.handle_container_close(player, SContainerClose::read(reader).unwrap()) .await; } SText::PACKET_ID => { - self.handle_chat_message(player, SText::read(payload).unwrap()) + self.handle_chat_message(player, SText::read(reader).unwrap()) + .await; + } + SCommandRequest::PACKET_ID => { + self.handle_chat_command(player, server, SCommandRequest::read(reader).unwrap()) .await; } _ => { @@ -573,7 +559,7 @@ impl BedrockClient { async fn handle_raknet_packet( self: &Arc, - server: &Server, + server: &Arc, packet_id: i32, mut payload: Cursor>, ) -> Result<(), Error> { @@ -667,9 +653,8 @@ impl BedrockClient { Err(err) => { if !matches!(err, PacketDecodeError::ConnectionClosed) { log::warn!("Failed to decode packet from client: {err}"); - let _text = format!("Error while reading incoming packet {err}"); - self.close().await; - //self.kick(client).await; + let text = format!("Error while reading incoming packet {err}"); + self.kick(DisconnectReason::BadPacket, text).await; } None } diff --git a/pumpkin/src/net/bedrock/play.rs b/pumpkin/src/net/bedrock/play.rs index de127530c..04dd09740 100644 --- a/pumpkin/src/net/bedrock/play.rs +++ b/pumpkin/src/net/bedrock/play.rs @@ -1,19 +1,27 @@ -use std::{num::NonZero, sync::Arc}; +use std::{ + num::{NonZero, NonZeroU32}, + sync::Arc, +}; use pumpkin_config::{BASIC_CONFIG, advanced_config}; use pumpkin_macros::send_cancellable; use pumpkin_protocol::{ bedrock::{ - client::{chunk_radius_update::CChunkRadiusUpdate, container_open::CContainerOpen}, + client::{ + chunk_radius_update::CChunkRadiusUpdate, container_open::CContainerOpen, + network_chunk_publisher_update::CNetworkChunkPublisherUpdate, + set_actor_motion::CSetActorMotion, + }, server::{ + command_request::SCommandRequest, container_close::SContainerClose, interaction::{Action, SInteraction}, - player_auth_input::SPlayerAuthInput, + player_auth_input::{InputData, SPlayerAuthInput}, request_chunk_radius::SRequestChunkRadius, text::SText, }, }, - codec::{bedrock_block_pos::NetworkPos, var_long::VarLong}, + codec::{bedrock_block_pos::NetworkPos, var_long::VarLong, var_ulong::VarULong}, java::client::play::CSystemChatMessage, }; use pumpkin_util::{ @@ -22,8 +30,12 @@ use pumpkin_util::{ }; use crate::{ - entity::player::Player, net::bedrock::BedrockClient, - plugin::player::player_chat::PlayerChatEvent, server::seasonal_events, world::chunker, + command::CommandSender, + entity::{EntityBase, player::Player}, + net::{DisconnectReason, bedrock::BedrockClient}, + plugin::player::{player_chat::PlayerChatEvent, player_command_send::PlayerCommandSendEvent}, + server::{Server, seasonal_events}, + world::chunker, }; impl BedrockClient { @@ -32,32 +44,96 @@ impl BedrockClient { player: &Arc, packet: SRequestChunkRadius, ) { - dbg!(&packet); - player.config.write().await.view_distance = - NonZero::new(packet.chunk_radius.0 as u8).unwrap(); - self.send_game_packet(&CChunkRadiusUpdate { - chunk_radius: packet.chunk_radius, - }) - .await; + let chunk_radius = packet.chunk_radius; + if chunk_radius.0 < 1 { + self.kick( + DisconnectReason::Kicked, + "Cannot have zero or negative view distance!".to_string(), + ) + .await; + return; + } + + self.send_game_packet(&CChunkRadiusUpdate { chunk_radius }) + .await; + + let old_view_distance = { + let mut config = player.config.write().await; + let old_view_distance = config.view_distance; + config.view_distance = NonZero::new(chunk_radius.0 as u8).unwrap(); + old_view_distance + }; + + if old_view_distance.get() != chunk_radius.0 as u8 { + log::debug!( + "Player {} updated their render distance: {} -> {}.", + player.gameprofile.name, + old_view_distance, + chunk_radius.0 + ); + self.send_game_packet(&CNetworkChunkPublisherUpdate::new( + player.get_entity().block_pos.load(), + chunk_radius.0 as u32, + )) + .await; + chunker::update_position(player).await; + } } pub async fn player_pos_update(&self, player: &Arc, packet: SPlayerAuthInput) { - let pos = packet.position; - player.living_entity.entity.set_pos(pos.to_f64()); + if !player.has_client_loaded() { + return; + } + let config = player.config.read().await; + let view_distance = config.view_distance; + self.send_game_packet(&CNetworkChunkPublisherUpdate::new( + BlockPos::new( + packet.position.x.floor() as i32, + packet.position.y.floor() as i32, + packet.position.z.floor() as i32, + ), + NonZeroU32::from(view_distance).into(), + )) + .await; + let new_pos = packet.position.to_f64(); + let old_pos = player.position(); - chunker::update_position(player).await; - //self.send_game_packet(&CMovePlayer { - // player_runtime_id: VarULong(player.entity_id() as u64), - // position: packet.position + Vector3::new(10.0, 0.0, 0.0), - // pitch: packet.pitch, - // yaw: packet.yaw, - // y_head_rotation: packet.head_rotation, - // position_mode: 1, - // on_ground: false, - // riding_runtime_id: VarULong(0), - // tick: packet.client_tick, - //}) - //.await; + if new_pos != old_pos { + player.living_entity.entity.set_pos(new_pos); + chunker::update_position(player).await; + } + + let input_data = packet.input_data; + let entity = player.get_entity(); + + if input_data.get(InputData::StartSprinting) { + entity.set_sprinting(true).await; + } else if input_data.get(InputData::StopSprinting) { + entity.set_sprinting(false).await; + } + + if input_data.get(InputData::StartFlying) { + player.abilities.lock().await.flying = true; + player.send_abilities_update().await; + } else if input_data.get(InputData::StopFlying) { + player.abilities.lock().await.flying = false; + player.send_abilities_update().await; + } + + if input_data.get(InputData::StartSneaking) { + entity.set_sneaking(true).await; + } else if input_data.get(InputData::StopSneaking) { + entity.set_sneaking(false).await; + } + + if !player.abilities.lock().await.flying { + self.send_game_packet(&CSetActorMotion { + target_runtime_id: VarULong(entity.entity_id as _), + motion: packet.pos_delta + Vector3::new(0.0, -0.08, 0.0), + tick: packet.client_tick, + }) + .await; + } } pub async fn handle_interaction(&self, _player: &Arc, packet: SInteraction) { @@ -65,7 +141,7 @@ impl BedrockClient { self.send_game_packet(&CContainerOpen { container_id: 0, container_type: 0xff, - position: NetworkPos(BlockPos(Vector3::new(0, 0, 0))), + position: NetworkPos(packet.position.to_block_pos()), target_entity_id: VarLong(-1), }) .await; @@ -124,4 +200,46 @@ impl BedrockClient { } }} } + + pub async fn handle_chat_command( + &self, + player: &Arc, + server: &Arc, + command: SCommandRequest, + ) { + let player_clone = player.clone(); + let server_clone: Arc = server.clone(); + send_cancellable! {{ + PlayerCommandSendEvent { + player: player.clone(), + command: command.command.clone(), + cancelled: false + }; + + 'after: { + let command = event.command; + let command_clone = command.clone(); + // Some commands can take a long time to execute. If they do, they block packet processing for the player. + // That's why we will spawn a task instead. + server.spawn_task(async move { + let dispatcher = server_clone.command_dispatcher.read().await; + dispatcher + .handle_command( + &mut CommandSender::Player(player_clone), + &server_clone, + &command_clone, + ) + .await; + }); + + if advanced_config().commands.log_console { + log::info!( + "Player ({}): executed command /{}", + player.gameprofile.name, + command + ); + } + } + }} + } } diff --git a/pumpkin/src/net/bedrock/unconnected.rs b/pumpkin/src/net/bedrock/unconnected.rs index 110c5aebe..139be925c 100644 --- a/pumpkin/src/net/bedrock/unconnected.rs +++ b/pumpkin/src/net/bedrock/unconnected.rs @@ -32,7 +32,7 @@ impl BedrockClient { edition: "MCPE", // TODO The default motd is to long to be displayed completely motd_line_1: "Pumpkin Server", - protocol_version: 819, + protocol_version: 827, version_name: CURRENT_BEDROCK_MC_VERSION, player_count, // A large number looks wreird on the client worlds window diff --git a/pumpkin/src/server/mod.rs b/pumpkin/src/server/mod.rs index 0bc260417..cbfbccf00 100644 --- a/pumpkin/src/server/mod.rs +++ b/pumpkin/src/server/mod.rs @@ -322,7 +322,7 @@ impl Server { }; let mut player = Player::new( - Arc::new(client), + client, profile, config.clone().unwrap_or_default(), world.clone(), diff --git a/pumpkin/src/world/chunker.rs b/pumpkin/src/world/chunker.rs index 7119ddba2..9b4624a2f 100644 --- a/pumpkin/src/world/chunker.rs +++ b/pumpkin/src/world/chunker.rs @@ -1,14 +1,7 @@ -use std::{ - num::{NonZeroU8, NonZeroU32}, - sync::Arc, -}; +use std::{num::NonZeroU8, sync::Arc}; use pumpkin_config::BASIC_CONFIG; -use pumpkin_protocol::{ - bedrock::client::network_chunk_publisher_update::CNetworkChunkPublisherUpdate, - java::client::play::{CCenterChunk, CUnloadChunk}, -}; -use pumpkin_util::math::position::BlockPos; +use pumpkin_protocol::java::client::play::{CCenterChunk, CUnloadChunk}; use pumpkin_world::cylindrical_chunk_iterator::Cylindrical; use crate::{entity::player::Player, net::ClientPlatform}; @@ -24,7 +17,6 @@ pub async fn get_view_distance(player: &Player) -> NonZeroU8 { pub async fn update_position(player: &Arc) { let entity = &player.living_entity.entity; - let pos = entity.pos.load(); let view_distance = get_view_distance(player).await; let new_chunk_center = entity.chunk_pos.load(); @@ -33,35 +25,20 @@ pub async fn update_position(player: &Arc) { let new_cylindrical = Cylindrical::new(new_chunk_center, view_distance); if old_cylindrical != new_cylindrical { - match player.client.as_ref() { - ClientPlatform::Java(client) => { - client - .send_packet_now(&CCenterChunk { - chunk_x: new_chunk_center.x.into(), - chunk_z: new_chunk_center.y.into(), - }) - .await; - } - ClientPlatform::Bedrock(client) => { - client - .send_game_packet(&CNetworkChunkPublisherUpdate::new( - BlockPos::new(pos.x as i32, pos.y as i32, pos.z as i32), - NonZeroU32::from(view_distance).get(), - )) - .await; - } + if let ClientPlatform::Java(java) = &player.client { + java.send_packet_now(&CCenterChunk { + chunk_x: new_chunk_center.x.into(), + chunk_z: new_chunk_center.y.into(), + }) + .await; } let mut loading_chunks = Vec::new(); let mut unloading_chunks = Vec::new(); Cylindrical::for_each_changed_chunk( old_cylindrical, new_cylindrical, - |chunk_pos| { - loading_chunks.push(chunk_pos); - }, - |chunk_pos| { - unloading_chunks.push(chunk_pos); - }, + &mut loading_chunks, + &mut unloading_chunks, ); // Make sure the watched section and the chunk watcher updates are async atomic. We want to diff --git a/pumpkin/src/world/mod.rs b/pumpkin/src/world/mod.rs index c1cb3e8fd..f33e05afe 100644 --- a/pumpkin/src/world/mod.rs +++ b/pumpkin/src/world/mod.rs @@ -1,3 +1,4 @@ +use std::num::NonZeroU32; use std::sync::Weak; use std::sync::atomic::Ordering::Relaxed; use std::time::Duration; @@ -52,19 +53,20 @@ use pumpkin_data::{BlockDirection, BlockState}; use pumpkin_inventory::screen_handler::InventoryPlayer; use pumpkin_macros::send_cancellable; 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::frame_set::FrameSet; use pumpkin_protocol::{ BClientPacket, ClientPacket, IdOr, SoundEvent, bedrock::{ client::{ - chunk_radius_update::CChunkRadiusUpdate, creative_content::{CreativeContent, Group}, gamerules_changed::GameRules, - inventory_content::CInventoryContent, play_status::CPlayStatus, start_game::{Experiments, GamePublishSetting, LevelSettings}, update_artributes::{Attribute, CUpdateAttributes}, }, - network_item::{ItemInstanceUserData, NetworkItemDescriptor, NetworkItemStackDescriptor}, + network_item::NetworkItemDescriptor, server::text::SText, }, codec::{ @@ -390,7 +392,7 @@ impl World { let current_players = self.players.read().await; for (_, player) in current_players.iter() { - match player.client.as_ref() { + match &player.client { ClientPlatform::Java(client) => client.enqueue_packet(je_packet).await, ClientPlatform::Bedrock(client) => client.send_game_packet(be_packet).await, } @@ -717,8 +719,8 @@ impl World { for i in self.players.read().await.values() { let center = i.living_entity.entity.chunk_pos.load(); - for dx in -8i32..=8 { - for dy in -8i32..=8 { + for dx in -8..=8 { + for dy in -8..=8 { // if dx.abs() <= 2 || dy.abs() <= 2 || dx.abs() >= 6 || dy.abs() >= 6 { // this is only for debug, spawning runs too slow // continue; // } @@ -732,10 +734,8 @@ impl World { } } - let mut spawning_chunks = Vec::with_capacity(spawning_chunks_map.len()); - for i in spawning_chunks_map { - spawning_chunks.push(i); - } + let mut spawning_chunks: Vec<(Vector2, Arc>)> = + spawning_chunks_map.into_iter().collect(); let get_chunks_clock = spawn_entity_clock_start.elapsed(); // log::debug!("spawning chunks size {}", spawning_chunks.len()); @@ -1085,10 +1085,8 @@ impl World { ) { // this.level.tickThunder(chunk); //TODO check in simulation distance - if self.weather.lock().await.raining - && self.weather.lock().await.thundering - && rng().random_range(0..100_000) == 0 - { + let weather = self.weather.lock().await; + if weather.raining && weather.thundering && rng().random_range(0..100_000) == 0 { let rand_value = rng().random::() >> 2; let delta = Vector3::new(rand_value & 15, rand_value >> 16 & 15, rand_value >> 8 & 15); let random_pos = Vector3::new( @@ -1191,14 +1189,14 @@ impl World { custom_biome_name: String::new(), dimension: VarInt(0), generator_type: VarInt(1), - world_gamemode: VarInt(server.defaultgamemode.lock().await.gamemode as i32), + world_gamemode: server.defaultgamemode.lock().await.gamemode, hardcore: base_config.hardcore, difficulty: VarInt(level_info.difficulty as i32), - spawn_position: NetworkPos(BlockPos(Vector3::new( + spawn_position: NetworkPos(BlockPos::new( level_info.spawn_x, level_info.spawn_y, level_info.spawn_z, - ))), + )), has_achievements_disabled: false, editor_world_type: VarInt(0), is_created_in_editor: false, @@ -1212,8 +1210,8 @@ impl World { has_confirmed_platform_locked_content: false, was_multiplayer_intended: true, was_lan_broadcasting_intended: true, - xbox_live_broadcast_setting: VarInt(GamePublishSetting::Public as _), - platform_broadcast_setting: VarInt(GamePublishSetting::Public as _), + xbox_live_broadcast_setting: GamePublishSetting::Public, + platform_broadcast_setting: GamePublishSetting::Public, commands_enabled: level_info.allow_commands, is_texture_packs_required: false, rule_data: GameRules { @@ -1256,11 +1254,12 @@ impl World { drop(weather); let client = player.client.bedrock(); + client .send_game_packet(&CStartGame { - entity_id: VarLong(runtime_id as i64), + entity_id: VarLong(runtime_id as _), runtime_entity_id: VarULong(runtime_id), - player_gamemode: VarInt(player.gamemode.load() as i32), + player_gamemode: player.gamemode.load(), position: Vector3::new(0.0, 100.0, 0.0), pitch: 0.0, yaw: 0.0, @@ -1290,9 +1289,16 @@ impl World { // TODO The client needs extra biome data for this enable_clientside_generation: false, blocknetwork_ids_are_hashed: false, + tick_death_system_enabled: false, server_auth_sounds: false, }) .await; + client + .send_game_packet(&CChunkRadiusUpdate { + chunk_radius: VarInt(player.config.read().await.view_distance.get().into()), + }) + .await; + chunker::update_position(&player).await; client .send_game_packet(&CreativeContent { groups: &[Group { @@ -1304,52 +1310,48 @@ impl World { }) .await; + let mut frame_set = FrameSet::default(); client - .send_game_packet(&CChunkRadiusUpdate { - chunk_radius: VarInt(16), - }) + .write_game_packet_to_set( + &CNetworkChunkPublisherUpdate::new( + BlockPos::new(0, 100, 0), + NonZeroU32::from(player.config.read().await.view_distance).into(), + ), + &mut frame_set, + ) .await; - chunker::update_position(&player).await; - client - .send_game_packet(&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), - }) + .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.send_game_packet(&CPlayStatus::PlayerSpawn).await; - client - .send_game_packet(&CInventoryContent { - inventory_id: VarUInt(124), - slots: vec![ - NetworkItemStackDescriptor { - id: VarInt(2), - stack_size: 64, - aux_value: VarUInt(0), - net_id: Some(VarInt(2)), - block_runtime_id: VarInt(2), - user_data_buffer: ItemInstanceUserData::default() - }; - 36 - ], - container_name: 0, - dynamic_id: None, - storage_item: NetworkItemStackDescriptor::default(), - }) + .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; + abilities.set_for_gamemode(player.gamemode.load()); + }; + + player.send_abilities_update().await; } #[expect(clippy::too_many_lines)] @@ -1689,7 +1691,7 @@ impl World { yaw: f32, pitch: f32, ) { - if let ClientPlatform::Java(client) = player.client.as_ref() { + if let ClientPlatform::Java(client) = &player.client { self.worldborder.lock().await.init_client(client).await; } @@ -1781,7 +1783,7 @@ impl World { )) .await; - player.reset_state().await; + player.living_entity.reset_state().await; log::debug!("Sending player abilities to {}", player.gameprofile.name); player.send_abilities_update().await; @@ -1870,9 +1872,9 @@ impl World { let mut receiver = self.level.receive_chunks(chunks.clone()); let level = self.level.clone(); - let player1 = player.clone(); let world = self.clone(); let world1 = self.clone(); + let player1 = player.clone(); player.clone().spawn_task(async move { 'main: loop { @@ -2316,7 +2318,7 @@ impl World { .remove(&player.gameprofile.id) .unwrap(); let uuid = player.gameprofile.id; - self.broadcast_packet_except(&[player.gameprofile.id], &CRemovePlayerInfo::new(&[uuid])) + self.broadcast_packet_all(&CRemovePlayerInfo::new(&[uuid])) .await; self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()])) .await; diff --git a/pumpkin/src/world/natural_spawner.rs b/pumpkin/src/world/natural_spawner.rs index 797557816..b544c0251 100644 --- a/pumpkin/src/world/natural_spawner.rs +++ b/pumpkin/src/world/natural_spawner.rs @@ -196,20 +196,20 @@ impl SpawnState { let mut local_mob_cap = LocalMobCapCalculator::new(world); let mut counter = MobCounts::default(); for entity in entities.read().await.values() { - let entity_type = &entity.get_entity().entity_type; - #[allow(clippy::overly_complex_bool_expr)] - if entity_type.mob && false || entity_type.category == &MobCategory::MISC { + let entity = entity.get_entity(); + let entity_type = entity.entity_type; + if !entity_type.mob || entity_type.category == &MobCategory::MISC { // TODO (mob.isPersistenceRequired() || mob.requiresCustomPersistence()) continue; } - let entity_pos = &entity.get_entity().block_pos.load(); - let biome = world.level.get_rough_biome(entity_pos).await; + let entity_pos = entity.block_pos.load(); + let biome = world.level.get_rough_biome(&entity_pos).await; if let Some(cost) = biome.spawn_costs.get(entity_type.resource_name) { - potential.add_charge(entity_pos, cost.charge); + potential.add_charge(&entity_pos, cost.charge); } if entity_type.mob { local_mob_cap - .add_mob(&entity.get_entity().chunk_pos.load(), entity_type.category) + .add_mob(&entity.chunk_pos.load(), entity_type.category) .await; } counter.add(entity_type.category); diff --git a/pumpkin/src/world/time.rs b/pumpkin/src/world/time.rs index 11e09d192..1d358c93c 100644 --- a/pumpkin/src/world/time.rs +++ b/pumpkin/src/world/time.rs @@ -35,7 +35,7 @@ impl LevelTime { pub async fn send_time(&self, world: &World) { let current_players = world.players.read().await; for player in current_players.values() { - match player.client.as_ref() { + match &player.client { ClientPlatform::Java(java_client) => { java_client .enqueue_packet(&CUpdateTime::new(self.world_age, self.time_of_day, true))