Fix movement fixed-point packets, misplaced players on 1.7/8 (#140)
Movement packets were handled incorrectly, because although the fields are specified as integers they are actually fixed-point values, which need to be converted to floating-point before use. These fields were converted with `as f64`, but they actually need to be scaled. To fix this add several new types, FixedPoint5 for 5-bit fractional fixed-point and FixedPoint12 for 12-bit. Both are parameterized by an integer type: FixedPoint5<i32> and FixedPoint5<i8> for 1.7.10/1.8.9, FixedPoint12<i16> for 1.9+. This moves the calculation into the packet field parsing, so it no longer has to be calculated in src/server/mod.rs since the scaling is taken care of as part of the field type. This fixes the long-standing invisible or actually misplaced players bug on 1.7.10 and 1.8.9, closes #139. * Add new FixedPoint5<T> type for 1.7/8, https://wiki.vg/Data_types#Fixed-point_numbers * Add FixedPoint12<i16> for 1.9+, moving type conversion into packet type https://wiki.vg/index.php?title=Protocol#Entity_Relative_Move * Add num-traits 0.2.6 dependency for NumCast to use instead of From * Use FixedPoint5<i32> in spawn object, experience orb, global entity, mob, player, teleport * Use FixedPoint5<i8> and FixedPoint12<i16> in entity move, look and move * Update packet handling bouncer functions, using f64::from for each conversion
This commit is contained in:
parent
87bfb8d2a4
commit
c8e13c38bd
|
@ -1822,6 +1822,7 @@ dependencies = [
|
|||
"image 0.21.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"lazy_static 1.3.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"log 0.4.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"num-traits 0.2.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand 0.6.5 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"rand_xorshift 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
"reqwest 0.9.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||
|
|
|
@ -40,6 +40,7 @@ aes = "0.3.2"
|
|||
cfb8 = "0.3.2"
|
||||
rsa_public_encrypt_pkcs1 = "0.2.0"
|
||||
structopt = "0.2.14"
|
||||
num-traits = "0.2.6"
|
||||
clipboard = { git = "https://github.com/aweinstock314/rust-clipboard", rev = "07d080be58a361a5bbdb548fafe9449843d968be" }
|
||||
# clippy = "*"
|
||||
|
||||
|
|
|
@ -597,6 +597,90 @@ impl Lengthable for i32 {
|
|||
}
|
||||
}
|
||||
|
||||
use num_traits::cast::{cast, NumCast};
|
||||
/// `FixedPoint5` has the 5 least-significant bits for the fractional
|
||||
/// part, upper for integer part: https://wiki.vg/Data_types#Fixed-point_numbers
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FixedPoint5<T>(T);
|
||||
|
||||
impl<T: Serializable> Serializable for FixedPoint5<T> {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
Ok(Self(Serializable::read_from(buf)?))
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
self.0.write_to(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: default::Default> default::Default for FixedPoint5<T> {
|
||||
fn default() -> Self {
|
||||
Self(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast> convert::From<f64> for FixedPoint5<T> {
|
||||
fn from(x: f64) -> Self {
|
||||
let n: T = cast(x * 32.0).unwrap();
|
||||
FixedPoint5::<T>(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast> convert::From<FixedPoint5<T>> for f64 {
|
||||
fn from(x: FixedPoint5<T>) -> Self {
|
||||
let f: f64 = cast(x.0).unwrap();
|
||||
f / 32.0
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for FixedPoint5<T> where T: fmt::Display, f64: convert::From<T>, T: NumCast + Copy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let x: f64 = (*self).into();
|
||||
write!(f, "FixedPoint5(#{} = {}f)", self.0, x)
|
||||
}
|
||||
}
|
||||
|
||||
/// `FixedPoint12` is like `FixedPoint5` but the fractional part is 12-bit
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct FixedPoint12<T>(T);
|
||||
|
||||
impl<T: Serializable> Serializable for FixedPoint12<T> {
|
||||
fn read_from<R: io::Read>(buf: &mut R) -> Result<Self, Error> {
|
||||
Ok(Self(Serializable::read_from(buf)?))
|
||||
}
|
||||
|
||||
fn write_to<W: io::Write>(&self, buf: &mut W) -> Result<(), Error> {
|
||||
self.0.write_to(buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: default::Default> default::Default for FixedPoint12<T> {
|
||||
fn default() -> Self {
|
||||
Self(T::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast> convert::From<f64> for FixedPoint12<T> {
|
||||
fn from(x: f64) -> Self {
|
||||
let n: T = cast(x * 32.0 * 128.0).unwrap();
|
||||
FixedPoint12::<T>(n)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: NumCast> convert::From<FixedPoint12<T>> for f64 {
|
||||
fn from(x: FixedPoint12<T>) -> Self {
|
||||
let f: f64 = cast(x.0).unwrap();
|
||||
f / (32.0 * 128.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> fmt::Debug for FixedPoint12<T> where T: fmt::Display, f64: convert::From<T>, T: NumCast + Copy {
|
||||
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||
let x: f64 = (*self).into();
|
||||
write!(f, "FixedPoint12(#{} = {}f)", self.0, x)
|
||||
}
|
||||
}
|
||||
|
||||
/// `VarInt` have a variable size (between 1 and 5 bytes) when encoded based
|
||||
/// on the size of the number
|
||||
#[derive(Clone, Copy)]
|
||||
|
|
|
@ -495,9 +495,9 @@ state_packets!(
|
|||
field entity_id: VarInt =,
|
||||
field uuid: UUID =,
|
||||
field ty: u8 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field pitch: i8 =,
|
||||
field yaw: i8 =,
|
||||
field data: i32 =,
|
||||
|
@ -508,9 +508,9 @@ state_packets!(
|
|||
packet SpawnObject_i32_NoUUID {
|
||||
field entity_id: VarInt =,
|
||||
field ty: u8 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field pitch: i8 =,
|
||||
field yaw: i8 =,
|
||||
field data: i32 =,
|
||||
|
@ -530,9 +530,9 @@ state_packets!(
|
|||
}
|
||||
packet SpawnExperienceOrb_i32 {
|
||||
field entity_id: VarInt =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field count: i16 =,
|
||||
}
|
||||
/// SpawnGlobalEntity spawns an entity which is visible from anywhere in the
|
||||
|
@ -547,9 +547,9 @@ state_packets!(
|
|||
packet SpawnGlobalEntity_i32 {
|
||||
field entity_id: VarInt =,
|
||||
field ty: u8 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
}
|
||||
/// SpawnMob is used to spawn a living entity into the world when it is in
|
||||
/// range of the client.
|
||||
|
@ -587,9 +587,9 @@ state_packets!(
|
|||
field entity_id: VarInt =,
|
||||
field uuid: UUID =,
|
||||
field ty: u8 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field head_pitch: i8 =,
|
||||
|
@ -601,9 +601,9 @@ state_packets!(
|
|||
packet SpawnMob_u8_i32_NoUUID {
|
||||
field entity_id: VarInt =,
|
||||
field ty: u8 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field head_pitch: i8 =,
|
||||
|
@ -651,9 +651,9 @@ state_packets!(
|
|||
packet SpawnPlayer_i32 {
|
||||
field entity_id: VarInt =,
|
||||
field uuid: UUID =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field metadata: types::Metadata =,
|
||||
|
@ -661,9 +661,9 @@ state_packets!(
|
|||
packet SpawnPlayer_i32_HeldItem {
|
||||
field entity_id: VarInt =,
|
||||
field uuid: UUID =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field current_item: u16 =,
|
||||
|
@ -674,9 +674,9 @@ state_packets!(
|
|||
field uuid: String =,
|
||||
field name: String =,
|
||||
field properties: LenPrefixed<VarInt, packet::SpawnProperty> =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field current_item: u16 =,
|
||||
|
@ -1159,48 +1159,48 @@ state_packets!(
|
|||
/// EntityMove moves the entity with the id by the offsets provided.
|
||||
packet EntityMove_i16 {
|
||||
field entity_id: VarInt =,
|
||||
field delta_x: i16 =,
|
||||
field delta_y: i16 =,
|
||||
field delta_z: i16 =,
|
||||
field delta_x: FixedPoint12<i16> =,
|
||||
field delta_y: FixedPoint12<i16> =,
|
||||
field delta_z: FixedPoint12<i16> =,
|
||||
field on_ground: bool =,
|
||||
}
|
||||
packet EntityMove_i8 {
|
||||
field entity_id: VarInt =,
|
||||
field delta_x: i8 =,
|
||||
field delta_y: i8 =,
|
||||
field delta_z: i8 =,
|
||||
field delta_x: FixedPoint5<i8> =,
|
||||
field delta_y: FixedPoint5<i8> =,
|
||||
field delta_z: FixedPoint5<i8> =,
|
||||
field on_ground: bool =,
|
||||
}
|
||||
packet EntityMove_i8_i32_NoGround {
|
||||
field entity_id: i32 =,
|
||||
field delta_x: i8 =,
|
||||
field delta_y: i8 =,
|
||||
field delta_z: i8 =,
|
||||
field delta_x: FixedPoint5<i8> =,
|
||||
field delta_y: FixedPoint5<i8> =,
|
||||
field delta_z: FixedPoint5<i8> =,
|
||||
}
|
||||
/// EntityLookAndMove is a combination of EntityMove and EntityLook.
|
||||
packet EntityLookAndMove_i16 {
|
||||
field entity_id: VarInt =,
|
||||
field delta_x: i16 =,
|
||||
field delta_y: i16 =,
|
||||
field delta_z: i16 =,
|
||||
field delta_x: FixedPoint12<i16> =,
|
||||
field delta_y: FixedPoint12<i16> =,
|
||||
field delta_z: FixedPoint12<i16> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field on_ground: bool =,
|
||||
}
|
||||
packet EntityLookAndMove_i8 {
|
||||
field entity_id: VarInt =,
|
||||
field delta_x: i8 =,
|
||||
field delta_y: i8 =,
|
||||
field delta_z: i8 =,
|
||||
field delta_x: FixedPoint5<i8> =,
|
||||
field delta_y: FixedPoint5<i8> =,
|
||||
field delta_z: FixedPoint5<i8> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field on_ground: bool =,
|
||||
}
|
||||
packet EntityLookAndMove_i8_i32_NoGround {
|
||||
field entity_id: i32 =,
|
||||
field delta_x: i8 =,
|
||||
field delta_y: i8 =,
|
||||
field delta_z: i8 =,
|
||||
field delta_x: FixedPoint5<i8> =,
|
||||
field delta_y: FixedPoint5<i8> =,
|
||||
field delta_z: FixedPoint5<i8> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
}
|
||||
|
@ -1677,18 +1677,18 @@ state_packets!(
|
|||
}
|
||||
packet EntityTeleport_i32 {
|
||||
field entity_id: VarInt =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
field on_ground: bool =,
|
||||
}
|
||||
packet EntityTeleport_i32_i32_NoGround {
|
||||
field entity_id: i32 =,
|
||||
field x: i32 =,
|
||||
field y: i32 =,
|
||||
field z: i32 =,
|
||||
field x: FixedPoint5<i32> =,
|
||||
field y: FixedPoint5<i32> =,
|
||||
field z: FixedPoint5<i32> =,
|
||||
field yaw: i8 =,
|
||||
field pitch: i8 =,
|
||||
}
|
||||
|
|
|
@ -857,12 +857,12 @@ impl Server {
|
|||
}
|
||||
|
||||
fn on_entity_teleport_i32(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32) {
|
||||
self.on_entity_teleport(entity_telport.entity_id.0, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground)
|
||||
self.on_entity_teleport(entity_telport.entity_id.0, f64::from(entity_telport.x), f64::from(entity_telport.y), f64::from(entity_telport.z), entity_telport.yaw as f64, entity_telport.pitch as f64, entity_telport.on_ground)
|
||||
}
|
||||
|
||||
fn on_entity_teleport_i32_i32_noground(&mut self, entity_telport: packet::play::clientbound::EntityTeleport_i32_i32_NoGround) {
|
||||
let on_ground = true; // TODO: how is this supposed to be set? (for 1.7)
|
||||
self.on_entity_teleport(entity_telport.entity_id, entity_telport.x as f64, entity_telport.y as f64, entity_telport.z as f64, entity_telport.yaw as f64, entity_telport.pitch as f64, on_ground)
|
||||
self.on_entity_teleport(entity_telport.entity_id, f64::from(entity_telport.x), f64::from(entity_telport.y), f64::from(entity_telport.z), entity_telport.yaw as f64, entity_telport.pitch as f64, on_ground)
|
||||
}
|
||||
|
||||
|
||||
|
@ -880,24 +880,24 @@ impl Server {
|
|||
}
|
||||
|
||||
fn on_entity_move_i16(&mut self, m: packet::play::clientbound::EntityMove_i16) {
|
||||
self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64)
|
||||
self.on_entity_move(m.entity_id.0, f64::from(m.delta_x), f64::from(m.delta_y), f64::from(m.delta_z))
|
||||
}
|
||||
|
||||
fn on_entity_move_i8(&mut self, m: packet::play::clientbound::EntityMove_i8) {
|
||||
self.on_entity_move(m.entity_id.0, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64)
|
||||
self.on_entity_move(m.entity_id.0, f64::from(m.delta_x), f64::from(m.delta_y), f64::from(m.delta_z))
|
||||
}
|
||||
|
||||
fn on_entity_move_i8_i32_noground(&mut self, m: packet::play::clientbound::EntityMove_i8_i32_NoGround) {
|
||||
self.on_entity_move(m.entity_id, m.delta_x as f64, m.delta_y as f64, m.delta_z as f64)
|
||||
self.on_entity_move(m.entity_id, f64::from(m.delta_x), f64::from(m.delta_y), f64::from(m.delta_z))
|
||||
}
|
||||
|
||||
|
||||
fn on_entity_move(&mut self, entity_id: i32, delta_x: f64, delta_y: f64, delta_z: f64) {
|
||||
if let Some(entity) = self.entity_map.get(&entity_id) {
|
||||
let position = self.entities.get_component_mut(*entity, self.target_position).unwrap();
|
||||
position.position.x += delta_x / (32.0 * 128.0);
|
||||
position.position.y += delta_y / (32.0 * 128.0);
|
||||
position.position.z += delta_z / (32.0 * 128.0);
|
||||
position.position.x += delta_x;
|
||||
position.position.y += delta_y;
|
||||
position.position.z += delta_z;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -920,19 +920,19 @@ impl Server {
|
|||
|
||||
fn on_entity_look_and_move_i16(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i16) {
|
||||
self.on_entity_look_and_move(lookmove.entity_id.0,
|
||||
lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64,
|
||||
f64::from(lookmove.delta_x), f64::from(lookmove.delta_y), f64::from(lookmove.delta_z),
|
||||
lookmove.yaw as f64, lookmove.pitch as f64)
|
||||
}
|
||||
|
||||
fn on_entity_look_and_move_i8(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8) {
|
||||
self.on_entity_look_and_move(lookmove.entity_id.0,
|
||||
lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64,
|
||||
f64::from(lookmove.delta_x), f64::from(lookmove.delta_y), f64::from(lookmove.delta_z),
|
||||
lookmove.yaw as f64, lookmove.pitch as f64)
|
||||
}
|
||||
|
||||
fn on_entity_look_and_move_i8_i32_noground(&mut self, lookmove: packet::play::clientbound::EntityLookAndMove_i8_i32_NoGround) {
|
||||
self.on_entity_look_and_move(lookmove.entity_id,
|
||||
lookmove.delta_x as f64, lookmove.delta_y as f64, lookmove.delta_z as f64,
|
||||
f64::from(lookmove.delta_x), f64::from(lookmove.delta_y), f64::from(lookmove.delta_z),
|
||||
lookmove.yaw as f64, lookmove.pitch as f64)
|
||||
}
|
||||
|
||||
|
@ -941,9 +941,9 @@ impl Server {
|
|||
if let Some(entity) = self.entity_map.get(&entity_id) {
|
||||
let position = self.entities.get_component_mut(*entity, self.target_position).unwrap();
|
||||
let rotation = self.entities.get_component_mut(*entity, self.target_rotation).unwrap();
|
||||
position.position.x += delta_x / (32.0 * 128.0);
|
||||
position.position.y += delta_y / (32.0 * 128.0);
|
||||
position.position.z += delta_z / (32.0 * 128.0);
|
||||
position.position.x += delta_x;
|
||||
position.position.y += delta_y;
|
||||
position.position.z += delta_z;
|
||||
rotation.yaw = -(yaw / 256.0) * PI * 2.0;
|
||||
rotation.pitch = -(pitch / 256.0) * PI * 2.0;
|
||||
}
|
||||
|
@ -954,15 +954,15 @@ impl Server {
|
|||
}
|
||||
|
||||
fn on_player_spawn_i32(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32) {
|
||||
self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64)
|
||||
self.on_player_spawn(spawn.entity_id.0, spawn.uuid, f64::from(spawn.x), f64::from(spawn.y), f64::from(spawn.z), spawn.yaw as f64, spawn.pitch as f64)
|
||||
}
|
||||
|
||||
fn on_player_spawn_i32_helditem(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem) {
|
||||
self.on_player_spawn(spawn.entity_id.0, spawn.uuid, spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64)
|
||||
self.on_player_spawn(spawn.entity_id.0, spawn.uuid, f64::from(spawn.x), f64::from(spawn.y), f64::from(spawn.z), spawn.yaw as f64, spawn.pitch as f64)
|
||||
}
|
||||
|
||||
fn on_player_spawn_i32_helditem_string(&mut self, spawn: packet::play::clientbound::SpawnPlayer_i32_HeldItem_String) {
|
||||
self.on_player_spawn(spawn.entity_id.0, protocol::UUID::from_str(&spawn.uuid), spawn.x as f64, spawn.y as f64, spawn.z as f64, spawn.yaw as f64, spawn.pitch as f64)
|
||||
self.on_player_spawn(spawn.entity_id.0, protocol::UUID::from_str(&spawn.uuid), f64::from(spawn.x), f64::from(spawn.y), f64::from(spawn.z), spawn.yaw as f64, spawn.pitch as f64)
|
||||
}
|
||||
|
||||
fn on_player_spawn(&mut self, entity_id: i32, uuid: protocol::UUID, x: f64, y: f64, z: f64, pitch: f64, yaw: f64) {
|
||||
|
|
Loading…
Reference in New Issue