fix world y offset & bedrock update (#1121)

This commit is contained in:
unschlagbar
2025-08-30 09:27:03 +02:00
committed by GitHub
parent 6cac55e0f0
commit 0bc7405f12
44 changed files with 882 additions and 419 deletions

Binary file not shown.

View File

@@ -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 {

View File

@@ -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<f32>,
pub pos_delta: Vector3<f32>,
pub on_ground: bool,
pub tick: VarULong,
}

View File

@@ -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)?;

View File

@@ -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;

View File

@@ -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<f32>,
pub tick: VarULong,
}

View File

@@ -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<f32>,
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<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
VarInt(*self as i32).write(writer)
}
}
#[derive(PacketWrite)]
pub struct GG {
pub name: String,

View File

@@ -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<AbilityLayer>,
}
#[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,
}

View File

@@ -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<u8>) -> 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<R: Read>(reader: &mut R) -> Result<Vec<Self>, Error> {
let mut frames = Vec::new();

View File

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

View File

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

View File

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

View File

@@ -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<u32>,
}
impl SLoadingScreen {
pub fn is_loading_done(&self) -> bool {
self.status.0 == 2
}
}

View File

@@ -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;

View File

@@ -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<f32>,
pub move_vec: Vector2<f32>,
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<f32>,
pub analog_move: Vector2<f32>,
pub camera_orientation: Vector3<f32>,
//pub raw_move_vec: Vector2<f32>,
pub raw_move_vec: Vector2<f32>,
}
#[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<InputData> for usize {
fn from(value: InputData) -> Self {
value as Self
}
}
pub enum InputMode {
Undefined = 0,
Mouse = 1,

View File

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

View File

@@ -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 {

View File

@@ -0,0 +1,54 @@
use std::io::{Error, ErrorKind, Read};
use crate::serial::PacketRead;
#[derive(Debug)]
pub struct Bitset<const N: usize> {
pub bits: u128,
}
impl<const N: usize> Bitset<N> {
pub fn get<T: Into<usize>>(&self, index: T) -> bool {
let index: usize = index.into();
if index > N {
panic!("")
}
(self.bits & (1 << index)) != 0
}
pub fn set<T: Into<usize>>(&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<const N: usize> Default for Bitset<N> {
fn default() -> Self {
if N > 80 {
panic!()
}
Self { bits: 0 }
}
}
impl<const N: usize> PacketRead for Bitset<N> {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
let mut bitset = Bitset::<N>::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, ""))
}
}

View File

@@ -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;

View File

@@ -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 {

View File

@@ -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<R: Read>(reader: &mut R) -> Result<Self, Error> {
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<R: Read>(reader: &mut R) -> Result<Self, Error> {
let mut bytes = [0; 16];
reader.read_exact(&mut bytes)?;
Ok(Uuid::from_bytes(bytes))
}
}
impl<T: PacketRead> PacketRead for Option<T> {
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
Ok(if bool::read(reader)? {
Some(T::read(reader)?)
} else {
None
})
}
}

View File

@@ -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<T: PacketWrite> PacketWrite for Option<T> {
impl PacketWrite for SocketAddr {
fn write<W: 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<W: 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)
}
}

View File

@@ -119,9 +119,9 @@ pub fn lerp_progress<T: Float>(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)

View File

@@ -228,19 +228,18 @@ impl BlockPos {
pub fn chunk_and_chunk_relative_position(&self) -> (Vector2<i32>, Vector3<i32>) {
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<i32> {
let (_z_chunk, z_rem) = self.0.z.div_rem_euclid(&16);

View File

@@ -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();

View File

@@ -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<Bytes, ChunkSerializingError> {
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();

View File

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

View File

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

View File

@@ -19,20 +19,20 @@ impl Cylindrical {
pub fn for_each_changed_chunk(
old_cylindrical: Cylindrical,
new_cylindrical: Cylindrical,
mut newly_included: impl FnMut(Vector2<i32>),
mut just_removed: impl FnMut(Vector2<i32>),
newly_included: &mut Vec<Vector2<i32>>,
just_removed: &mut Vec<Vector2<i32>>,
) {
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<Vector2<i32>> {
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

View File

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

View File

@@ -378,6 +378,12 @@ impl<'a> ProtoChunk<'a> {
state.is_air()
}
#[inline]
pub fn get_block_state_raw(&self, local_pos: &Vector3<i32>) -> 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<i32>) -> 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<i32>, block_state: &BlockState) {

View File

@@ -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 {

View File

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

View File

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

View File

@@ -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<ClientPlatform>,
pub client: ClientPlatform,
/// The player's inventory.
pub inventory: Arc<PlayerInventory>,
/// 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<ClientPlatform>,
client: ClientPlatform,
gameprofile: GameProfile,
config: PlayerConfig,
world: Arc<World>,
@@ -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
}
}

View File

@@ -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()),

View File

@@ -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<UDPNetworkDecoder>,
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<Self>, server: &Server, packet: Cursor<Vec<u8>>) {
pub async fn process_packet(self: &Arc<Self>, server: &Arc<Server>, packet: Cursor<Vec<u8>>) {
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<P: BClientPacket>(&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<P: BClientPacket>(
packet: &P,
addr: SocketAddr,
@@ -290,6 +266,17 @@ impl BedrockClient {
.await;
}
pub async fn write_game_packet_to_set<P: BClientPacket>(
&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<P: BClientPacket>(
&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<Self>,
server: &Server,
server: &Arc<Server>,
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<Self>, server: &Server, frame_set: FrameSet) {
async fn handle_frame_set(self: &Arc<Self>, server: &Arc<Server>, 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<Self>,
server: &Server,
server: &Arc<Server>,
mut frame: Frame,
) -> Result<(), Error> {
if frame.split_size > 0 {
@@ -509,7 +486,7 @@ impl BedrockClient {
async fn handle_game_packet(
self: &Arc<Self>,
server: &Server,
server: &Arc<Server>,
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<Player>,
_server: &Server,
server: &Arc<Server>,
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<Self>,
server: &Server,
server: &Arc<Server>,
packet_id: i32,
mut payload: Cursor<Vec<u8>>,
) -> 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
}

View File

@@ -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<Player>,
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<Player>, 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<Player>, 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<Player>,
server: &Arc<Server>,
command: SCommandRequest,
) {
let player_clone = player.clone();
let server_clone: Arc<Server> = 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
);
}
}
}}
}
}

View File

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

View File

@@ -322,7 +322,7 @@ impl Server {
};
let mut player = Player::new(
Arc::new(client),
client,
profile,
config.clone().unwrap_or_default(),
world.clone(),

View File

@@ -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<Player>) {
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<Player>) {
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

View File

@@ -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<i32>, Arc<RwLock<ChunkData>>)> =
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::<i32>() >> 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;

View File

@@ -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);

View File

@@ -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))