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",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "bytemuck"
|
||||
version = "1.25.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c8efb64bd706a16a1bdde310ae86b351e4d21550d98d056f22f8a7f7a2183fec"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder"
|
||||
version = "1.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b"
|
||||
|
||||
[[package]]
|
||||
name = "byteorder-lite"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
|
||||
|
||||
[[package]]
|
||||
name = "bytes"
|
||||
version = "1.11.1"
|
||||
@ -1173,6 +1185,15 @@ version = "2.4.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "ff"
|
||||
version = "0.13.1"
|
||||
@ -1775,6 +1796,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "indexmap"
|
||||
version = "2.14.0"
|
||||
@ -2091,6 +2125,16 @@ dependencies = [
|
||||
"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]]
|
||||
name = "nix"
|
||||
version = "0.31.3"
|
||||
@ -2472,6 +2516,19 @@ version = "0.3.33"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "postcard"
|
||||
version = "1.1.3"
|
||||
@ -2619,6 +2676,7 @@ dependencies = [
|
||||
"futures",
|
||||
"hmac 0.13.0",
|
||||
"hyper",
|
||||
"image",
|
||||
"libloading",
|
||||
"notify",
|
||||
"num-bigint",
|
||||
@ -2841,6 +2899,12 @@ dependencies = [
|
||||
"xxhash-rust",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "pxfm"
|
||||
version = "0.1.29"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e0c5ccf5294c6ccd63a74f1565028353830a9c2f5eb0c682c355c471726a6e3f"
|
||||
|
||||
[[package]]
|
||||
name = "quote"
|
||||
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 boss_event;
|
||||
pub mod chunk_radius_update;
|
||||
pub mod common;
|
||||
pub mod container_open;
|
||||
pub mod correct_player_move;
|
||||
pub mod creative_content;
|
||||
@ -11,12 +14,16 @@ pub mod inventory_content;
|
||||
pub mod level_chunk;
|
||||
pub mod level_event;
|
||||
pub mod modal_form_request;
|
||||
pub mod move_actor_absolute;
|
||||
pub mod move_actor_delta;
|
||||
pub mod move_player;
|
||||
pub mod network_chunk_publisher_update;
|
||||
pub mod network_settings;
|
||||
pub mod play_status;
|
||||
pub mod player_hotbar;
|
||||
pub mod player_list;
|
||||
pub mod raknet;
|
||||
pub mod remove_actor;
|
||||
pub mod resource_pack_stack;
|
||||
pub mod resource_packs_info;
|
||||
pub mod scoreboard;
|
||||
@ -31,9 +38,12 @@ pub mod transfer;
|
||||
pub mod update_abilities;
|
||||
pub mod update_attributes;
|
||||
|
||||
pub use add_actor::*;
|
||||
pub use add_player::*;
|
||||
pub use available_commands::*;
|
||||
pub use boss_event::*;
|
||||
pub use chunk_radius_update::*;
|
||||
pub use common::*;
|
||||
pub use container_open::*;
|
||||
pub use correct_player_move::*;
|
||||
pub use creative_content::*;
|
||||
@ -44,12 +54,16 @@ pub use inventory_content::*;
|
||||
pub use level_chunk::*;
|
||||
pub use level_event::*;
|
||||
pub use modal_form_request::*;
|
||||
pub use move_actor_absolute::*;
|
||||
pub use move_actor_delta::*;
|
||||
pub use move_player::*;
|
||||
pub use network_chunk_publisher_update::*;
|
||||
pub use network_settings::*;
|
||||
pub use play_status::*;
|
||||
pub use player_hotbar::*;
|
||||
pub use player_list::*;
|
||||
pub use raknet::*;
|
||||
pub use remove_actor::*;
|
||||
pub use resource_pack_stack::*;
|
||||
pub use resource_packs_info::*;
|
||||
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_util::math::vector3::Vector3;
|
||||
use std::io::{Error, Write};
|
||||
|
||||
use crate::{codec::var_ulong::VarULong, serial::PacketWrite};
|
||||
|
||||
#[derive(Debug, PacketWrite)]
|
||||
#[derive(Debug)]
|
||||
#[packet(19)]
|
||||
pub struct CMovePlayer {
|
||||
// https://mojang.github.io/bedrock-protocol-docs/html/MovePlayerPacket.html
|
||||
pub player_runtime_id: VarULong,
|
||||
pub position: Vector3<f32>,
|
||||
pub pitch: f32,
|
||||
@ -15,6 +14,8 @@ pub struct CMovePlayer {
|
||||
pub mode: u8,
|
||||
pub on_ground: bool,
|
||||
pub riding_runtime_id: VarULong,
|
||||
pub teleport_cause: i32,
|
||||
pub teleport_source_entity_type: i32,
|
||||
pub tick: VarULong,
|
||||
}
|
||||
|
||||
@ -22,5 +23,54 @@ impl CMovePlayer {
|
||||
pub const MODE_NORMAL: u8 = 0;
|
||||
pub const MODE_RESET: u8 = 1;
|
||||
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 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 super::common::AbilityLayer;
|
||||
|
||||
#[packet(187)]
|
||||
#[derive(PacketWrite)]
|
||||
pub struct CUpdateAbilities {
|
||||
@ -13,17 +15,6 @@ pub struct CUpdateAbilities {
|
||||
pub layers: Vec<AbilityLayer>,
|
||||
}
|
||||
|
||||
#[derive(PacketWrite)]
|
||||
pub struct AbilityLayer {
|
||||
// https://mojang.github.io/bedrock-protocol-docs/html/SerializedAbilitiesData__SerializedLayer.html
|
||||
pub serialized_layer: u16,
|
||||
pub abilities_set: u32,
|
||||
pub ability_value: u32,
|
||||
pub fly_speed: f32,
|
||||
pub vertical_fly_speed: f32,
|
||||
pub walk_speed: f32,
|
||||
}
|
||||
|
||||
#[repr(u32)]
|
||||
pub enum Ability {
|
||||
Build = 0,
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
use std::io::{Error, Read, Write};
|
||||
use std::{
|
||||
io::{Error, Read, Write},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use pumpkin_macros::packet;
|
||||
|
||||
@ -7,36 +10,24 @@ use crate::{
|
||||
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)]
|
||||
pub enum AnimateAction {
|
||||
NoAction = 0,
|
||||
SwingArm = 1,
|
||||
WakeUp = 2,
|
||||
CriticalHit = 3,
|
||||
MagicCriticalHit = 4,
|
||||
RowRight = 128,
|
||||
RowLeft = 129,
|
||||
StopSleep = 3,
|
||||
CriticalHit = 4,
|
||||
MagicCriticalHit = 5,
|
||||
}
|
||||
|
||||
impl PacketRead for AnimateAction {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let action = u8::read(reader)?;
|
||||
match action {
|
||||
0 => Ok(Self::NoAction),
|
||||
1 => Ok(Self::SwingArm),
|
||||
2 => Ok(Self::WakeUp),
|
||||
3 => Ok(Self::CriticalHit),
|
||||
4 => Ok(Self::MagicCriticalHit),
|
||||
128 => Ok(Self::RowRight),
|
||||
129 => Ok(Self::RowLeft),
|
||||
3 => Ok(Self::StopSleep),
|
||||
4 => Ok(Self::CriticalHit),
|
||||
5 => Ok(Self::MagicCriticalHit),
|
||||
_ => 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 {
|
||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||
let action = AnimateAction::read(reader)?;
|
||||
let runtime_entity_id = VarULong::read(reader)?;
|
||||
let boat_rowing_time =
|
||||
if action == AnimateAction::RowRight || action == AnimateAction::RowLeft {
|
||||
Some(f32::read(reader)?)
|
||||
} else {
|
||||
None
|
||||
let data = f32::read(reader)?;
|
||||
|
||||
let swing_source_str = Option::<String>::read(reader)?;
|
||||
let swing_source = match swing_source_str {
|
||||
Some(s) => Some(AnimateSwingSource::from_str(&s)?),
|
||||
None => None,
|
||||
};
|
||||
let _swing_source = bool::read(reader)?;
|
||||
|
||||
Ok(Self {
|
||||
action,
|
||||
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> {
|
||||
self.action.write(writer)?;
|
||||
self.runtime_entity_id.write(writer)?;
|
||||
if let Some(rowing_time) = self.boat_rowing_time {
|
||||
rowing_time.write(writer)?;
|
||||
}
|
||||
// swingSource
|
||||
false.write(writer)?;
|
||||
self.data.write(writer)?;
|
||||
|
||||
let swing_source_str: Option<String> = self.swing_source.map(|s| s.as_str().to_string());
|
||||
swing_source_str.write(writer)?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,6 +18,8 @@ pub struct SInteraction {
|
||||
#[repr(i8)]
|
||||
pub enum Action {
|
||||
Invalid = 0,
|
||||
Interact = 1,
|
||||
Attack = 2,
|
||||
StopRiding = 3,
|
||||
InteractUpdate = 4,
|
||||
NpcOpen = 5,
|
||||
@ -31,6 +33,8 @@ impl PacketRead for Action {
|
||||
|
||||
let this = match byte[0] {
|
||||
0 => Self::Invalid,
|
||||
1 => Self::Interact,
|
||||
2 => Self::Attack,
|
||||
3 => Self::StopRiding,
|
||||
4 => Self::InteractUpdate,
|
||||
5 => Self::NpcOpen,
|
||||
|
||||
@ -199,7 +199,7 @@ pub enum InputData {
|
||||
StartFlying = 42,
|
||||
StopFlying = 43,
|
||||
ClientAckServerData = 44,
|
||||
ClientPredictedVehicle = 45, // Renamed to match Go
|
||||
ClientPredictedVehicle = 45,
|
||||
PaddlingLeft = 46,
|
||||
PaddlingRight = 47,
|
||||
BlockBreakingDelayEnabled = 48,
|
||||
|
||||
@ -20,7 +20,7 @@ pub type VarLongType = i64;
|
||||
/**
|
||||
* 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);
|
||||
|
||||
impl VarLong {
|
||||
|
||||
@ -20,7 +20,7 @@ pub type VarULongType = u64;
|
||||
/**
|
||||
* 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);
|
||||
|
||||
impl VarULong {
|
||||
|
||||
@ -109,6 +109,7 @@ postcard = { workspace = true }
|
||||
tracing-serde-structured = { workspace = true }
|
||||
|
||||
bitflags = { workspace = true }
|
||||
image = { version = "0.25.10", default-features = false, features = ["png"] }
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile.workspace = true
|
||||
|
||||
@ -197,7 +197,7 @@ impl Goal for MeleeAttackGoal {
|
||||
.await
|
||||
{
|
||||
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;
|
||||
}
|
||||
})
|
||||
|
||||
@ -47,8 +47,7 @@ use pumpkin_nbt::compound::NbtCompound;
|
||||
use pumpkin_nbt::tag::NbtTag;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
Animation, CEntityAnimation, CEntityStatus, CHurtAnimation, CSetPlayerInventory,
|
||||
CTakeItemEntity, CUpdateMobEffect,
|
||||
CEntityStatus, CHurtAnimation, CSetPlayerInventory, CTakeItemEntity, CUpdateMobEffect,
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
@ -715,15 +714,22 @@ impl LivingEntity {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn swing_hand(&self) {
|
||||
// TODO: radius
|
||||
self.entity
|
||||
.world
|
||||
.load()
|
||||
.broadcast_packet_all(&CEntityAnimation::new(
|
||||
self.entity_id().into(),
|
||||
Animation::SwingMainArm,
|
||||
));
|
||||
pub async fn swing_hand(&self) {
|
||||
let world = self.entity.world.load();
|
||||
let entity_id = self.entity_id();
|
||||
|
||||
let je_packet = pumpkin_protocol::java::client::play::CEntityAnimation::new(
|
||||
entity_id.into(),
|
||||
pumpkin_protocol::java::client::play::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>) {
|
||||
|
||||
@ -35,10 +35,19 @@ use pumpkin_nbt::{compound::NbtCompound, tag::NbtTag};
|
||||
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
||||
use pumpkin_protocol::{
|
||||
PositionFlag,
|
||||
bedrock::client::set_actor_data::{
|
||||
bedrock::client::{
|
||||
move_actor_delta::{
|
||||
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_ulong::VarULong,
|
||||
java::client::play::{
|
||||
@ -1129,6 +1138,7 @@ impl Entity {
|
||||
suffocating
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub fn send_pos_rot(&self) {
|
||||
let old = self.last_sent_pos.load();
|
||||
let new = self.pos.load();
|
||||
@ -1161,35 +1171,150 @@ impl Entity {
|
||||
|
||||
// Dynamically pick the most efficient packet
|
||||
if pos_changed && rot_changed {
|
||||
self.world.load().broadcast_to_chunk(
|
||||
chunk_pos,
|
||||
&CUpdateEntityPosRot::new(
|
||||
let je_packet = CUpdateEntityPosRot::new(
|
||||
self.entity_id.into(),
|
||||
Vector3::new(converted.x, converted.y, converted.z),
|
||||
yaw,
|
||||
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 {
|
||||
self.world.load().broadcast_to_chunk(
|
||||
chunk_pos,
|
||||
&CUpdateEntityPos::new(
|
||||
let je_packet = CUpdateEntityPos::new(
|
||||
self.entity_id.into(),
|
||||
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 if rot_changed {
|
||||
self.world.load().broadcast_to_chunk(
|
||||
} 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,
|
||||
&CUpdateEntityRot::new(
|
||||
&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 {
|
||||
let je_packet = CUpdateEntityRot::new(
|
||||
self.entity_id.into(),
|
||||
yaw,
|
||||
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);
|
||||
}
|
||||
@ -1220,14 +1345,53 @@ impl Entity {
|
||||
|
||||
self.last_sent_pos.store(new);
|
||||
|
||||
self.world.load().broadcast_to_chunk(
|
||||
chunk_pos,
|
||||
&CUpdateEntityPos::new(
|
||||
let je_packet = CUpdateEntityPos::new(
|
||||
self.entity_id.into(),
|
||||
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
|
||||
|
||||
@ -15,12 +15,11 @@ use pumpkin_data::dimension::Dimension;
|
||||
use pumpkin_data::meta_data_type::MetaDataType;
|
||||
use pumpkin_data::tracked_data::TrackedData;
|
||||
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::play_status::CPlayStatus;
|
||||
use pumpkin_protocol::bedrock::client::set_time::CSetTime;
|
||||
use pumpkin_protocol::bedrock::client::update_abilities::{
|
||||
Ability, AbilityLayer, CUpdateAbilities,
|
||||
};
|
||||
use pumpkin_protocol::bedrock::client::update_abilities::{Ability, CUpdateAbilities};
|
||||
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
||||
use pumpkin_protocol::bedrock::server::text::SText;
|
||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||
@ -493,9 +492,66 @@ pub struct Player {
|
||||
pub tab_list_listed: AtomicBool,
|
||||
pub enchantment_seed: 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 {
|
||||
#[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)]
|
||||
pub async fn new(
|
||||
client: ClientPlatform,
|
||||
@ -544,6 +600,14 @@ impl Player {
|
||||
let mut abilities = Abilities::default();
|
||||
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 {
|
||||
living_entity,
|
||||
config: ArcSwap::new(Arc::new(config)),
|
||||
@ -633,6 +697,7 @@ impl Player {
|
||||
tab_list_latency: AtomicI32::new(0),
|
||||
tab_list_listed: AtomicBool::new(false),
|
||||
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
|
||||
pub fn swing_hand(&self, hand: Hand, all: bool) {
|
||||
pub async fn swing_hand(&self, hand: Hand, all: bool) {
|
||||
let world = self.world();
|
||||
let entity_id = VarInt(self.entity_id());
|
||||
let chunk_pos = self.living_entity.entity.chunk_pos.load();
|
||||
let entity_id = self.entity_id();
|
||||
|
||||
let animation = match hand {
|
||||
Hand::Left => Animation::SwingMainArm,
|
||||
Hand::Right => Animation::SwingOffhand,
|
||||
Hand::Right => Animation::SwingMainArm,
|
||||
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 {
|
||||
world.broadcast_to_chunk(chunk_pos, &packet);
|
||||
world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||
} 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::logging::{GzipRollingLogger, PumpkinCommandCompleter, ReadlineLogWrapper};
|
||||
use crate::net::bedrock::BedrockClient;
|
||||
use crate::net::java::{JavaClient, PacketHandlerResult};
|
||||
use crate::net::{ClientPlatform, DisconnectReason};
|
||||
use crate::net::java::JavaClient;
|
||||
use crate::net::{ClientPlatform, DisconnectReason, PacketHandlerResult};
|
||||
use crate::net::{lan_broadcast::LANBroadcast, query, rcon::RCONServer};
|
||||
use crate::server::{Server, ticker::Ticker};
|
||||
use plugin::server::server_command::ServerCommandEvent;
|
||||
@ -418,6 +418,7 @@ impl PumpkinServer {
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_lines)]
|
||||
pub async fn unified_listener_task(
|
||||
&self,
|
||||
master_client_id_counter: &mut u64,
|
||||
@ -501,7 +502,9 @@ impl PumpkinServer {
|
||||
let be_clients = bedrock_clients.clone();
|
||||
let mut clients_guard = bedrock_clients.lock().await;
|
||||
|
||||
let mut is_new = false;
|
||||
let client = clients_guard.entry(client_addr).or_insert_with(|| {
|
||||
is_new = true;
|
||||
*master_client_id_counter += 1;
|
||||
|
||||
let new_client = Arc::new(BedrockClient::new(
|
||||
@ -514,6 +517,39 @@ impl PumpkinServer {
|
||||
new_client
|
||||
}).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 server = self.server.clone();
|
||||
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
use crate::{
|
||||
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
||||
net::{
|
||||
DisconnectReason, GameProfile, PacketHandlerResult, PlayerConfig, bedrock::BedrockClient,
|
||||
},
|
||||
server::Server,
|
||||
};
|
||||
use arc_swap::ArcSwap;
|
||||
@ -23,7 +25,7 @@ use serde::{Deserialize, de::Error};
|
||||
use serde_repr::Deserialize_repr;
|
||||
use std::sync::Arc;
|
||||
use thiserror::Error;
|
||||
use tracing::{debug, warn};
|
||||
use tracing::debug;
|
||||
use uuid::Uuid;
|
||||
|
||||
#[derive(Debug, Error)]
|
||||
@ -117,27 +119,19 @@ impl BedrockClient {
|
||||
self.set_compression(compression).await;
|
||||
}
|
||||
|
||||
pub async fn handle_login(self: &Arc<Self>, packet: SLogin, server: &Server) -> Option<()> {
|
||||
match self.try_handle_login(packet, server).await {
|
||||
Ok(()) => Some(()),
|
||||
Err(error) => {
|
||||
warn!("Bedrock login failed: {error}");
|
||||
let message = match error {
|
||||
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 handle_login(
|
||||
self: &Arc<Self>,
|
||||
packet: SLogin,
|
||||
server: &Server,
|
||||
) -> Result<PacketHandlerResult, LoginError> {
|
||||
self.try_handle_login(packet, server).await
|
||||
}
|
||||
|
||||
pub async fn try_handle_login(
|
||||
self: &Arc<Self>,
|
||||
packet: SLogin,
|
||||
server: &Server,
|
||||
) -> Result<(), LoginError> {
|
||||
) -> Result<PacketHandlerResult, LoginError> {
|
||||
let auth_payload: AuthPayload = serde_json::from_slice(&packet.jwt)?;
|
||||
let player_data = if server.basic_config.online_mode {
|
||||
match auth_payload.authentication_type {
|
||||
@ -222,26 +216,15 @@ impl BedrockClient {
|
||||
|
||||
self.send_frame_set(frame_set, 0x84).await;
|
||||
|
||||
if let Some((player, _world)) = server
|
||||
.add_player(ClientPlatform::Bedrock(self.clone()), profile, None)
|
||||
.await
|
||||
{
|
||||
// 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))));
|
||||
|
||||
// player spawn happens after resource packs are resolved
|
||||
*self.player.lock().await = Some(player);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
Ok(PacketHandlerResult::ReadyToPlay(profile, new_config))
|
||||
}
|
||||
|
||||
pub async fn handle_resource_pack_response(
|
||||
|
||||
@ -57,8 +57,8 @@ use pumpkin_protocol::{
|
||||
use std::net::SocketAddr;
|
||||
use tokio::{
|
||||
net::UdpSocket,
|
||||
sync::Mutex,
|
||||
sync::mpsc::{Receiver, Sender},
|
||||
sync::{Mutex, oneshot},
|
||||
task::JoinHandle,
|
||||
};
|
||||
use tokio_util::{sync::CancellationToken, task::TaskTracker};
|
||||
@ -67,11 +67,36 @@ pub mod connection;
|
||||
pub mod login;
|
||||
pub mod open_connection;
|
||||
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 pumpkin_protocol::bedrock::server::login::ClientData;
|
||||
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 {
|
||||
socket: Arc<UdpSocket>,
|
||||
/// The client's IP address.
|
||||
@ -84,9 +109,12 @@ pub struct BedrockClient {
|
||||
pub be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||
|
||||
tasks: TaskTracker,
|
||||
outgoing_packet_queue_send: Sender<Bytes>,
|
||||
outgoing_packet_queue_send: Sender<OutgoingPacket>,
|
||||
/// 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.
|
||||
network_writer: Arc<Mutex<UDPNetworkEncoder>>,
|
||||
@ -114,6 +142,8 @@ pub struct BedrockClient {
|
||||
expected_order_index: Mutex<HashMap<u8, u32>>,
|
||||
highest_sequence_index: Mutex<HashMap<u8, u32>>,
|
||||
ordered_queues: Mutex<HashMap<u8, BTreeMap<u32, Frame>>>,
|
||||
incoming_game_packet_send: Sender<RawPacket>,
|
||||
incoming_game_packet_recv: Mutex<Option<Receiver<RawPacket>>>,
|
||||
}
|
||||
|
||||
impl BedrockClient {
|
||||
@ -124,6 +154,8 @@ impl BedrockClient {
|
||||
be_clients: Arc<Mutex<HashMap<SocketAddr, Arc<Self>>>>,
|
||||
) -> Self {
|
||||
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 {
|
||||
socket,
|
||||
player: Mutex::new(None),
|
||||
@ -136,6 +168,8 @@ impl BedrockClient {
|
||||
tasks: TaskTracker::new(),
|
||||
outgoing_packet_queue_send: send,
|
||||
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),
|
||||
output_sequence_number: AtomicU32::new(0),
|
||||
output_reliable_number: AtomicU32::new(0),
|
||||
@ -153,6 +187,17 @@ impl BedrockClient {
|
||||
highest_sequence_index: Mutex::new(HashMap::new()),
|
||||
ordered_queues: Mutex::new(HashMap::new()),
|
||||
//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()
|
||||
.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));
|
||||
|
||||
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() => {
|
||||
// Check for timeout (10 seconds)
|
||||
if client.last_seen.load().elapsed() > std::time::Duration::from_secs(10) {
|
||||
debug!("Bedrock client {} timed out", client.address);
|
||||
client.close().await;
|
||||
break;
|
||||
return;
|
||||
}
|
||||
|
||||
// Flush ACKs
|
||||
@ -207,23 +261,21 @@ impl BedrockClient {
|
||||
warn!("Failed to resend packet for sequence {}: {}", seq, err);
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
recv_result = packet_receiver.recv() => {
|
||||
let Some(packet_data) = recv_result else {
|
||||
res = packet_receiver.recv() => res,
|
||||
};
|
||||
|
||||
let Some(packet) = recv_result else {
|
||||
break;
|
||||
};
|
||||
|
||||
if let Err(err) = client.network_writer
|
||||
.lock()
|
||||
.await
|
||||
.write_packet(&packet_data, client.address, &client.socket)
|
||||
.await
|
||||
&& !client.close_token.is_cancelled() {
|
||||
warn!("Failed to send packet to client: {err}",);
|
||||
client.close_token.cancel();
|
||||
break;
|
||||
}
|
||||
}
|
||||
client
|
||||
.send_framed_packet_data(packet.data.to_vec(), RakReliability::ReliableOrdered)
|
||||
.await;
|
||||
|
||||
if let Some(completion) = packet.completion {
|
||||
let _ = completion.send(());
|
||||
}
|
||||
}
|
||||
});
|
||||
@ -259,6 +311,43 @@ impl BedrockClient {
|
||||
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
|
||||
/// in-order to the client
|
||||
///
|
||||
@ -266,7 +355,11 @@ impl BedrockClient {
|
||||
///
|
||||
/// * `packet`: A reference to a packet object implementing the `ClientPacket` trait.
|
||||
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
|
||||
if !self.is_closed() {
|
||||
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) {
|
||||
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 {
|
||||
tokio::sync::mpsc::error::TrySendError::Full(_) => {
|
||||
debug!(
|
||||
@ -309,8 +405,8 @@ impl BedrockClient {
|
||||
let mut packet_payload = Vec::new();
|
||||
packet.write_packet(&mut packet_payload)?;
|
||||
|
||||
// TODO
|
||||
self.network_writer.lock().await.write_game_packet(
|
||||
let mut encoder = self.network_writer.lock().await;
|
||||
encoder.write_game_packet(
|
||||
P::PACKET_ID as u16,
|
||||
SubClient::Main,
|
||||
SubClient::Main,
|
||||
@ -337,8 +433,18 @@ impl BedrockClient {
|
||||
let mut packet_buf = Vec::new();
|
||||
match self.write_game_packet(packet, &mut packet_buf).await {
|
||||
Ok(()) => {
|
||||
self.send_framed_packet_data(packet_buf, RakReliability::ReliableOrdered)
|
||||
.await;
|
||||
let (tx, rx) = oneshot::channel();
|
||||
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}"),
|
||||
}
|
||||
@ -710,48 +816,70 @@ impl BedrockClient {
|
||||
}
|
||||
|
||||
async fn handle_game_packet(
|
||||
self: &Arc<Self>,
|
||||
server: &Arc<Server>,
|
||||
&self,
|
||||
_server: &Arc<Server>,
|
||||
packet: RawPacket,
|
||||
) -> Result<(), Error> {
|
||||
let packet_id = packet.id;
|
||||
if let Err(err) = self.incoming_game_packet_send.send(packet).await {
|
||||
debug!("Failed to send game packet to session task: {err}");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub async fn handle_login_sequence(
|
||||
self: &Arc<Self>,
|
||||
server: &Arc<Server>,
|
||||
) -> PacketHandlerResult {
|
||||
while let Some(packet) = self.get_packet().await {
|
||||
let payload = &mut Cursor::new(&packet.payload);
|
||||
let result = match packet.id {
|
||||
match packet.id {
|
||||
SRequestNetworkSettings::PACKET_ID => {
|
||||
self.handle_request_network_settings(
|
||||
SRequestNetworkSettings::read(payload)?,
|
||||
server,
|
||||
)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
SLogin::PACKET_ID => {
|
||||
self.handle_login(SLogin::read(payload)?, server).await;
|
||||
Ok(())
|
||||
}
|
||||
SClientCacheStatus::PACKET_ID => {
|
||||
// TODO
|
||||
Ok(())
|
||||
}
|
||||
SResourcePackResponse::PACKET_ID => {
|
||||
self.handle_resource_pack_response(SResourcePackResponse::read(payload)?, server)
|
||||
.await;
|
||||
Ok(())
|
||||
}
|
||||
_ => {
|
||||
let player_lock = self.player.lock().await;
|
||||
if let Some(player) = player_lock.as_ref() {
|
||||
self.handle_play_packet(player, server, packet).await
|
||||
} else {
|
||||
debug!(
|
||||
"Received game packet {} before player was initialized",
|
||||
packet.id
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
let packet = match SRequestNetworkSettings::read(payload) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
error!("Failed to read SRequestNetworkSettings: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
result.map_err(|e| Error::new(e.kind(), format!("Game packet {packet_id}: {e}")))
|
||||
self.handle_request_network_settings(packet, server).await;
|
||||
}
|
||||
SLogin::PACKET_ID => {
|
||||
let packet = match SLogin::read(payload) {
|
||||
Ok(p) => p,
|
||||
Err(err) => {
|
||||
error!("Failed to read SLogin: {err}");
|
||||
continue;
|
||||
}
|
||||
};
|
||||
match self.handle_login(packet, server).await {
|
||||
Ok(result) => return result,
|
||||
Err(err) => {
|
||||
warn!("Bedrock login failed: {err}");
|
||||
return PacketHandlerResult::Stop;
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
debug!(
|
||||
"Received unexpected game packet {} during login sequence",
|
||||
packet.id
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
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(
|
||||
@ -760,8 +888,16 @@ impl BedrockClient {
|
||||
server: &Arc<Server>,
|
||||
packet: RawPacket,
|
||||
) -> Result<(), Error> {
|
||||
let reader = &mut &packet.payload[..];
|
||||
let payload = &packet.payload[..];
|
||||
let reader = &mut &payload[..];
|
||||
match packet.id {
|
||||
SClientCacheStatus::PACKET_ID => {
|
||||
// TODO
|
||||
}
|
||||
SResourcePackResponse::PACKET_ID => {
|
||||
self.handle_resource_pack_response(SResourcePackResponse::read(reader)?, server)
|
||||
.await;
|
||||
}
|
||||
SPlayerAuthInput::PACKET_ID => {
|
||||
self.handle_player_auth_input(player, SPlayerAuthInput::read(reader)?, server)
|
||||
.await;
|
||||
@ -797,7 +933,7 @@ impl BedrockClient {
|
||||
.await;
|
||||
}
|
||||
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 => {
|
||||
self.handle_modal_form_response(
|
||||
|
||||
@ -19,8 +19,8 @@ use pumpkin_protocol::{
|
||||
text::SText,
|
||||
},
|
||||
},
|
||||
codec::{var_int::VarInt, var_long::VarLong},
|
||||
java::client::play::{Animation, CSystemChatMessage},
|
||||
codec::{var_int::VarInt, var_long::VarLong, var_ulong::VarULong},
|
||||
java::client::play::{Animation, CEntityAnimation, CSystemChatMessage},
|
||||
};
|
||||
use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent};
|
||||
|
||||
@ -95,6 +95,7 @@ impl BedrockClient {
|
||||
player.set_client_loaded(true);
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn handle_player_auth_input(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
@ -104,16 +105,135 @@ impl BedrockClient {
|
||||
if !player.has_client_loaded() {
|
||||
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();
|
||||
|
||||
if new_pos != old_pos {
|
||||
let new_pitch = packet.pitch;
|
||||
let new_yaw = packet.yaw;
|
||||
|
||||
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 entity = player.get_entity();
|
||||
|
||||
if input_data.get(InputData::StartSprinting as usize) {
|
||||
entity.set_sprinting(true).await;
|
||||
@ -189,36 +309,37 @@ impl BedrockClient {
|
||||
.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() {
|
||||
return;
|
||||
}
|
||||
|
||||
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::WakeUp => Some(Animation::LeaveBed),
|
||||
AnimateAction::CriticalHit => Some(Animation::CriticalEffect),
|
||||
AnimateAction::MagicCriticalHit => Some(Animation::MagicCriticaleffect),
|
||||
_ => None,
|
||||
AnimateAction::StopSleep => None, // TODO
|
||||
};
|
||||
|
||||
// if let Some(animation) = java_animation {
|
||||
// let je_packet = CEntityAnimation::new(VarInt(entity.entity_id), animation);
|
||||
// let be_packet = SAnimate {
|
||||
// action: packet.action,
|
||||
// runtime_entity_id: VarULong(entity.entity_id as u64),
|
||||
// boat_rowing_time: packet.boat_rowing_time,
|
||||
// };
|
||||
// world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||
// }
|
||||
if let Some(animation) = java_animation {
|
||||
let je_packet = CEntityAnimation::new(VarInt(entity.entity_id), animation);
|
||||
let be_packet = SAnimate {
|
||||
action: packet.action,
|
||||
runtime_entity_id: VarULong(entity.entity_id as u64),
|
||||
data: 0.0,
|
||||
swing_source: None,
|
||||
};
|
||||
world.broadcast_editioned(&je_packet, &be_packet).await;
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_interaction(&self, _player: &Arc<Player>, packet: SInteraction) {
|
||||
if matches!(packet.action, Action::OpenInventory) {
|
||||
pub async fn handle_interaction(&self, player: &Arc<Player>, packet: SInteraction) {
|
||||
match packet.action {
|
||||
Action::OpenInventory => {
|
||||
self.send_game_packet(&CContainerOpen {
|
||||
container_id: 0,
|
||||
container_type: 0xff,
|
||||
@ -227,6 +348,15 @@ impl BedrockClient {
|
||||
})
|
||||
.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;
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn handle_container_close(&self, _player: &Arc<Player>, packet: SContainerClose) {
|
||||
|
||||
@ -67,7 +67,7 @@ pub mod recipe_helper;
|
||||
pub mod status;
|
||||
|
||||
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::{error::PumpkinError, net::EncryptionError, server::Server};
|
||||
|
||||
@ -112,10 +112,9 @@ pub struct JavaClient {
|
||||
pub last_keep_alive_time: AtomicCell<Instant>,
|
||||
}
|
||||
|
||||
pub enum PacketHandlerResult {
|
||||
Stop,
|
||||
// Signal to spawn the player
|
||||
ReadyToPlay(GameProfile, PlayerConfig),
|
||||
pub enum OutgoingPacketType {
|
||||
Normal,
|
||||
HighPriority,
|
||||
}
|
||||
|
||||
struct OutgoingPacket {
|
||||
|
||||
@ -54,7 +54,9 @@ use pumpkin_inventory::merchant::merchant_screen_handler::MerchantScreenHandler;
|
||||
use pumpkin_inventory::player::player_inventory::PlayerInventory;
|
||||
use pumpkin_inventory::screen_handler::{InventoryPlayer, ScreenHandler};
|
||||
use pumpkin_macros::send_cancellable;
|
||||
use pumpkin_protocol::bedrock::client::CMovePlayer;
|
||||
use pumpkin_protocol::codec::var_int::VarInt;
|
||||
use pumpkin_protocol::codec::var_ulong::VarULong;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
CBlockUpdate, CCommandSuggestions, CEntityPositionSync, CHeadRot, COpenSignEditor,
|
||||
CPingResponse, CPlayerInfoUpdate, CPlayerPosition, CSetSelectedSlot, CSystemChatMessage,
|
||||
@ -309,6 +311,7 @@ impl JavaClient {
|
||||
true
|
||||
}
|
||||
|
||||
#[expect(clippy::too_many_lines)]
|
||||
pub async fn handle_position(
|
||||
&self,
|
||||
player: &Arc<Player>,
|
||||
@ -369,8 +372,7 @@ impl JavaClient {
|
||||
// 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) {
|
||||
// Send the new position to all other players.
|
||||
world
|
||||
.broadcast_packet_except(
|
||||
world.broadcast_packet_except_editioned_sync(
|
||||
&[player.gameprofile.id],
|
||||
&CUpdateEntityPos::new(
|
||||
player.entity_id().into(),
|
||||
@ -381,8 +383,20 @@ impl JavaClient {
|
||||
),
|
||||
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
|
||||
@ -494,8 +508,7 @@ impl JavaClient {
|
||||
sync_position(player, &world, pos, last_pos, yaw, pitch, (packet.collision & FLAG_ON_GROUND) != 0)
|
||||
{
|
||||
// Send the new position to all other players.
|
||||
world
|
||||
.broadcast_packet_except(
|
||||
world.broadcast_packet_except_editioned_sync(
|
||||
&[player.gameprofile.id],
|
||||
&CUpdateEntityPosRot::new(
|
||||
entity_id.into(),
|
||||
@ -508,8 +521,20 @@ impl JavaClient {
|
||||
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
|
||||
@ -591,11 +616,37 @@ impl JavaClient {
|
||||
// let head_yaw = modulus(entity.head_yaw * 256.0 / 360.0, 256.0);
|
||||
|
||||
let world = entity.world.load_full();
|
||||
let packet =
|
||||
let je_packet =
|
||||
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);
|
||||
world.broadcast_packet_except(&[player.gameprofile.id], &packet);
|
||||
|
||||
let pos = entity.pos.load();
|
||||
|
||||
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(
|
||||
@ -1176,7 +1227,7 @@ impl JavaClient {
|
||||
server;
|
||||
event;
|
||||
'after: {
|
||||
player.swing_hand(hand, false);
|
||||
player.swing_hand(hand, false).await;
|
||||
}
|
||||
}}
|
||||
}
|
||||
@ -2028,7 +2079,7 @@ impl JavaClient {
|
||||
// TODO: Trigger ANY_BLOCK_USE Criteria
|
||||
|
||||
if matches!(result, BlockActionResult::SuccessServer) {
|
||||
player.swing_hand(hand, true);
|
||||
player.swing_hand(hand, true).await;
|
||||
}
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@ use std::{
|
||||
};
|
||||
|
||||
use pumpkin_data::translation;
|
||||
use pumpkin_protocol::{ClientPacket, Property};
|
||||
use pumpkin_protocol::{BClientPacket, ClientPacket, Property};
|
||||
use pumpkin_util::{
|
||||
Hand, ProfileAction,
|
||||
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
|
||||
#[expect(clippy::large_enum_variant)]
|
||||
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 {
|
||||
Self::Java(java) => java.enqueue_packet(packet).await,
|
||||
Self::Bedrock(_) => (),
|
||||
Self::Java(java) => java.enqueue_packet(je_packet).await,
|
||||
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) {
|
||||
match self {
|
||||
Self::Java(java) => java.try_enqueue_packet(packet),
|
||||
Self::Bedrock(_) => (),
|
||||
if let Self::Java(java) = self {
|
||||
java.try_enqueue_packet(packet);
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
match self {
|
||||
Self::Java(java) => java.send_packet_now(packet).await,
|
||||
Self::Bedrock(_) => (),
|
||||
if let Self::Java(java) = self {
|
||||
java.send_packet_now(packet).await;
|
||||
}
|
||||
}
|
||||
|
||||
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 pumpkin_data::attributes::Attributes;
|
||||
use pumpkin_data::chunk::Biome;
|
||||
use pumpkin_protocol::bedrock::client::EntityProperties;
|
||||
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_world::generation::proto_chunk::GenerationCache;
|
||||
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::frame_set::FrameSet;
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage, CExplosion,
|
||||
CRespawn, CSetBlockDestroyStage, CWorldEvent,
|
||||
};
|
||||
use pumpkin_protocol::java::client::play::{
|
||||
CPlayerSpawnPosition, CRecipeBookAdd, CRecipeBookSettings, CSystemChatMessage,
|
||||
};
|
||||
@ -81,15 +87,17 @@ use pumpkin_protocol::{
|
||||
BClientPacket, ClientPacket, IdOr, SoundEvent,
|
||||
bedrock::{
|
||||
client::{
|
||||
add_player::CAddPlayer,
|
||||
creative_content::{CreativeContent, Group},
|
||||
gamerules_changed::GameRules,
|
||||
player_list::{CPlayerList, PlayerListEntry, Skin},
|
||||
remove_actor::CRemoveActor,
|
||||
start_game::{Experiments, GamePublishSetting, LevelSettings},
|
||||
update_attributes::{Attribute, CUpdateAttributes},
|
||||
},
|
||||
network_item::NetworkItemDescriptor,
|
||||
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::{
|
||||
self,
|
||||
client::play::{
|
||||
@ -105,13 +113,7 @@ use pumpkin_protocol::{
|
||||
codec::item_stack_seralizer::ItemStackSerializer,
|
||||
java::client::play::{CBlockEvent, CRemoveMobEffect, CSetEquipment, CUpdateMobEffect},
|
||||
};
|
||||
use pumpkin_protocol::{
|
||||
codec::var_int::VarInt,
|
||||
java::client::play::{
|
||||
CBlockUpdate, CChunkBatchEnd, CChunkBatchStart, CChunkData, CDisguisedChatMessage,
|
||||
CExplosion, CRespawn, CSetBlockDestroyStage, CWorldEvent,
|
||||
},
|
||||
};
|
||||
use pumpkin_util::GameMode;
|
||||
use pumpkin_util::resource_location::ResourceLocation;
|
||||
use pumpkin_util::text::{TextComponent, color::NamedColor};
|
||||
use pumpkin_util::version::JavaMinecraftVersion;
|
||||
@ -618,6 +620,59 @@ impl World {
|
||||
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.
|
||||
///
|
||||
/// 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 weather = self.weather.lock().await;
|
||||
let runtime_id = player.entity_id() as u64;
|
||||
|
||||
let (position, yaw, pitch) = if player.has_played_before.load(Ordering::Relaxed) {
|
||||
let position = player.position();
|
||||
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)
|
||||
};
|
||||
|
||||
// Todo make the data less spread
|
||||
let level_settings = LevelSettings {
|
||||
seed: self.level.seed.0,
|
||||
@ -1708,17 +1765,12 @@ impl World {
|
||||
// TODO Make this unique
|
||||
multiplayer_correlation_id: Uuid::default().to_string(),
|
||||
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(),
|
||||
|
||||
compound_id: 10,
|
||||
compound_len: VarUInt(0),
|
||||
compound_end: 0,
|
||||
|
||||
block_registry_checksum: 0,
|
||||
world_template_id: Uuid::nil(),
|
||||
// TODO The client needs extra biome data for this
|
||||
enable_clientside_generation: false,
|
||||
blocknetwork_ids_are_hashed: false,
|
||||
server_auth_sounds: false,
|
||||
@ -1731,7 +1783,9 @@ impl World {
|
||||
},
|
||||
})
|
||||
.await;
|
||||
|
||||
chunker::update_position(&player).await;
|
||||
|
||||
client
|
||||
.send_game_packet(&CreativeContent {
|
||||
groups: &[Group {
|
||||
@ -1747,8 +1801,8 @@ impl World {
|
||||
let mut abilities = player.abilities.lock().await;
|
||||
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::HEIGHT, MetadataValue::Float(1.8));
|
||||
|
||||
@ -1781,18 +1835,15 @@ impl World {
|
||||
tick: VarULong(0),
|
||||
};
|
||||
client.send_game_packet(&actor_data).await;
|
||||
|
||||
player.send_abilities_update().await;
|
||||
|
||||
{
|
||||
let command_dispatcher = server.command_dispatcher.read().await;
|
||||
|
||||
client_suggestions::send_bedrock_commands_packet(&player, server, &command_dispatcher)
|
||||
.await;
|
||||
};
|
||||
|
||||
let mut frame_set = FrameSet::default();
|
||||
|
||||
// https://github.com/pmmp/PocketMine-MP/blob/0b6d8f8cb2aaa05ffad0b6386bd88d73ef54b395/src/entity/AttributeFactory.php#L34
|
||||
client
|
||||
.write_game_packet_to_set(
|
||||
&CUpdateAttributes {
|
||||
@ -1865,6 +1916,188 @@ impl World {
|
||||
)
|
||||
.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)]
|
||||
@ -2093,9 +2326,84 @@ impl World {
|
||||
|
||||
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.
|
||||
self.broadcast_packet_except(
|
||||
self.broadcast_packet_except_editioned_sync(
|
||||
&[player.gameprofile.id],
|
||||
&CSpawnEntity::new(
|
||||
entity_id.into(),
|
||||
@ -2108,6 +2416,7 @@ impl World {
|
||||
0.into(),
|
||||
velocity,
|
||||
),
|
||||
&bedrock_add_player,
|
||||
);
|
||||
|
||||
// Spawn players for our client.
|
||||
@ -2121,10 +2430,91 @@ impl World {
|
||||
let entity = &existing_player.living_entity.entity;
|
||||
let pos = entity.pos.load();
|
||||
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
|
||||
.enqueue_packet(&CSpawnEntity::new(
|
||||
let bedrock_player_list = CPlayerList {
|
||||
action: CPlayerList::ACTION_ADD,
|
||||
entries: vec![PlayerListEntry {
|
||||
uuid: gameprofile.id,
|
||||
entity_unique_id: VarLong(existing_player.entity_id() as i64),
|
||||
username: gameprofile.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],
|
||||
}],
|
||||
};
|
||||
|
||||
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(),
|
||||
@ -2134,7 +2524,9 @@ impl World {
|
||||
entity.head_yaw.load(),
|
||||
0.into(),
|
||||
entity.velocity.load(),
|
||||
))
|
||||
),
|
||||
&bedrock_add_player,
|
||||
)
|
||||
.await;
|
||||
{
|
||||
let config = existing_player.config.load();
|
||||
@ -3145,8 +3537,33 @@ impl World {
|
||||
});
|
||||
if let Some(ref player) = removed_player {
|
||||
let uuid = player.gameprofile.id;
|
||||
self.broadcast_packet_all(&CRemovePlayerInfo::new(&[uuid]));
|
||||
self.broadcast_packet_all(&CRemoveEntities::new(&[player.entity_id().into()]));
|
||||
let entity_id = player.entity_id();
|
||||
|
||||
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 {
|
||||
let msg_comp = TextComponent::translate_cross(
|
||||
@ -4273,6 +4690,33 @@ impl World {
|
||||
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.
|
||||
pub fn broadcast_to_chunk_except<P: ClientPacket>(
|
||||
&self,
|
||||
|
||||
Loading…
Reference in New Issue
Block a user