Merge pull request #125 from SirGhostal/new-packets

* Add Angle type for byte-encoded rotation angles.
* Fix: SpawnPlayerPacket and SpawnObjectPacket use wrong type for pitch/yaw.
* Add FacePlayerPacket and EntityLookPacket.
This commit is contained in:
joo 2019-05-18 03:04:59 +02:00 committed by GitHub
commit b83b33f8df
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 176 additions and 24 deletions

View File

@ -3,8 +3,8 @@ from minecraft.networking.packets import (
)
from minecraft.networking.types import (
Integer, FixedPointInteger, UnsignedByte, Byte, Boolean, UUID, Short,
VarInt, Double, Float, String, Enum, Difficulty, Dimension, GameMode,
Integer, FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID,
Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension, GameMode
)
from .combat_event_packet import CombatEventPacket
@ -15,6 +15,7 @@ from .spawn_object_packet import SpawnObjectPacket
from .block_change_packet import BlockChangePacket, MultiBlockChangePacket
from .explosion_packet import ExplosionPacket
from .sound_effect_packet import SoundEffectPacket
from .face_player_packet import FacePlayerPacket
# Formerly known as state_playing_clientbound.
@ -39,6 +40,7 @@ def get_packets(context):
RespawnPacket,
PluginMessagePacket,
PlayerListHeaderAndFooterPacket,
EntityLookPacket
}
if context.protocol_version <= 47:
packets |= {
@ -48,6 +50,10 @@ def get_packets(context):
packets |= {
SoundEffectPacket,
}
if context.protocol_version >= 352:
packets |= {
FacePlayerPacket
}
return packets
@ -173,8 +179,8 @@ class SpawnPlayerPacket(Packet):
else {'y': FixedPointInteger},
{'z': Double} if context.protocol_version >= 100
else {'z': FixedPointInteger},
{'yaw': Float},
{'pitch': Float},
{'yaw': Angle},
{'pitch': Angle},
# TODO: read entity metadata
{'current_item': Short} if context.protocol_version <= 49 else {}
])
@ -291,3 +297,22 @@ class PlayerListHeaderAndFooterPacket(Packet):
definition = [
{'header': String},
{'footer': String}]
class EntityLookPacket(Packet):
@staticmethod
def get_id(context):
return 0x2A if context.protocol_version >= 389 else \
0x29 if context.protocol_version >= 345 else \
0x28 if context.protocol_version >= 318 else \
0x27 if context.protocol_version >= 94 else \
0x28 if context.protocol_version >= 70 else \
0x16
packet_name = 'entity look'
definition = [
{'entity_id': VarInt},
{'yaw': Angle},
{'pitch': Angle},
{'on_ground': Boolean}
]

View File

@ -0,0 +1,69 @@
from minecraft.networking.types import (
VarInt, Double, Boolean, OriginPoint, Vector, multi_attribute_alias
)
from minecraft.networking.packets import Packet
class FacePlayerPacket(Packet):
@staticmethod
def get_id(context):
return 0x34 if context.protocol_version >= 471 else \
0x32 if context.protocol_version >= 451 else \
0x31 if context.protocol_version >= 389 else \
0x30
packet_name = 'face player'
# Access the 'x', 'y', 'z' fields as a Vector.
target = multi_attribute_alias(Vector, 'x', 'y', 'z')
def read(self, file_object):
if self.context.protocol_version >= 353:
self.origin = VarInt.read(file_object)
self.x = Double.read(file_object)
self.y = Double.read(file_object)
self.z = Double.read(file_object)
is_entity = Boolean.read(file_object)
if is_entity:
# If the entity given by entity ID cannot be found,
# this packet should be treated as if is_entity was false.
self.entity_id = VarInt.read(file_object)
self.entity_origin = VarInt.read(file_object)
else:
self.entity_id = None
else: # Protocol version 352
is_entity = Boolean.read(file_object)
self.entity_id = VarInt.read(file_object) if is_entity else None
if not is_entity:
self.x = Double.read(file_object)
self.y = Double.read(file_object)
self.z = Double.read(file_object)
def write_fields(self, packet_buffer):
if self.context.protocol_version >= 353:
VarInt.send(self.origin, packet_buffer)
Double.send(self.x, packet_buffer)
Double.send(self.y, packet_buffer)
Double.send(self.z, packet_buffer)
if self.entity_id is not None:
Boolean.send(True, packet_buffer)
VarInt.send(self.entity_id, packet_buffer)
VarInt.send(self.entity_origin, packet_buffer)
else:
Boolean.send(False, packet_buffer)
else: # Protocol version 352
if self.entity_id is not None:
Boolean.send(True, packet_buffer)
VarInt.send(self.entity_id, packet_buffer)
else:
Boolean.send(False, packet_buffer)
Double.send(self.x, packet_buffer)
Double.send(self.y, packet_buffer)
Double.send(self.z, packet_buffer)
# These aliases declare the Enum type corresponding to each field:
Origin = OriginPoint
EntityOrigin = OriginPoint

