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 87bfb8d2a4
commit c8e13c38bd
5 changed files with 154 additions and 68 deletions

1
Cargo.lock generated
View File

@ -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)",

View File

@ -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 = "*"

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

View File

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