feat: bedrock & java cross play
This commit is contained in:
parent
100b8791cf
commit
e1967bed2b
64
Cargo.lock
generated
64
Cargo.lock
generated
@ -250,12 +250,24 @@ dependencies = [
|
|||||||
"allocator-api2",
|
"allocator-api2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "bytemuck"
|
||||||
|
version = "1.25.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "byteorder"
|
name = "byteorder"
|
||||||
version = "1.5.0"
|
version = "1.5.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "byteorder-lite"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "bytes"
|
name = "bytes"
|
||||||
version = "1.11.1"
|
version = "1.11.1"
|
||||||
@ -1173,6 +1185,15 @@ version = "2.4.1"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fdeflate"
|
||||||
|
version = "0.3.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
|
||||||
|
dependencies = [
|
||||||
|
"simd-adler32",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ff"
|
name = "ff"
|
||||||
version = "0.13.1"
|
version = "0.13.1"
|
||||||
@ -1775,6 +1796,19 @@ dependencies = [
|
|||||||
"icu_properties",
|
"icu_properties",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "image"
|
||||||
|
version = "0.25.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "85ab80394333c02fe689eaf900ab500fbd0c2213da414687ebf995a65d5a6104"
|
||||||
|
dependencies = [
|
||||||
|
"bytemuck",
|
||||||
|
"byteorder-lite",
|
||||||
|
"moxcms",
|
||||||
|
"num-traits",
|
||||||
|
"png",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "indexmap"
|
name = "indexmap"
|
||||||
version = "2.14.0"
|
version = "2.14.0"
|
||||||
@ -2091,6 +2125,16 @@ dependencies = [
|
|||||||
"windows-sys 0.61.2",
|
"windows-sys 0.61.2",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "moxcms"
|
||||||
|
version = "0.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "bb85c154ba489f01b25c0d36ae69a87e4a1c73a72631fc6c0eb6dde34a73e44b"
|
||||||
|
dependencies = [
|
||||||
|
"num-traits",
|
||||||
|
"pxfm",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "nix"
|
name = "nix"
|
||||||
version = "0.31.3"
|
version = "0.31.3"
|
||||||
@ -2472,6 +2516,19 @@ version = "0.3.33"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "png"
|
||||||
|
version = "0.18.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "60769b8b31b2a9f263dae2776c37b1b28ae246943cf719eb6946a1db05128a61"
|
||||||
|
dependencies = [
|
||||||
|
"bitflags",
|
||||||
|
"crc32fast",
|
||||||
|
"fdeflate",
|
||||||
|
"flate2",
|
||||||
|
"miniz_oxide",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "postcard"
|
name = "postcard"
|
||||||
version = "1.1.3"
|
version = "1.1.3"
|
||||||
@ -2619,6 +2676,7 @@ dependencies = [
|
|||||||
"futures",
|
"futures",
|
||||||
"hmac 0.13.0",
|
"hmac 0.13.0",
|
||||||
"hyper",
|
"hyper",
|
||||||
|
"image",
|
||||||
"libloading",
|
"libloading",
|
||||||
"notify",
|
"notify",
|
||||||
"num-bigint",
|
"num-bigint",
|
||||||
@ -2841,6 +2899,12 @@ dependencies = [
|
|||||||
"xxhash-rust",
|
"xxhash-rust",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "pxfm"
|
||||||
|
version = "0.1.29"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "quote"
|
name = "quote"
|
||||||
version = "1.0.45"
|
version = "1.0.45"
|
||||||
|
|||||||
98
pumpkin-protocol/src/bedrock/client/add_actor.rs
Normal file
98
pumpkin-protocol/src/bedrock/client/add_actor.rs
Normal file
@ -0,0 +1,98 @@
|
|||||||
|
use crate::{
|
||||||
|
codec::{var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
|
||||||
|
serial::PacketWrite,
|
||||||
|
};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
common::EntityLink,
|
||||||
|
set_actor_data::{EntityMetadata, PropertySyncData},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[packet(13)]
|
||||||
|
pub struct CAddActor {
|
||||||
|
pub entity_unique_id: VarLong,
|
||||||
|
pub entity_runtime_id: VarULong,
|
||||||
|
pub entity_type: String,
|
||||||
|
pub position: Vector3<f32>,
|
||||||
|
pub velocity: Vector3<f32>,
|
||||||
|
pub pitch: f32,
|
||||||
|
pub yaw: f32,
|
||||||
|
pub head_yaw: f32,
|
||||||
|
pub body_yaw: f32,
|
||||||
|
pub attributes: Vec<AttributeValue>,
|
||||||
|
pub metadata: EntityMetadata,
|
||||||
|
pub synced_properties: PropertySyncData,
|
||||||
|
pub links: Vec<EntityLink>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CAddActor {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.entity_unique_id.write(writer)?;
|
||||||
|
self.entity_runtime_id.write(writer)?;
|
||||||
|
self.entity_type.write(writer)?;
|
||||||
|
self.position.write(writer)?;
|
||||||
|
self.velocity.write(writer)?;
|
||||||
|
self.pitch.write(writer)?;
|
||||||
|
self.yaw.write(writer)?;
|
||||||
|
self.head_yaw.write(writer)?;
|
||||||
|
self.body_yaw.write(writer)?;
|
||||||
|
VarUInt(self.attributes.len() as u32).write(writer)?;
|
||||||
|
for attr in &self.attributes {
|
||||||
|
attr.write(writer)?;
|
||||||
|
}
|
||||||
|
self.metadata.write(writer)?;
|
||||||
|
self.synced_properties.write(writer)?;
|
||||||
|
VarUInt(self.links.len() as u32).write(writer)?;
|
||||||
|
for link in &self.links {
|
||||||
|
link.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CAddActor {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
entity_unique_id: VarLong,
|
||||||
|
entity_runtime_id: VarULong,
|
||||||
|
entity_type: String,
|
||||||
|
position: Vector3<f32>,
|
||||||
|
velocity: Vector3<f32>,
|
||||||
|
pitch: f32,
|
||||||
|
yaw: f32,
|
||||||
|
head_yaw: f32,
|
||||||
|
body_yaw: f32,
|
||||||
|
attributes: Vec<AttributeValue>,
|
||||||
|
metadata: EntityMetadata,
|
||||||
|
synced_properties: PropertySyncData,
|
||||||
|
links: Vec<EntityLink>,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_unique_id,
|
||||||
|
entity_runtime_id,
|
||||||
|
entity_type,
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw,
|
||||||
|
body_yaw,
|
||||||
|
attributes,
|
||||||
|
metadata,
|
||||||
|
synced_properties,
|
||||||
|
links,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
pub struct AttributeValue {
|
||||||
|
pub name: String,
|
||||||
|
pub min: f32,
|
||||||
|
pub value: f32,
|
||||||
|
pub max: f32,
|
||||||
|
}
|
||||||
147
pumpkin-protocol/src/bedrock/client/add_player.rs
Normal file
147
pumpkin-protocol/src/bedrock/client/add_player.rs
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
use crate::{
|
||||||
|
bedrock::network_item::NetworkItemDescriptor,
|
||||||
|
codec::{var_int::VarInt, var_uint::VarUInt, var_ulong::VarULong},
|
||||||
|
serial::PacketWrite,
|
||||||
|
};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
common::{AbilityLayer, EntityLink},
|
||||||
|
set_actor_data::EntityMetadata,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[packet(12)]
|
||||||
|
pub struct CAddPlayer {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub username: String,
|
||||||
|
pub entity_runtime_id: VarULong,
|
||||||
|
pub platform_chat_id: String,
|
||||||
|
pub position: Vector3<f32>,
|
||||||
|
pub velocity: Vector3<f32>,
|
||||||
|
pub pitch: f32,
|
||||||
|
pub yaw: f32,
|
||||||
|
pub head_yaw: f32,
|
||||||
|
pub held_item: NetworkItemDescriptor,
|
||||||
|
pub game_mode: VarInt,
|
||||||
|
pub metadata: EntityMetadata,
|
||||||
|
pub properties: EntityProperties,
|
||||||
|
pub ability_data: AbilityData,
|
||||||
|
pub links: Vec<EntityLink>,
|
||||||
|
pub device_id: String,
|
||||||
|
pub build_platform: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CAddPlayer {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.uuid.write(writer)?;
|
||||||
|
self.username.write(writer)?;
|
||||||
|
self.entity_runtime_id.write(writer)?;
|
||||||
|
self.platform_chat_id.write(writer)?;
|
||||||
|
self.position.write(writer)?;
|
||||||
|
self.velocity.write(writer)?;
|
||||||
|
self.pitch.write(writer)?;
|
||||||
|
self.yaw.write(writer)?;
|
||||||
|
self.head_yaw.write(writer)?;
|
||||||
|
self.held_item.write(writer)?;
|
||||||
|
self.game_mode.write(writer)?;
|
||||||
|
self.metadata.write(writer)?;
|
||||||
|
self.properties.write(writer)?;
|
||||||
|
self.ability_data.write(writer)?;
|
||||||
|
VarUInt(self.links.len() as u32).write(writer)?;
|
||||||
|
for link in &self.links {
|
||||||
|
link.write(writer)?;
|
||||||
|
}
|
||||||
|
self.device_id.write(writer)?;
|
||||||
|
self.build_platform.write(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CAddPlayer {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
uuid: Uuid,
|
||||||
|
username: String,
|
||||||
|
entity_runtime_id: VarULong,
|
||||||
|
platform_chat_id: String,
|
||||||
|
position: Vector3<f32>,
|
||||||
|
velocity: Vector3<f32>,
|
||||||
|
pitch: f32,
|
||||||
|
yaw: f32,
|
||||||
|
head_yaw: f32,
|
||||||
|
held_item: NetworkItemDescriptor,
|
||||||
|
game_mode: VarInt,
|
||||||
|
metadata: EntityMetadata,
|
||||||
|
properties: EntityProperties,
|
||||||
|
ability_data: AbilityData,
|
||||||
|
links: Vec<EntityLink>,
|
||||||
|
device_id: String,
|
||||||
|
build_platform: i32,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
uuid,
|
||||||
|
username,
|
||||||
|
entity_runtime_id,
|
||||||
|
platform_chat_id,
|
||||||
|
position,
|
||||||
|
velocity,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw,
|
||||||
|
held_item,
|
||||||
|
game_mode,
|
||||||
|
metadata,
|
||||||
|
properties,
|
||||||
|
ability_data,
|
||||||
|
links,
|
||||||
|
device_id,
|
||||||
|
build_platform,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct EntityProperties {
|
||||||
|
pub ints: Vec<(VarUInt, VarInt)>,
|
||||||
|
pub floats: Vec<(VarUInt, f32)>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for EntityProperties {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
VarUInt(self.ints.len() as u32).write(writer)?;
|
||||||
|
for (id, val) in &self.ints {
|
||||||
|
id.write(writer)?;
|
||||||
|
val.write(writer)?;
|
||||||
|
}
|
||||||
|
VarUInt(self.floats.len() as u32).write(writer)?;
|
||||||
|
for (id, val) in &self.floats {
|
||||||
|
id.write(writer)?;
|
||||||
|
val.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone)]
|
||||||
|
pub struct AbilityData {
|
||||||
|
pub entity_unique_id: i64,
|
||||||
|
pub player_permissions: u8,
|
||||||
|
pub command_permissions: u8,
|
||||||
|
pub layers: Vec<AbilityLayer>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for AbilityData {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.entity_unique_id.write(writer)?;
|
||||||
|
self.player_permissions.write(writer)?;
|
||||||
|
self.command_permissions.write(writer)?;
|
||||||
|
(self.layers.len() as u8).write(writer)?;
|
||||||
|
for layer in &self.layers {
|
||||||
|
layer.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
21
pumpkin-protocol/src/bedrock/client/common.rs
Normal file
21
pumpkin-protocol/src/bedrock/client/common.rs
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
use crate::{codec::var_long::VarLong, serial::PacketWrite};
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PacketWrite)]
|
||||||
|
pub struct AbilityLayer {
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default, Clone, PacketWrite)]
|
||||||
|
pub struct EntityLink {
|
||||||
|
pub ridden_unique_id: VarLong,
|
||||||
|
pub rider_unique_id: VarLong,
|
||||||
|
pub link_type: u8,
|
||||||
|
pub immediate: bool,
|
||||||
|
pub rider_initiated: bool,
|
||||||
|
pub vehicle_angular_velocity: f32,
|
||||||
|
}
|
||||||
@ -1,6 +1,9 @@
|
|||||||
|
pub mod add_actor;
|
||||||
|
pub mod add_player;
|
||||||
pub mod available_commands;
|
pub mod available_commands;
|
||||||
pub mod boss_event;
|
pub mod boss_event;
|
||||||
pub mod chunk_radius_update;
|
pub mod chunk_radius_update;
|
||||||
|
pub mod common;
|
||||||
pub mod container_open;
|
pub mod container_open;
|
||||||
pub mod correct_player_move;
|
pub mod correct_player_move;
|
||||||
pub mod creative_content;
|
pub mod creative_content;
|
||||||
@ -11,12 +14,16 @@ pub mod inventory_content;
|
|||||||
pub mod level_chunk;
|
pub mod level_chunk;
|
||||||
pub mod level_event;
|
pub mod level_event;
|
||||||
pub mod modal_form_request;
|
pub mod modal_form_request;
|
||||||
|
pub mod move_actor_absolute;
|
||||||
|
pub mod move_actor_delta;
|
||||||
pub mod move_player;
|
pub mod move_player;
|
||||||
pub mod network_chunk_publisher_update;
|
pub mod network_chunk_publisher_update;
|
||||||
pub mod network_settings;
|
pub mod network_settings;
|
||||||
pub mod play_status;
|
pub mod play_status;
|
||||||
pub mod player_hotbar;
|
pub mod player_hotbar;
|
||||||
|
pub mod player_list;
|
||||||
pub mod raknet;
|
pub mod raknet;
|
||||||
|
pub mod remove_actor;
|
||||||
pub mod resource_pack_stack;
|
pub mod resource_pack_stack;
|
||||||
pub mod resource_packs_info;
|
pub mod resource_packs_info;
|
||||||
pub mod scoreboard;
|
pub mod scoreboard;
|
||||||
@ -31,9 +38,12 @@ pub mod transfer;
|
|||||||
pub mod update_abilities;
|
pub mod update_abilities;
|
||||||
pub mod update_attributes;
|
pub mod update_attributes;
|
||||||
|
|
||||||
|
pub use add_actor::*;
|
||||||
|
pub use add_player::*;
|
||||||
pub use available_commands::*;
|
pub use available_commands::*;
|
||||||
pub use boss_event::*;
|
pub use boss_event::*;
|
||||||
pub use chunk_radius_update::*;
|
pub use chunk_radius_update::*;
|
||||||
|
pub use common::*;
|
||||||
pub use container_open::*;
|
pub use container_open::*;
|
||||||
pub use correct_player_move::*;
|
pub use correct_player_move::*;
|
||||||
pub use creative_content::*;
|
pub use creative_content::*;
|
||||||
@ -44,12 +54,16 @@ pub use inventory_content::*;
|
|||||||
pub use level_chunk::*;
|
pub use level_chunk::*;
|
||||||
pub use level_event::*;
|
pub use level_event::*;
|
||||||
pub use modal_form_request::*;
|
pub use modal_form_request::*;
|
||||||
|
pub use move_actor_absolute::*;
|
||||||
|
pub use move_actor_delta::*;
|
||||||
pub use move_player::*;
|
pub use move_player::*;
|
||||||
pub use network_chunk_publisher_update::*;
|
pub use network_chunk_publisher_update::*;
|
||||||
pub use network_settings::*;
|
pub use network_settings::*;
|
||||||
pub use play_status::*;
|
pub use play_status::*;
|
||||||
pub use player_hotbar::*;
|
pub use player_hotbar::*;
|
||||||
|
pub use player_list::*;
|
||||||
pub use raknet::*;
|
pub use raknet::*;
|
||||||
|
pub use remove_actor::*;
|
||||||
pub use resource_pack_stack::*;
|
pub use resource_pack_stack::*;
|
||||||
pub use resource_packs_info::*;
|
pub use resource_packs_info::*;
|
||||||
pub use scoreboard::*;
|
pub use scoreboard::*;
|
||||||
|
|||||||
39
pumpkin-protocol/src/bedrock/client/move_actor_absolute.rs
Normal file
39
pumpkin-protocol/src/bedrock/client/move_actor_absolute.rs
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
#[packet(18)]
|
||||||
|
pub struct CMoveActorAbsolute {
|
||||||
|
pub entity_runtime_id: VarULong,
|
||||||
|
pub flags: u8,
|
||||||
|
pub position: Vector3<f32>,
|
||||||
|
pub pitch: u8,
|
||||||
|
pub yaw: u8,
|
||||||
|
pub head_yaw: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CMoveActorAbsolute {
|
||||||
|
pub const FLAG_ON_GROUND: u8 = 0x01;
|
||||||
|
pub const FLAG_TELEPORT: u8 = 0x02;
|
||||||
|
pub const FLAG_FORCE_MOVE: u8 = 0x04;
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
entity_runtime_id: VarULong,
|
||||||
|
flags: u8,
|
||||||
|
position: Vector3<f32>,
|
||||||
|
pitch: u8,
|
||||||
|
yaw: u8,
|
||||||
|
head_yaw: u8,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_runtime_id,
|
||||||
|
flags,
|
||||||
|
position,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
78
pumpkin-protocol/src/bedrock/client/move_actor_delta.rs
Normal file
78
pumpkin-protocol/src/bedrock/client/move_actor_delta.rs
Normal file
@ -0,0 +1,78 @@
|
|||||||
|
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_X: u16 = 0x0001;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_Y: u16 = 0x0002;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_Z: u16 = 0x0004;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_PITCH: u16 = 0x0008;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_YAW: u16 = 0x0010;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_HAS_HEAD_YAW: u16 = 0x0020;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_ON_GROUND: u16 = 0x0040;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_TELEPORT: u16 = 0x0080;
|
||||||
|
pub const MOVE_ACTOR_DELTA_FLAG_FORCE_MOVE: u16 = 0x0100;
|
||||||
|
|
||||||
|
#[packet(111)]
|
||||||
|
pub struct CMoveActorDelta {
|
||||||
|
pub entity_runtime_id: VarULong,
|
||||||
|
pub flags: u16,
|
||||||
|
pub x: f32,
|
||||||
|
pub y: f32,
|
||||||
|
pub z: f32,
|
||||||
|
pub pitch: u8,
|
||||||
|
pub yaw: u8,
|
||||||
|
pub head_yaw: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CMoveActorDelta {
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
entity_runtime_id: VarULong,
|
||||||
|
flags: u16,
|
||||||
|
x: f32,
|
||||||
|
y: f32,
|
||||||
|
z: f32,
|
||||||
|
pitch: u8,
|
||||||
|
yaw: u8,
|
||||||
|
head_yaw: u8,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
entity_runtime_id,
|
||||||
|
flags,
|
||||||
|
x,
|
||||||
|
y,
|
||||||
|
z,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CMoveActorDelta {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.entity_runtime_id.write(writer)?;
|
||||||
|
self.flags.write(writer)?;
|
||||||
|
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_X != 0 {
|
||||||
|
self.x.write(writer)?;
|
||||||
|
}
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_Y != 0 {
|
||||||
|
self.y.write(writer)?;
|
||||||
|
}
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_Z != 0 {
|
||||||
|
self.z.write(writer)?;
|
||||||
|
}
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_PITCH != 0 {
|
||||||
|
self.pitch.write(writer)?;
|
||||||
|
}
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_YAW != 0 {
|
||||||
|
self.yaw.write(writer)?;
|
||||||
|
}
|
||||||
|
if self.flags & MOVE_ACTOR_DELTA_FLAG_HAS_HEAD_YAW != 0 {
|
||||||
|
self.head_yaw.write(writer)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,12 +1,11 @@
|
|||||||
|
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
use pumpkin_util::math::vector3::Vector3;
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
|
||||||
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
|
#[derive(Debug)]
|
||||||
|
|
||||||
#[derive(Debug, PacketWrite)]
|
|
||||||
#[packet(19)]
|
#[packet(19)]
|
||||||
pub struct CMovePlayer {
|
pub struct CMovePlayer {
|
||||||
// https://mojang.github.io/bedrock-protocol-docs/html/MovePlayerPacket.html
|
|
||||||
pub player_runtime_id: VarULong,
|
pub player_runtime_id: VarULong,
|
||||||
pub position: Vector3<f32>,
|
pub position: Vector3<f32>,
|
||||||
pub pitch: f32,
|
pub pitch: f32,
|
||||||
@ -15,6 +14,8 @@ pub struct CMovePlayer {
|
|||||||
pub mode: u8,
|
pub mode: u8,
|
||||||
pub on_ground: bool,
|
pub on_ground: bool,
|
||||||
pub riding_runtime_id: VarULong,
|
pub riding_runtime_id: VarULong,
|
||||||
|
pub teleport_cause: i32,
|
||||||
|
pub teleport_source_entity_type: i32,
|
||||||
pub tick: VarULong,
|
pub tick: VarULong,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,5 +23,54 @@ impl CMovePlayer {
|
|||||||
pub const MODE_NORMAL: u8 = 0;
|
pub const MODE_NORMAL: u8 = 0;
|
||||||
pub const MODE_RESET: u8 = 1;
|
pub const MODE_RESET: u8 = 1;
|
||||||
pub const MODE_TELEPORT: u8 = 2;
|
pub const MODE_TELEPORT: u8 = 2;
|
||||||
pub const MODE_PITCH: u8 = 3;
|
pub const MODE_ROTATION: u8 = 3;
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_arguments)]
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(
|
||||||
|
player_runtime_id: VarULong,
|
||||||
|
position: Vector3<f32>,
|
||||||
|
pitch: f32,
|
||||||
|
yaw: f32,
|
||||||
|
head_yaw: f32,
|
||||||
|
mode: u8,
|
||||||
|
on_ground: bool,
|
||||||
|
riding_runtime_id: VarULong,
|
||||||
|
teleport_cause: i32,
|
||||||
|
teleport_source_entity_type: i32,
|
||||||
|
tick: VarULong,
|
||||||
|
) -> Self {
|
||||||
|
Self {
|
||||||
|
player_runtime_id,
|
||||||
|
position,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw,
|
||||||
|
mode,
|
||||||
|
on_ground,
|
||||||
|
riding_runtime_id,
|
||||||
|
teleport_cause,
|
||||||
|
teleport_source_entity_type,
|
||||||
|
tick,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CMovePlayer {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.player_runtime_id.write(writer)?;
|
||||||
|
self.position.write(writer)?;
|
||||||
|
self.pitch.write(writer)?;
|
||||||
|
self.yaw.write(writer)?;
|
||||||
|
self.head_yaw.write(writer)?;
|
||||||
|
self.mode.write(writer)?;
|
||||||
|
self.on_ground.write(writer)?;
|
||||||
|
self.riding_runtime_id.write(writer)?;
|
||||||
|
if self.mode == Self::MODE_TELEPORT {
|
||||||
|
self.teleport_cause.write(writer)?;
|
||||||
|
self.teleport_source_entity_type.write(writer)?;
|
||||||
|
}
|
||||||
|
self.tick.write(writer)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
204
pumpkin-protocol/src/bedrock/client/player_list.rs
Normal file
204
pumpkin-protocol/src/bedrock/client/player_list.rs
Normal file
@ -0,0 +1,204 @@
|
|||||||
|
use crate::{
|
||||||
|
codec::{var_long::VarLong, var_uint::VarUInt},
|
||||||
|
serial::PacketWrite,
|
||||||
|
};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
use std::io::{Error, Write};
|
||||||
|
use uuid::Uuid;
|
||||||
|
|
||||||
|
#[packet(63)]
|
||||||
|
pub struct CPlayerList {
|
||||||
|
pub action: u8,
|
||||||
|
pub entries: Vec<PlayerListEntry>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for CPlayerList {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.action.write(writer)?;
|
||||||
|
VarUInt(self.entries.len() as u32).write(writer)?;
|
||||||
|
match self.action {
|
||||||
|
Self::ACTION_ADD => {
|
||||||
|
for entry in &self.entries {
|
||||||
|
entry.write(writer)?;
|
||||||
|
}
|
||||||
|
for entry in &self.entries {
|
||||||
|
entry.skin.is_trusted.write(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Self::ACTION_REMOVE => {
|
||||||
|
for entry in &self.entries {
|
||||||
|
entry.uuid.write(writer)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => return Err(Error::other("Invalid PlayerList action")),
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CPlayerList {
|
||||||
|
pub const ACTION_ADD: u8 = 0;
|
||||||
|
pub const ACTION_REMOVE: u8 = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PlayerListEntry {
|
||||||
|
pub uuid: Uuid,
|
||||||
|
pub entity_unique_id: VarLong,
|
||||||
|
pub username: String,
|
||||||
|
pub xuid: String,
|
||||||
|
pub platform_chat_id: String,
|
||||||
|
pub build_platform: i32,
|
||||||
|
pub skin: Skin,
|
||||||
|
pub is_teacher: bool,
|
||||||
|
pub is_host: bool,
|
||||||
|
pub is_sub_client: bool,
|
||||||
|
pub player_color: [u8; 4], // ARGB
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for PlayerListEntry {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.uuid.write(writer)?;
|
||||||
|
self.entity_unique_id.write(writer)?;
|
||||||
|
self.username.write(writer)?;
|
||||||
|
self.xuid.write(writer)?;
|
||||||
|
self.platform_chat_id.write(writer)?;
|
||||||
|
self.build_platform.write(writer)?;
|
||||||
|
self.skin.write(writer)?;
|
||||||
|
self.is_teacher.write(writer)?;
|
||||||
|
self.is_host.write(writer)?;
|
||||||
|
self.is_sub_client.write(writer)?;
|
||||||
|
self.player_color.write(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct Skin {
|
||||||
|
pub skin_id: String,
|
||||||
|
pub play_fab_id: String,
|
||||||
|
pub resource_patch: Vec<u8>,
|
||||||
|
pub image_width: u32,
|
||||||
|
pub image_height: u32,
|
||||||
|
pub skin_data: Vec<u8>,
|
||||||
|
pub animations: Vec<SkinAnimation>,
|
||||||
|
pub cape_width: u32,
|
||||||
|
pub cape_height: u32,
|
||||||
|
pub cape_data: Vec<u8>,
|
||||||
|
pub geometry_data: Vec<u8>,
|
||||||
|
pub animation_data: Vec<u8>,
|
||||||
|
pub geometry_data_engine_version: Vec<u8>,
|
||||||
|
pub cape_id: String,
|
||||||
|
pub full_id: String,
|
||||||
|
pub arm_size: String,
|
||||||
|
pub skin_color: String,
|
||||||
|
pub persona_pieces: Vec<PersonaPiece>,
|
||||||
|
pub piece_tint_colors: Vec<PieceTintColor>,
|
||||||
|
pub is_premium: bool,
|
||||||
|
pub is_persona: bool,
|
||||||
|
pub persona_cape_on_classic: bool,
|
||||||
|
pub is_primary_user: bool,
|
||||||
|
pub override_appearance: bool,
|
||||||
|
pub is_trusted: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Skin {
|
||||||
|
#[must_use]
|
||||||
|
pub fn steve() -> Self {
|
||||||
|
Self {
|
||||||
|
skin_id: "Standard_Custom".to_string(),
|
||||||
|
play_fab_id: String::new(),
|
||||||
|
resource_patch: r#"{"geometry":{"default":"geometry.humanoid.custom"}}"#.into(),
|
||||||
|
image_width: 64,
|
||||||
|
image_height: 64,
|
||||||
|
// 64 * 64 * 4 = 16384 bytes of raw RGBA data
|
||||||
|
skin_data: vec![0; 16384],
|
||||||
|
animations: Vec::new(),
|
||||||
|
cape_width: 0,
|
||||||
|
cape_height: 0,
|
||||||
|
cape_data: Vec::new(),
|
||||||
|
geometry_data: Vec::new(),
|
||||||
|
animation_data: Vec::new(),
|
||||||
|
geometry_data_engine_version: Vec::new(),
|
||||||
|
cape_id: String::new(),
|
||||||
|
full_id: "Standard_Custom".to_string(),
|
||||||
|
arm_size: "wide".to_string(),
|
||||||
|
skin_color: "#0".to_string(),
|
||||||
|
persona_pieces: Vec::new(),
|
||||||
|
piece_tint_colors: Vec::new(),
|
||||||
|
is_premium: false,
|
||||||
|
is_persona: false,
|
||||||
|
persona_cape_on_classic: false,
|
||||||
|
is_primary_user: false,
|
||||||
|
override_appearance: false,
|
||||||
|
is_trusted: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for Skin {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
self.skin_id.write(writer)?;
|
||||||
|
self.play_fab_id.write(writer)?;
|
||||||
|
VarUInt(self.resource_patch.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.resource_patch)?;
|
||||||
|
self.image_width.write(writer)?;
|
||||||
|
self.image_height.write(writer)?;
|
||||||
|
VarUInt(self.skin_data.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.skin_data)?;
|
||||||
|
(self.animations.len() as u32).write(writer)?;
|
||||||
|
for anim in &self.animations {
|
||||||
|
anim.write(writer)?;
|
||||||
|
}
|
||||||
|
self.cape_width.write(writer)?;
|
||||||
|
self.cape_height.write(writer)?;
|
||||||
|
VarUInt(self.cape_data.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.cape_data)?;
|
||||||
|
VarUInt(self.geometry_data.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.geometry_data)?;
|
||||||
|
VarUInt(self.animation_data.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.animation_data)?;
|
||||||
|
VarUInt(self.geometry_data_engine_version.len() as u32).write(writer)?;
|
||||||
|
writer.write_all(&self.geometry_data_engine_version)?;
|
||||||
|
self.cape_id.write(writer)?;
|
||||||
|
self.full_id.write(writer)?;
|
||||||
|
self.arm_size.write(writer)?;
|
||||||
|
self.skin_color.write(writer)?;
|
||||||
|
(self.persona_pieces.len() as u32).write(writer)?;
|
||||||
|
for piece in &self.persona_pieces {
|
||||||
|
piece.write(writer)?;
|
||||||
|
}
|
||||||
|
(self.piece_tint_colors.len() as u32).write(writer)?;
|
||||||
|
for color in &self.piece_tint_colors {
|
||||||
|
color.write(writer)?;
|
||||||
|
}
|
||||||
|
self.is_premium.write(writer)?;
|
||||||
|
self.is_persona.write(writer)?;
|
||||||
|
self.persona_cape_on_classic.write(writer)?;
|
||||||
|
self.is_primary_user.write(writer)?;
|
||||||
|
self.override_appearance.write(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PacketWrite)]
|
||||||
|
pub struct SkinAnimation {
|
||||||
|
pub image_width: u32,
|
||||||
|
pub image_height: u32,
|
||||||
|
pub image_data: Vec<u8>,
|
||||||
|
pub animation_type: u32,
|
||||||
|
pub frames: f32,
|
||||||
|
pub expression_type: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PacketWrite)]
|
||||||
|
pub struct PersonaPiece {
|
||||||
|
pub piece_id: String,
|
||||||
|
pub piece_type: String,
|
||||||
|
pub pack_id: String,
|
||||||
|
pub is_default: bool,
|
||||||
|
pub product_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, PacketWrite)]
|
||||||
|
pub struct PieceTintColor {
|
||||||
|
pub piece_type: String,
|
||||||
|
pub colors: Vec<String>,
|
||||||
|
}
|
||||||
15
pumpkin-protocol/src/bedrock/client/remove_actor.rs
Normal file
15
pumpkin-protocol/src/bedrock/client/remove_actor.rs
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
use crate::{codec::var_long::VarLong, serial::PacketWrite};
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
#[packet(14)]
|
||||||
|
pub struct CRemoveActor {
|
||||||
|
pub entity_unique_id: VarLong,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CRemoveActor {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(entity_unique_id: VarLong) -> Self {
|
||||||
|
Self { entity_unique_id }
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,3 +10,14 @@ pub struct CSetActorMotion {
|
|||||||
pub motion: Vector3<f32>,
|
pub motion: Vector3<f32>,
|
||||||
pub tick: VarULong,
|
pub tick: VarULong,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl CSetActorMotion {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new(target_runtime_id: VarULong, motion: Vector3<f32>, tick: VarULong) -> Self {
|
||||||
|
Self {
|
||||||
|
target_runtime_id,
|
||||||
|
motion,
|
||||||
|
tick,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -2,6 +2,8 @@ use pumpkin_macros::packet;
|
|||||||
|
|
||||||
use crate::serial::PacketWrite;
|
use crate::serial::PacketWrite;
|
||||||
|
|
||||||
|
use super::common::AbilityLayer;
|
||||||
|
|
||||||
#[packet(187)]
|
#[packet(187)]
|
||||||
#[derive(PacketWrite)]
|
#[derive(PacketWrite)]
|
||||||
pub struct CUpdateAbilities {
|
pub struct CUpdateAbilities {
|
||||||
@ -13,17 +15,6 @@ pub struct CUpdateAbilities {
|
|||||||
pub layers: Vec<AbilityLayer>,
|
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)]
|
#[repr(u32)]
|
||||||
pub enum Ability {
|
pub enum Ability {
|
||||||
Build = 0,
|
Build = 0,
|
||||||
|
|||||||
@ -1,4 +1,7 @@
|
|||||||
use std::io::{Error, Read, Write};
|
use std::{
|
||||||
|
io::{Error, Read, Write},
|
||||||
|
str::FromStr,
|
||||||
|
};
|
||||||
|
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
@ -7,36 +10,24 @@ use crate::{
|
|||||||
serial::{PacketRead, PacketWrite},
|
serial::{PacketRead, PacketWrite},
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
#[packet(44)]
|
|
||||||
pub struct SAnimate {
|
|
||||||
pub action: AnimateAction,
|
|
||||||
pub runtime_entity_id: VarULong,
|
|
||||||
pub boat_rowing_time: Option<f32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum AnimateAction {
|
pub enum AnimateAction {
|
||||||
NoAction = 0,
|
|
||||||
SwingArm = 1,
|
SwingArm = 1,
|
||||||
WakeUp = 2,
|
WakeUp = 2,
|
||||||
CriticalHit = 3,
|
StopSleep = 3,
|
||||||
MagicCriticalHit = 4,
|
CriticalHit = 4,
|
||||||
RowRight = 128,
|
MagicCriticalHit = 5,
|
||||||
RowLeft = 129,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketRead for AnimateAction {
|
impl PacketRead for AnimateAction {
|
||||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let action = u8::read(reader)?;
|
let action = u8::read(reader)?;
|
||||||
match action {
|
match action {
|
||||||
0 => Ok(Self::NoAction),
|
|
||||||
1 => Ok(Self::SwingArm),
|
1 => Ok(Self::SwingArm),
|
||||||
2 => Ok(Self::WakeUp),
|
2 => Ok(Self::WakeUp),
|
||||||
3 => Ok(Self::CriticalHit),
|
3 => Ok(Self::StopSleep),
|
||||||
4 => Ok(Self::MagicCriticalHit),
|
4 => Ok(Self::CriticalHit),
|
||||||
128 => Ok(Self::RowRight),
|
5 => Ok(Self::MagicCriticalHit),
|
||||||
129 => Ok(Self::RowLeft),
|
|
||||||
_ => Err(Error::other(format!("Invalid animate action ID: {action}"))),
|
_ => Err(Error::other(format!("Invalid animate action ID: {action}"))),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -48,21 +39,81 @@ impl PacketWrite for AnimateAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AnimateSwingSource {
|
||||||
|
None = 1,
|
||||||
|
Build = 2,
|
||||||
|
Mine = 3,
|
||||||
|
Interact = 4,
|
||||||
|
Attack = 5,
|
||||||
|
UseItem = 6,
|
||||||
|
ThrowItem = 7,
|
||||||
|
DropItem = 8,
|
||||||
|
Event = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for AnimateSwingSource {
|
||||||
|
type Err = Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Error> {
|
||||||
|
match s {
|
||||||
|
"none" => Ok(Self::None),
|
||||||
|
"build" => Ok(Self::Build),
|
||||||
|
"mine" => Ok(Self::Mine),
|
||||||
|
"interact" => Ok(Self::Interact),
|
||||||
|
"attack" => Ok(Self::Attack),
|
||||||
|
"useitem" => Ok(Self::UseItem),
|
||||||
|
"throwitem" => Ok(Self::ThrowItem),
|
||||||
|
"dropitem" => Ok(Self::DropItem),
|
||||||
|
"event" => Ok(Self::Event),
|
||||||
|
_ => Err(Error::other(format!("Unknown swing source: {s}"))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AnimateSwingSource {
|
||||||
|
#[must_use]
|
||||||
|
pub const fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::None => "none",
|
||||||
|
Self::Build => "build",
|
||||||
|
Self::Mine => "mine",
|
||||||
|
Self::Interact => "interact",
|
||||||
|
Self::Attack => "attack",
|
||||||
|
Self::UseItem => "useitem",
|
||||||
|
Self::ThrowItem => "throwitem",
|
||||||
|
Self::DropItem => "dropitem",
|
||||||
|
Self::Event => "event",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[packet(44)]
|
||||||
|
pub struct SAnimate {
|
||||||
|
pub action: AnimateAction,
|
||||||
|
pub runtime_entity_id: VarULong,
|
||||||
|
pub data: f32,
|
||||||
|
pub swing_source: Option<AnimateSwingSource>,
|
||||||
|
}
|
||||||
|
|
||||||
impl PacketRead for SAnimate {
|
impl PacketRead for SAnimate {
|
||||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
let action = AnimateAction::read(reader)?;
|
let action = AnimateAction::read(reader)?;
|
||||||
let runtime_entity_id = VarULong::read(reader)?;
|
let runtime_entity_id = VarULong::read(reader)?;
|
||||||
let boat_rowing_time =
|
let data = f32::read(reader)?;
|
||||||
if action == AnimateAction::RowRight || action == AnimateAction::RowLeft {
|
|
||||||
Some(f32::read(reader)?)
|
let swing_source_str = Option::<String>::read(reader)?;
|
||||||
} else {
|
let swing_source = match swing_source_str {
|
||||||
None
|
Some(s) => Some(AnimateSwingSource::from_str(&s)?),
|
||||||
};
|
None => None,
|
||||||
let _swing_source = bool::read(reader)?;
|
};
|
||||||
|
|
||||||
Ok(Self {
|
Ok(Self {
|
||||||
action,
|
action,
|
||||||
runtime_entity_id,
|
runtime_entity_id,
|
||||||
boat_rowing_time,
|
data,
|
||||||
|
swing_source,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -71,11 +122,11 @@ impl PacketWrite for SAnimate {
|
|||||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
self.action.write(writer)?;
|
self.action.write(writer)?;
|
||||||
self.runtime_entity_id.write(writer)?;
|
self.runtime_entity_id.write(writer)?;
|
||||||
if let Some(rowing_time) = self.boat_rowing_time {
|
self.data.write(writer)?;
|
||||||
rowing_time.write(writer)?;
|
|
||||||
}
|
let swing_source_str: Option<String> = self.swing_source.map(|s| s.as_str().to_string());
|
||||||
// swingSource
|
swing_source_str.write(writer)?;
|
||||||
false.write(writer)?;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -18,6 +18,8 @@ pub struct SInteraction {
|
|||||||
#[repr(i8)]
|
#[repr(i8)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
Invalid = 0,
|
Invalid = 0,
|
||||||
|
Interact = 1,
|
||||||
|
Attack = 2,
|
||||||
StopRiding = 3,
|
StopRiding = 3,
|
||||||
InteractUpdate = 4,
|
InteractUpdate = 4,
|
||||||
NpcOpen = 5,
|
NpcOpen = 5,
|
||||||
@ -31,6 +33,8 @@ impl PacketRead for Action {
|
|||||||
|
|
||||||
let this = match byte[0] {
|
let this = match byte[0] {
|
||||||
0 => Self::Invalid,
|
0 => Self::Invalid,
|
||||||
|
1 => Self::Interact,
|
||||||
|
2 => Self::Attack,
|
||||||
3 => Self::StopRiding,
|
3 => Self::StopRiding,
|
||||||
4 => Self::InteractUpdate,
|
4 => Self::InteractUpdate,
|
||||||
5 => Self::NpcOpen,
|
5 => Self::NpcOpen,
|
||||||
|
|||||||
@ -199,7 +199,7 @@ pub enum InputData {
|
|||||||
StartFlying = 42,
|
StartFlying = 42,
|
||||||
StopFlying = 43,
|
StopFlying = 43,
|
||||||
ClientAckServerData = 44,
|
ClientAckServerData = 44,
|
||||||
ClientPredictedVehicle = 45, // Renamed to match Go
|
ClientPredictedVehicle = 45,
|
||||||
PaddlingLeft = 46,
|
PaddlingLeft = 46,
|
||||||
PaddlingRight = 47,
|
PaddlingRight = 47,
|
||||||
BlockBreakingDelayEnabled = 48,
|
BlockBreakingDelayEnabled = 48,
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub type VarLongType = i64;
|
|||||||
/**
|
/**
|
||||||
* A variable-length long type used by the Minecraft network protocol.
|
* A variable-length long type used by the Minecraft network protocol.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||||
pub struct VarLong(pub VarLongType);
|
pub struct VarLong(pub VarLongType);
|
||||||
|
|
||||||
impl VarLong {
|
impl VarLong {
|
||||||
|
|||||||
@ -20,7 +20,7 @@ pub type VarULongType = u64;
|
|||||||
/**
|
/**
|
||||||
* A variable-length long type used by the Minecraft network protocol.
|
* A variable-length long type used by the Minecraft network protocol.
|
||||||
*/
|
*/
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
|
||||||
pub struct VarULong(pub VarULongType);
|
pub struct VarULong(pub VarULongType);
|
||||||
|
|
||||||
impl VarULong {
|
impl VarULong {
|
||||||
|
|||||||
@ -109,6 +109,7 @@ postcard = { workspace = true }
|
|||||||
tracing-serde-structured = { workspace = true }
|
tracing-serde-structured = { workspace = true }
|
||||||
|
|
||||||
bitflags = { workspace = true }
|
bitflags = { workspace = true }
|
||||||
|
image = { version = "0.25.10", default-features = false, features = ["png"] }
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tempfile.workspace = true
|
tempfile.workspace = true
|
||||||
|
|||||||
@ -197,7 +197,7 @@ impl Goal for MeleeAttackGoal {
|
|||||||
.await
|
.await
|
||||||
{
|
{
|
||||||
self.cooldown = self.get_max_cooldown();
|
self.cooldown = self.get_max_cooldown();
|
||||||
mob.get_mob_entity().living_entity.swing_hand();
|
mob.get_mob_entity().living_entity.swing_hand().await;
|
||||||
mob.get_mob_entity().try_attack(mob, target.as_ref()).await;
|
mob.get_mob_entity().try_attack(mob, target.as_ref()).await;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -47,8 +47,7 @@ use pumpkin_nbt::compound::NbtCompound;
|
|||||||
use pumpkin_nbt::tag::NbtTag;
|
use pumpkin_nbt::tag::NbtTag;
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
Animation, CEntityAnimation, CEntityStatus, CHurtAnimation, CSetPlayerInventory,
|
CEntityStatus, CHurtAnimation, CSetPlayerInventory, CTakeItemEntity, CUpdateMobEffect,
|
||||||
CTakeItemEntity, CUpdateMobEffect,
|
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
codec::item_stack_seralizer::ItemStackSerializer,
|
codec::item_stack_seralizer::ItemStackSerializer,
|
||||||
@ -715,15 +714,22 @@ impl LivingEntity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn swing_hand(&self) {
|
pub async fn swing_hand(&self) {
|
||||||
// TODO: radius
|
let world = self.entity.world.load();
|
||||||
self.entity
|
let entity_id = self.entity_id();
|
||||||
.world
|
|
||||||
.load()
|
let je_packet = pumpkin_protocol::java::client::play::CEntityAnimation::new(
|
||||||
.broadcast_packet_all(&CEntityAnimation::new(
|
entity_id.into(),
|
||||||
self.entity_id().into(),
|
pumpkin_protocol::java::client::play::Animation::SwingMainArm,
|
||||||
Animation::SwingMainArm,
|
);
|
||||||
));
|
let be_packet = pumpkin_protocol::bedrock::server::animate::SAnimate {
|
||||||
|
action: pumpkin_protocol::bedrock::server::animate::AnimateAction::SwingArm,
|
||||||
|
runtime_entity_id: pumpkin_protocol::codec::var_ulong::VarULong(entity_id as u64),
|
||||||
|
data: 0.0,
|
||||||
|
swing_source: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn tick_movement<'a>(&'a self, server: &'a Server, caller: &'a Arc<dyn EntityBase>) {
|
async fn tick_movement<'a>(&'a self, server: &'a Server, caller: &'a Arc<dyn EntityBase>) {
|
||||||
|
|||||||
@ -35,9 +35,18 @@ use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
|||||||
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
PositionFlag,
|
PositionFlag,
|
||||||
bedrock::client::set_actor_data::{
|
bedrock::client::{
|
||||||
CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag,
|
move_actor_delta::{
|
||||||
entity_data_key,
|
CMoveActorDelta, MOVE_ACTOR_DELTA_FLAG_HAS_HEAD_YAW, MOVE_ACTOR_DELTA_FLAG_HAS_PITCH,
|
||||||
|
MOVE_ACTOR_DELTA_FLAG_HAS_X, MOVE_ACTOR_DELTA_FLAG_HAS_Y,
|
||||||
|
MOVE_ACTOR_DELTA_FLAG_HAS_YAW, MOVE_ACTOR_DELTA_FLAG_HAS_Z,
|
||||||
|
MOVE_ACTOR_DELTA_FLAG_ON_GROUND,
|
||||||
|
},
|
||||||
|
move_player::CMovePlayer,
|
||||||
|
set_actor_data::{
|
||||||
|
CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag,
|
||||||
|
entity_data_key,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
codec::var_int::VarInt,
|
codec::var_int::VarInt,
|
||||||
codec::var_ulong::VarULong,
|
codec::var_ulong::VarULong,
|
||||||
@ -1129,6 +1138,7 @@ impl Entity {
|
|||||||
suffocating
|
suffocating
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub fn send_pos_rot(&self) {
|
pub fn send_pos_rot(&self) {
|
||||||
let old = self.last_sent_pos.load();
|
let old = self.last_sent_pos.load();
|
||||||
let new = self.pos.load();
|
let new = self.pos.load();
|
||||||
@ -1161,35 +1171,150 @@ impl Entity {
|
|||||||
|
|
||||||
// Dynamically pick the most efficient packet
|
// Dynamically pick the most efficient packet
|
||||||
if pos_changed && rot_changed {
|
if pos_changed && rot_changed {
|
||||||
self.world.load().broadcast_to_chunk(
|
let je_packet = CUpdateEntityPosRot::new(
|
||||||
chunk_pos,
|
self.entity_id.into(),
|
||||||
&CUpdateEntityPosRot::new(
|
Vector3::new(converted.x, converted.y, converted.z),
|
||||||
self.entity_id.into(),
|
yaw,
|
||||||
Vector3::new(converted.x, converted.y, converted.z),
|
pitch,
|
||||||
yaw,
|
self.on_ground.load(Relaxed),
|
||||||
pitch,
|
|
||||||
self.on_ground.load(Relaxed),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if self.entity_type == &EntityType::PLAYER {
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
Vector3::new(new.x as f32, new.y as f32, new.z as f32),
|
||||||
|
self.pitch.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
CMovePlayer::MODE_NORMAL,
|
||||||
|
self.on_ground.load(Relaxed),
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut flags = MOVE_ACTOR_DELTA_FLAG_HAS_X
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Y
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Z
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_PITCH
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_YAW
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_HEAD_YAW;
|
||||||
|
if self.on_ground.load(Relaxed) {
|
||||||
|
flags |= MOVE_ACTOR_DELTA_FLAG_ON_GROUND;
|
||||||
|
}
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMoveActorDelta::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
flags,
|
||||||
|
new.x as f32,
|
||||||
|
new.y as f32,
|
||||||
|
new.z as f32,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
yaw,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if pos_changed {
|
} else if pos_changed {
|
||||||
self.world.load().broadcast_to_chunk(
|
let je_packet = CUpdateEntityPos::new(
|
||||||
chunk_pos,
|
self.entity_id.into(),
|
||||||
&CUpdateEntityPos::new(
|
Vector3::new(converted.x, converted.y, converted.z),
|
||||||
self.entity_id.into(),
|
self.on_ground.load(Relaxed),
|
||||||
Vector3::new(converted.x, converted.y, converted.z),
|
|
||||||
self.on_ground.load(Relaxed),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if self.entity_type == &EntityType::PLAYER {
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
Vector3::new(new.x as f32, new.y as f32, new.z as f32),
|
||||||
|
self.pitch.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
CMovePlayer::MODE_NORMAL,
|
||||||
|
self.on_ground.load(Relaxed),
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut flags = MOVE_ACTOR_DELTA_FLAG_HAS_X
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Y
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Z;
|
||||||
|
if self.on_ground.load(Relaxed) {
|
||||||
|
flags |= MOVE_ACTOR_DELTA_FLAG_ON_GROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMoveActorDelta::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
flags,
|
||||||
|
new.x as f32,
|
||||||
|
new.y as f32,
|
||||||
|
new.z as f32,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
} else if rot_changed {
|
} else if rot_changed {
|
||||||
self.world.load().broadcast_to_chunk(
|
let je_packet = CUpdateEntityRot::new(
|
||||||
chunk_pos,
|
self.entity_id.into(),
|
||||||
&CUpdateEntityRot::new(
|
yaw,
|
||||||
self.entity_id.into(),
|
pitch,
|
||||||
yaw,
|
self.on_ground.load(Relaxed),
|
||||||
pitch,
|
|
||||||
self.on_ground.load(Relaxed),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
if self.entity_type == &EntityType::PLAYER {
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
Vector3::new(new.x as f32, new.y as f32, new.z as f32),
|
||||||
|
self.pitch.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
CMovePlayer::MODE_ROTATION,
|
||||||
|
self.on_ground.load(Relaxed),
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut flags = MOVE_ACTOR_DELTA_FLAG_HAS_PITCH
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_YAW
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_HEAD_YAW;
|
||||||
|
if self.on_ground.load(Relaxed) {
|
||||||
|
flags |= MOVE_ACTOR_DELTA_FLAG_ON_GROUND;
|
||||||
|
}
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMoveActorDelta::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
flags,
|
||||||
|
new.x as f32,
|
||||||
|
new.y as f32,
|
||||||
|
new.z as f32,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
yaw,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
self.send_head_rot(yaw);
|
self.send_head_rot(yaw);
|
||||||
}
|
}
|
||||||
@ -1220,14 +1345,53 @@ impl Entity {
|
|||||||
|
|
||||||
self.last_sent_pos.store(new);
|
self.last_sent_pos.store(new);
|
||||||
|
|
||||||
self.world.load().broadcast_to_chunk(
|
let je_packet = CUpdateEntityPos::new(
|
||||||
chunk_pos,
|
self.entity_id.into(),
|
||||||
&CUpdateEntityPos::new(
|
Vector3::new(converted.x, converted.y, converted.z),
|
||||||
self.entity_id.into(),
|
self.on_ground.load(Relaxed),
|
||||||
Vector3::new(converted.x, converted.y, converted.z),
|
|
||||||
self.on_ground.load(Relaxed),
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if self.entity_type == &EntityType::PLAYER {
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
Vector3::new(new.x as f32, new.y as f32, new.z as f32),
|
||||||
|
self.pitch.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
self.yaw.load(),
|
||||||
|
CMovePlayer::MODE_NORMAL,
|
||||||
|
self.on_ground.load(Relaxed),
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
let mut flags = MOVE_ACTOR_DELTA_FLAG_HAS_X
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Y
|
||||||
|
| MOVE_ACTOR_DELTA_FLAG_HAS_Z;
|
||||||
|
if self.on_ground.load(Relaxed) {
|
||||||
|
flags |= MOVE_ACTOR_DELTA_FLAG_ON_GROUND;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.world.load().broadcast_to_chunk_editioned_sync(
|
||||||
|
chunk_pos,
|
||||||
|
&je_packet,
|
||||||
|
&CMoveActorDelta::new(
|
||||||
|
VarULong(self.entity_id as u64),
|
||||||
|
flags,
|
||||||
|
new.x as f32,
|
||||||
|
new.y as f32,
|
||||||
|
new.z as f32,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// updateWaterState() in yarn
|
// updateWaterState() in yarn
|
||||||
|
|||||||
@ -15,12 +15,11 @@ use pumpkin_data::dimension::Dimension;
|
|||||||
use pumpkin_data::meta_data_type::MetaDataType;
|
use pumpkin_data::meta_data_type::MetaDataType;
|
||||||
use pumpkin_data::tracked_data::TrackedData;
|
use pumpkin_data::tracked_data::TrackedData;
|
||||||
use pumpkin_inventory::player::ender_chest_inventory::EnderChestInventory;
|
use pumpkin_inventory::player::ender_chest_inventory::EnderChestInventory;
|
||||||
|
use pumpkin_protocol::bedrock::client::AbilityLayer;
|
||||||
use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk;
|
use pumpkin_protocol::bedrock::client::level_chunk::CLevelChunk;
|
||||||
use pumpkin_protocol::bedrock::client::play_status::CPlayStatus;
|
use pumpkin_protocol::bedrock::client::play_status::CPlayStatus;
|
||||||
use pumpkin_protocol::bedrock::client::set_time::CSetTime;
|
use pumpkin_protocol::bedrock::client::set_time::CSetTime;
|
||||||
use pumpkin_protocol::bedrock::client::update_abilities::{
|
use pumpkin_protocol::bedrock::client::update_abilities::{Ability, CUpdateAbilities};
|
||||||
Ability, AbilityLayer, CUpdateAbilities,
|
|
||||||
};
|
|
||||||
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
||||||
use pumpkin_protocol::bedrock::server::text::SText;
|
use pumpkin_protocol::bedrock::server::text::SText;
|
||||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||||
@ -493,9 +492,66 @@ pub struct Player {
|
|||||||
pub tab_list_listed: AtomicBool,
|
pub tab_list_listed: AtomicBool,
|
||||||
pub enchantment_seed: AtomicI32,
|
pub enchantment_seed: AtomicI32,
|
||||||
pub fishing_bobber: AtomicI32,
|
pub fishing_bobber: AtomicI32,
|
||||||
|
pub bedrock_skin: arc_swap::ArcSwap<pumpkin_protocol::bedrock::client::Skin>,
|
||||||
|
}
|
||||||
|
|
||||||
|
use base64::prelude::*;
|
||||||
|
use pumpkin_protocol::Property;
|
||||||
|
use serde::Deserialize;
|
||||||
|
use std::io::Read;
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct TexturesProperty {
|
||||||
|
textures: Textures,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct Textures {
|
||||||
|
#[serde(rename = "SKIN")]
|
||||||
|
skin: Option<SkinTexture>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SkinTexture {
|
||||||
|
url: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Player {
|
impl Player {
|
||||||
|
#[must_use]
|
||||||
|
pub fn fetch_skin(properties: &[Property]) -> Option<pumpkin_protocol::bedrock::client::Skin> {
|
||||||
|
let textures_prop = properties.iter().find(|p| &*p.name == "textures")?;
|
||||||
|
let decoded = BASE64_STANDARD
|
||||||
|
.decode(textures_prop.value.as_bytes())
|
||||||
|
.ok()?;
|
||||||
|
let textures: TexturesProperty = serde_json::from_slice(&decoded).ok()?;
|
||||||
|
let url = textures.textures.skin?.url;
|
||||||
|
|
||||||
|
let resp = ureq::get(&url).call().ok()?;
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
resp.into_body().into_reader().read_to_end(&mut buf).ok()?;
|
||||||
|
let img = image::load_from_memory(&buf).ok()?;
|
||||||
|
|
||||||
|
let width = img.width();
|
||||||
|
let mut height = img.height();
|
||||||
|
|
||||||
|
if width != 64 || (height != 32 && height != 64) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut rgba = img.into_rgba8().into_raw();
|
||||||
|
|
||||||
|
if height == 32 {
|
||||||
|
rgba.resize(64 * 64 * 4, 0);
|
||||||
|
height = 64;
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut skin = pumpkin_protocol::bedrock::client::Skin::steve();
|
||||||
|
skin.image_width = width;
|
||||||
|
skin.image_height = height;
|
||||||
|
skin.skin_data = rgba;
|
||||||
|
Some(skin)
|
||||||
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn new(
|
pub async fn new(
|
||||||
client: ClientPlatform,
|
client: ClientPlatform,
|
||||||
@ -544,6 +600,14 @@ impl Player {
|
|||||||
let mut abilities = Abilities::default();
|
let mut abilities = Abilities::default();
|
||||||
abilities.set_for_gamemode(gamemode);
|
abilities.set_for_gamemode(gamemode);
|
||||||
|
|
||||||
|
let properties = gameprofile.properties.load().clone();
|
||||||
|
let bedrock_skin = tokio::task::spawn_blocking(move || {
|
||||||
|
Self::fetch_skin(&properties)
|
||||||
|
.unwrap_or_else(pumpkin_protocol::bedrock::client::Skin::steve)
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
.unwrap_or_else(|_| pumpkin_protocol::bedrock::client::Skin::steve());
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
living_entity,
|
living_entity,
|
||||||
config: ArcSwap::new(Arc::new(config)),
|
config: ArcSwap::new(Arc::new(config)),
|
||||||
@ -633,6 +697,7 @@ impl Player {
|
|||||||
tab_list_latency: AtomicI32::new(0),
|
tab_list_latency: AtomicI32::new(0),
|
||||||
tab_list_listed: AtomicBool::new(false),
|
tab_list_listed: AtomicBool::new(false),
|
||||||
fishing_bobber: AtomicI32::new(-1),
|
fishing_bobber: AtomicI32::new(-1),
|
||||||
|
bedrock_skin: ArcSwap::new(Arc::new(bedrock_skin)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3545,21 +3610,33 @@ impl Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Swing the hand of the player
|
/// Swing the hand of the player
|
||||||
pub fn swing_hand(&self, hand: Hand, all: bool) {
|
pub async fn swing_hand(&self, hand: Hand, all: bool) {
|
||||||
let world = self.world();
|
let world = self.world();
|
||||||
let entity_id = VarInt(self.entity_id());
|
let entity_id = self.entity_id();
|
||||||
let chunk_pos = self.living_entity.entity.chunk_pos.load();
|
|
||||||
|
|
||||||
let animation = match hand {
|
let animation = match hand {
|
||||||
Hand::Left => Animation::SwingMainArm,
|
Hand::Right => Animation::SwingMainArm,
|
||||||
Hand::Right => Animation::SwingOffhand,
|
Hand::Left => Animation::SwingOffhand,
|
||||||
|
};
|
||||||
|
|
||||||
|
let je_packet = pumpkin_protocol::java::client::play::CEntityAnimation::new(
|
||||||
|
VarInt(entity_id),
|
||||||
|
animation,
|
||||||
|
);
|
||||||
|
|
||||||
|
let be_packet = pumpkin_protocol::bedrock::server::animate::SAnimate {
|
||||||
|
action: pumpkin_protocol::bedrock::server::animate::AnimateAction::SwingArm,
|
||||||
|
runtime_entity_id: pumpkin_protocol::codec::var_ulong::VarULong(entity_id as u64),
|
||||||
|
data: 0.0,
|
||||||
|
swing_source: None,
|
||||||
};
|
};
|
||||||
|
|
||||||
let packet = CEntityAnimation::new(entity_id, animation);
|
|
||||||
if all {
|
if all {
|
||||||
world.broadcast_to_chunk(chunk_pos, &packet);
|
world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||||
} else {
|
} else {
|
||||||
world.broadcast_to_chunk_except(chunk_pos, &[self.get_entity().entity_uuid], &packet);
|
world
|
||||||
|
.broadcast_packet_except_editioned(&[self.gameprofile.id], &je_packet, &be_packet)
|
||||||
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,8 +5,8 @@ use crate::crash::CrashReport;
|
|||||||
use crate::data::VanillaData;
|
use crate::data::VanillaData;
|
||||||
use crate::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper};
|
use crate::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper};
|
||||||
use crate::net::bedrock::BedrockClient;
|
use crate::net::bedrock::BedrockClient;
|
||||||
use crate::net::java::{JavaClient, PacketHandlerResult};
|
use crate::net::java::JavaClient;
|
||||||
use crate::net::{ClientPlatform, DisconnectReason};
|
use crate::net::{ClientPlatform, DisconnectReason, PacketHandlerResult};
|
||||||
use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
|
use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
|
||||||
use crate::server::{Server, ticker::Ticker};
|
use crate::server::{Server, ticker::Ticker};
|
||||||
use plugin::server::server_command::ServerCommandEvent;
|
use plugin::server::server_command::ServerCommandEvent;
|
||||||
@ -418,6 +418,7 @@ impl PumpkinServer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::too_many_lines)]
|
||||||
pub async fn unified_listener_task(
|
pub async fn unified_listener_task(
|
||||||
&self,
|
&self,
|
||||||
master_client_id_counter: &mut u64,
|
master_client_id_counter: &mut u64,
|
||||||
@ -501,7 +502,9 @@ impl PumpkinServer {
|
|||||||
let be_clients = bedrock_clients.clone();
|
let be_clients = bedrock_clients.clone();
|
||||||
let mut clients_guard = bedrock_clients.lock().await;
|
let mut clients_guard = bedrock_clients.lock().await;
|
||||||
|
|
||||||
|
let mut is_new = false;
|
||||||
let client = clients_guard.entry(client_addr).or_insert_with(|| {
|
let client = clients_guard.entry(client_addr).or_insert_with(|| {
|
||||||
|
is_new = true;
|
||||||
*master_client_id_counter += 1;
|
*master_client_id_counter += 1;
|
||||||
|
|
||||||
let new_client = Arc::new(BedrockClient::new(
|
let new_client = Arc::new(BedrockClient::new(
|
||||||
@ -514,6 +517,39 @@ impl PumpkinServer {
|
|||||||
new_client
|
new_client
|
||||||
}).clone();
|
}).clone();
|
||||||
|
|
||||||
|
if is_new {
|
||||||
|
let server_clone = self.server.clone();
|
||||||
|
let client_clone = client.clone();
|
||||||
|
tasks.spawn(async move {
|
||||||
|
let login_result = client_clone.handle_login_sequence(&server_clone).await;
|
||||||
|
|
||||||
|
match login_result {
|
||||||
|
PacketHandlerResult::Stop => {
|
||||||
|
client_clone.close().await;
|
||||||
|
}
|
||||||
|
PacketHandlerResult::ReadyToPlay(profile, config) => {
|
||||||
|
if let Some((player, _world)) = server_clone
|
||||||
|
.add_player(ClientPlatform::Bedrock(client_clone.clone()), profile, Some(config))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
*client_clone.player.lock().await = Some(player.clone());
|
||||||
|
|
||||||
|
client_clone.progress_player_packets(&player, &server_clone).await;
|
||||||
|
|
||||||
|
client_clone.close().await;
|
||||||
|
player.remove().await;
|
||||||
|
server_clone.remove_player(&player).await;
|
||||||
|
if let Err(e) = server_clone.player_data_storage
|
||||||
|
.handle_player_leave(&player)
|
||||||
|
.await {
|
||||||
|
error!("Failed to save player data on disconnect: {e}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
let packet_bytes = udp_buf[..len].to_vec();
|
let packet_bytes = udp_buf[..len].to_vec();
|
||||||
let server = self.server.clone();
|
let server = self.server.clone();
|
||||||
|
|
||||||
|
|||||||
@ -1,5 +1,7 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
net::{
|
||||||
|
DisconnectReason, GameProfile, PacketHandlerResult, PlayerConfig, bedrock::BedrockClient,
|
||||||
|
},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
@ -23,7 +25,7 @@ use serde::{Deserialize, de::Error};
|
|||||||
use serde_repr::Deserialize_repr;
|
use serde_repr::Deserialize_repr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
use tracing::{debug, warn};
|
use tracing::debug;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
@ -117,27 +119,19 @@ impl BedrockClient {
|
|||||||
self.set_compression(compression).await;
|
self.set_compression(compression).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_login(self: &Arc<Self>, packet: SLogin, server: &Server) -> Option<()> {
|
pub async fn handle_login(
|
||||||
match self.try_handle_login(packet, server).await {
|
self: &Arc<Self>,
|
||||||
Ok(()) => Some(()),
|
packet: SLogin,
|
||||||
Err(error) => {
|
server: &Server,
|
||||||
warn!("Bedrock login failed: {error}");
|
) -> Result<PacketHandlerResult, LoginError> {
|
||||||
let message = match error {
|
self.try_handle_login(packet, server).await
|
||||||
LoginError::InvalidUsername => "Your username is invalid.".to_string(),
|
|
||||||
_ => "Failed to log in. The data sent by your client was invalid.".to_string(),
|
|
||||||
};
|
|
||||||
self.kick(DisconnectReason::LoginPacketNoRequest, message)
|
|
||||||
.await;
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn try_handle_login(
|
pub async fn try_handle_login(
|
||||||
self: &Arc<Self>,
|
self: &Arc<Self>,
|
||||||
packet: SLogin,
|
packet: SLogin,
|
||||||
server: &Server,
|
server: &Server,
|
||||||
) -> Result<(), LoginError> {
|
) -> Result<PacketHandlerResult, LoginError> {
|
||||||
let auth_payload: AuthPayload = serde_json::from_slice(&packet.jwt)?;
|
let auth_payload: AuthPayload = serde_json::from_slice(&packet.jwt)?;
|
||||||
let player_data = if server.basic_config.online_mode {
|
let player_data = if server.basic_config.online_mode {
|
||||||
match auth_payload.authentication_type {
|
match auth_payload.authentication_type {
|
||||||
@ -222,26 +216,15 @@ impl BedrockClient {
|
|||||||
|
|
||||||
self.send_frame_set(frame_set, 0x84).await;
|
self.send_frame_set(frame_set, 0x84).await;
|
||||||
|
|
||||||
if let Some((player, _world)) = server
|
let new_config = PlayerConfig {
|
||||||
.add_player(ClientPlatform::Bedrock(self.clone()), profile, None)
|
locale: client_data.language_code.clone(),
|
||||||
.await
|
..Default::default()
|
||||||
{
|
};
|
||||||
// TODO: kinda sad we don't use more of client_data, we should store it somewhere, at least for plugin devs
|
|
||||||
let new_config = PlayerConfig {
|
|
||||||
locale: client_data.language_code.clone(),
|
|
||||||
..Default::default()
|
|
||||||
};
|
|
||||||
|
|
||||||
player.config.store(std::sync::Arc::new(new_config));
|
self.client_data
|
||||||
|
.store(std::sync::Arc::new(Some(std::sync::Arc::new(client_data))));
|
||||||
|
|
||||||
self.client_data
|
Ok(PacketHandlerResult::ReadyToPlay(profile, new_config))
|
||||||
.store(std::sync::Arc::new(Some(std::sync::Arc::new(client_data))));
|
|
||||||
|
|
||||||
// player spawn happens after resource packs are resolved
|
|
||||||
*self.player.lock().await = Some(player);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_resource_pack_response(
|
pub async fn handle_resource_pack_response(
|
||||||
|
|||||||
@ -57,8 +57,8 @@ use pumpkin_protocol::{
|
|||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
use tokio::{
|
use tokio::{
|
||||||
net::UdpSocket,
|
net::UdpSocket,
|
||||||
sync::Mutex,
|
|
||||||
sync::mpsc::{Receiver, Sender},
|
sync::mpsc::{Receiver, Sender},
|
||||||
|
sync::{Mutex, oneshot},
|
||||||
task::JoinHandle,
|
task::JoinHandle,
|
||||||
};
|
};
|
||||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||||
@ -67,11 +67,36 @@ pub mod connection;
|
|||||||
pub mod login;
|
pub mod login;
|
||||||
pub mod open_connection;
|
pub mod open_connection;
|
||||||
pub mod unconnected;
|
pub mod unconnected;
|
||||||
use crate::{entity::player::Player, net::DisconnectReason, server::Server};
|
use crate::{
|
||||||
|
entity::player::Player,
|
||||||
|
net::{DisconnectReason, PacketHandlerResult},
|
||||||
|
server::Server,
|
||||||
|
};
|
||||||
use arc_swap::ArcSwap;
|
use arc_swap::ArcSwap;
|
||||||
use pumpkin_protocol::bedrock::server::login::ClientData;
|
use pumpkin_protocol::bedrock::server::login::ClientData;
|
||||||
use pumpkin_util::version::BedrockMinecraftVersion;
|
use pumpkin_util::version::BedrockMinecraftVersion;
|
||||||
|
|
||||||
|
pub struct OutgoingPacket {
|
||||||
|
pub data: Bytes,
|
||||||
|
pub completion: Option<oneshot::Sender<()>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OutgoingPacket {
|
||||||
|
pub const fn normal(data: Bytes) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
completion: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub const fn priority(data: Bytes, completion: oneshot::Sender<()>) -> Self {
|
||||||
|
Self {
|
||||||
|
data,
|
||||||
|
completion: Some(completion),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub struct BedrockClient {
|
pub struct BedrockClient {
|
||||||
socket: Arc<UdpSocket>,
|
socket: Arc<UdpSocket>,
|
||||||
/// The client's IP address.
|
/// The client's IP address.
|
||||||
@ -84,9 +109,12 @@ pub struct BedrockClient {
|
|||||||
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||||
|
|
||||||
tasks: TaskTracker,
|
tasks: TaskTracker,
|
||||||
outgoing_packet_queue_send: Sender<Bytes>,
|
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||||
/// A queue of serialized packets to send to the network
|
/// A queue of serialized packets to send to the network
|
||||||
outgoing_packet_queue_recv: Mutex<Option<Receiver<Bytes>>>,
|
outgoing_packet_queue_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||||
|
|
||||||
|
outgoing_packet_priority_send: Sender<OutgoingPacket>,
|
||||||
|
outgoing_packet_priority_recv: Mutex<Option<Receiver<OutgoingPacket>>>,
|
||||||
|
|
||||||
/// The packet encoder for outgoing packets.
|
/// The packet encoder for outgoing packets.
|
||||||
network_writer: Arc<Mutex<UDPNetworkEncoder>>,
|
network_writer: Arc<Mutex<UDPNetworkEncoder>>,
|
||||||
@ -114,6 +142,8 @@ pub struct BedrockClient {
|
|||||||
expected_order_index: Mutex<HashMap<u8, u32>>,
|
expected_order_index: Mutex<HashMap<u8, u32>>,
|
||||||
highest_sequence_index: Mutex<HashMap<u8, u32>>,
|
highest_sequence_index: Mutex<HashMap<u8, u32>>,
|
||||||
ordered_queues: Mutex<HashMap<u8, BTreeMap<u32, Frame>>>,
|
ordered_queues: Mutex<HashMap<u8, BTreeMap<u32, Frame>>>,
|
||||||
|
incoming_game_packet_send: Sender<RawPacket>,
|
||||||
|
incoming_game_packet_recv: Mutex<Option<Receiver<RawPacket>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl BedrockClient {
|
impl BedrockClient {
|
||||||
@ -124,6 +154,8 @@ impl BedrockClient {
|
|||||||
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
let (send, recv) = tokio::sync::mpsc::channel(128);
|
let (send, recv) = tokio::sync::mpsc::channel(128);
|
||||||
|
let (priority_send, priority_recv) = tokio::sync::mpsc::channel(128);
|
||||||
|
let (incoming_send, incoming_recv) = tokio::sync::mpsc::channel(128);
|
||||||
Self {
|
Self {
|
||||||
socket,
|
socket,
|
||||||
player: Mutex::new(None),
|
player: Mutex::new(None),
|
||||||
@ -136,6 +168,8 @@ impl BedrockClient {
|
|||||||
tasks: TaskTracker::new(),
|
tasks: TaskTracker::new(),
|
||||||
outgoing_packet_queue_send: send,
|
outgoing_packet_queue_send: send,
|
||||||
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
outgoing_packet_queue_recv: Mutex::new(Some(recv)),
|
||||||
|
outgoing_packet_priority_send: priority_send,
|
||||||
|
outgoing_packet_priority_recv: Mutex::new(Some(priority_recv)),
|
||||||
_use_frame_sets: AtomicBool::new(false),
|
_use_frame_sets: AtomicBool::new(false),
|
||||||
output_sequence_number: AtomicU32::new(0),
|
output_sequence_number: AtomicU32::new(0),
|
||||||
output_reliable_number: AtomicU32::new(0),
|
output_reliable_number: AtomicU32::new(0),
|
||||||
@ -153,6 +187,17 @@ impl BedrockClient {
|
|||||||
highest_sequence_index: Mutex::new(HashMap::new()),
|
highest_sequence_index: Mutex::new(HashMap::new()),
|
||||||
ordered_queues: Mutex::new(HashMap::new()),
|
ordered_queues: Mutex::new(HashMap::new()),
|
||||||
//input_sequence_number: AtomicU32::new(0),
|
//input_sequence_number: AtomicU32::new(0),
|
||||||
|
incoming_game_packet_send: incoming_send,
|
||||||
|
incoming_game_packet_recv: Mutex::new(Some(incoming_recv)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn get_packet(&self) -> Option<RawPacket> {
|
||||||
|
let mut guard = self.incoming_game_packet_recv.lock().await;
|
||||||
|
let recv = guard.as_mut()?;
|
||||||
|
tokio::select! {
|
||||||
|
() = self.await_close_interrupt() => None,
|
||||||
|
packet = recv.recv() => packet,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -165,16 +210,25 @@ impl BedrockClient {
|
|||||||
.take()
|
.take()
|
||||||
.expect("Outgoing packet receiver was already taken")
|
.expect("Outgoing packet receiver was already taken")
|
||||||
};
|
};
|
||||||
|
let mut priority_packet_receiver = {
|
||||||
|
let mut guard = client.outgoing_packet_priority_recv.lock().await;
|
||||||
|
guard
|
||||||
|
.take()
|
||||||
|
.expect("Outgoing packet receiver was already taken")
|
||||||
|
};
|
||||||
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
|
let mut interval = tokio::time::interval(std::time::Duration::from_millis(100));
|
||||||
|
|
||||||
while !client.close_token.is_cancelled() {
|
while !client.close_token.is_cancelled() {
|
||||||
tokio::select! {
|
let recv_result = tokio::select! {
|
||||||
|
biased;
|
||||||
|
() = client.close_token.cancelled() => None,
|
||||||
|
res = priority_packet_receiver.recv() => res,
|
||||||
_ = interval.tick() => {
|
_ = interval.tick() => {
|
||||||
// Check for timeout (10 seconds)
|
// Check for timeout (10 seconds)
|
||||||
if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) {
|
if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) {
|
||||||
debug!("Bedrock client {} timed out", client.address);
|
debug!("Bedrock client {} timed out", client.address);
|
||||||
client.close().await;
|
client.close().await;
|
||||||
break;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Flush ACKs
|
// Flush ACKs
|
||||||
@ -207,23 +261,21 @@ impl BedrockClient {
|
|||||||
warn!("Failed to resend packet for sequence {}: {}", seq, err);
|
warn!("Failed to resend packet for sequence {}: {}", seq, err);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
continue;
|
||||||
}
|
}
|
||||||
recv_result = packet_receiver.recv() => {
|
res = packet_receiver.recv() => res,
|
||||||
let Some(packet_data) = recv_result else {
|
};
|
||||||
break;
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Err(err) = client.network_writer
|
let Some(packet) = recv_result else {
|
||||||
.lock()
|
break;
|
||||||
.await
|
};
|
||||||
.write_packet(&packet_data, client.address, &client.socket)
|
|
||||||
.await
|
client
|
||||||
&& !client.close_token.is_cancelled() {
|
.send_framed_packet_data(packet.data.to_vec(), RakReliability::ReliableOrdered)
|
||||||
warn!("Failed to send packet to client: {err}",);
|
.await;
|
||||||
client.close_token.cancel();
|
|
||||||
break;
|
if let Some(completion) = packet.completion {
|
||||||
}
|
let _ = completion.send(());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -259,6 +311,43 @@ impl BedrockClient {
|
|||||||
self.close().await;
|
self.close().await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn enqueue_packet<P: BClientPacket>(&self, packet: &P) {
|
||||||
|
let mut packet_buf = Vec::new();
|
||||||
|
match self.write_game_packet(packet, &mut packet_buf).await {
|
||||||
|
Ok(()) => self.enqueue_packet_data(packet_buf.into()).await,
|
||||||
|
Err(err) => error!("Failed to write game packet: {err}"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_enqueue_packet<P: BClientPacket>(&self, packet: &P) {
|
||||||
|
let mut packet_buf = Vec::new();
|
||||||
|
let mut packet_payload = Vec::new();
|
||||||
|
if let Err(err) = packet.write_packet(&mut packet_payload) {
|
||||||
|
error!("Failed to write packet for try_enqueue_packet: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let Ok(mut network_writer) = self.network_writer.try_lock() else {
|
||||||
|
debug!("Failed to lock network writer for try_enqueue_packet");
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Err(err) = network_writer.write_game_packet(
|
||||||
|
P::PACKET_ID as u16,
|
||||||
|
SubClient::Main,
|
||||||
|
SubClient::Main,
|
||||||
|
&packet_payload,
|
||||||
|
&mut packet_buf,
|
||||||
|
) {
|
||||||
|
error!("Failed to write game packet for try_enqueue_packet: {err}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
self.try_enqueue_packet_data(packet_buf.into());
|
||||||
|
}
|
||||||
|
|
||||||
/// Queues a clientbound packet to be sent to the connected client. Queued chunks are sent
|
/// Queues a clientbound packet to be sent to the connected client. Queued chunks are sent
|
||||||
/// in-order to the client
|
/// in-order to the client
|
||||||
///
|
///
|
||||||
@ -266,7 +355,11 @@ impl BedrockClient {
|
|||||||
///
|
///
|
||||||
/// * `packet`: A reference to a packet object implementing the `ClientPacket` trait.
|
/// * `packet`: A reference to a packet object implementing the `ClientPacket` trait.
|
||||||
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
pub async fn enqueue_packet_data(&self, packet_data: Bytes) {
|
||||||
if let Err(err) = self.outgoing_packet_queue_send.send(packet_data).await {
|
if let Err(err) = self
|
||||||
|
.outgoing_packet_queue_send
|
||||||
|
.send(OutgoingPacket::normal(packet_data))
|
||||||
|
.await
|
||||||
|
{
|
||||||
// This is expected to fail if we are closed
|
// This is expected to fail if we are closed
|
||||||
if !self.is_closed() {
|
if !self.is_closed() {
|
||||||
error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
error!("Failed to add packet to the outgoing packet queue for client: {err}");
|
||||||
@ -275,7 +368,10 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
|
pub fn try_enqueue_packet_data(&self, packet_data: Bytes) {
|
||||||
if let Err(err) = self.outgoing_packet_queue_send.try_send(packet_data) {
|
if let Err(err) = self
|
||||||
|
.outgoing_packet_queue_send
|
||||||
|
.try_send(OutgoingPacket::normal(packet_data))
|
||||||
|
{
|
||||||
match err {
|
match err {
|
||||||
tokio::sync::mpsc::error::TrySendError::Full(_) => {
|
tokio::sync::mpsc::error::TrySendError::Full(_) => {
|
||||||
debug!(
|
debug!(
|
||||||
@ -309,8 +405,8 @@ impl BedrockClient {
|
|||||||
let mut packet_payload = Vec::new();
|
let mut packet_payload = Vec::new();
|
||||||
packet.write_packet(&mut packet_payload)?;
|
packet.write_packet(&mut packet_payload)?;
|
||||||
|
|
||||||
// TODO
|
let mut encoder = self.network_writer.lock().await;
|
||||||
self.network_writer.lock().await.write_game_packet(
|
encoder.write_game_packet(
|
||||||
P::PACKET_ID as u16,
|
P::PACKET_ID as u16,
|
||||||
SubClient::Main,
|
SubClient::Main,
|
||||||
SubClient::Main,
|
SubClient::Main,
|
||||||
@ -337,8 +433,18 @@ impl BedrockClient {
|
|||||||
let mut packet_buf = Vec::new();
|
let mut packet_buf = Vec::new();
|
||||||
match self.write_game_packet(packet, &mut packet_buf).await {
|
match self.write_game_packet(packet, &mut packet_buf).await {
|
||||||
Ok(()) => {
|
Ok(()) => {
|
||||||
self.send_framed_packet_data(packet_buf, RakReliability::ReliableOrdered)
|
let (tx, rx) = oneshot::channel();
|
||||||
.await;
|
if let Err(err) = self
|
||||||
|
.outgoing_packet_priority_send
|
||||||
|
.send(OutgoingPacket::priority(packet_buf.into(), tx))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
if !self.is_closed() {
|
||||||
|
error!("Failed to add priority packet to the outgoing packet queue: {err}");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let _ = rx.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Err(err) => error!("Failed to write game packet: {err}"),
|
Err(err) => error!("Failed to write game packet: {err}"),
|
||||||
}
|
}
|
||||||
@ -710,48 +816,70 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async fn handle_game_packet(
|
async fn handle_game_packet(
|
||||||
self: &Arc<Self>,
|
&self,
|
||||||
server: &Arc<Server>,
|
_server: &Arc<Server>,
|
||||||
packet: RawPacket,
|
packet: RawPacket,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let packet_id = packet.id;
|
if let Err(err) = self.incoming_game_packet_send.send(packet).await {
|
||||||
let payload = &mut Cursor::new(&packet.payload);
|
debug!("Failed to send game packet to session task: {err}");
|
||||||
let result = match packet.id {
|
}
|
||||||
SRequestNetworkSettings::PACKET_ID => {
|
Ok(())
|
||||||
self.handle_request_network_settings(
|
}
|
||||||
SRequestNetworkSettings::read(payload)?,
|
|
||||||
server,
|
pub async fn handle_login_sequence(
|
||||||
)
|
self: &Arc<Self>,
|
||||||
.await;
|
server: &Arc<Server>,
|
||||||
Ok(())
|
) -> PacketHandlerResult {
|
||||||
}
|
while let Some(packet) = self.get_packet().await {
|
||||||
SLogin::PACKET_ID => {
|
let payload = &mut Cursor::new(&packet.payload);
|
||||||
self.handle_login(SLogin::read(payload)?, server).await;
|
match packet.id {
|
||||||
Ok(())
|
SRequestNetworkSettings::PACKET_ID => {
|
||||||
}
|
let packet = match SRequestNetworkSettings::read(payload) {
|
||||||
SClientCacheStatus::PACKET_ID => {
|
Ok(p) => p,
|
||||||
// TODO
|
Err(err) => {
|
||||||
Ok(())
|
error!("Failed to read SRequestNetworkSettings: {err}");
|
||||||
}
|
continue;
|
||||||
SResourcePackResponse::PACKET_ID => {
|
}
|
||||||
self.handle_resource_pack_response(SResourcePackResponse::read(payload)?, server)
|
};
|
||||||
.await;
|
self.handle_request_network_settings(packet, server).await;
|
||||||
Ok(())
|
}
|
||||||
}
|
SLogin::PACKET_ID => {
|
||||||
_ => {
|
let packet = match SLogin::read(payload) {
|
||||||
let player_lock = self.player.lock().await;
|
Ok(p) => p,
|
||||||
if let Some(player) = player_lock.as_ref() {
|
Err(err) => {
|
||||||
self.handle_play_packet(player, server, packet).await
|
error!("Failed to read SLogin: {err}");
|
||||||
} else {
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
match self.handle_login(packet, server).await {
|
||||||
|
Ok(result) => return result,
|
||||||
|
Err(err) => {
|
||||||
|
warn!("Bedrock login failed: {err}");
|
||||||
|
return PacketHandlerResult::Stop;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
debug!(
|
debug!(
|
||||||
"Received game packet {} before player was initialized",
|
"Received unexpected game packet {} during login sequence",
|
||||||
packet.id
|
packet.id
|
||||||
);
|
);
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
result.map_err(|e| Error::new(e.kind(), format!("Game packet {packet_id}: {e}")))
|
PacketHandlerResult::Stop
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn progress_player_packets(
|
||||||
|
self: &Arc<Self>,
|
||||||
|
player: &Arc<Player>,
|
||||||
|
server: &Arc<Server>,
|
||||||
|
) {
|
||||||
|
while let Some(packet) = self.get_packet().await {
|
||||||
|
if let Err(err) = self.handle_play_packet(player, server, packet).await {
|
||||||
|
error!("Failed to handle Bedrock play packet: {err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_play_packet(
|
pub async fn handle_play_packet(
|
||||||
@ -760,8 +888,16 @@ impl BedrockClient {
|
|||||||
server: &Arc<Server>,
|
server: &Arc<Server>,
|
||||||
packet: RawPacket,
|
packet: RawPacket,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let reader = &mut &packet.payload[..];
|
let payload = &packet.payload[..];
|
||||||
|
let reader = &mut &payload[..];
|
||||||
match packet.id {
|
match packet.id {
|
||||||
|
SClientCacheStatus::PACKET_ID => {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
SResourcePackResponse::PACKET_ID => {
|
||||||
|
self.handle_resource_pack_response(SResourcePackResponse::read(reader)?, server)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
SPlayerAuthInput::PACKET_ID => {
|
SPlayerAuthInput::PACKET_ID => {
|
||||||
self.handle_player_auth_input(player, SPlayerAuthInput::read(reader)?, server)
|
self.handle_player_auth_input(player, SPlayerAuthInput::read(reader)?, server)
|
||||||
.await;
|
.await;
|
||||||
@ -797,7 +933,7 @@ impl BedrockClient {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
SAnimate::PACKET_ID => {
|
SAnimate::PACKET_ID => {
|
||||||
self.handle_animate(player, server, &SAnimate::read(reader)?);
|
self.handle_animate(player, server, &SAnimate::read(reader)?).await;
|
||||||
}
|
}
|
||||||
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => {
|
pumpkin_protocol::bedrock::server::modal_form_response::SModalFormResponse::PACKET_ID => {
|
||||||
self.handle_modal_form_response(
|
self.handle_modal_form_response(
|
||||||
|
|||||||
@ -19,8 +19,8 @@ use pumpkin_protocol::{
|
|||||||
text::SText,
|
text::SText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
codec::{var_int::VarInt, var_long::VarLong},
|
codec::{var_int::VarInt, var_long::VarLong, var_ulong::VarULong},
|
||||||
java::client::play::{Animation, CSystemChatMessage},
|
java::client::play::{Animation, CEntityAnimation, CSystemChatMessage},
|
||||||
};
|
};
|
||||||
use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent};
|
use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent};
|
||||||
|
|
||||||
@ -95,6 +95,7 @@ impl BedrockClient {
|
|||||||
player.set_client_loaded(true);
|
player.set_client_loaded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn handle_player_auth_input(
|
pub async fn handle_player_auth_input(
|
||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
@ -104,16 +105,135 @@ impl BedrockClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let new_pos = packet.position.to_f64();
|
let entity = player.get_entity();
|
||||||
|
|
||||||
|
let new_pos = packet
|
||||||
|
.position
|
||||||
|
.add_raw(0.0, -entity.entity_type.eye_height, 0.0)
|
||||||
|
.to_f64();
|
||||||
let old_pos = player.position();
|
let old_pos = player.position();
|
||||||
|
|
||||||
if new_pos != old_pos {
|
let new_pitch = packet.pitch;
|
||||||
player.living_entity.entity.set_pos(new_pos);
|
let new_yaw = packet.yaw;
|
||||||
chunker::update_position(player).await;
|
|
||||||
|
let old_pitch = entity.pitch.load();
|
||||||
|
let old_yaw = entity.yaw.load();
|
||||||
|
|
||||||
|
let pos_changed = new_pos != old_pos;
|
||||||
|
let rot_changed = new_pitch != old_pitch || new_yaw != old_yaw;
|
||||||
|
|
||||||
|
if pos_changed || rot_changed {
|
||||||
|
let world = player.world();
|
||||||
|
let on_ground = entity.on_ground.load(std::sync::atomic::Ordering::Relaxed);
|
||||||
|
|
||||||
|
if pos_changed {
|
||||||
|
player.living_entity.entity.set_pos(new_pos);
|
||||||
|
}
|
||||||
|
if rot_changed {
|
||||||
|
entity.pitch.store(new_pitch);
|
||||||
|
entity.yaw.store(new_yaw);
|
||||||
|
}
|
||||||
|
|
||||||
|
let je_yaw = (new_yaw * 256.0 / 360.0).rem_euclid(256.0);
|
||||||
|
let je_pitch = (new_pitch * 256.0 / 360.0).rem_euclid(256.0);
|
||||||
|
|
||||||
|
let delta = pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
new_pos.x - old_pos.x,
|
||||||
|
new_pos.y - old_pos.y,
|
||||||
|
new_pos.z - old_pos.z,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bedrock_move_packet = pumpkin_protocol::bedrock::client::CMovePlayer::new(
|
||||||
|
pumpkin_protocol::codec::var_ulong::VarULong(player.entity_id() as u64),
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
new_pos.x as f32,
|
||||||
|
new_pos.y as f32 + entity.entity_type.eye_height,
|
||||||
|
new_pos.z as f32,
|
||||||
|
),
|
||||||
|
new_pitch,
|
||||||
|
new_yaw,
|
||||||
|
new_yaw, // Head yaw
|
||||||
|
pumpkin_protocol::bedrock::client::CMovePlayer::MODE_NORMAL,
|
||||||
|
on_ground,
|
||||||
|
pumpkin_protocol::codec::var_ulong::VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
pumpkin_protocol::codec::var_ulong::VarULong(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
if pos_changed && delta.length_squared() >= 64.0 {
|
||||||
|
world.broadcast_packet_except(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&pumpkin_protocol::java::client::play::CEntityPositionSync::new(
|
||||||
|
player.entity_id().into(),
|
||||||
|
new_pos,
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(0.0, 0.0, 0.0),
|
||||||
|
je_yaw,
|
||||||
|
je_pitch,
|
||||||
|
on_ground,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
} else if pos_changed && rot_changed {
|
||||||
|
world.broadcast_packet_except_editioned_sync(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&pumpkin_protocol::java::client::play::CUpdateEntityPosRot::new(
|
||||||
|
player.entity_id().into(),
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
new_pos.x.mul_add(4096.0, -(old_pos.x * 4096.0)) as i16,
|
||||||
|
new_pos.y.mul_add(4096.0, -(old_pos.y * 4096.0)) as i16,
|
||||||
|
new_pos.z.mul_add(4096.0, -(old_pos.z * 4096.0)) as i16,
|
||||||
|
),
|
||||||
|
je_yaw as u8, // Use converted Java byte
|
||||||
|
je_pitch as u8, // Use converted Java byte
|
||||||
|
on_ground,
|
||||||
|
),
|
||||||
|
&bedrock_move_packet,
|
||||||
|
);
|
||||||
|
} else if pos_changed {
|
||||||
|
world.broadcast_packet_except_editioned_sync(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&pumpkin_protocol::java::client::play::CUpdateEntityPos::new(
|
||||||
|
player.entity_id().into(),
|
||||||
|
pumpkin_util::math::vector3::Vector3::new(
|
||||||
|
new_pos.x.mul_add(4096.0, -(old_pos.x * 4096.0)) as i16,
|
||||||
|
new_pos.y.mul_add(4096.0, -(old_pos.y * 4096.0)) as i16,
|
||||||
|
new_pos.z.mul_add(4096.0, -(old_pos.z * 4096.0)) as i16,
|
||||||
|
),
|
||||||
|
on_ground,
|
||||||
|
),
|
||||||
|
&bedrock_move_packet,
|
||||||
|
);
|
||||||
|
} else if rot_changed {
|
||||||
|
world.broadcast_packet_except_editioned_sync(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&pumpkin_protocol::java::client::play::CUpdateEntityRot::new(
|
||||||
|
player.entity_id().into(),
|
||||||
|
je_yaw as u8, // Use converted Java byte
|
||||||
|
je_pitch as u8, // Use converted Java byte
|
||||||
|
on_ground,
|
||||||
|
),
|
||||||
|
&bedrock_move_packet,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if rot_changed {
|
||||||
|
world.broadcast_packet_except(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
// Adjust to `CHeadRot` if that is what your crate currently calls it
|
||||||
|
&pumpkin_protocol::java::client::play::CHeadRot::new(
|
||||||
|
player.entity_id().into(),
|
||||||
|
je_yaw as u8,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if pos_changed {
|
||||||
|
chunker::update_position(player).await;
|
||||||
|
player.progress_motion(delta).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let input_data = packet.input_data;
|
let input_data = packet.input_data;
|
||||||
let entity = player.get_entity();
|
|
||||||
|
|
||||||
if input_data.get(InputData::StartSprinting as usize) {
|
if input_data.get(InputData::StartSprinting as usize) {
|
||||||
entity.set_sprinting(true).await;
|
entity.set_sprinting(true).await;
|
||||||
@ -189,43 +309,53 @@ impl BedrockClient {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn handle_animate(&self, player: &Arc<Player>, _server: &Server, packet: &SAnimate) {
|
pub async fn handle_animate(&self, player: &Arc<Player>, _server: &Server, packet: &SAnimate) {
|
||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
let entity = &player.living_entity.entity;
|
let entity = &player.living_entity.entity;
|
||||||
let _world = entity.world.load();
|
let world = entity.world.load();
|
||||||
|
|
||||||
// Broadcast the animation to other players
|
let java_animation = match packet.action {
|
||||||
let _java_animation = match packet.action {
|
|
||||||
AnimateAction::SwingArm => Some(Animation::SwingMainArm),
|
AnimateAction::SwingArm => Some(Animation::SwingMainArm),
|
||||||
AnimateAction::WakeUp => Some(Animation::LeaveBed),
|
AnimateAction::WakeUp => Some(Animation::LeaveBed),
|
||||||
AnimateAction::CriticalHit => Some(Animation::CriticalEffect),
|
AnimateAction::CriticalHit => Some(Animation::CriticalEffect),
|
||||||
AnimateAction::MagicCriticalHit => Some(Animation::MagicCriticaleffect),
|
AnimateAction::MagicCriticalHit => Some(Animation::MagicCriticaleffect),
|
||||||
_ => None,
|
AnimateAction::StopSleep => None, // TODO
|
||||||
};
|
};
|
||||||
|
|
||||||
// if let Some(animation) = java_animation {
|
if let Some(animation) = java_animation {
|
||||||
// let je_packet = CEntityAnimation::new(VarInt(entity.entity_id), animation);
|
let je_packet = CEntityAnimation::new(VarInt(entity.entity_id), animation);
|
||||||
// let be_packet = SAnimate {
|
let be_packet = SAnimate {
|
||||||
// action: packet.action,
|
action: packet.action,
|
||||||
// runtime_entity_id: VarULong(entity.entity_id as u64),
|
runtime_entity_id: VarULong(entity.entity_id as u64),
|
||||||
// boat_rowing_time: packet.boat_rowing_time,
|
data: 0.0,
|
||||||
// };
|
swing_source: None,
|
||||||
// world.broadcast_editioned(&je_packet, &be_packet).await;
|
};
|
||||||
// }
|
world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_interaction(&self, _player: &Arc<Player>, packet: SInteraction) {
|
pub async fn handle_interaction(&self, player: &Arc<Player>, packet: SInteraction) {
|
||||||
if matches!(packet.action, Action::OpenInventory) {
|
match packet.action {
|
||||||
self.send_game_packet(&CContainerOpen {
|
Action::OpenInventory => {
|
||||||
container_id: 0,
|
self.send_game_packet(&CContainerOpen {
|
||||||
container_type: 0xff,
|
container_id: 0,
|
||||||
position: BlockPos::ZERO,
|
container_type: 0xff,
|
||||||
target_entity_id: VarLong(-1),
|
position: BlockPos::ZERO,
|
||||||
})
|
target_entity_id: VarLong(-1),
|
||||||
.await;
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
Action::Attack => {
|
||||||
|
let target_runtime_id = packet.target_runtime_id.0 as i32;
|
||||||
|
let world = player.world();
|
||||||
|
if let Some(target) = world.get_entity_by_id(target_runtime_id) {
|
||||||
|
player.attack(target).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -67,7 +67,7 @@ pub mod recipe_helper;
|
|||||||
pub mod status;
|
pub mod status;
|
||||||
|
|
||||||
use crate::entity::player::Player;
|
use crate::entity::player::Player;
|
||||||
use crate::net::{GameProfile, PlayerConfig};
|
use crate::net::{GameProfile, PacketHandlerResult, PlayerConfig};
|
||||||
use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent;
|
use crate::plugin::player::player_custom_payload::PlayerCustomPayloadEvent;
|
||||||
use crate::{error::PumpkinError, net::EncryptionError, server::Server};
|
use crate::{error::PumpkinError, net::EncryptionError, server::Server};
|
||||||
|
|
||||||
@ -112,10 +112,9 @@ pub struct JavaClient {
|
|||||||
pub last_keep_alive_time: AtomicCell<Instant>,
|
pub last_keep_alive_time: AtomicCell<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum PacketHandlerResult {
|
pub enum OutgoingPacketType {
|
||||||
Stop,
|
Normal,
|
||||||
// Signal to spawn the player
|
HighPriority,
|
||||||
ReadyToPlay(GameProfile, PlayerConfig),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct OutgoingPacket {
|
struct OutgoingPacket {
|
||||||
|
|||||||
@ -54,7 +54,9 @@ use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
|||||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||||
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
|
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
|
||||||
use pumpkin_macros::send_cancellable;
|
use pumpkin_macros::send_cancellable;
|
||||||
|
use pumpkin_protocol::bedrock::client::CMovePlayer;
|
||||||
use pumpkin_protocol::codec::var_int::VarInt;
|
use pumpkin_protocol::codec::var_int::VarInt;
|
||||||
|
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
||||||
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetSelectedSlot, CSystemChatMessage,
|
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetSelectedSlot, CSystemChatMessage,
|
||||||
@ -309,6 +311,7 @@ impl JavaClient {
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[expect(clippy::too_many_lines)]
|
||||||
pub async fn handle_position(
|
pub async fn handle_position(
|
||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
@ -369,20 +372,31 @@ impl JavaClient {
|
|||||||
// TODO: Warn when player moves to quickly
|
// TODO: Warn when player moves to quickly
|
||||||
if !Self::sync_position(player, world, pos, last_pos, entity.yaw.load(), entity.pitch.load(), packet.collision & FLAG_ON_GROUND != 0) {
|
if !Self::sync_position(player, world, pos, last_pos, entity.yaw.load(), entity.pitch.load(), packet.collision & FLAG_ON_GROUND != 0) {
|
||||||
// Send the new position to all other players.
|
// Send the new position to all other players.
|
||||||
world
|
world.broadcast_packet_except_editioned_sync(
|
||||||
.broadcast_packet_except(
|
&[player.gameprofile.id],
|
||||||
&[player.gameprofile.id],
|
&CUpdateEntityPos::new(
|
||||||
&CUpdateEntityPos::new(
|
player.entity_id().into(),
|
||||||
player.entity_id().into(),
|
Vector3::new(
|
||||||
Vector3::new(
|
pos.x.mul_add(4096.0, -(last_pos.x * 4096.0)) as i16,
|
||||||
pos.x.mul_add(4096.0, -(last_pos.x * 4096.0)) as i16,
|
pos.y.mul_add(4096.0, -(last_pos.y * 4096.0)) as i16,
|
||||||
pos.y.mul_add(4096.0, -(last_pos.y * 4096.0)) as i16,
|
pos.z.mul_add(4096.0, -(last_pos.z * 4096.0)) as i16,
|
||||||
pos.z.mul_add(4096.0, -(last_pos.z * 4096.0)) as i16,
|
|
||||||
),
|
|
||||||
packet.collision & FLAG_ON_GROUND != 0,
|
|
||||||
),
|
),
|
||||||
)
|
packet.collision & FLAG_ON_GROUND != 0,
|
||||||
;
|
),
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(player.entity_id() as u64),
|
||||||
|
Vector3::new(pos.x as f32, pos.y as f32 + player.get_entity().entity_type.eye_height, pos.z as f32),
|
||||||
|
entity.pitch.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
CMovePlayer::MODE_NORMAL,
|
||||||
|
(packet.collision & FLAG_ON_GROUND) != 0,
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Only process fall damage if player is alive
|
// Only process fall damage if player is alive
|
||||||
@ -494,22 +508,33 @@ impl JavaClient {
|
|||||||
sync_position(player, &world, pos, last_pos, yaw, pitch, (packet.collision & FLAG_ON_GROUND) != 0)
|
sync_position(player, &world, pos, last_pos, yaw, pitch, (packet.collision & FLAG_ON_GROUND) != 0)
|
||||||
{
|
{
|
||||||
// Send the new position to all other players.
|
// Send the new position to all other players.
|
||||||
world
|
world.broadcast_packet_except_editioned_sync(
|
||||||
.broadcast_packet_except(
|
&[player.gameprofile.id],
|
||||||
&[player.gameprofile.id],
|
&CUpdateEntityPosRot::new(
|
||||||
&CUpdateEntityPosRot::new(
|
entity_id.into(),
|
||||||
entity_id.into(),
|
Vector3::new(
|
||||||
Vector3::new(
|
pos.x.mul_add(4096.0, -(last_pos.x * 4096.0)) as i16,
|
||||||
pos.x.mul_add(4096.0, -(last_pos.x * 4096.0)) as i16,
|
pos.y.mul_add(4096.0, -(last_pos.y * 4096.0)) as i16,
|
||||||
pos.y.mul_add(4096.0, -(last_pos.y * 4096.0)) as i16,
|
pos.z.mul_add(4096.0, -(last_pos.z * 4096.0)) as i16,
|
||||||
pos.z.mul_add(4096.0, -(last_pos.z * 4096.0)) as i16,
|
|
||||||
),
|
|
||||||
yaw as u8,
|
|
||||||
pitch as u8,
|
|
||||||
(packet.collision & FLAG_ON_GROUND) != 0,
|
|
||||||
),
|
),
|
||||||
)
|
yaw as u8,
|
||||||
;
|
pitch as u8,
|
||||||
|
(packet.collision & FLAG_ON_GROUND) != 0,
|
||||||
|
),
|
||||||
|
&CMovePlayer::new(
|
||||||
|
VarULong(entity_id as u64),
|
||||||
|
Vector3::new(pos.x as f32, pos.y as f32 + player.get_entity().entity_type.eye_height, pos.z as f32),
|
||||||
|
entity.pitch.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
CMovePlayer::MODE_NORMAL,
|
||||||
|
(packet.collision & FLAG_ON_GROUND) != 0,
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
world
|
world
|
||||||
@ -591,11 +616,37 @@ impl JavaClient {
|
|||||||
// let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0);
|
// let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0);
|
||||||
|
|
||||||
let world = entity.world.load_full();
|
let world = entity.world.load_full();
|
||||||
let packet =
|
let je_packet =
|
||||||
CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, rotation.ground);
|
CUpdateEntityRot::new(entity_id.into(), yaw as u8, pitch as u8, rotation.ground);
|
||||||
world.broadcast_packet_except(&[player.gameprofile.id], &packet);
|
|
||||||
let packet = CHeadRot::new(entity_id.into(), yaw as u8);
|
let pos = entity.pos.load();
|
||||||
world.broadcast_packet_except(&[player.gameprofile.id], &packet);
|
|
||||||
|
let be_packet = CMovePlayer::new(
|
||||||
|
VarULong(entity_id as u64),
|
||||||
|
Vector3::new(
|
||||||
|
pos.x as f32,
|
||||||
|
pos.y as f32 + player.get_entity().entity_type.eye_height,
|
||||||
|
pos.z as f32,
|
||||||
|
),
|
||||||
|
entity.pitch.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
CMovePlayer::MODE_ROTATION,
|
||||||
|
rotation.ground,
|
||||||
|
VarULong(0),
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
VarULong(0),
|
||||||
|
);
|
||||||
|
|
||||||
|
world.broadcast_packet_except_editioned_sync(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&je_packet,
|
||||||
|
&be_packet,
|
||||||
|
);
|
||||||
|
|
||||||
|
let je_packet = CHeadRot::new(entity_id.into(), yaw as u8);
|
||||||
|
world.broadcast_packet_except(&[player.gameprofile.id], &je_packet);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn handle_chat_command(
|
pub async fn handle_chat_command(
|
||||||
@ -1176,7 +1227,7 @@ impl JavaClient {
|
|||||||
server;
|
server;
|
||||||
event;
|
event;
|
||||||
'after: {
|
'after: {
|
||||||
player.swing_hand(hand, false);
|
player.swing_hand(hand, false).await;
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
@ -2028,7 +2079,7 @@ impl JavaClient {
|
|||||||
// TODO: Trigger ANY_BLOCK_USE Criteria
|
// TODO: Trigger ANY_BLOCK_USE Criteria
|
||||||
|
|
||||||
if matches!(result, BlockActionResult::SuccessServer) {
|
if matches!(result, BlockActionResult::SuccessServer) {
|
||||||
player.swing_hand(hand, true);
|
player.swing_hand(hand, true).await;
|
||||||
}
|
}
|
||||||
return Ok(());
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
use pumpkin_data::translation;
|
use pumpkin_data::translation;
|
||||||
use pumpkin_protocol::{ClientPacket, Property};
|
use pumpkin_protocol::{BClientPacket, ClientPacket, Property};
|
||||||
use pumpkin_util::{
|
use pumpkin_util::{
|
||||||
Hand, ProfileAction,
|
Hand, ProfileAction,
|
||||||
text::TextComponent,
|
text::TextComponent,
|
||||||
@ -108,6 +108,11 @@ impl Default for PlayerConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub enum PacketHandlerResult {
|
||||||
|
Stop,
|
||||||
|
ReadyToPlay(GameProfile, PlayerConfig),
|
||||||
|
}
|
||||||
|
|
||||||
/// This is just a Wrapper for both Java & Bedrock connections
|
/// This is just a Wrapper for both Java & Bedrock connections
|
||||||
#[expect(clippy::large_enum_variant)]
|
#[expect(clippy::large_enum_variant)]
|
||||||
pub enum ClientPlatform {
|
pub enum ClientPlatform {
|
||||||
@ -190,24 +195,61 @@ impl ClientPlatform {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn enqueue_packet<P: ClientPacket>(&self, packet: &P) {
|
pub async fn enqueue_packet_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
match self {
|
match self {
|
||||||
Self::Java(java) => java.enqueue_packet(packet).await,
|
Self::Java(java) => java.enqueue_packet(je_packet).await,
|
||||||
Self::Bedrock(_) => (),
|
Self::Bedrock(bedrock) => bedrock.enqueue_packet(be_packet).await,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_enqueue_packet_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
match self {
|
||||||
|
Self::Java(java) => java.try_enqueue_packet(je_packet),
|
||||||
|
Self::Bedrock(bedrock) => bedrock.try_enqueue_packet(be_packet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn enqueue_packet<P: ClientPacket>(&self, packet: &P) {
|
||||||
|
if let Self::Java(java) = self {
|
||||||
|
java.enqueue_packet(packet).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn enqueue_be_packet<P: BClientPacket>(&self, packet: &P) {
|
||||||
|
if let Self::Bedrock(bedrock) = self {
|
||||||
|
bedrock.enqueue_packet(packet).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn try_enqueue_packet<P: ClientPacket>(&self, packet: &P) {
|
pub fn try_enqueue_packet<P: ClientPacket>(&self, packet: &P) {
|
||||||
match self {
|
if let Self::Java(java) = self {
|
||||||
Self::Java(java) => java.try_enqueue_packet(packet),
|
java.try_enqueue_packet(packet);
|
||||||
Self::Bedrock(_) => (),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn try_enqueue_be_packet<P: BClientPacket>(&self, packet: &P) {
|
||||||
|
if let Self::Bedrock(bedrock) = self {
|
||||||
|
bedrock.try_enqueue_packet(packet);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn send_packet_now<P: ClientPacket>(&self, packet: &P) {
|
pub async fn send_packet_now<P: ClientPacket>(&self, packet: &P) {
|
||||||
match self {
|
if let Self::Java(java) = self {
|
||||||
Self::Java(java) => java.send_packet_now(packet).await,
|
java.send_packet_now(packet).await;
|
||||||
Self::Bedrock(_) => (),
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn send_be_packet_now<P: BClientPacket>(&self, packet: &P) {
|
||||||
|
if let Self::Bedrock(bedrock) = self {
|
||||||
|
bedrock.send_game_packet(packet).await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -2,7 +2,9 @@ use crate::block::entities::BlockEntity;
|
|||||||
use dashmap::DashMap;
|
use dashmap::DashMap;
|
||||||
use pumpkin_data::attributes::Attributes;
|
use pumpkin_data::attributes::Attributes;
|
||||||
use pumpkin_data::chunk::Biome;
|
use pumpkin_data::chunk::Biome;
|
||||||
|
use pumpkin_protocol::bedrock::client::EntityProperties;
|
||||||
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
|
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
|
||||||
|
use pumpkin_protocol::bedrock::network_item::{ItemInstanceUserData, NetworkItemDescriptor};
|
||||||
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
|
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
|
||||||
use pumpkin_world::generation::proto_chunk::GenerationCache;
|
use pumpkin_world::generation::proto_chunk::GenerationCache;
|
||||||
use std::sync::atomic::Ordering::Relaxed;
|
use std::sync::atomic::Ordering::Relaxed;
|
||||||
@ -73,6 +75,10 @@ use pumpkin_protocol::bedrock::client::set_actor_data::{
|
|||||||
};
|
};
|
||||||
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
use pumpkin_protocol::bedrock::client::start_game::{CStartGame, ServerTelemetryData};
|
||||||
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
||||||
|
use pumpkin_protocol::java::client::play::{
|
||||||
|
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
|
||||||
|
CRespawn, CSetBlockDestroyStage, CWorldEvent,
|
||||||
|
};
|
||||||
use pumpkin_protocol::java::client::play::{
|
use pumpkin_protocol::java::client::play::{
|
||||||
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
|
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
|
||||||
};
|
};
|
||||||
@ -81,15 +87,17 @@ use pumpkin_protocol::{
|
|||||||
BClientPacket, ClientPacket, IdOr, SoundEvent,
|
BClientPacket, ClientPacket, IdOr, SoundEvent,
|
||||||
bedrock::{
|
bedrock::{
|
||||||
client::{
|
client::{
|
||||||
|
add_player::CAddPlayer,
|
||||||
creative_content::{CreativeContent, Group},
|
creative_content::{CreativeContent, Group},
|
||||||
gamerules_changed::GameRules,
|
gamerules_changed::GameRules,
|
||||||
|
player_list::{CPlayerList, PlayerListEntry, Skin},
|
||||||
|
remove_actor::CRemoveActor,
|
||||||
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
||||||
update_attributes::{Attribute, CUpdateAttributes},
|
update_attributes::{Attribute, CUpdateAttributes},
|
||||||
},
|
},
|
||||||
network_item::NetworkItemDescriptor,
|
|
||||||
server::text::SText,
|
server::text::SText,
|
||||||
},
|
},
|
||||||
codec::{var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
|
codec::{var_int::VarInt, var_long::VarLong, var_uint::VarUInt, var_ulong::VarULong},
|
||||||
java::{
|
java::{
|
||||||
self,
|
self,
|
||||||
client::play::{
|
client::play::{
|
||||||
@ -105,13 +113,7 @@ use pumpkin_protocol::{
|
|||||||
codec::item_stack_seralizer::ItemStackSerializer,
|
codec::item_stack_seralizer::ItemStackSerializer,
|
||||||
java::client::play::{CBlockEvent, CRemoveMobEffect, CSetEquipment, CUpdateMobEffect},
|
java::client::play::{CBlockEvent, CRemoveMobEffect, CSetEquipment, CUpdateMobEffect},
|
||||||
};
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_util::GameMode;
|
||||||
codec::var_int::VarInt,
|
|
||||||
java::client::play::{
|
|
||||||
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage,
|
|
||||||
CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
use pumpkin_util::resource_location::ResourceLocation;
|
use pumpkin_util::resource_location::ResourceLocation;
|
||||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||||
use pumpkin_util::version::JavaMinecraftVersion;
|
use pumpkin_util::version::JavaMinecraftVersion;
|
||||||
@ -618,6 +620,59 @@ impl World {
|
|||||||
sender.chat_session.lock().await.messages_sent += 1;
|
sender.chat_session.lock().await.messages_sent += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn broadcast_packet_except_editioned_sync<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
except: &[uuid::Uuid],
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
let players = self.players.load();
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
|
||||||
|
for p in players.iter() {
|
||||||
|
if except.contains(&p.gameprofile.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match &p.client {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let recipients_by_version =
|
||||||
|
Self::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
Self::broadcast_java_grouped(je_packet, recipients_by_version);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub async fn broadcast_packet_except_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
except: &[uuid::Uuid],
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
let players = self.players.load();
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
|
||||||
|
for p in players.iter() {
|
||||||
|
if except.contains(&p.gameprofile.id) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
match &p.client {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let recipients_by_version =
|
||||||
|
Self::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
Self::broadcast_java_grouped(je_packet, recipients_by_version);
|
||||||
|
|
||||||
|
for be_client in bedrock_recipients {
|
||||||
|
be_client.enqueue_packet(be_packet).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Broadcasts a packet to all connected players within the world, excluding the specified players.
|
/// Broadcasts a packet to all connected players within the world, excluding the specified players.
|
||||||
///
|
///
|
||||||
/// Sends the specified packet to every player currently logged in to the world, excluding the players listed in the `except` parameter.
|
/// Sends the specified packet to every player currently logged in to the world, excluding the players listed in the `except` parameter.
|
||||||
@ -1597,6 +1652,7 @@ impl World {
|
|||||||
let level_info = server.level_info.load();
|
let level_info = server.level_info.load();
|
||||||
let weather = self.weather.lock().await;
|
let weather = self.weather.lock().await;
|
||||||
let runtime_id = player.entity_id() as u64;
|
let runtime_id = player.entity_id() as u64;
|
||||||
|
|
||||||
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
|
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
|
||||||
let position = player.position();
|
let position = player.position();
|
||||||
let yaw = player.living_entity.entity.yaw.load(); //info.spawn_angle;
|
let yaw = player.living_entity.entity.yaw.load(); //info.spawn_angle;
|
||||||
@ -1616,6 +1672,7 @@ impl World {
|
|||||||
);
|
);
|
||||||
(position, level_info.spawn_yaw, level_info.spawn_pitch)
|
(position, level_info.spawn_yaw, level_info.spawn_pitch)
|
||||||
};
|
};
|
||||||
|
|
||||||
// Todo make the data less spread
|
// Todo make the data less spread
|
||||||
let level_settings = LevelSettings {
|
let level_settings = LevelSettings {
|
||||||
seed: self.level.seed.0,
|
seed: self.level.seed.0,
|
||||||
@ -1708,17 +1765,12 @@ impl World {
|
|||||||
// TODO Make this unique
|
// TODO Make this unique
|
||||||
multiplayer_correlation_id: Uuid::default().to_string(),
|
multiplayer_correlation_id: Uuid::default().to_string(),
|
||||||
enable_itemstack_net_manager: true,
|
enable_itemstack_net_manager: true,
|
||||||
// TODO Make this description better!
|
|
||||||
// This gets send from the client to mojang for telemetry
|
|
||||||
server_version: "Pumpkin Rust Server".to_string(),
|
server_version: "Pumpkin Rust Server".to_string(),
|
||||||
|
|
||||||
compound_id: 10,
|
compound_id: 10,
|
||||||
compound_len: VarUInt(0),
|
compound_len: VarUInt(0),
|
||||||
compound_end: 0,
|
compound_end: 0,
|
||||||
|
|
||||||
block_registry_checksum: 0,
|
block_registry_checksum: 0,
|
||||||
world_template_id: Uuid::nil(),
|
world_template_id: Uuid::nil(),
|
||||||
// TODO The client needs extra biome data for this
|
|
||||||
enable_clientside_generation: false,
|
enable_clientside_generation: false,
|
||||||
blocknetwork_ids_are_hashed: false,
|
blocknetwork_ids_are_hashed: false,
|
||||||
server_auth_sounds: false,
|
server_auth_sounds: false,
|
||||||
@ -1731,7 +1783,9 @@ impl World {
|
|||||||
},
|
},
|
||||||
})
|
})
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
chunker::update_position(&player).await;
|
chunker::update_position(&player).await;
|
||||||
|
|
||||||
client
|
client
|
||||||
.send_game_packet(&CreativeContent {
|
.send_game_packet(&CreativeContent {
|
||||||
groups: &[Group {
|
groups: &[Group {
|
||||||
@ -1747,8 +1801,8 @@ impl World {
|
|||||||
let mut abilities = player.abilities.lock().await;
|
let mut abilities = player.abilities.lock().await;
|
||||||
abilities.set_for_gamemode(player.gamemode.load());
|
abilities.set_for_gamemode(player.gamemode.load());
|
||||||
};
|
};
|
||||||
let mut metadata = EntityMetadata::new();
|
|
||||||
|
|
||||||
|
let mut metadata = EntityMetadata::new();
|
||||||
metadata.set(entity_data_key::WIDTH, MetadataValue::Float(0.6));
|
metadata.set(entity_data_key::WIDTH, MetadataValue::Float(0.6));
|
||||||
metadata.set(entity_data_key::HEIGHT, MetadataValue::Float(1.8));
|
metadata.set(entity_data_key::HEIGHT, MetadataValue::Float(1.8));
|
||||||
|
|
||||||
@ -1781,18 +1835,15 @@ impl World {
|
|||||||
tick: VarULong(0),
|
tick: VarULong(0),
|
||||||
};
|
};
|
||||||
client.send_game_packet(&actor_data).await;
|
client.send_game_packet(&actor_data).await;
|
||||||
|
|
||||||
player.send_abilities_update().await;
|
player.send_abilities_update().await;
|
||||||
|
|
||||||
{
|
{
|
||||||
let command_dispatcher = server.command_dispatcher.read().await;
|
let command_dispatcher = server.command_dispatcher.read().await;
|
||||||
|
|
||||||
client_suggestions::send_bedrock_commands_packet(&player, server, &command_dispatcher)
|
client_suggestions::send_bedrock_commands_packet(&player, server, &command_dispatcher)
|
||||||
.await;
|
.await;
|
||||||
};
|
};
|
||||||
|
|
||||||
let mut frame_set = FrameSet::default();
|
let mut frame_set = FrameSet::default();
|
||||||
|
|
||||||
// https://github.com/pmmp/PocketMine-MP/blob/0b6d8f8cb2aaa05ffad0b6386bd88d73ef54b395/src/entity/AttributeFactory.php#L34
|
|
||||||
client
|
client
|
||||||
.write_game_packet_to_set(
|
.write_game_packet_to_set(
|
||||||
&CUpdateAttributes {
|
&CUpdateAttributes {
|
||||||
@ -1865,6 +1916,188 @@ impl World {
|
|||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
client.send_frame_set(frame_set, 0x84).await;
|
client.send_frame_set(frame_set, 0x84).await;
|
||||||
|
|
||||||
|
// --- MULTIPLAYER BROADCASTING ---
|
||||||
|
|
||||||
|
let gameprofile = &player.gameprofile;
|
||||||
|
let velocity = player.living_entity.entity.velocity.load();
|
||||||
|
|
||||||
|
// 1. Broadcast the new Bedrock player to everyone else (Java + Bedrock)
|
||||||
|
let bedrock_player_list = CPlayerList {
|
||||||
|
action: CPlayerList::ACTION_ADD,
|
||||||
|
entries: vec![PlayerListEntry {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
entity_unique_id: VarLong(runtime_id as i64),
|
||||||
|
username: gameprofile.name.clone(),
|
||||||
|
xuid: String::new(),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
skin: (**player.bedrock_skin.load()).clone(),
|
||||||
|
is_teacher: false,
|
||||||
|
is_host: false,
|
||||||
|
is_sub_client: false,
|
||||||
|
player_color: [0, 0, 0, 0],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.broadcast_packet_except_editioned_sync(
|
||||||
|
&[gameprofile.id],
|
||||||
|
&CPlayerInfoUpdate::new(
|
||||||
|
PlayerInfoFlags::ADD_PLAYER.bits(),
|
||||||
|
&[pumpkin_protocol::java::client::play::Player {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
actions: &[PlayerAction::AddPlayer {
|
||||||
|
name: &gameprofile.name,
|
||||||
|
properties: &gameprofile.properties.load(),
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
&bedrock_player_list,
|
||||||
|
);
|
||||||
|
|
||||||
|
let bedrock_add_player = CAddPlayer {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
username: gameprofile.name.clone(),
|
||||||
|
entity_runtime_id: VarULong(runtime_id),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
position: Vector3::new(position.x as f32, position.y as f32, position.z as f32),
|
||||||
|
velocity: Vector3::new(velocity.x as f32, velocity.y as f32, velocity.z as f32),
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw: yaw,
|
||||||
|
held_item: NetworkItemDescriptor::default(),
|
||||||
|
game_mode: VarInt(match player.gamemode.load() {
|
||||||
|
GameMode::Survival => 0,
|
||||||
|
GameMode::Creative => 1,
|
||||||
|
GameMode::Adventure => 2,
|
||||||
|
GameMode::Spectator => 6,
|
||||||
|
}),
|
||||||
|
metadata: EntityMetadata::default(),
|
||||||
|
properties: EntityProperties::default(),
|
||||||
|
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
|
||||||
|
entity_unique_id: runtime_id as i64,
|
||||||
|
player_permissions: 0,
|
||||||
|
command_permissions: 0,
|
||||||
|
layers: vec![pumpkin_protocol::bedrock::client::AbilityLayer {
|
||||||
|
serialized_layer: 0,
|
||||||
|
abilities_set: 0,
|
||||||
|
ability_value: 0,
|
||||||
|
fly_speed: 0.05,
|
||||||
|
vertical_fly_speed: 0.05,
|
||||||
|
walk_speed: 0.1,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
links: Vec::new(),
|
||||||
|
device_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
self.broadcast_packet_except_editioned_sync(
|
||||||
|
&[gameprofile.id],
|
||||||
|
&CSpawnEntity::new(
|
||||||
|
(runtime_id as i32).into(),
|
||||||
|
gameprofile.id,
|
||||||
|
i32::from(EntityType::PLAYER.id).into(),
|
||||||
|
position,
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
yaw,
|
||||||
|
0.into(),
|
||||||
|
velocity,
|
||||||
|
),
|
||||||
|
&bedrock_add_player,
|
||||||
|
);
|
||||||
|
|
||||||
|
// 2. Spawn existing players for our new Bedrock client
|
||||||
|
let mut existing_entries = Vec::new();
|
||||||
|
let players = self.players.load();
|
||||||
|
|
||||||
|
for existing_player in players
|
||||||
|
.iter()
|
||||||
|
.filter(|p| p.gameprofile.id != gameprofile.id)
|
||||||
|
{
|
||||||
|
let ex_profile = &existing_player.gameprofile;
|
||||||
|
let ex_entity = &existing_player.living_entity.entity;
|
||||||
|
let ex_pos = ex_entity.pos.load();
|
||||||
|
let ex_vel = ex_entity.velocity.load();
|
||||||
|
|
||||||
|
existing_entries.push(PlayerListEntry {
|
||||||
|
uuid: ex_profile.id,
|
||||||
|
entity_unique_id: VarLong(existing_player.entity_id() as i64),
|
||||||
|
username: ex_profile.name.clone(),
|
||||||
|
xuid: String::new(),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
skin: (**existing_player.bedrock_skin.load()).clone(),
|
||||||
|
is_teacher: false,
|
||||||
|
is_host: false,
|
||||||
|
is_sub_client: false,
|
||||||
|
player_color: [0, 0, 0, 0],
|
||||||
|
});
|
||||||
|
|
||||||
|
let ex_add_player = CAddPlayer {
|
||||||
|
uuid: ex_profile.id,
|
||||||
|
username: ex_profile.name.clone(),
|
||||||
|
entity_runtime_id: VarULong(existing_player.entity_id() as u64),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
position: Vector3::new(ex_pos.x as f32, ex_pos.y as f32, ex_pos.z as f32),
|
||||||
|
velocity: Vector3::new(ex_vel.x as f32, ex_vel.y as f32, ex_vel.z as f32),
|
||||||
|
pitch: ex_entity.pitch.load(),
|
||||||
|
yaw: ex_entity.yaw.load(),
|
||||||
|
head_yaw: ex_entity.head_yaw.load(),
|
||||||
|
held_item: NetworkItemDescriptor::default(),
|
||||||
|
game_mode: VarInt(match existing_player.gamemode.load() {
|
||||||
|
GameMode::Survival => 0,
|
||||||
|
GameMode::Creative => 1,
|
||||||
|
GameMode::Adventure => 2,
|
||||||
|
GameMode::Spectator => 6,
|
||||||
|
}),
|
||||||
|
metadata: EntityMetadata::default(),
|
||||||
|
properties: EntityProperties::default(),
|
||||||
|
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
|
||||||
|
entity_unique_id: existing_player.entity_id() as i64,
|
||||||
|
player_permissions: 0,
|
||||||
|
command_permissions: 0,
|
||||||
|
layers: vec![pumpkin_protocol::bedrock::client::AbilityLayer {
|
||||||
|
serialized_layer: 0,
|
||||||
|
abilities_set: 0,
|
||||||
|
ability_value: 0,
|
||||||
|
fly_speed: 0.05,
|
||||||
|
vertical_fly_speed: 0.05,
|
||||||
|
walk_speed: 0.1,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
links: Vec::new(),
|
||||||
|
device_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
client.send_game_packet(&ex_add_player).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !existing_entries.is_empty() {
|
||||||
|
let ex_player_list = CPlayerList {
|
||||||
|
action: CPlayerList::ACTION_ADD,
|
||||||
|
entries: existing_entries,
|
||||||
|
};
|
||||||
|
client.send_game_packet(&ex_player_list).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Trigger Join Event and Broadcast Join Message
|
||||||
|
let msg_comp = TextComponent::translate_cross(
|
||||||
|
translation::java::MULTIPLAYER_PLAYER_JOINED,
|
||||||
|
translation::bedrock::MULTIPLAYER_PLAYER_JOINED,
|
||||||
|
[TextComponent::text(player.gameprofile.name.clone())],
|
||||||
|
)
|
||||||
|
.color_named(NamedColor::Yellow);
|
||||||
|
|
||||||
|
let event = PlayerJoinEvent::new(player.clone(), msg_comp);
|
||||||
|
let event = server.plugin_manager.fire(event).await;
|
||||||
|
|
||||||
|
if !event.cancelled {
|
||||||
|
self.broadcast_system_message(&event.join_message, false);
|
||||||
|
info!("{}", event.join_message.to_pretty_console());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[expect(clippy::too_many_lines)]
|
#[expect(clippy::too_many_lines)]
|
||||||
@ -2093,9 +2326,84 @@ impl World {
|
|||||||
|
|
||||||
let gameprofile = &player.gameprofile;
|
let gameprofile = &player.gameprofile;
|
||||||
|
|
||||||
debug!("Broadcasting player spawn for {}", player.gameprofile.name);
|
let bedrock_add_player = CAddPlayer {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
username: gameprofile.name.clone(),
|
||||||
|
entity_runtime_id: VarULong(entity_id as u64),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
position: Vector3::new(position.x as f32, position.y as f32, position.z as f32),
|
||||||
|
velocity: Vector3::new(velocity.x as f32, velocity.y as f32, velocity.z as f32),
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
head_yaw: yaw,
|
||||||
|
held_item: NetworkItemDescriptor {
|
||||||
|
id: VarInt(0),
|
||||||
|
stack_size: 0,
|
||||||
|
aux_value: VarUInt(0),
|
||||||
|
block_runtime_id: VarInt(0),
|
||||||
|
user_data_buffer: ItemInstanceUserData::default(),
|
||||||
|
},
|
||||||
|
game_mode: VarInt(match player.gamemode.load() {
|
||||||
|
GameMode::Survival => 0,
|
||||||
|
GameMode::Creative => 1,
|
||||||
|
GameMode::Adventure => 2,
|
||||||
|
GameMode::Spectator => 6,
|
||||||
|
}),
|
||||||
|
metadata: EntityMetadata::default(),
|
||||||
|
properties: EntityProperties::default(),
|
||||||
|
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
|
||||||
|
entity_unique_id: entity_id as i64,
|
||||||
|
player_permissions: 0,
|
||||||
|
command_permissions: 0,
|
||||||
|
layers: vec![pumpkin_protocol::bedrock::client::AbilityLayer {
|
||||||
|
serialized_layer: 0,
|
||||||
|
abilities_set: 0,
|
||||||
|
ability_value: 0,
|
||||||
|
fly_speed: 0.05,
|
||||||
|
vertical_fly_speed: 0.05,
|
||||||
|
walk_speed: 0.1,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
links: Vec::new(),
|
||||||
|
device_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let bedrock_player_list = CPlayerList {
|
||||||
|
action: CPlayerList::ACTION_ADD,
|
||||||
|
entries: vec![PlayerListEntry {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
entity_unique_id: VarLong(entity_id as i64),
|
||||||
|
username: gameprofile.name.clone(),
|
||||||
|
xuid: String::new(),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
skin: (**player.bedrock_skin.load()).clone(),
|
||||||
|
is_teacher: false,
|
||||||
|
|
||||||
|
is_host: false,
|
||||||
|
is_sub_client: false,
|
||||||
|
player_color: [0, 0, 0, 0],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.broadcast_packet_except_editioned_sync(
|
||||||
|
&[player.gameprofile.id],
|
||||||
|
&CPlayerInfoUpdate::new(
|
||||||
|
PlayerInfoFlags::ADD_PLAYER.bits(),
|
||||||
|
&[pumpkin_protocol::java::client::play::Player {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
actions: &[PlayerAction::AddPlayer {
|
||||||
|
name: &gameprofile.name,
|
||||||
|
properties: &gameprofile.properties.load(),
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
&bedrock_player_list,
|
||||||
|
);
|
||||||
|
|
||||||
// Spawn the player for every client.
|
// Spawn the player for every client.
|
||||||
self.broadcast_packet_except(
|
self.broadcast_packet_except_editioned_sync(
|
||||||
&[player.gameprofile.id],
|
&[player.gameprofile.id],
|
||||||
&CSpawnEntity::new(
|
&CSpawnEntity::new(
|
||||||
entity_id.into(),
|
entity_id.into(),
|
||||||
@ -2108,6 +2416,7 @@ impl World {
|
|||||||
0.into(),
|
0.into(),
|
||||||
velocity,
|
velocity,
|
||||||
),
|
),
|
||||||
|
&bedrock_add_player,
|
||||||
);
|
);
|
||||||
|
|
||||||
// Spawn players for our client.
|
// Spawn players for our client.
|
||||||
@ -2121,20 +2430,103 @@ impl World {
|
|||||||
let entity = &existing_player.living_entity.entity;
|
let entity = &existing_player.living_entity.entity;
|
||||||
let pos = entity.pos.load();
|
let pos = entity.pos.load();
|
||||||
let gameprofile = &existing_player.gameprofile;
|
let gameprofile = &existing_player.gameprofile;
|
||||||
debug!("Sending player entities to {}", player.gameprofile.name);
|
let bedrock_add_player = CAddPlayer {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
username: gameprofile.name.clone(),
|
||||||
|
entity_runtime_id: VarULong(existing_player.entity_id() as u64),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
position: Vector3::new(pos.x as f32, pos.y as f32, pos.z as f32),
|
||||||
|
velocity: Vector3::new(
|
||||||
|
entity.velocity.load().x as f32,
|
||||||
|
entity.velocity.load().y as f32,
|
||||||
|
entity.velocity.load().z as f32,
|
||||||
|
),
|
||||||
|
pitch: entity.pitch.load(),
|
||||||
|
yaw: entity.yaw.load(),
|
||||||
|
head_yaw: entity.head_yaw.load(),
|
||||||
|
held_item: NetworkItemDescriptor {
|
||||||
|
id: VarInt(0),
|
||||||
|
stack_size: 0,
|
||||||
|
aux_value: VarUInt(0),
|
||||||
|
block_runtime_id: VarInt(0),
|
||||||
|
user_data_buffer: ItemInstanceUserData::default(),
|
||||||
|
},
|
||||||
|
game_mode: VarInt(match existing_player.gamemode.load() {
|
||||||
|
GameMode::Survival => 0,
|
||||||
|
GameMode::Creative => 1,
|
||||||
|
GameMode::Adventure => 2,
|
||||||
|
GameMode::Spectator => 6,
|
||||||
|
}),
|
||||||
|
metadata: EntityMetadata::default(),
|
||||||
|
properties: EntityProperties::default(),
|
||||||
|
ability_data: pumpkin_protocol::bedrock::client::add_player::AbilityData {
|
||||||
|
entity_unique_id: existing_player.entity_id() as i64,
|
||||||
|
player_permissions: 0,
|
||||||
|
command_permissions: 0,
|
||||||
|
layers: vec![pumpkin_protocol::bedrock::client::AbilityLayer {
|
||||||
|
serialized_layer: 0,
|
||||||
|
abilities_set: 0,
|
||||||
|
ability_value: 0,
|
||||||
|
fly_speed: 0.05,
|
||||||
|
vertical_fly_speed: 0.05,
|
||||||
|
walk_speed: 0.1,
|
||||||
|
}],
|
||||||
|
},
|
||||||
|
links: Vec::new(),
|
||||||
|
device_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
};
|
||||||
|
|
||||||
client
|
let bedrock_player_list = CPlayerList {
|
||||||
.enqueue_packet(&CSpawnEntity::new(
|
action: CPlayerList::ACTION_ADD,
|
||||||
existing_player.entity_id().into(),
|
entries: vec![PlayerListEntry {
|
||||||
gameprofile.id,
|
uuid: gameprofile.id,
|
||||||
i32::from(EntityType::PLAYER.id).into(),
|
entity_unique_id: VarLong(existing_player.entity_id() as i64),
|
||||||
pos,
|
username: gameprofile.name.clone(),
|
||||||
entity.pitch.load(),
|
xuid: String::new(),
|
||||||
entity.yaw.load(),
|
platform_chat_id: String::new(),
|
||||||
entity.head_yaw.load(),
|
build_platform: 0,
|
||||||
0.into(),
|
skin: (**existing_player.bedrock_skin.load()).clone(),
|
||||||
entity.velocity.load(),
|
is_teacher: false,
|
||||||
))
|
is_host: false,
|
||||||
|
is_sub_client: false,
|
||||||
|
player_color: [0, 0, 0, 0],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.enqueue_packet_editioned(
|
||||||
|
&CPlayerInfoUpdate::new(
|
||||||
|
PlayerInfoFlags::ADD_PLAYER.bits(),
|
||||||
|
&[pumpkin_protocol::java::client::play::Player {
|
||||||
|
uuid: gameprofile.id,
|
||||||
|
actions: &[PlayerAction::AddPlayer {
|
||||||
|
name: &gameprofile.name,
|
||||||
|
properties: &gameprofile.properties.load(),
|
||||||
|
}],
|
||||||
|
}],
|
||||||
|
),
|
||||||
|
&bedrock_player_list,
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
player
|
||||||
|
.client
|
||||||
|
.enqueue_packet_editioned(
|
||||||
|
&CSpawnEntity::new(
|
||||||
|
existing_player.entity_id().into(),
|
||||||
|
gameprofile.id,
|
||||||
|
i32::from(EntityType::PLAYER.id).into(),
|
||||||
|
pos,
|
||||||
|
entity.pitch.load(),
|
||||||
|
entity.yaw.load(),
|
||||||
|
entity.head_yaw.load(),
|
||||||
|
0.into(),
|
||||||
|
entity.velocity.load(),
|
||||||
|
),
|
||||||
|
&bedrock_add_player,
|
||||||
|
)
|
||||||
.await;
|
.await;
|
||||||
{
|
{
|
||||||
let config = existing_player.config.load();
|
let config = existing_player.config.load();
|
||||||
@ -3145,8 +3537,33 @@ impl World {
|
|||||||
});
|
});
|
||||||
if let Some(ref player) = removed_player {
|
if let Some(ref player) = removed_player {
|
||||||
let uuid = player.gameprofile.id;
|
let uuid = player.gameprofile.id;
|
||||||
self.broadcast_packet_all(&CRemovePlayerInfo::new(&[uuid]));
|
let entity_id = player.entity_id();
|
||||||
self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()]));
|
|
||||||
|
let bedrock_remove_player = CPlayerList {
|
||||||
|
action: CPlayerList::ACTION_REMOVE,
|
||||||
|
entries: vec![PlayerListEntry {
|
||||||
|
uuid,
|
||||||
|
entity_unique_id: VarLong(entity_id as i64),
|
||||||
|
username: player.gameprofile.name.clone(),
|
||||||
|
xuid: String::new(),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
build_platform: 0,
|
||||||
|
skin: Skin::steve(),
|
||||||
|
is_teacher: false,
|
||||||
|
is_host: false,
|
||||||
|
is_sub_client: false,
|
||||||
|
player_color: [0, 0, 0, 0],
|
||||||
|
}],
|
||||||
|
};
|
||||||
|
|
||||||
|
self.broadcast_editioned(&CRemovePlayerInfo::new(&[uuid]), &bedrock_remove_player)
|
||||||
|
.await;
|
||||||
|
|
||||||
|
self.broadcast_editioned(
|
||||||
|
&CRemoveEntities::new(&[entity_id.into()]),
|
||||||
|
&CRemoveActor::new(VarLong(entity_id as i64)),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
|
||||||
if fire_event {
|
if fire_event {
|
||||||
let msg_comp = TextComponent::translate_cross(
|
let msg_comp = TextComponent::translate_cross(
|
||||||
@ -4273,6 +4690,33 @@ impl World {
|
|||||||
Self::broadcast_java_grouped(packet, recipients_by_version);
|
Self::broadcast_java_grouped(packet, recipients_by_version);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn broadcast_to_chunk_editioned_sync<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
chunk_pos: Vector2<i32>,
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
let players = self.players.load();
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
|
||||||
|
let recipients = players.iter().filter(|p| {
|
||||||
|
let center = p.living_entity.entity.chunk_pos.load();
|
||||||
|
let view_distance = get_view_distance(p).get() as i32;
|
||||||
|
is_within_view_distance(chunk_pos, center, view_distance)
|
||||||
|
});
|
||||||
|
|
||||||
|
for p in recipients {
|
||||||
|
match &p.client {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => be_client.try_enqueue_packet(be_packet),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let recipients_by_version =
|
||||||
|
Self::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
Self::broadcast_java_grouped(je_packet, recipients_by_version);
|
||||||
|
}
|
||||||
|
|
||||||
/// Broadcasts a packet to chunk watchers, excluding specific players.
|
/// Broadcasts a packet to chunk watchers, excluding specific players.
|
||||||
pub fn broadcast_to_chunk_except<P: ClientPacket>(
|
pub fn broadcast_to_chunk_except<P: ClientPacket>(
|
||||||
&self,
|
&self,
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user