View File

@ -2,7 +2,7 @@ from minecraft.networking.packets import Packet
from minecraft.networking.types.utility import descriptor
from minecraft.networking.types import (
VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short, Enum, Vector,
VarInt, UUID, Byte, Double, Integer, Angle, Short, Enum, Vector,
PositionAndLook, attribute_alias, multi_attribute_alias,
)
@ -103,7 +103,7 @@ class SpawnObjectPacket(Packet):
for attr in 'x', 'y', 'z':
setattr(self, attr, xyz_type.read(file_object))
for attr in 'pitch', 'yaw':
setattr(self, attr, UnsignedByte.read(file_object))
setattr(self, attr, Angle.read(file_object))
self.data = Integer.read(file_object)
if self.context.protocol_version >= 49 or self.data > 0:
@ -125,7 +125,7 @@ class SpawnObjectPacket(Packet):
for coord in self.x, self.y, self.z:
xyz_type.send(coord, packet_buffer)
for coord in self.pitch, self.yaw:
UnsignedByte.send(coord, packet_buffer)
Angle.send(coord, packet_buffer)
Integer.send(self.data, packet_buffer)
if self.context.protocol_version >= 49 or self.data > 0:

View File

@ -11,9 +11,10 @@ from .utility import Vector
__all__ = (
'Type', 'Boolean', 'UnsignedByte', 'Byte', 'Short', 'UnsignedShort',
'Integer', 'FixedPointInteger', 'VarInt', 'Long', 'UnsignedLong', 'Float',
'Double', 'ShortPrefixedByteArray', 'VarIntPrefixedByteArray',
'TrailingByteArray', 'String', 'UUID', 'Position',
'Integer', 'FixedPointInteger', 'Angle', 'VarInt', 'Long',
'UnsignedLong', 'Float', 'Double', 'ShortPrefixedByteArray',
'VarIntPrefixedByteArray', 'TrailingByteArray', 'String', 'UUID',
'Position',
)
@ -117,6 +118,18 @@ class FixedPointInteger(Type):
Integer.send(int(value * 32), socket)
class Angle(Type):
@staticmethod
def read(file_object):
# Linearly transform angle in steps of 1/256 into steps of 1/360
return 360 * UnsignedByte.read(file_object) / 256
@staticmethod
def send(value, socket):
# Normalize angle between 0 and 255 and convert to int.
UnsignedByte.send(round(256 * ((value % 360) / 360)), socket)
class VarInt(Type):
@staticmethod
def read(file_object):

View File

@ -12,7 +12,7 @@ from .utility import Vector
__all__ = (
'Enum', 'BitFieldEnum', 'AbsoluteHand', 'RelativeHand', 'BlockFace',
'Difficulty', 'Dimension', 'GameMode'
'Difficulty', 'Dimension', 'GameMode', 'OriginPoint'
)
@ -106,3 +106,10 @@ class GameMode(Enum):
CREATIVE = 1
ADVENTURE = 2
SPECTATOR = 3
# Currently designates an entity's feet or eyes.
# Used in the Face Player Packet
class OriginPoint(Enum):
FEET = 0
EYES = 1

View File

