From 8578326c2f75684e27e449b0831a2e2b30f72b07 Mon Sep 17 00:00:00 2001 From: joo Date: Sun, 27 May 2018 15:36:13 +0100 Subject: [PATCH] Add serialisation and tests for SpawnObjectPacket. --- .../clientbound/play/block_change_packet.py | 15 +--- .../clientbound/play/combat_event_packet.py | 16 +--- .../packets/clientbound/play/map_packet.py | 15 +--- .../play/player_position_and_look_packet.py | 10 +-- .../clientbound/play/spawn_object_packet.py | 85 +++++++++++++------ minecraft/networking/types.py | 33 +++++++ tests/test_packets.py | 43 +++++++++- 7 files changed, 145 insertions(+), 72 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/block_change_packet.py b/minecraft/networking/packets/clientbound/play/block_change_packet.py index 09cd0d5..54814d7 100644 --- a/minecraft/networking/packets/clientbound/play/block_change_packet.py +++ b/minecraft/networking/packets/clientbound/play/block_change_packet.py @@ -1,6 +1,6 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, Integer, UnsignedByte, Position, Vector + VarInt, Integer, UnsignedByte, Position, Vector, MutableRecord ) @@ -46,21 +46,12 @@ class MultiBlockChangePacket(Packet): packet_name = 'multi block change' - class Record(object): + class Record(MutableRecord): __slots__ = 'x', 'y', 'z', 'block_state_id' def __init__(self, **kwds): self.block_state_id = 0 - for attr, value in kwds.items(): - setattr(self, attr, value) - - def __repr__(self): - return '%s(%s)' % (type(self).__name__, ', '.join( - '%s=%r' % (a, getattr(self, a)) for a in self.__slots__)) - - def __eq__(self, other): - return type(self) is type(other) and all( - getattr(self, a) == getattr(other, a) for a in self.__slots__) + super(MultiBlockChangePacket.Record, self).__init__(**kwds) # Access the 'x', 'y', 'z' fields as a Vector of ints. def position(self, position): diff --git a/minecraft/networking/packets/clientbound/play/combat_event_packet.py b/minecraft/networking/packets/clientbound/play/combat_event_packet.py index 17f5dc5..ffc1f75 100644 --- a/minecraft/networking/packets/clientbound/play/combat_event_packet.py +++ b/minecraft/networking/packets/clientbound/play/combat_event_packet.py @@ -1,7 +1,7 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, Integer, String + VarInt, Integer, String, MutableRecord ) @@ -20,22 +20,10 @@ class CombatEventPacket(Packet): packet_name = 'combat event' # The abstract type of the 'event' field of this packet. - class EventType(object): + class EventType(MutableRecord): __slots__ = () type_from_id_dict = {} - def __init__(self, **kwds): - for attr, value in kwds.items(): - setattr(self, attr, value) - - def __repr__(self, **kwds): - return '%s(%s)' % (type(self).__name__, ', '.join( - '%s=%r' % (a, getattr(self, a)) for a in self.__slots__)) - - def __eq__(self, other): - return type(self) is type(other) and all( - getattr(self, a) == getattr(other, a) for a in self.__slots__) - # Read the fields of the event (not including the ID) from the file. def read(self, file_object): raise NotImplementedError( diff --git a/minecraft/networking/packets/clientbound/play/map_packet.py b/minecraft/networking/packets/clientbound/play/map_packet.py index cf027b0..ca271d6 100644 --- a/minecraft/networking/packets/clientbound/play/map_packet.py +++ b/minecraft/networking/packets/clientbound/play/map_packet.py @@ -1,6 +1,7 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, Byte, Boolean, UnsignedByte, VarIntPrefixedByteArray, String + VarInt, Byte, Boolean, UnsignedByte, VarIntPrefixedByteArray, String, + MutableRecord ) @@ -15,7 +16,7 @@ class MapPacket(Packet): packet_name = 'map' - class MapIcon(object): + class MapIcon(MutableRecord): __slots__ = 'type', 'direction', 'location', 'display_name' def __init__(self, type, direction, location, display_name=None): @@ -24,11 +25,7 @@ class MapPacket(Packet): self.location = location self.display_name = display_name - def __repr__(self): - fs = ('%s=%r' % (at, getattr(self, at)) for at in self.__slots__) - return 'MapIcon(%s)' % ', '.join(fs) - - class Map(object): + class Map(MutableRecord): __slots__ = ('id', 'scale', 'icons', 'pixels', 'width', 'height', 'is_tracking_position') @@ -41,10 +38,6 @@ class MapPacket(Packet): self.pixels = bytearray(0 for i in range(width*height)) self.is_tracking_position = True - def __repr__(self): - fs = ('%s=%r' % (at, getattr(self, at)) for at in self.__slots__) - return 'Map(%s)' % ', '.join(fs) - class MapSet(object): __slots__ = 'maps_by_id' diff --git a/minecraft/networking/packets/clientbound/play/player_position_and_look_packet.py b/minecraft/networking/packets/clientbound/play/player_position_and_look_packet.py index de52075..7fff99f 100644 --- a/minecraft/networking/packets/clientbound/play/player_position_and_look_packet.py +++ b/minecraft/networking/packets/clientbound/play/player_position_and_look_packet.py @@ -1,7 +1,7 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - Double, Float, Byte, VarInt, BitFieldEnum + Double, Float, Byte, VarInt, BitFieldEnum, PositionAndLook ) @@ -36,12 +36,8 @@ class PlayerPositionAndLookPacket(Packet, BitFieldEnum): FLAG_REL_YAW = 0x08 FLAG_REL_PITCH = 0x10 - class PositionAndLook(object): - __slots__ = 'x', 'y', 'z', 'yaw', 'pitch' - - def __init__(self, **kwds): - for attr in self.__slots__: - setattr(self, attr, kwds.get(attr)) + # This alias is retained for backward compatibility. + PositionAndLook = PositionAndLook # Update a PositionAndLook instance using this packet. def apply(self, target): diff --git a/minecraft/networking/packets/clientbound/play/spawn_object_packet.py b/minecraft/networking/packets/clientbound/play/spawn_object_packet.py index 16a9422..eac00ad 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_object_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_object_packet.py @@ -1,7 +1,8 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short, Enum + VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short, Enum, Vector, + PositionAndLook ) @@ -44,33 +45,65 @@ class SpawnObjectPacket(Packet): def read(self, file_object): self.entity_id = VarInt.read(file_object) - if self._context.protocol_version >= 49: - self.objectUUID = UUID.read(file_object) - type_id = Byte.read(file_object) - self.type = SpawnObjectPacket.EntityType.name_from_value(type_id) + if self.context.protocol_version >= 49: + self.object_uuid = UUID.read(file_object) + self.type_id = Byte.read(file_object) - if self._context.protocol_version >= 100: - self.x = Double.read(file_object) - self.y = Double.read(file_object) - self.z = Double.read(file_object) - else: - self.x = Integer.read(file_object) - self.y = Integer.read(file_object) - self.z = Integer.read(file_object) + xyz_type = Double if self.context.protocol_version >= 100 else Integer + 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)) - self.pitch = UnsignedByte.read(file_object) - self.yaw = UnsignedByte.read(file_object) self.data = Integer.read(file_object) - - if self._context.protocol_version < 49: - if self.data > 0: - self.velocity_x = Short.read(file_object) - self.velocity_y = Short.read(file_object) - self.velocity_z = Short.read(file_object) - else: - self.velocity_x = Short.read(file_object) - self.velocity_y = Short.read(file_object) - self.velocity_z = Short.read(file_object) + if self.context.protocol_version >= 49 or self.data > 0: + for attr in 'velocity_x', 'velocity_y', 'velocity_z': + setattr(self, attr, Short.read(file_object)) def write_fields(self, packet_buffer): - raise NotImplementedError + VarInt.send(self.entity_id, packet_buffer) + if self.context.protocol_version >= 49: + UUID.send(self.object_uuid, packet_buffer) + Byte.send(self.type_id, packet_buffer) + + xyz_type = Double if self.context.protocol_version >= 100 else Integer + 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) + + Integer.send(self.data, packet_buffer) + if self.context.protocol_version >= 49 or self.data > 0: + for coord in self.velocity_x, self.velocity_y, self.velocity_z: + Short.send(coord, packet_buffer) + + # Access the entity type as a string, according to the EntityType enum. + def type(self, type_name): + self.type_id = getattr(self.EntityType, type_name) + type = property(lambda p: p.EntityType.name_from_value(p.type_id), type) + + # Access the fields 'x', 'y', 'z' as a Vector. + def position(self, position): + self.x, self.y, self.z = position + position = property(lambda p: Vector(p.x, p.y, p.z), position) + + # Access the fields 'x', 'y', 'z', 'yaw', 'pitch' as a PositionAndLook. + # NOTE: modifying the object retrieved from this property will not change + # the packet; it can only be changed by attribute or property assignment. + def position_and_look(self, position_and_look): + self.x, self.y, self.z = position_and_look.position + self.yaw, self.pitch = position_and_look.look + position_and_look = property(lambda p: PositionAndLook( + x=p.x, y=p.y, z=p.z, yaw=p.yaw, pitch=p.pitch), + position_and_look) + + # Access the fields 'velocity_x', 'velocity_y', 'velocity_z' as a Vector. + def velocity(self, velocity): + self.velocity_x, self.velocity_y, self.velocity_z = velocity + velocity = property(lambda p: Vector(p.velocity_x, p.velocity_y, + p.velocity_z), velocity) + + # This alias is retained for backward compatibility. + def objectUUID(self, object_uuid): + self.object_uuid = object_uuid + objectUUID = property(lambda self: self.object_uuid, objectUUID) diff --git a/minecraft/networking/types.py b/minecraft/networking/types.py index c6a08bb..fc74b1e 100644 --- a/minecraft/networking/types.py +++ b/minecraft/networking/types.py @@ -12,6 +12,39 @@ from collections import namedtuple Vector = namedtuple('Vector', ('x', 'y', 'z')) +class MutableRecord(object): + __slots__ = () + + def __init__(self, **kwds): + for attr, value in kwds.items(): + setattr(self, attr, value) + + def __repr__(self): + return '%s(%s)' % (type(self).__name__, ', '.join( + '%s=%r' % (a, getattr(self, a)) for a in self.__slots__)) + + def __eq__(self, other): + return type(self) is type(other) and \ + all(getattr(self, a) == getattr(other, a) for a in self.__slots__) + + def __hash__(self): + return hash(getattr(self, a) for a in self.__slots__) + + +class PositionAndLook(MutableRecord): + __slots__ = 'x', 'y', 'z', 'yaw', 'pitch' + + # Access the fields 'x', 'y', 'z' as a Vector. + def position(self, position): + self.x, self.y, self.z = position + position = property(lambda self: Vector(self.x, self.y, self.z), position) + + # Access the fields 'yaw', 'pitch' as a tuple. + def look(self, look): + self.yaw, self.pitch = look + look = property(lambda self: (self.yaw, self.pitch), look) + + class Type(object): __slots__ = () diff --git a/tests/test_packets.py b/tests/test_packets.py index 3e5637e..8fa03eb 100644 --- a/tests/test_packets.py +++ b/tests/test_packets.py @@ -6,10 +6,12 @@ from random import choice from minecraft import SUPPORTED_PROTOCOL_VERSIONS from minecraft.networking.connection import ConnectionContext -from minecraft.networking.types import VarInt, Enum, BitFieldEnum, Vector +from minecraft.networking.types import ( + VarInt, Enum, BitFieldEnum, Vector, PositionAndLook +) from minecraft.networking.packets import ( Packet, PacketBuffer, PacketListener, KeepAlivePacket, serverbound, - clientbound, + clientbound ) @@ -213,10 +215,47 @@ class TestReadWritePackets(unittest.TestCase): Record(x=1, y=2, z=3, blockId=56, blockMeta=13), Record(position=Vector(1, 2, 3), block_state_id=909), Record(position=(1, 2, 3), blockStateId=909)]) + self.assertEqual(packet.records[0].blockId, 56) + self.assertEqual(packet.records[0].blockMeta, 13) + self.assertEqual(packet.records[0].blockStateId, 909) + self.assertEqual(packet.records[0].position, Vector(1, 2, 3)) self.assertEqual(packet.records[0], packet.records[1]) self.assertEqual(packet.records[1], packet.records[2]) self._test_read_write_packet(packet) + def test_spawn_object_packet(self): + EntityType = clientbound.play.SpawnObjectPacket.EntityType + + object_uuid = 'd9568851-85bc-4a10-8d6a-261d130626fa' + pos_look = PositionAndLook(x=68.0, y=38.0, z=76.0, yaw=16, pitch=23) + velocity = Vector(21, 55, 41) + entity_id, type_name, type_id = 49846, 'EGG', EntityType.EGG + + packet = clientbound.play.SpawnObjectPacket( + x=pos_look.x, y=pos_look.y, z=pos_look.z, + yaw=pos_look.yaw, pitch=pos_look.pitch, + velocity_x=velocity.x, velocity_y=velocity.y, + velocity_z=velocity.z, object_uuid=object_uuid, + entity_id=entity_id, type_id=type_id, data=1) + self.assertEqual(packet.position_and_look, pos_look) + self.assertEqual(packet.position, pos_look.position) + self.assertEqual(packet.velocity, velocity) + self.assertEqual(packet.objectUUID, object_uuid) + self.assertEqual(packet.type, type_name) + + packet2 = clientbound.play.SpawnObjectPacket( + position_and_look=pos_look, velocity=velocity, + type=type_name, object_uuid=object_uuid, + entity_id=entity_id, data=1) + self.assertEqual(packet.__dict__, packet2.__dict__) + + packet2.position = pos_look.position + self.assertEqual(packet.position, packet2.position) + + packet2.data = 0 + self._test_read_write_packet(packet) + self._test_read_write_packet(packet2) + def _test_read_write_packet(self, packet_in): packet_in.context = self.context packet_buffer = PacketBuffer()