mirror of
https://github.com/Pumpkin-MC/Pumpkin.git
synced 2026-08-30 20:14:23 +00:00
chore: some more bedrock work
This commit is contained in:
12927
assets/en_us_bedrock.lang
Normal file
12927
assets/en_us_bedrock.lang
Normal file
File diff suppressed because it is too large
Load Diff
20
pumpkin-protocol/src/bedrock/client/level_event.rs
Normal file
20
pumpkin-protocol/src/bedrock/client/level_event.rs
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
use pumpkin_macros::packet;
|
||||||
|
use pumpkin_util::math::vector3::Vector3;
|
||||||
|
|
||||||
|
use crate::{codec::var_int::VarInt, serial::PacketWrite};
|
||||||
|
|
||||||
|
#[derive(PacketWrite)]
|
||||||
|
#[packet(25)]
|
||||||
|
pub struct CLevelEvent {
|
||||||
|
pub event_id: VarInt,
|
||||||
|
pub position: Vector3<f32>,
|
||||||
|
pub data: VarInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(i32)]
|
||||||
|
pub enum LevelEvent {
|
||||||
|
// There are hundreds of these, adding only what we need for now
|
||||||
|
BlockStartBreak = 3600,
|
||||||
|
BlockStopBreak = 3601,
|
||||||
|
BlockUpdateBreak = 3602,
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ pub mod gamerules_changed;
|
|||||||
pub mod handshake;
|
pub mod handshake;
|
||||||
pub mod inventory_content;
|
pub mod inventory_content;
|
||||||
pub mod level_chunk;
|
pub mod level_chunk;
|
||||||
|
pub mod level_event;
|
||||||
pub mod move_player;
|
pub mod move_player;
|
||||||
pub mod network_chunk_publisher_update;
|
pub mod network_chunk_publisher_update;
|
||||||
pub mod network_settings;
|
pub mod network_settings;
|
||||||
|
|||||||
@@ -45,21 +45,29 @@ impl EntityMetadata {
|
|||||||
self.0.insert(key, value);
|
self.0.insert(key, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn set_flag(&mut self, key: u32, index: u8) {
|
pub fn set_flag(&mut self, key: u32, index: u8, value: bool) {
|
||||||
if key == entity_data_key::PLAYER_FLAGS {
|
if key == entity_data_key::PLAYER_FLAGS {
|
||||||
let current_value = match self.0.get(&key) {
|
let current_value = match self.0.get(&key) {
|
||||||
Some(MetadataValue::Byte(v)) => *v,
|
Some(MetadataValue::Byte(v)) => *v,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
self.0
|
let new_value = if value {
|
||||||
.insert(key, MetadataValue::Byte(current_value | (1i8 << index)));
|
current_value | (1i8 << index)
|
||||||
|
} else {
|
||||||
|
current_value & !(1i8 << index)
|
||||||
|
};
|
||||||
|
self.0.insert(key, MetadataValue::Byte(new_value));
|
||||||
} else {
|
} else {
|
||||||
let current_value = match self.0.get(&key) {
|
let current_value = match self.0.get(&key) {
|
||||||
Some(MetadataValue::Long(v)) => *v,
|
Some(MetadataValue::Long(v)) => *v,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
self.0
|
let new_value = if value {
|
||||||
.insert(key, MetadataValue::Long(current_value | (1i64 << index)));
|
current_value | (1i64 << index)
|
||||||
|
} else {
|
||||||
|
current_value & !(1i64 << index)
|
||||||
|
};
|
||||||
|
self.0.insert(key, MetadataValue::Long(new_value));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
84
pumpkin-protocol/src/bedrock/server/animate.rs
Normal file
84
pumpkin-protocol/src/bedrock/server/animate.rs
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
use std::io::{Error, Read, Write};
|
||||||
|
|
||||||
|
use pumpkin_macros::packet;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
codec::{var_int::VarInt, var_ulong::VarULong},
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
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),
|
||||||
|
_ => Err(Error::other(format!(
|
||||||
|
"Invalid animate action ID: {}",
|
||||||
|
action
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PacketWrite for AnimateAction {
|
||||||
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
|
(*self as u8).write(writer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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 _swing_source = bool::read(reader)?;
|
||||||
|
Ok(Self {
|
||||||
|
action,
|
||||||
|
runtime_entity_id,
|
||||||
|
boat_rowing_time,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,19 +1,19 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
use uuid::Uuid;
|
use uuid::Uuid;
|
||||||
|
|
||||||
use crate::{
|
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
|
||||||
codec::{var_int::VarInt, var_uint::VarUInt},
|
|
||||||
serial::PacketRead,
|
|
||||||
};
|
|
||||||
|
|
||||||
#[derive(Debug, PacketRead)]
|
#[derive(Debug, PacketRead)]
|
||||||
#[packet(77)]
|
#[packet(77)]
|
||||||
pub struct SCommandRequest {
|
pub struct SCommandRequest {
|
||||||
// https://mojang.github.io/bedrock-protocol-docs/html/CommandRequestPacket.html
|
|
||||||
pub command: String,
|
pub command: String,
|
||||||
pub command_type: VarUInt,
|
|
||||||
|
// Command Origin
|
||||||
|
pub command_type: String,
|
||||||
pub command_uuid: Uuid,
|
pub command_uuid: Uuid,
|
||||||
pub request_id: String,
|
pub request_id: String,
|
||||||
|
pub player_actor_unique_id: i64,
|
||||||
|
|
||||||
pub is_internal_source: bool,
|
pub is_internal_source: bool,
|
||||||
pub version: VarInt,
|
pub version: String,
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
|
use serde::Deserialize;
|
||||||
use std::io::{Error, ErrorKind, Read};
|
use std::io::{Error, ErrorKind, Read};
|
||||||
|
|
||||||
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
|
use crate::{codec::var_uint::VarUInt, serial::PacketRead};
|
||||||
@@ -46,3 +47,133 @@ impl PacketRead for SLogin {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct SkinAnimation {
|
||||||
|
pub frames: f64,
|
||||||
|
pub image: String,
|
||||||
|
pub image_height: i32,
|
||||||
|
pub image_width: i32,
|
||||||
|
#[serde(rename = "Type")]
|
||||||
|
pub animation_type: i32, // 'type' is a reserved keyword in Rust
|
||||||
|
pub animation_expression: i32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct PersonaPiece {
|
||||||
|
#[serde(rename = "IsDefault")]
|
||||||
|
pub is_default: bool,
|
||||||
|
#[serde(rename = "PackId")]
|
||||||
|
pub pack_id: String,
|
||||||
|
#[serde(rename = "PieceId")]
|
||||||
|
pub piece_id: String,
|
||||||
|
pub piece_type: String,
|
||||||
|
#[serde(rename = "ProductId")]
|
||||||
|
pub product_id: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct PersonaPieceTintColour {
|
||||||
|
#[serde(rename = "Colors")]
|
||||||
|
pub colours: [String; 4],
|
||||||
|
pub piece_type: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Deserialize, Clone)]
|
||||||
|
#[serde(rename_all = "PascalCase")]
|
||||||
|
pub struct ClientData {
|
||||||
|
pub client_random_id: i64,
|
||||||
|
#[serde(rename = "DeviceOS")]
|
||||||
|
pub device_os: i32,
|
||||||
|
#[serde(rename = "DeviceId")]
|
||||||
|
pub device_id: String,
|
||||||
|
pub game_version: String,
|
||||||
|
pub language_code: String,
|
||||||
|
pub current_input_mode: i32,
|
||||||
|
pub default_input_mode: i32,
|
||||||
|
#[serde(rename = "UIProfile")]
|
||||||
|
pub ui_profile: i32,
|
||||||
|
pub server_address: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub device_model: String,
|
||||||
|
#[serde(rename = "GuiScale", default)]
|
||||||
|
pub gui_scale: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub is_editor_mode: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub max_view_distance: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub memory_tier: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub platform_type: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub graphics_mode: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub compatible_with_client_side_chunk_gen: bool,
|
||||||
|
|
||||||
|
#[serde(rename = "PlatformOfflineId", default)]
|
||||||
|
pub platform_offline_id: String,
|
||||||
|
#[serde(rename = "PlatformOnlineId", default)]
|
||||||
|
pub platform_online_id: String,
|
||||||
|
#[serde(rename = "PlatformUserId", default)]
|
||||||
|
pub platform_user_id: String,
|
||||||
|
#[serde(rename = "SelfSignedId", default)]
|
||||||
|
pub self_signed_id: String,
|
||||||
|
#[serde(rename = "PlayFabId", default)]
|
||||||
|
pub play_fab_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub third_party_name: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub third_party_name_only: bool,
|
||||||
|
|
||||||
|
#[serde(rename = "SkinId", default)]
|
||||||
|
pub skin_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skin_data: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skin_image_height: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skin_image_width: i32,
|
||||||
|
#[serde(rename = "SkinColor", default)]
|
||||||
|
pub skin_colour: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub arm_size: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub persona_skin: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub premium_skin: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub trusted_skin: bool,
|
||||||
|
#[serde(default)]
|
||||||
|
pub override_skin: bool,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub cape_data: String,
|
||||||
|
#[serde(rename = "CapeId", default)]
|
||||||
|
pub cape_id: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cape_image_height: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cape_image_width: i32,
|
||||||
|
#[serde(default)]
|
||||||
|
pub cape_on_classic_skin: bool,
|
||||||
|
#[serde(rename = "SkinGeometryData", default)]
|
||||||
|
pub skin_geometry: String,
|
||||||
|
#[serde(rename = "SkinGeometryDataEngineVersion", default)]
|
||||||
|
pub skin_geometry_version: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skin_resource_patch: String,
|
||||||
|
|
||||||
|
#[serde(default)]
|
||||||
|
pub animated_image_data: Vec<SkinAnimation>,
|
||||||
|
#[serde(default)]
|
||||||
|
pub skin_animation_data: String,
|
||||||
|
#[serde(default)]
|
||||||
|
pub persona_pieces: Vec<PersonaPiece>,
|
||||||
|
#[serde(rename = "PieceTintColors", default)]
|
||||||
|
pub piece_tint_colours: Vec<PersonaPieceTintColour>,
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
pub mod animate;
|
||||||
pub mod client_cache_status;
|
pub mod client_cache_status;
|
||||||
pub mod command_request;
|
pub mod command_request;
|
||||||
pub mod container_close;
|
pub mod container_close;
|
||||||
|
|||||||
@@ -57,10 +57,11 @@ pub enum Action {
|
|||||||
ClientAckServerData = 36,
|
ClientAckServerData = 36,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl PacketRead for Action {
|
impl TryFrom<i32> for Action {
|
||||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
type Error = String;
|
||||||
let action = VarInt::read(reader)?;
|
|
||||||
match action.0 {
|
fn try_from(value: i32) -> Result<Self, Self::Error> {
|
||||||
|
match value {
|
||||||
0 => Ok(Self::StartBreak),
|
0 => Ok(Self::StartBreak),
|
||||||
1 => Ok(Self::AbortBreak),
|
1 => Ok(Self::AbortBreak),
|
||||||
2 => Ok(Self::StopBreak),
|
2 => Ok(Self::StopBreak),
|
||||||
@@ -98,7 +99,15 @@ impl PacketRead for Action {
|
|||||||
34 => Ok(Self::StartFlying),
|
34 => Ok(Self::StartFlying),
|
||||||
35 => Ok(Self::StopFlying),
|
35 => Ok(Self::StopFlying),
|
||||||
36 => Ok(Self::ClientAckServerData),
|
36 => Ok(Self::ClientAckServerData),
|
||||||
_ => Err(Error::other(format!("Invalid action ID: {}", action.0))),
|
_ => Err(format!("Invalid action ID: {}", value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PacketRead for Action {
|
||||||
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let action = VarInt::read(reader)?;
|
||||||
|
|
||||||
|
Action::try_from(action.0).map_err(Error::other)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,37 +1,166 @@
|
|||||||
|
use std::io::{Error, Read};
|
||||||
|
|
||||||
use pumpkin_macros::packet;
|
use pumpkin_macros::packet;
|
||||||
use pumpkin_util::math::{vector2::Vector2, vector3::Vector3};
|
use pumpkin_util::math::{position::BlockPos, vector2::Vector2, vector3::Vector3};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
codec::{bitset::Bitset, var_uint::VarUInt, var_ulong::VarULong},
|
codec::{
|
||||||
|
bitset::Bitset,
|
||||||
|
var_int::VarInt,
|
||||||
|
var_long::{VarLong, VarLongType},
|
||||||
|
var_uint::VarUInt,
|
||||||
|
var_ulong::VarULong,
|
||||||
|
},
|
||||||
|
packet::{self, Packet},
|
||||||
serial::PacketRead,
|
serial::PacketRead,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Debug, PacketRead)]
|
#[derive(Debug)]
|
||||||
#[packet(144)]
|
#[packet(144)]
|
||||||
pub struct SPlayerAuthInput {
|
pub struct SPlayerAuthInput {
|
||||||
// https://mojang.github.io/bedrock-protocol-docs/html/PlayerAuthInputPacket.html
|
|
||||||
pub pitch: f32,
|
pub pitch: f32,
|
||||||
pub yaw: f32,
|
pub yaw: f32,
|
||||||
pub position: Vector3<f32>,
|
pub position: Vector3<f32>,
|
||||||
pub move_vec: Vector2<f32>,
|
pub move_vec: Vector2<f32>,
|
||||||
pub head_rotation: f32,
|
pub head_yaw: f32,
|
||||||
pub input_data: Bitset<65>,
|
pub input_data: Bitset<65>,
|
||||||
pub input_mode: VarUInt,
|
pub input_mode: VarUInt,
|
||||||
pub play_mode: VarUInt,
|
pub play_mode: VarUInt,
|
||||||
pub new_interaction_model: VarUInt,
|
pub interaction_model: VarUInt,
|
||||||
pub interact_rotation: Vector2<f32>,
|
pub interact_pitch: f32,
|
||||||
pub client_tick: VarULong,
|
pub interact_yaw: f32,
|
||||||
pub pos_delta: Vector3<f32>,
|
pub tick: VarULong,
|
||||||
|
pub delta: Vector3<f32>,
|
||||||
|
pub block_actions: Option<Vec<PlayerBlockAction>>,
|
||||||
|
pub vehicle_rotation: Option<Vector2<f32>>,
|
||||||
|
pub vehicle_unique_id: Option<VarLong>,
|
||||||
pub analog_move: Vector2<f32>,
|
pub analog_move: Vector2<f32>,
|
||||||
pub camera_orientation: Vector3<f32>,
|
pub camera_orientation: Vector3<f32>,
|
||||||
pub raw_move_vec: Vector2<f32>,
|
pub raw_move: Vector2<f32>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
impl PacketRead for SPlayerAuthInput {
|
||||||
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let pitch = f32::read(reader)?;
|
||||||
|
let yaw = f32::read(reader)?;
|
||||||
|
let position = Vector3::<f32>::read(reader)?;
|
||||||
|
let move_vec = Vector2::<f32>::read(reader)?;
|
||||||
|
let head_yaw = f32::read(reader)?;
|
||||||
|
let input_data = Bitset::<65>::read(reader)?;
|
||||||
|
let input_mode = VarUInt::read(reader)?;
|
||||||
|
let play_mode = VarUInt::read(reader)?;
|
||||||
|
let interaction_model = VarUInt::read(reader)?;
|
||||||
|
let interact_pitch = f32::read(reader)?;
|
||||||
|
let interact_yaw = f32::read(reader)?;
|
||||||
|
let tick = VarULong::read(reader)?;
|
||||||
|
let delta = Vector3::<f32>::read(reader)?;
|
||||||
|
|
||||||
|
// 1. Perform Item Interaction
|
||||||
|
if input_data.get(InputData::PerformItemInteraction as usize) {
|
||||||
|
// protocol.UseItemTransactionData (Simplified skip)
|
||||||
|
let _action_type = VarUInt::read(reader)?;
|
||||||
|
let action_count = VarUInt::read(reader)?.0;
|
||||||
|
for _ in 0..action_count {
|
||||||
|
let _type = VarUInt::read(reader)?;
|
||||||
|
let _pos = BlockPos::read(reader)?;
|
||||||
|
let _face = VarInt::read(reader)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Item Stack Request
|
||||||
|
if input_data.get(InputData::PerformItemStackRequest as usize) {
|
||||||
|
// protocol.Single ItemStackRequest
|
||||||
|
return Err(Error::other("ItemStackRequest decoding not implemented"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Block Actions
|
||||||
|
let block_actions = if input_data.get(InputData::PerformBlockActions as usize) {
|
||||||
|
let count = VarInt::read(reader)?.0 as usize;
|
||||||
|
let mut actions = Vec::with_capacity(count);
|
||||||
|
for _ in 0..count {
|
||||||
|
actions.push(PlayerBlockAction::read(reader)?);
|
||||||
|
}
|
||||||
|
Some(actions)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// 4. Vehicle Info (Matches Go logic)
|
||||||
|
let mut vehicle_rotation = None;
|
||||||
|
let mut vehicle_unique_id = None;
|
||||||
|
if input_data.get(InputData::ClientPredictedVehicle as usize) {
|
||||||
|
vehicle_rotation = Some(Vector2::<f32>::read(reader)?);
|
||||||
|
vehicle_unique_id = Some(VarLong::read(reader)?);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Trailing Data
|
||||||
|
let analog_move = Vector2::<f32>::read(reader)?;
|
||||||
|
let camera_orientation = Vector3::<f32>::read(reader)?;
|
||||||
|
let raw_move = Vector2::<f32>::read(reader)?;
|
||||||
|
|
||||||
|
Ok(Self {
|
||||||
|
pitch,
|
||||||
|
yaw,
|
||||||
|
position,
|
||||||
|
move_vec,
|
||||||
|
head_yaw,
|
||||||
|
input_data,
|
||||||
|
input_mode,
|
||||||
|
play_mode,
|
||||||
|
interaction_model,
|
||||||
|
interact_pitch,
|
||||||
|
interact_yaw,
|
||||||
|
tick,
|
||||||
|
delta,
|
||||||
|
block_actions,
|
||||||
|
vehicle_rotation,
|
||||||
|
vehicle_unique_id,
|
||||||
|
analog_move,
|
||||||
|
camera_orientation,
|
||||||
|
raw_move,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PacketRead)]
|
||||||
|
pub struct PlayerBlockAction {
|
||||||
|
pub action: VarInt,
|
||||||
|
pub block_pos: BlockPos,
|
||||||
|
pub face: VarInt,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum InputMode {
|
||||||
|
Mouse = 1,
|
||||||
|
Touch = 2,
|
||||||
|
GamePad = 3,
|
||||||
|
MotionController = 4,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum PlayMode {
|
||||||
|
Normal = 0,
|
||||||
|
Teaser = 1,
|
||||||
|
Screen = 2,
|
||||||
|
ExitLevel = 7,
|
||||||
|
NumModes = 9,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
#[repr(u32)]
|
||||||
|
pub enum InteractionModel {
|
||||||
|
Touch = 0,
|
||||||
|
Crosshair = 1,
|
||||||
|
Classic = 2,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
pub enum InputData {
|
pub enum InputData {
|
||||||
// https://mojang.github.io/bedrock-protocol-docs/html/enums.html#PlayerAuthInputPacket::InputData
|
|
||||||
Ascend = 0,
|
Ascend = 0,
|
||||||
Descend = 1,
|
Descend = 1,
|
||||||
|
NorthJump = 2,
|
||||||
JumpDown = 3,
|
JumpDown = 3,
|
||||||
SprintDown = 4,
|
SprintDown = 4,
|
||||||
ChangeHeight = 5,
|
ChangeHeight = 5,
|
||||||
@@ -74,7 +203,7 @@ pub enum InputData {
|
|||||||
StartFlying = 42,
|
StartFlying = 42,
|
||||||
StopFlying = 43,
|
StopFlying = 43,
|
||||||
ClientAckServerData = 44,
|
ClientAckServerData = 44,
|
||||||
IsInClientPredictedVehicle = 45,
|
ClientPredictedVehicle = 45, // Renamed to match Go
|
||||||
PaddlingLeft = 46,
|
PaddlingLeft = 46,
|
||||||
PaddlingRight = 47,
|
PaddlingRight = 47,
|
||||||
BlockBreakingDelayEnabled = 48,
|
BlockBreakingDelayEnabled = 48,
|
||||||
@@ -83,30 +212,15 @@ pub enum InputData {
|
|||||||
DownLeft = 51,
|
DownLeft = 51,
|
||||||
DownRight = 52,
|
DownRight = 52,
|
||||||
StartUsingItem = 53,
|
StartUsingItem = 53,
|
||||||
IsCameraRelativeMovementEnabled = 54,
|
CameraRelativeMovementEnabled = 54,
|
||||||
IsRotControlledByMoveDirection = 55,
|
RotControlledByMoveDirection = 55,
|
||||||
StartSpinAttack = 56,
|
StartSpinAttack = 56,
|
||||||
StopSpinAttack = 57,
|
StopSpinAttack = 57,
|
||||||
IsHotbarOnlyTouch = 58,
|
IsHotbarTouchOnly = 58,
|
||||||
JumpReleasedRaw = 59,
|
JumpReleasedRaw = 59,
|
||||||
JumpPressedRaw = 60,
|
JumpPressedRaw = 60,
|
||||||
JumpCurrentRaw = 61,
|
JumpCurrentRaw = 61,
|
||||||
SneakReleasedRaw = 62,
|
SneakReleasedRaw = 62,
|
||||||
SneakPressedRaw = 63,
|
SneakPressedRaw = 63,
|
||||||
SneakCurrentRaw = 64,
|
SneakCurrentRaw = 64,
|
||||||
InputNum = 65,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<InputData> for usize {
|
|
||||||
fn from(value: InputData) -> Self {
|
|
||||||
value as Self
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub enum InputMode {
|
|
||||||
Undefined = 0,
|
|
||||||
Mouse = 1,
|
|
||||||
Touch = 2,
|
|
||||||
GamePad = 3,
|
|
||||||
MotionController = 4,
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -37,6 +37,20 @@ impl SText {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn translation(message: String, parameters: Vec<String>) -> Self {
|
||||||
|
Self {
|
||||||
|
needs_translation: true,
|
||||||
|
r#type: TextPacketType::Translation,
|
||||||
|
source_name: String::new(),
|
||||||
|
message,
|
||||||
|
parameters,
|
||||||
|
xuid: String::new(),
|
||||||
|
platform_chat_id: String::new(),
|
||||||
|
filtered_message: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn system_message(message: String) -> Self {
|
pub fn system_message(message: String) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use serde::{
|
|||||||
use crate::{
|
use crate::{
|
||||||
WritingError,
|
WritingError,
|
||||||
ser::{NetworkReadExt, NetworkWriteExt, ReadingError},
|
ser::{NetworkReadExt, NetworkWriteExt, ReadingError},
|
||||||
serial::PacketWrite,
|
serial::{PacketRead, PacketWrite},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type VarLongType = i64;
|
pub type VarLongType = i64;
|
||||||
@@ -144,6 +144,34 @@ impl<'de> Deserialize<'de> for VarLong {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl PacketRead for VarLong {
|
||||||
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
|
let mut val: u64 = 0;
|
||||||
|
let mut shift = 0;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let byte = u8::read(reader)?;
|
||||||
|
val |= ((byte & 0x7F) as u64) << shift;
|
||||||
|
|
||||||
|
if (byte & 0x80) == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
shift += 7;
|
||||||
|
if shift >= 64 {
|
||||||
|
return Err(Error::new(
|
||||||
|
std::io::ErrorKind::InvalidData,
|
||||||
|
"VarLong is too big (overflow)",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let decoded = ((val >> 1) as i64) ^ -((val & 1) as i64);
|
||||||
|
|
||||||
|
Ok(VarLong(decoded))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl PacketWrite for VarLong {
|
impl PacketWrite for VarLong {
|
||||||
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
fn write<W: Write>(&self, writer: &mut W) -> Result<(), Error> {
|
||||||
let mut val = ((self.0 << 1) ^ (self.0 >> 63)) as u64;
|
let mut val = ((self.0 << 1) ^ (self.0 >> 63)) as u64;
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ impl PacketRead for String {
|
|||||||
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
fn read<R: Read>(reader: &mut R) -> Result<Self, Error> {
|
||||||
const MAX_STRING_LENGTH: usize = 32767;
|
const MAX_STRING_LENGTH: usize = 32767;
|
||||||
|
|
||||||
let len = u32::read(reader)? as usize;
|
let len = VarUInt::read(reader)?.0 as usize;
|
||||||
|
|
||||||
if len > MAX_STRING_LENGTH {
|
if len > MAX_STRING_LENGTH {
|
||||||
return Err(Error::new(
|
return Err(Error::new(
|
||||||
|
|||||||
@@ -307,6 +307,28 @@ impl NamedColor {
|
|||||||
Self::White => RGBColor::new(255, 255, 255),
|
Self::White => RGBColor::new(255, 255, 255),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub const fn to_legacy_char(&self) -> char {
|
||||||
|
match self {
|
||||||
|
Self::Black => '0',
|
||||||
|
Self::DarkBlue => '1',
|
||||||
|
Self::DarkGreen => '2',
|
||||||
|
Self::DarkAqua => '3',
|
||||||
|
Self::DarkRed => '4',
|
||||||
|
Self::DarkPurple => '5',
|
||||||
|
Self::Gold => '6',
|
||||||
|
Self::Gray => '7',
|
||||||
|
Self::DarkGray => '8',
|
||||||
|
Self::Blue => '9',
|
||||||
|
Self::Green => 'a',
|
||||||
|
Self::Aqua => 'b',
|
||||||
|
Self::Red => 'c',
|
||||||
|
Self::LightPurple => 'd',
|
||||||
|
Self::Yellow => 'e',
|
||||||
|
Self::White => 'f',
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl TryFrom<&str> for NamedColor {
|
impl TryFrom<&str> for NamedColor {
|
||||||
|
|||||||
@@ -136,6 +136,70 @@ impl TextComponentBase {
|
|||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn to_bedrock_legacy(&self, locale: Locale) -> String {
|
||||||
|
let mut text = String::new();
|
||||||
|
|
||||||
|
// 1. Inject Bedrock formatting codes
|
||||||
|
if let Some(color) = &self.style.color {
|
||||||
|
match color {
|
||||||
|
Color::Named(named) => {
|
||||||
|
text.push_str(&format!("§{}", named.to_legacy_char()));
|
||||||
|
}
|
||||||
|
Color::Rgb(_rgb) => {
|
||||||
|
// Bedrock doesn't strictly support Java's §x hex format.
|
||||||
|
// Most Bedrock implementations fallback to Gray or ignore it.
|
||||||
|
}
|
||||||
|
Color::Reset => {
|
||||||
|
// Explicitly handle the Reset variant
|
||||||
|
text.push_str("§r");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.style.bold == Some(true) {
|
||||||
|
text.push_str("§l");
|
||||||
|
}
|
||||||
|
if self.style.italic == Some(true) {
|
||||||
|
text.push_str("§o");
|
||||||
|
}
|
||||||
|
if self.style.underlined == Some(true) {
|
||||||
|
text.push_str("§n");
|
||||||
|
}
|
||||||
|
if self.style.obfuscated == Some(true) {
|
||||||
|
text.push_str("§k");
|
||||||
|
}
|
||||||
|
// Note: Bedrock does not support strikethrough natively without resource packs.
|
||||||
|
|
||||||
|
// 2. Resolve Content
|
||||||
|
match &*self.content {
|
||||||
|
TextContent::Text { text: t } => text.push_str(t),
|
||||||
|
TextContent::Translate { translate, with } => {
|
||||||
|
// TODO
|
||||||
|
text.push_str(&get_translation_text(
|
||||||
|
translate.to_string(),
|
||||||
|
locale,
|
||||||
|
with.clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
TextContent::EntityNames { selector, .. } => text.push_str(selector),
|
||||||
|
TextContent::Keybind { keybind } => text.push_str(keybind),
|
||||||
|
TextContent::Custom { key, with, .. } => {
|
||||||
|
text.push_str(&get_translation_text(key.clone(), locale, with.clone()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Recursively append extra components
|
||||||
|
for child in &self.extra {
|
||||||
|
text.push_str(&child.to_bedrock_legacy(locale));
|
||||||
|
// Bedrock styles bleed into subsequent text. We append a reset code
|
||||||
|
// to ensure child styles are properly isolated from one another.
|
||||||
|
text.push_str("§r");
|
||||||
|
}
|
||||||
|
|
||||||
|
text
|
||||||
|
}
|
||||||
|
|
||||||
/// Extracts the raw text content of this component for the given locale.
|
/// Extracts the raw text content of this component for the given locale.
|
||||||
///
|
///
|
||||||
/// # Arguments
|
/// # Arguments
|
||||||
@@ -145,7 +209,7 @@ impl TextComponentBase {
|
|||||||
/// The plain text content of the component.
|
/// The plain text content of the component.
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn get_text(self, locale: Locale) -> String {
|
pub fn get_text(self, locale: Locale) -> String {
|
||||||
match *self.content {
|
let mut text = match *self.content {
|
||||||
TextContent::Text { text } => text.into_owned(),
|
TextContent::Text { text } => text.into_owned(),
|
||||||
TextContent::Translate { translate, with } => {
|
TextContent::Translate { translate, with } => {
|
||||||
get_translation_text(format!("minecraft:{translate}"), locale, with)
|
get_translation_text(format!("minecraft:{translate}"), locale, with)
|
||||||
@@ -156,7 +220,14 @@ impl TextComponentBase {
|
|||||||
} => selector.into_owned(),
|
} => selector.into_owned(),
|
||||||
TextContent::Keybind { keybind } => keybind.into_owned(),
|
TextContent::Keybind { keybind } => keybind.into_owned(),
|
||||||
TextContent::Custom { key, with, .. } => get_translation_text(key, locale, with),
|
TextContent::Custom { key, with, .. } => get_translation_text(key, locale, with),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Recursively append the text of all child components
|
||||||
|
for child in self.extra {
|
||||||
|
text += &child.get_text(locale);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Converts this component by resolving all translations.
|
/// Converts this component by resolving all translations.
|
||||||
|
|||||||
@@ -34,7 +34,12 @@ use pumpkin_data::{
|
|||||||
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
use pumpkin_protocol::java::client::play::{CUpdateEntityPos, CUpdateEntityPosRot};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
PositionFlag,
|
PositionFlag,
|
||||||
|
bedrock::client::set_actor_data::{
|
||||||
|
CSetActorData, EntityMetadata, MetadataValue, PropertySyncData, entity_data_flag,
|
||||||
|
entity_data_key,
|
||||||
|
},
|
||||||
codec::var_int::VarInt,
|
codec::var_int::VarInt,
|
||||||
|
codec::var_ulong::VarULong,
|
||||||
java::client::play::{
|
java::client::play::{
|
||||||
CEntityPositionSync, CEntityVelocity, CHeadRot, CPlayerPosition, CSetEntityMetadata,
|
CEntityPositionSync, CEntityVelocity, CHeadRot, CPlayerPosition, CSetEntityMetadata,
|
||||||
CSetPassengers, CSpawnEntity, CUpdateEntityRot, Metadata,
|
CSetPassengers, CSpawnEntity, CUpdateEntityRot, Metadata,
|
||||||
@@ -508,6 +513,10 @@ pub struct Entity {
|
|||||||
pub data: AtomicI32,
|
pub data: AtomicI32,
|
||||||
/// Stores entity boolean flags (on fire, sneaking, invisible, glowing, etc.)
|
/// Stores entity boolean flags (on fire, sneaking, invisible, glowing, etc.)
|
||||||
pub flags: std::sync::atomic::AtomicI8,
|
pub flags: std::sync::atomic::AtomicI8,
|
||||||
|
/// Stores Bedrock-specific entity boolean flags (bit 0-63)
|
||||||
|
pub bedrock_flags: std::sync::atomic::AtomicI64,
|
||||||
|
/// Stores more Bedrock-specific entity boolean flags (bit 0-63)
|
||||||
|
pub bedrock_flags_two: std::sync::atomic::AtomicI64,
|
||||||
/// If true, the entity cannot collide with anything (e.g. spectator)
|
/// If true, the entity cannot collide with anything (e.g. spectator)
|
||||||
pub no_clip: AtomicBool,
|
pub no_clip: AtomicBool,
|
||||||
/// Multiplies movement for one tick before being reset
|
/// Multiplies movement for one tick before being reset
|
||||||
@@ -615,6 +624,8 @@ impl Entity {
|
|||||||
damage_immunities: Mutex::new(Vec::new()),
|
damage_immunities: Mutex::new(Vec::new()),
|
||||||
data: AtomicI32::new(0),
|
data: AtomicI32::new(0),
|
||||||
flags: std::sync::atomic::AtomicI8::new(0),
|
flags: std::sync::atomic::AtomicI8::new(0),
|
||||||
|
bedrock_flags: std::sync::atomic::AtomicI64::new(0),
|
||||||
|
bedrock_flags_two: std::sync::atomic::AtomicI64::new(0),
|
||||||
fire_immune: AtomicBool::new(false),
|
fire_immune: AtomicBool::new(false),
|
||||||
fire_ticks: AtomicI32::new(-1),
|
fire_ticks: AtomicI32::new(-1),
|
||||||
has_visual_fire: AtomicBool::new(false),
|
has_visual_fire: AtomicBool::new(false),
|
||||||
@@ -2202,19 +2213,75 @@ impl Entity {
|
|||||||
async fn set_flag(&self, flag: Flag, value: bool) {
|
async fn set_flag(&self, flag: Flag, value: bool) {
|
||||||
let index = flag as u8;
|
let index = flag as u8;
|
||||||
let mask = (1i8).wrapping_shl(index as u32);
|
let mask = (1i8).wrapping_shl(index as u32);
|
||||||
let mut b = self.flags.load(Ordering::Relaxed);
|
let new_je_flags = if value {
|
||||||
if value {
|
self.flags.fetch_or(mask, Ordering::Relaxed) | mask
|
||||||
b |= mask;
|
|
||||||
} else {
|
} else {
|
||||||
b &= !mask;
|
self.flags.fetch_and(!mask, Ordering::Relaxed) & !mask
|
||||||
}
|
};
|
||||||
self.flags.store(b, Ordering::Relaxed);
|
|
||||||
self.send_meta_data(&[Metadata::new(
|
self.send_meta_data(&[Metadata::new(
|
||||||
TrackedData::SHARED_FLAGS_ID,
|
TrackedData::SHARED_FLAGS_ID,
|
||||||
MetaDataType::BYTE,
|
MetaDataType::BYTE,
|
||||||
b,
|
new_je_flags,
|
||||||
)])
|
)])
|
||||||
.await;
|
.await;
|
||||||
|
|
||||||
|
if let Some(bedrock_flag) = flag.to_bedrock() {
|
||||||
|
let (key, index) = if bedrock_flag >= 64 {
|
||||||
|
(entity_data_key::FLAGS_TWO, (bedrock_flag - 64) as u8)
|
||||||
|
} else {
|
||||||
|
(entity_data_key::FLAGS, bedrock_flag as u8)
|
||||||
|
};
|
||||||
|
|
||||||
|
if value {
|
||||||
|
let mask = 1i64 << index;
|
||||||
|
if key == entity_data_key::FLAGS {
|
||||||
|
self.bedrock_flags.fetch_or(mask, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
self.bedrock_flags_two.fetch_or(mask, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
let mask = !(1i64 << index);
|
||||||
|
if key == entity_data_key::FLAGS {
|
||||||
|
self.bedrock_flags.fetch_and(mask, Ordering::Relaxed);
|
||||||
|
} else {
|
||||||
|
self.bedrock_flags_two.fetch_and(mask, Ordering::Relaxed);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let world = self.world.load();
|
||||||
|
let chunk_pos = self.chunk_pos.load();
|
||||||
|
for player in world.players.load().iter() {
|
||||||
|
if let ClientPlatform::Bedrock(client) = &player.client {
|
||||||
|
let center = player.living_entity.entity.chunk_pos.load();
|
||||||
|
let view_distance =
|
||||||
|
crate::world::chunker::get_view_distance(player).get() as i32;
|
||||||
|
|
||||||
|
if is_within_view_distance(chunk_pos, center, view_distance) {
|
||||||
|
let mut metadata = EntityMetadata(std::collections::HashMap::new());
|
||||||
|
metadata.set(
|
||||||
|
entity_data_key::FLAGS,
|
||||||
|
MetadataValue::Long(self.bedrock_flags.load(Ordering::Relaxed)),
|
||||||
|
);
|
||||||
|
metadata.set(
|
||||||
|
entity_data_key::FLAGS_TWO,
|
||||||
|
MetadataValue::Long(self.bedrock_flags_two.load(Ordering::Relaxed)),
|
||||||
|
);
|
||||||
|
client
|
||||||
|
.send_game_packet(&CSetActorData {
|
||||||
|
actor_runtime_id: VarULong(self.entity_id as u64),
|
||||||
|
metadata,
|
||||||
|
synced_properties: PropertySyncData {
|
||||||
|
int_properties: std::collections::HashMap::new(),
|
||||||
|
float_properties: std::collections::HashMap::new(),
|
||||||
|
},
|
||||||
|
tick: VarULong(0),
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Plays sound at this entity's position with the entity's sound category
|
/// Plays sound at this entity's position with the entity's sound category
|
||||||
@@ -2915,6 +2982,20 @@ pub enum Flag {
|
|||||||
FallFlying = 7,
|
FallFlying = 7,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Flag {
|
||||||
|
pub const fn to_bedrock(&self) -> Option<u32> {
|
||||||
|
match self {
|
||||||
|
Self::OnFire => Some(entity_data_flag::ON_FIRE),
|
||||||
|
Self::Sneaking => Some(entity_data_flag::SNEAKING),
|
||||||
|
Self::Sprinting => Some(entity_data_flag::SPRINTING),
|
||||||
|
Self::Swimming => Some(entity_data_flag::SWIMMING),
|
||||||
|
Self::Invisible => Some(entity_data_flag::INVISIBLE),
|
||||||
|
Self::FallFlying => Some(entity_data_flag::GLIDING),
|
||||||
|
Self::Glowing => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ use std::collections::{BinaryHeap, HashMap, HashSet, VecDeque};
|
|||||||
use std::f64::consts::TAU;
|
use std::f64::consts::TAU;
|
||||||
use std::mem;
|
use std::mem;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
|
use std::str::FromStr;
|
||||||
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU8, AtomicU32, Ordering};
|
use std::sync::atomic::{AtomicBool, AtomicI32, AtomicI64, AtomicU8, AtomicU32, Ordering};
|
||||||
use std::sync::{Arc, Weak};
|
use std::sync::{Arc, Weak};
|
||||||
use std::time::{Duration, Instant};
|
use std::time::{Duration, Instant};
|
||||||
@@ -23,6 +24,7 @@ use pumpkin_protocol::bedrock::client::update_abilities::{
|
|||||||
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
use pumpkin_protocol::bedrock::frame_set::FrameSet;
|
||||||
use pumpkin_protocol::bedrock::server::text::SText;
|
use pumpkin_protocol::bedrock::server::text::SText;
|
||||||
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
use pumpkin_protocol::codec::item_stack_seralizer::ItemStackSerializer;
|
||||||
|
use pumpkin_util::translation::Locale;
|
||||||
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
use pumpkin_world::chunk::{ChunkData, ChunkEntityData};
|
||||||
use pumpkin_world::inventory::Inventory;
|
use pumpkin_world::inventory::Inventory;
|
||||||
use tokio::sync::Mutex;
|
use tokio::sync::Mutex;
|
||||||
@@ -2704,7 +2706,9 @@ impl Player {
|
|||||||
}
|
}
|
||||||
ClientPlatform::Bedrock(client) => {
|
ClientPlatform::Bedrock(client) => {
|
||||||
client
|
client
|
||||||
.send_game_packet(&SText::system_message(text.clone().get_text()))
|
.send_game_packet(&SText::system_message(text.clone().0.to_bedrock_legacy(
|
||||||
|
Locale::from_str(&self.config.load().locale).unwrap_or(Locale::EnUs),
|
||||||
|
)))
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,11 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
net::{ClientPlatform, DisconnectReason, GameProfile, bedrock::BedrockClient},
|
net::{ClientPlatform, DisconnectReason, GameProfile, PlayerConfig, bedrock::BedrockClient},
|
||||||
server::Server,
|
server::Server,
|
||||||
};
|
};
|
||||||
use pumpkin_config::networking::compression::CompressionInfo;
|
use pumpkin_config::networking::compression::CompressionInfo;
|
||||||
use pumpkin_protocol::bedrock::server::resource_pack_response::SResourcePackResponse;
|
use pumpkin_protocol::bedrock::server::{
|
||||||
|
login::ClientData, resource_pack_response::SResourcePackResponse,
|
||||||
|
};
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
bedrock::{
|
bedrock::{
|
||||||
client::{
|
client::{
|
||||||
@@ -18,7 +20,7 @@ use pumpkin_protocol::{
|
|||||||
};
|
};
|
||||||
use pumpkin_util::jwt::AuthError;
|
use pumpkin_util::jwt::AuthError;
|
||||||
use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
|
use pumpkin_world::{CURRENT_BEDROCK_MC_PROTOCOL, CURRENT_BEDROCK_MC_VERSION};
|
||||||
use serde::Deserialize;
|
use serde::{Deserialize, de::Error};
|
||||||
use serde_repr::Deserialize_repr;
|
use serde_repr::Deserialize_repr;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -39,6 +41,8 @@ pub enum LoginError {
|
|||||||
SelfSignedNotAllowed,
|
SelfSignedNotAllowed,
|
||||||
#[error("Got a guest/splitscreen login request. Currently unimplemented.")]
|
#[error("Got a guest/splitscreen login request. Currently unimplemented.")]
|
||||||
GuestUnimplemented,
|
GuestUnimplemented,
|
||||||
|
#[error("Failed to decode extra using decode_b64_url_nopad.")]
|
||||||
|
DecodeExtraError,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Deserialize_repr)]
|
#[derive(Deserialize_repr)]
|
||||||
@@ -135,9 +139,27 @@ impl BedrockClient {
|
|||||||
pumpkin_util::jwt::extract_oidc_token_player_claims(&auth_payload.token)?
|
pumpkin_util::jwt::extract_oidc_token_player_claims(&auth_payload.token)?
|
||||||
};
|
};
|
||||||
|
|
||||||
|
let raw_token_str = std::str::from_utf8(&packet.raw_token).map_err(|_| {
|
||||||
|
LoginError::InvalidTokenFormat(serde_json::Error::custom(
|
||||||
|
"raw_token is not valid UTF-8",
|
||||||
|
))
|
||||||
|
})?; // You'll need to add a string conversion error to LoginError, or handle it cleanly.
|
||||||
|
|
||||||
|
let mut parts = raw_token_str.split('.');
|
||||||
|
let _header = parts.next().ok_or(AuthError::InvalidTokenFormat)?;
|
||||||
|
let payload_b64 = parts.next().ok_or(AuthError::InvalidTokenFormat)?;
|
||||||
|
|
||||||
|
let payload_bytes = pumpkin_util::jwt::decode_b64_url_nopad(payload_b64)
|
||||||
|
.map_err(|_| LoginError::DecodeExtraError)?;
|
||||||
|
let client_data: ClientData = serde_json::from_slice(&payload_bytes)?;
|
||||||
|
|
||||||
|
let real_name = player_data.display_name;
|
||||||
|
// IMPORTANT: Bedrock allows spaces in names. While we could support this, it would significantly complicate parsing player arguments in commands, so we don't
|
||||||
|
let under_score_name = real_name.replace(" ", "_");
|
||||||
|
|
||||||
let profile = GameProfile {
|
let profile = GameProfile {
|
||||||
id: Uuid::parse_str(&player_data.uuid).map_err(|_| LoginError::InvalidUuid)?,
|
id: Uuid::parse_str(&player_data.uuid).map_err(|_| LoginError::InvalidUuid)?,
|
||||||
name: player_data.display_name,
|
name: under_score_name,
|
||||||
properties: Vec::new(),
|
properties: Vec::new(),
|
||||||
profile_actions: None,
|
profile_actions: None,
|
||||||
};
|
};
|
||||||
@@ -158,6 +180,14 @@ impl BedrockClient {
|
|||||||
.add_player(ClientPlatform::Bedrock(self.clone()), profile, None)
|
.add_player(ClientPlatform::Bedrock(self.clone()), profile, None)
|
||||||
.await
|
.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,
|
||||||
|
..Default::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
player.config.store(std::sync::Arc::new(new_config));
|
||||||
|
|
||||||
// player spawn happens after resource packs are resolved
|
// player spawn happens after resource packs are resolved
|
||||||
*self.player.lock().await = Some(player);
|
*self.player.lock().await = Some(player);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,10 +22,12 @@ use pumpkin_protocol::{
|
|||||||
packet_decoder::UDPNetworkDecoder,
|
packet_decoder::UDPNetworkDecoder,
|
||||||
packet_encoder::UDPNetworkEncoder,
|
packet_encoder::UDPNetworkEncoder,
|
||||||
server::{
|
server::{
|
||||||
|
animate::SAnimate,
|
||||||
client_cache_status::SClientCacheStatus,
|
client_cache_status::SClientCacheStatus,
|
||||||
command_request::SCommandRequest,
|
command_request::SCommandRequest,
|
||||||
container_close::SContainerClose,
|
container_close::SContainerClose,
|
||||||
interaction::SInteraction,
|
interaction::SInteraction,
|
||||||
|
loading_screen::SLoadingScreen,
|
||||||
login::SLogin,
|
login::SLogin,
|
||||||
player_action::SPlayerAction,
|
player_action::SPlayerAction,
|
||||||
player_auth_input::SPlayerAuthInput,
|
player_auth_input::SPlayerAuthInput,
|
||||||
@@ -685,7 +687,7 @@ impl BedrockClient {
|
|||||||
let reader = &mut &packet.payload[..];
|
let reader = &mut &packet.payload[..];
|
||||||
match packet.id {
|
match packet.id {
|
||||||
SPlayerAuthInput::PACKET_ID => {
|
SPlayerAuthInput::PACKET_ID => {
|
||||||
self.player_pos_update(player, SPlayerAuthInput::read(reader)?, server)
|
self.handle_player_auth_input(player, SPlayerAuthInput::read(reader)?, server)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
SRequestChunkRadius::PACKET_ID => {
|
SRequestChunkRadius::PACKET_ID => {
|
||||||
@@ -718,6 +720,13 @@ impl BedrockClient {
|
|||||||
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
|
self.handle_player_action(player, server, SPlayerAction::read(reader)?)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
|
SAnimate::PACKET_ID => {
|
||||||
|
self.handle_animate(player, server, SAnimate::read(reader)?)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
SLoadingScreen::PACKET_ID => {
|
||||||
|
// Ignore for now
|
||||||
|
}
|
||||||
_ => {
|
_ => {
|
||||||
warn!("Bedrock: Received Unknown Game packet: {}", packet.id);
|
warn!("Bedrock: Received Unknown Game packet: {}", packet.id);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +1,18 @@
|
|||||||
use std::{
|
use std::{
|
||||||
num::{NonZero, NonZeroI32},
|
num::{NonZero, NonZeroI32},
|
||||||
sync::Arc,
|
sync::{Arc, atomic::Ordering},
|
||||||
};
|
};
|
||||||
|
|
||||||
use pumpkin_macros::send_cancellable;
|
use pumpkin_macros::send_cancellable;
|
||||||
use pumpkin_protocol::{
|
use pumpkin_protocol::{
|
||||||
bedrock::{
|
bedrock::{
|
||||||
client::{chunk_radius_update::CChunkRadiusUpdate, container_open::CContainerOpen},
|
client::{
|
||||||
|
chunk_radius_update::CChunkRadiusUpdate,
|
||||||
|
container_open::CContainerOpen,
|
||||||
|
level_event::{CLevelEvent, LevelEvent},
|
||||||
|
},
|
||||||
server::{
|
server::{
|
||||||
|
animate::{AnimateAction, SAnimate},
|
||||||
command_request::SCommandRequest,
|
command_request::SCommandRequest,
|
||||||
container_close::SContainerClose,
|
container_close::SContainerClose,
|
||||||
interaction::{Action, SInteraction},
|
interaction::{Action, SInteraction},
|
||||||
@@ -18,10 +23,14 @@ use pumpkin_protocol::{
|
|||||||
text::SText,
|
text::SText,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
codec::{var_int::VarInt, var_long::VarLong},
|
codec::{var_int::VarInt, var_long::VarLong, var_ulong::VarULong},
|
||||||
java::client::play::CSystemChatMessage,
|
java::client::play::{Animation, CEntityAnimation, CSetBlockDestroyStage, CSystemChatMessage},
|
||||||
|
};
|
||||||
|
use pumpkin_util::{
|
||||||
|
GameMode,
|
||||||
|
math::{position::BlockPos, vector3::Vector3},
|
||||||
|
text::TextComponent,
|
||||||
};
|
};
|
||||||
use pumpkin_util::{GameMode, math::position::BlockPos, text::TextComponent};
|
|
||||||
|
|
||||||
use pumpkin_world::world::BlockFlags;
|
use pumpkin_world::world::BlockFlags;
|
||||||
|
|
||||||
@@ -94,7 +103,7 @@ impl BedrockClient {
|
|||||||
player.set_client_loaded(true);
|
player.set_client_loaded(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn player_pos_update(
|
pub async fn handle_player_auth_input(
|
||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
packet: SPlayerAuthInput,
|
packet: SPlayerAuthInput,
|
||||||
@@ -114,13 +123,19 @@ impl BedrockClient {
|
|||||||
let input_data = packet.input_data;
|
let input_data = packet.input_data;
|
||||||
let entity = player.get_entity();
|
let entity = player.get_entity();
|
||||||
|
|
||||||
if input_data.get(InputData::StartSprinting) {
|
if input_data.get(InputData::StartSprinting as usize) {
|
||||||
entity.set_sprinting(true).await;
|
entity.set_sprinting(true).await;
|
||||||
} else if input_data.get(InputData::StopSprinting) {
|
} else if input_data.get(InputData::StopSprinting as usize) {
|
||||||
entity.set_sprinting(false).await;
|
entity.set_sprinting(false).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
if input_data.get(InputData::StartFlying) {
|
if input_data.get(InputData::StartSneaking as usize) {
|
||||||
|
entity.set_sneaking(true).await;
|
||||||
|
} else if input_data.get(InputData::StopSneaking as usize) {
|
||||||
|
entity.set_sneaking(false).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
if input_data.get(InputData::StartFlying as usize) {
|
||||||
let mut abilities = player.abilities.lock().await;
|
let mut abilities = player.abilities.lock().await;
|
||||||
if !abilities.flying {
|
if !abilities.flying {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
@@ -135,7 +150,7 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
}
|
}
|
||||||
} else if input_data.get(InputData::StopFlying) {
|
} else if input_data.get(InputData::StopFlying as usize) {
|
||||||
let mut abilities = player.abilities.lock().await;
|
let mut abilities = player.abilities.lock().await;
|
||||||
if abilities.flying {
|
if abilities.flying {
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
@@ -152,13 +167,64 @@ impl BedrockClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if input_data.get(InputData::StartSneaking) {
|
if let Some(block_actions) = packet.block_actions {
|
||||||
entity.set_sneaking(true).await;
|
for action in block_actions {
|
||||||
} else if input_data.get(InputData::StopSneaking) {
|
self.handle_player_block_action(player, server, action)
|
||||||
entity.set_sneaking(false).await;
|
.await;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn handle_player_block_action(
|
||||||
|
&self,
|
||||||
|
player: &Arc<Player>,
|
||||||
|
server: &Server,
|
||||||
|
packet: pumpkin_protocol::bedrock::server::player_auth_input::PlayerBlockAction,
|
||||||
|
) {
|
||||||
|
use pumpkin_protocol::bedrock::server::player_action::Action as PlayerAction;
|
||||||
|
let action = PlayerAction::try_from(packet.action.0).unwrap();
|
||||||
|
self.handle_player_action(
|
||||||
|
player,
|
||||||
|
server,
|
||||||
|
SPlayerAction {
|
||||||
|
runtime_id: VarInt(0), // Unused
|
||||||
|
action,
|
||||||
|
block_pos: packet.block_pos,
|
||||||
|
result_pos: BlockPos::ZERO,
|
||||||
|
face: packet.face,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
|
||||||
|
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();
|
||||||
|
|
||||||
|
// Broadcast the animation to other players
|
||||||
|
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,
|
||||||
|
};
|
||||||
|
|
||||||
|
// 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;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
pub async fn handle_interaction(&self, _player: &Arc<Player>, packet: SInteraction) {
|
pub async fn handle_interaction(&self, _player: &Arc<Player>, packet: SInteraction) {
|
||||||
if matches!(packet.action, Action::OpenInventory) {
|
if matches!(packet.action, Action::OpenInventory) {
|
||||||
self.send_game_packet(&CContainerOpen {
|
self.send_game_packet(&CContainerOpen {
|
||||||
@@ -234,6 +300,7 @@ impl BedrockClient {
|
|||||||
if !player.has_client_loaded() {
|
if !player.has_client_loaded() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
player.update_last_action_time();
|
||||||
|
|
||||||
match packet.action {
|
match packet.action {
|
||||||
PlayerAction::StartBreak | PlayerAction::CreativePlayerDestroyBlock => {
|
PlayerAction::StartBreak | PlayerAction::CreativePlayerDestroyBlock => {
|
||||||
@@ -261,6 +328,9 @@ impl BedrockClient {
|
|||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
} else if !state.is_air() {
|
} else if !state.is_air() {
|
||||||
|
// Broadcast that breaking started
|
||||||
|
world.set_block_breaking(entity, location, 0).await;
|
||||||
|
|
||||||
let speed = crate::block::calc_block_breaking(player, state, block).await;
|
let speed = crate::block::calc_block_breaking(player, state, block).await;
|
||||||
if speed >= 1.0 {
|
if speed >= 1.0 {
|
||||||
let broken_state = world.get_block_state(&location).await;
|
let broken_state = world.get_block_state(&location).await;
|
||||||
@@ -279,10 +349,28 @@ impl BedrockClient {
|
|||||||
player.apply_tool_damage_for_block_break(broken_state).await;
|
player.apply_tool_damage_for_block_break(broken_state).await;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// TODO: Survival progressive breaking
|
player.mining.store(true, Ordering::Relaxed);
|
||||||
|
*player.mining_pos.lock().await = location;
|
||||||
|
let progress = (speed * 10.0) as i32;
|
||||||
|
world.set_block_breaking(entity, location, progress).await;
|
||||||
|
player
|
||||||
|
.current_block_destroy_stage
|
||||||
|
.store(progress, Ordering::Relaxed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
PlayerAction::CrackBreak => {
|
||||||
|
// Don't do anything for this action. It is no longer used. Block
|
||||||
|
// cracking is done fully server-side.
|
||||||
|
}
|
||||||
|
PlayerAction::AbortBreak | PlayerAction::StopBreak => {
|
||||||
|
let location = packet.block_pos;
|
||||||
|
let entity = &player.living_entity.entity;
|
||||||
|
let world = entity.world.load();
|
||||||
|
|
||||||
|
player.mining.store(false, Ordering::Relaxed);
|
||||||
|
world.set_block_breaking(entity, location, -1).await;
|
||||||
|
}
|
||||||
// TODO
|
// TODO
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
@@ -292,16 +380,17 @@ impl BedrockClient {
|
|||||||
&self,
|
&self,
|
||||||
player: &Arc<Player>,
|
player: &Arc<Player>,
|
||||||
server: &Arc<Server>,
|
server: &Arc<Server>,
|
||||||
command: SCommandRequest,
|
packet: SCommandRequest,
|
||||||
) {
|
) {
|
||||||
let player_clone = player.clone();
|
let player_clone = player.clone();
|
||||||
let server_clone = server.clone();
|
let server_clone = server.clone();
|
||||||
|
let command = packet.command.strip_prefix("/").unwrap_or(&packet.command);
|
||||||
|
|
||||||
send_cancellable! {{
|
send_cancellable! {{
|
||||||
server;
|
server;
|
||||||
PlayerCommandSendEvent {
|
PlayerCommandSendEvent {
|
||||||
player: player.clone(),
|
player: player.clone(),
|
||||||
command: command.command.clone(),
|
command: command.to_string(),
|
||||||
cancelled: false
|
cancelled: false
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use pumpkin_protocol::bedrock::client::level_event::{CLevelEvent, LevelEvent};
|
||||||
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
|
use pumpkin_protocol::codec::data_component::data_to_proto_sound;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::atomic::Ordering::Relaxed;
|
use std::sync::atomic::Ordering::Relaxed;
|
||||||
@@ -1708,16 +1709,24 @@ impl World {
|
|||||||
metadata.set(entity_data_key::HEIGHT, MetadataValue::Float(1.8));
|
metadata.set(entity_data_key::HEIGHT, MetadataValue::Float(1.8));
|
||||||
|
|
||||||
// This is super important, otherwise the client will float by default
|
// This is super important, otherwise the client will float by default
|
||||||
metadata.set_flag(entity_data_key::FLAGS, entity_data_flag::HAS_GRAVITY as u8);
|
let entity = &player.living_entity.entity;
|
||||||
metadata.set_flag(entity_data_key::FLAGS, entity_data_flag::CLIMB as u8);
|
entity.bedrock_flags.fetch_or(
|
||||||
// Player-specific: survival has collision
|
(1i64 << entity_data_flag::HAS_GRAVITY)
|
||||||
metadata.set_flag(
|
| (1i64 << entity_data_flag::CLIMB)
|
||||||
|
| (1i64 << entity_data_flag::HAS_COLLISION)
|
||||||
|
| (1i64 << entity_data_flag::BREATHING),
|
||||||
|
Ordering::Relaxed,
|
||||||
|
);
|
||||||
|
|
||||||
|
metadata.set(
|
||||||
entity_data_key::FLAGS,
|
entity_data_key::FLAGS,
|
||||||
entity_data_flag::HAS_COLLISION as u8,
|
MetadataValue::Long(entity.bedrock_flags.load(Ordering::Relaxed)),
|
||||||
|
);
|
||||||
|
metadata.set(
|
||||||
|
entity_data_key::FLAGS_TWO,
|
||||||
|
MetadataValue::Long(entity.bedrock_flags_two.load(Ordering::Relaxed)),
|
||||||
);
|
);
|
||||||
|
|
||||||
// Prevents the client from showing air buddles on hud even when not in water
|
|
||||||
metadata.set_flag(entity_data_key::FLAGS, entity_data_flag::BREATHING as u8);
|
|
||||||
let actor_data = CSetActorData {
|
let actor_data = CSetActorData {
|
||||||
actor_runtime_id: VarULong(runtime_id),
|
actor_runtime_id: VarULong(runtime_id),
|
||||||
metadata,
|
metadata,
|
||||||
@@ -3215,10 +3224,29 @@ impl World {
|
|||||||
|
|
||||||
pub async fn set_block_breaking(&self, from: &Entity, location: BlockPos, progress: i32) {
|
pub async fn set_block_breaking(&self, from: &Entity, location: BlockPos, progress: i32) {
|
||||||
let chunk_pos = location.chunk_position(); // pumpkin's BlockPos already has this method
|
let chunk_pos = location.chunk_position(); // pumpkin's BlockPos already has this method
|
||||||
self.broadcast_to_chunk_except(
|
let je_packet = CSetBlockDestroyStage::new(from.entity_id.into(), location, progress as i8);
|
||||||
|
|
||||||
|
let (event_id, data) = match progress {
|
||||||
|
-1 => (LevelEvent::BlockStopBreak, 0),
|
||||||
|
0 => (LevelEvent::BlockStartBreak, 0),
|
||||||
|
_ => (LevelEvent::BlockUpdateBreak, progress),
|
||||||
|
};
|
||||||
|
|
||||||
|
let be_packet = CLevelEvent {
|
||||||
|
event_id: VarInt(event_id as i32),
|
||||||
|
position: Vector3::new(
|
||||||
|
location.0.x as f32,
|
||||||
|
location.0.y as f32,
|
||||||
|
location.0.z as f32,
|
||||||
|
),
|
||||||
|
data: VarInt(data),
|
||||||
|
};
|
||||||
|
|
||||||
|
self.broadcast_to_chunk_except_editioned(
|
||||||
chunk_pos,
|
chunk_pos,
|
||||||
&[from.entity_uuid],
|
&[from.entity_uuid],
|
||||||
&CSetBlockDestroyStage::new(from.entity_id.into(), location, progress as i8),
|
&je_packet,
|
||||||
|
&be_packet,
|
||||||
)
|
)
|
||||||
.await;
|
.await;
|
||||||
}
|
}
|
||||||
@@ -4162,6 +4190,43 @@ impl World {
|
|||||||
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
let recipients_by_version = Self::collect_java_recipients_by_version(recipients);
|
||||||
Self::broadcast_java_grouped(packet, recipients_by_version).await;
|
Self::broadcast_java_grouped(packet, recipients_by_version).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub async fn broadcast_to_chunk_except_editioned<J: ClientPacket, B: BClientPacket>(
|
||||||
|
&self,
|
||||||
|
chunk_pos: Vector2<i32>,
|
||||||
|
except: &[uuid::Uuid],
|
||||||
|
je_packet: &J,
|
||||||
|
be_packet: &B,
|
||||||
|
) {
|
||||||
|
let players = self.players.load();
|
||||||
|
let recipients = players.iter().filter(|p| {
|
||||||
|
if except.contains(&p.living_entity.entity.entity_uuid) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut java_recipients = Vec::new();
|
||||||
|
let mut bedrock_recipients = Vec::new();
|
||||||
|
|
||||||
|
for p in recipients {
|
||||||
|
match &p.client {
|
||||||
|
ClientPlatform::Java(_) => java_recipients.push(p),
|
||||||
|
ClientPlatform::Bedrock(be_client) => bedrock_recipients.push(be_client.clone()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let je_recipients_by_version =
|
||||||
|
Self::collect_java_recipients_by_version(java_recipients.into_iter());
|
||||||
|
Self::broadcast_java_grouped(je_packet, je_recipients_by_version).await;
|
||||||
|
|
||||||
|
for recipient in bedrock_recipients {
|
||||||
|
recipient.send_game_packet(be_packet).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl pumpkin_world::world::SimpleWorld for World {
|
impl pumpkin_world::world::SimpleWorld for World {
|
||||||
|
|||||||
Reference in New Issue
Block a user