@ -9,7 +9,7 @@ from random import choice
from minecraft import SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS
from minecraft.networking.connection import ConnectionContext
from minecraft.networking.types import (
VarInt, Enum, Vector, PositionAndLook
VarInt, Enum, Vector, PositionAndLook, OriginPoint,
)
from minecraft.networking.packets import (
Packet, PacketBuffer, PacketListener, KeepAlivePacket, serverbound,
@ -199,9 +199,9 @@ class TestReadWritePackets(unittest.TestCase):
'type_id', context)
pos_look = PositionAndLook(
position=(Vector(68.0, 38.0, 76.0) if context.protocol_version
>= 100 else Vector(68, 38, 76)),
yaw=16, pitch=23)
position=(Vector(68.0, 38.0, 76.0) if protocol_version >= 100
else Vector(68, 38, 76)),
yaw=263.494, pitch=180)
velocity = Vector(21, 55, 41)
entity_id, type_name, type_id = 49846, 'EGG', EntityType.EGG
@ -212,7 +212,7 @@ class TestReadWritePackets(unittest.TestCase):
velocity_x=velocity.x, velocity_y=velocity.y,
velocity_z=velocity.z,
entity_id=entity_id, type_id=type_id, data=1)
if context.protocol_version >= 49:
if protocol_version >= 49:
object_uuid = 'd9568851-85bc-4a10-8d6a-261d130626fa'
packet.object_uuid = object_uuid
self.assertEqual(packet.objectUUID, object_uuid)
@ -225,7 +225,7 @@ class TestReadWritePackets(unittest.TestCase):
context=context, position_and_look=pos_look,
velocity=velocity, type=type_name,
entity_id=entity_id, data=1)
if context.protocol_version >= 49:
if protocol_version >= 49:
packet2.object_uuid = object_uuid
self.assertEqual(packet.__dict__, packet2.__dict__)
@ -233,10 +233,12 @@ class TestReadWritePackets(unittest.TestCase):
self.assertEqual(packet.position, packet2.position)
packet2.data = 0
if context.protocol_version < 49:
if protocol_version < 49:
del packet2.velocity
self._test_read_write_packet(packet, context)
self._test_read_write_packet(packet2, context)
self._test_read_write_packet(packet, context,
yaw=360/256, pitch=360/256)
self._test_read_write_packet(packet2, context,
yaw=360/256, pitch=360/256)
def test_sound_effect_packet(self):
for protocol_version in TEST_VERSIONS:
@ -245,7 +247,7 @@ class TestReadWritePackets(unittest.TestCase):
packet = clientbound.play.SoundEffectPacket(
sound_id=545, effect_position=Vector(0.125, 300.0, 50.5),
volume=0.75)
if context.protocol_version >= 201:
if protocol_version >= 201:
packet.pitch = struct.unpack('f', struct.pack('f', 1.5))[0]
else:
packet.pitch = int(1.5 / 63.5) * 63.5
@ -254,7 +256,30 @@ class TestReadWritePackets(unittest.TestCase):
clientbound.play.SoundEffectPacket.SoundCategory.NEUTRAL
self._test_read_write_packet(packet, context)
def _test_read_write_packet(self, packet_in, context=None):
def test_face_player_packet(self):
for protocol_version in TEST_VERSIONS:
context = ConnectionContext(protocol_version=protocol_version)
packet = clientbound.play.FacePlayerPacket()
packet.target = 1.0, -2.0, 3.5
packet.entity_id = None
if protocol_version >= 353:
packet.origin = OriginPoint.EYES
self._test_read_write_packet(packet, context)
packet.entity_id = 123
if protocol_version >= 353:
packet.entity_origin = OriginPoint.FEET
else:
del packet.target
self._test_read_write_packet(packet, context)
def _test_read_write_packet(self, packet_in, context=None, **kwargs):
"""
If kwargs are specified, the key will be tested against the
respective delta value. Useful for testing FixedPointNumbers
where there is precision lost in the resulting value.
"""
if context is None:
for protocol_version in TEST_VERSIONS:
logging.debug('protocol_version = %r' % protocol_version)
@ -272,4 +297,12 @@ class TestReadWritePackets(unittest.TestCase):
packet_out = type(packet_in)(context=context)
packet_out.read(packet_buffer)
self.assertIs(type(packet_in), type(packet_out))
for packet_attr, precision in kwargs.items():
packet_attribute_in = packet_in.__dict__.pop(packet_attr)
packet_attribute_out = packet_out.__dict__.pop(packet_attr)
self.assertAlmostEqual(packet_attribute_in,
packet_attribute_out,
delta=precision)
self.assertEqual(packet_in.__dict__, packet_out.__dict__)

View File

@ -3,7 +3,7 @@
import unittest
from minecraft.networking.types import (
Type, Boolean, UnsignedByte, Byte, Short, UnsignedShort,
Integer, FixedPointInteger, VarInt, Long, Float, Double,
Integer, FixedPointInteger, Angle, VarInt, Long, Float, Double,
ShortPrefixedByteArray, VarIntPrefixedByteArray, UUID,
String as StringType, Position, TrailingByteArray, UnsignedLong,
)
@ -25,6 +25,7 @@ TEST_DATA = {
UnsignedLong: [0, 400],
Integer: [-1000, 1000],
FixedPointInteger: [float(-13098.3435), float(-0.83), float(1000)],
Angle: [0, 360.0, 720, 47.12947238973, -108.7],
VarInt: [1, 250, 50000, 10000000],
Long: [50000000],
Float: [21.000301],
@ -58,8 +59,12 @@ class SerializationTest(unittest.TestCase):
if data_type is FixedPointInteger:
self.assertAlmostEqual(
test_data, deserialized, delta=1.0/32.0)
elif data_type is Angle:
self.assertAlmostEqual(test_data % 360,
deserialized,
delta=360/256)
elif data_type is Float or data_type is Double:
self.assertAlmostEquals(test_data, deserialized, 3)
self.assertAlmostEqual(test_data, deserialized, 3)
else:
self.assertEqual(test_data, deserialized)