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:
iceiix 2019-05-11 13:27:52 -07:00 committed by ice_iix
parent 72d73f529f
commit 0034756339
2 changed files with 135 additions and 51 deletions

View File

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

View File

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