From 7a79b4dbcd0419b0271eb73505ebd3bccb409f37 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Thu, 29 Aug 2019 18:24:49 +0200 Subject: [PATCH 01/13] Implement VehicleMove & Entity Clientbound Packets --- .../packets/clientbound/play/__init__.py | 51 ++++++++++++++++++- 1 file changed, 49 insertions(+), 2 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index 45be9ddb..1aeae0ae 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -41,7 +41,8 @@ def get_packets(context): RespawnPacket, PluginMessagePacket, PlayerListHeaderAndFooterPacket, - EntityLookPacket + EntityLookPacket, + EntityPacket, } if context.protocol_version <= 47: packets |= { @@ -50,10 +51,11 @@ def get_packets(context): if context.protocol_version >= 94: packets |= { SoundEffectPacket, + VehicleMovePacket, } if context.protocol_version >= 352: packets |= { - FacePlayerPacket + FacePlayerPacket, } return packets @@ -321,3 +323,48 @@ def get_id(context): {'pitch': Angle}, {'on_ground': Boolean} ] + + +class EntityPacket(Packet): + @staticmethod + def get_id(context): + return 0x2B if context.protocol_version >= 471 else \ + 0x27 if context.protocol_version >= 389 else \ + 0x26 if context.protocol_version >= 345 else \ + 0x25 if context.protocol_version >= 332 else \ + 0x29 if context.protocol_version >= 318 else \ + 0x28 if context.protocol_version >= 94 else \ + 0x29 if context.protocol_version >= 70 else \ + 0x14 + + packet_name = "entity" + definition = [{"entity_id": VarInt}] + + +class VehicleMovePacket(Packet): + @staticmethod + def get_id(context): + return 0x2C if context.protocol_version >= 471 else \ + 0x2B if context.protocol_version >= 389 else \ + 0x2A if context.protocol_version >= 345 else \ + 0x29 if context.protocol_version >= 332 else \ + 0x2A if context.protocol_version >= 318 else \ + 0x29 + + packet_name = "vehicle move clientbound" + definition = [ + {'x': Double}, + {'y': Double}, + {'z': Double}, + {'yaw': Float}, + {'pitch': Float}, + ] + + # Note: See clientbound.play.PositionAndLookPacket for notes + # regarding accessing / modifying attributes here. + position = multi_attribute_alias(Vector, 'x', 'y', 'z') + + look = multi_attribute_alias(Direction, 'yaw', 'pitch') + + position_and_look = multi_attribute_alias( + PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') From 969b042d4fcfd3c712a83b3f9a4f64361f9179d1 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Thu, 29 Aug 2019 18:35:37 +0200 Subject: [PATCH 02/13] Implement VehicleMovePacket Serverbound Packet --- .../packets/clientbound/play/__init__.py | 11 ++++-- .../packets/serverbound/play/__init__.py | 38 +++++++++++++++++++ 2 files changed, 45 insertions(+), 4 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index 1aeae0ae..c5f1de46 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -349,7 +349,7 @@ def get_id(context): 0x2A if context.protocol_version >= 345 else \ 0x29 if context.protocol_version >= 332 else \ 0x2A if context.protocol_version >= 318 else \ - 0x29 + 0x29 # Note: Packet added in protocol version 94 packet_name = "vehicle move clientbound" definition = [ @@ -360,11 +360,14 @@ def get_id(context): {'pitch': Float}, ] - # Note: See clientbound.play.PositionAndLookPacket for notes - # regarding accessing / modifying attributes here. + # Access the 'x', 'y', 'z' fields as a Vector tuple. position = multi_attribute_alias(Vector, 'x', 'y', 'z') + # Access the 'yaw', 'pitch' fields as a Direction tuple. look = multi_attribute_alias(Direction, 'yaw', 'pitch') + # Access the 'x', 'y', 'z', 'yaw', 'pitch' fields 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. position_and_look = multi_attribute_alias( - PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') + PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') \ No newline at end of file diff --git a/minecraft/networking/packets/serverbound/play/__init__.py b/minecraft/networking/packets/serverbound/play/__init__.py index fff5ecdc..ff5dacb1 100644 --- a/minecraft/networking/packets/serverbound/play/__init__.py +++ b/minecraft/networking/packets/serverbound/play/__init__.py @@ -27,6 +27,10 @@ def get_packets(context): packets |= { UseItemPacket, } + if context.protocol_version >= 94: + packets |= { + VehicleMovePacket, + } if context.protocol_version >= 107: packets |= { TeleportConfirmPacket, @@ -251,3 +255,37 @@ def get_id(context): {'hand': VarInt}]) Hand = RelativeHand + + +class VehicleMovePacket(Packet): + @staticmethod + def get_id(context): + return 0x15 if context.protocol_version >= 464 else \ + 0x13 if context.protocol_version >= 389 else \ + 0x11 if context.protocol_version >= 386 else \ + 0x10 if context.protocol_version >= 345 else \ + 0x0F if context.protocol_version >= 343 else \ + 0x10 if context.protocol_version >= 336 else \ + 0x11 if context.protocol_version >= 318 else \ + 0x10 # Note: Packet added in protocol version 94 + + packet_name = "vehicle move serverbound" + definition = [ + {'x': Double}, + {'y': Double}, + {'z': Double}, + {'yaw': Float}, + {'pitch': Float}, + ] + + # Access the 'x', 'y', 'z' fields as a Vector tuple. + position = multi_attribute_alias(Vector, 'x', 'y', 'z') + + # Access the 'yaw', 'pitch' fields as a Direction tuple. + look = multi_attribute_alias(Direction, 'yaw', 'pitch') + + # Access the 'x', 'y', 'z', 'yaw', 'pitch' fields 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. + position_and_look = multi_attribute_alias( + PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') From c880f4ef4af5700869f7d00c38cc5ae477dc78f3 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Thu, 29 Aug 2019 19:50:29 +0200 Subject: [PATCH 03/13] Implement DestroyEntitiesPacket Clientbound --- .../packets/clientbound/play/__init__.py | 4 ++- .../play/destroy_entities_packet.py | 30 +++++++++++++++++++ 2 files changed, 33 insertions(+), 1 deletion(-) create mode 100644 minecraft/networking/packets/clientbound/play/destroy_entities_packet.py diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index c5f1de46..071c6e78 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -17,6 +17,7 @@ from .explosion_packet import ExplosionPacket from .sound_effect_packet import SoundEffectPacket from .face_player_packet import FacePlayerPacket +from .destroy_entities_packet import DestroyEntitiesPacket # Formerly known as state_playing_clientbound. @@ -43,6 +44,7 @@ def get_packets(context): PlayerListHeaderAndFooterPacket, EntityLookPacket, EntityPacket, + DestroyEntitiesPacket, } if context.protocol_version <= 47: packets |= { @@ -370,4 +372,4 @@ def get_id(context): # NOTE: modifying the object retrieved from this property will not change # the packet; it can only be changed by attribute or property assignment. position_and_look = multi_attribute_alias( - PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') \ No newline at end of file + PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') diff --git a/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py new file mode 100644 index 00000000..e0d07492 --- /dev/null +++ b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py @@ -0,0 +1,30 @@ +from minecraft.networking.packets import Packet + +from minecraft.networking.types import VarInt + + + +class DestroyEntitiesPacket(Packet): + @staticmethod + def get_id(context): + return 0x37 if context.protocol_version >= 471 else \ + 0x35 if context.protocol_version >= 461 else \ + 0x36 if context.protocol_version >= 451 else \ + 0x35 if context.protocol_version >= 389 else \ + 0x34 if context.protocol_version >= 352 else \ + 0x33 if context.protocol_version >= 345 else \ + 0x32 if context.protocol_version >= 336 else \ + 0x31 if context.protocol_version >= 332 else \ + 0x32 if context.protocol_version >= 318 else \ + 0x30 if context.protocol_version >= 70 else \ + 0x13 + + def read(self, file_object): + self.entity_ids = [VarInt.read(file_object) + for i in range(VarInt.read(file_object))] + + def write(self, packet_buffer): + count = len(self.entity_ids) + VarInt.send(count, packet_buffer) + for entity_id in self.entity_ids: + VarInt.send(entity_id, packet_buffer) \ No newline at end of file From 2f5bfeed71d770673b8216c94f489ebf2f839239 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Fri, 30 Aug 2019 16:52:16 +0200 Subject: [PATCH 04/13] Implement SpawnMobPacket Clientbound --- .../packets/clientbound/play/__init__.py | 2 + .../clientbound/play/spawn_mob_packet.py | 309 ++++++++++++++++++ .../clientbound/play/spawn_object_packet.py | 4 +- 3 files changed, 313 insertions(+), 2 deletions(-) create mode 100644 minecraft/networking/packets/clientbound/play/spawn_mob_packet.py diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index 071c6e78..3c968336 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -18,6 +18,7 @@ from .sound_effect_packet import SoundEffectPacket from .face_player_packet import FacePlayerPacket from .destroy_entities_packet import DestroyEntitiesPacket +from .spawn_mob_packet import SpawnMobPacket # Formerly known as state_playing_clientbound. @@ -45,6 +46,7 @@ def get_packets(context): EntityLookPacket, EntityPacket, DestroyEntitiesPacket, + SpawnMobPacket, } if context.protocol_version <= 47: packets |= { diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py new file mode 100644 index 00000000..b1af084f --- /dev/null +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -0,0 +1,309 @@ +from minecraft.networking.packets import Packet +from minecraft.networking.types.utility import descriptor + +from minecraft.networking.types import ( + VarInt, UUID, Double, Integer, Angle, Short, UnsignedByte, + Enum, Vector, Direction, PositionAndLook, attribute_alias, + multi_attribute_alias +) + + +class SpawnMobPacket(Packet): + @staticmethod + def get_id(context): + return 0x03 if context.protocol_version >= 70 else \ + 0x0F + + packet_name = 'spawn mob' + + fields = ('entity_id', 'entity_uuid', 'type_id', 'x', 'y', 'z', 'pitch', + 'yaw', 'head_pitch', 'velocity_x', 'velocity_y', 'velocity_z') + + @classmethod + def field_enum(cls, field, context): + if field != 'type_id' or context is None: + return + + pv = context.protocol_version + name = "EntityType_%d" % pv + if hasattr(cls, name): + return getattr(cls, name) + + class EntityType(Enum): + BAT = 3 if pv >= 393 else \ + 65 + BLAZE = 4 if pv >= 393 else \ + 61 + CAVE_SPIDER = 7 if pv >= 447 else \ + 6 if pv >= 393 else \ + 59 + CHICKEN = 8 if pv >= 447 else \ + 7 if pv >= 393 else \ + 93 + COW = 10 if pv >= 447 else \ + 9 if pv >= 393 else \ + 92 + CREEPER = 11 if pv >= 447 else \ + 10 if pv >= 393 else \ + 50 + ENDER_DRAGON = 18 if pv >= 447 else \ + 17 if pv >= 393 else \ + 63 + ENDERMAN = 19 if pv >= 447 else \ + 18 if pv >= 393 else \ + 58 + ENDERMITE = 20 if pv >= 447 else \ + 19 if pv >= 393 else \ + 67 + GHAST = 28 if pv >= 447 else \ + 26 if pv >= 393 else \ + 56 + GIANT = 29 if pv >= 447 else \ + 27 if pv >= 393 else \ + 53 + GUARDIAN = 30 if pv >= 447 else \ + 28 if pv >= 393 else \ + 68 + MAGMA_CUBE = 40 if pv >= 447 else \ + 38 if pv >= 393 else \ + 62 # Lava Slime + MOOSHROOM = 49 if pv >= 447 else \ + 47 if pv >= 393 else \ + 96 # Mushroom Cow + OCELOT = 50 if pv >= 447 else \ + 48 if pv >= 393 else \ + 98 # Ozelot + PIG = 54 if pv >= 447 else \ + 51 if pv >= 393 else \ + 90 + ZOMBIE_PIGMAN = 56 if pv >= 447 else \ + 53 if pv >= 393 else \ + 57 + RABBIT = 59 if pv >= 447 else \ + 56 if pv >= 393 else \ + 101 + SHEEP = 61 if pv >= 447 else \ + 58 if pv >= 393 else \ + 91 + SILVERFISH = 64 if pv >= 447 else \ + 61 if pv >= 393 else \ + 60 + SKELETON = 65 if pv >= 447 else \ + 62 if pv >= 393 else \ + 51 + SLIME = 67 if pv >= 447 else \ + 64 if pv >= 393 else \ + 55 + SNOW_GOLEM = 69 if pv >= 447 else \ + 66 if pv >= 393 else \ + 97 # Snow Man + SPIDER = 72 if pv >= 447 else \ + 69 if pv >= 393 else \ + 52 + SQUID = 73 if pv >= 447 else \ + 70 if pv >= 393 else \ + 94 + VILLAGER = 84 if pv >= 447 else \ + 79 if pv >= 393 else \ + 120 + IRON_GOLEM = 85 if pv >= 447 else \ + 80 if pv >= 393 else \ + 99 # Villager Golem + WITCH = 89 if pv >= 447 else \ + 82 if pv >= 393 else \ + 66 + WITHER = 90 if pv >= 447 else \ + 83 if pv >= 393 else \ + 64 # Wither Boss + # Issue with Wither Skeletons in PrismarineJS? + # Not present in some protocol versions so + # only 99% certain this enum is 100% accurate. + WITHER_SKELETON = 91 if pv >= 447 else \ + 84 if pv >= 393 else \ + 5 + WOLF = 93 if pv >= 447 else \ + 86 if pv >= 393 else \ + 95 + ZOMBIE = 94 if pv >= 447 else \ + 87 if pv >= 393 else \ + 54 + if pv >= 447: + TRADER_LLAMA = 75 + if pv >= 393: + COD = 9 if pv >= 447 else \ + 8 + DOLPHIN = 13 if pv >= 447 else \ + 12 + DROWNED = 15 if pv >= 447 else \ + 14 + PUFFERFISH = 55 if pv >= 447 else \ + 52 + SALMON = 60 if pv >= 447 else \ + 57 + TROPICAL_FISH = 76 if pv >= 447 else \ + 72 + TURTLE = 77 if pv >= 447 else \ + 73 + PHANTOM = 97 if pv >= 447 else \ + 90 + if pv >= 335: + ILLUSIONER = 33 if pv >= 447 else \ + 31 if pv >= 393 else \ + 37 # Illusion Illager + PARROT = 53 if pv >= 447 else \ + 50 if pv >= 393 else \ + 105 + if pv >= 315: + DONKEY = 12 if pv >= 447 else \ + 11 if pv >= 393 else \ + 31 + ELDER_GUARDIAN = 16 if pv >= 447 else \ + 15 if pv >= 393 else \ + 4 + EVOKER = 22 if pv >= 447 else \ + 21 if pv >= 393 else \ + 34 + HORSE = 31 if pv >= 447 else \ + 29 if pv >= 393 else \ + 100 + HUSK = 32 if pv >= 447 else \ + 30 if pv >= 393 else \ + 23 + LLAMA = 38 if pv >= 447 else \ + 36 if pv >= 393 else \ + 103 + MULE = 48 if pv >= 447 else \ + 46 if pv >= 393 else \ + 32 + POLAR_BEAR = 57 if pv >= 447 else \ + 54 if pv >= 393 else \ + 102 + SKELETON_HORSE = 66 if pv >= 447 else \ + 63 if pv >= 393 else \ + 28 + STRAY = 74 if pv >= 447 else \ + 71 if pv >= 393 else \ + 6 + VEX = 83 if pv >= 447 else \ + 78 if pv >= 393 else \ + 35 + VINDICATOR = 86 if pv >= 447 else \ + 81 if pv >= 393 else \ + 36 # Vindication Illager + ZOMBIE_HORSE = 95 if pv >= 447 else \ + 88 if pv >= 393 else \ + 29 + ZOMBIE_VILLAGER = 96 if pv >= 447 else \ + 89 if pv >= 393 else \ + 27 + if pv >= 76: + SHULKER = 62 if pv >= 447 else \ + 59 if pv >= 393 else \ + 69 + + setattr(cls, name, EntityType) + return EntityType + + def read(self, file_object): + self.entity_id = VarInt.read(file_object) + if self.context.protocol_version >= 69: + self.entity_uuid = UUID.read(file_object) + + if self.context.protocol_version >= 301: + self.type_id = VarInt.read(file_object) + else: + self.type_id = UnsignedByte.read(file_object) + + xyz_type = Double if self.context.protocol_version >= 97 else Integer + for attr in 'x', 'y', 'z': + setattr(self, attr, xyz_type.read(file_object)) + + for attr in 'pitch', 'yaw', 'head_pitch': + setattr(self, attr, Angle.read(file_object)) + + for attr in 'velocity_x', 'velocity_y', 'velocity_z': + setattr(self, attr, Short.read(file_object)) + + # TODO: read entity metadata + + def write_fields(self, packet_buffer): + VarInt.read(self.entity_id, packet_buffer) + if self.context.protocol_version >= 69: + UUID.send(self.entity_id, packet_buffer) + + if self.context.protocol_version >= 301: + VarInt.send(self.type_id, packet_buffer) + else: + UnsignedByte.send(self.type_id, packet_buffer) + + xyz_type = Double if self.context.protocol_version >= 97 else Integer + for coord in self.x, self.y, self.z: + xyz_type.send(coord, packet_buffer) + + for angle in self.pitch, self.yaw, self.head_pitch: + Angle.send(angle, packet_buffer) + + for velocity in self.velocity_x, self.velocity_y, self.velocity_z: + Short.send(velocity, packet_buffer) + + # TODO: write entity metadata + + # Access the entity type as a string, according to the EntityType enum. + @property + def type(self): + if self.context is None: + raise ValueError('This packet must have a non-None "context" ' + 'in order to read the "type" property.') + # pylint: disable=no-member + return self.EntityType.name_from_value(self.type_id) + + @type.setter + def type(self, type_name): + if self.context is None: + raise ValueError('This packet must have a non-None "context" ' + 'in order to set the "type" property.') + self.type_id = getattr(self.EntityType, type_name) + + @type.deleter + def type(self): + del self.type_id + + # Access the 'x', 'y', 'z' fields as a Vector. + position = multi_attribute_alias(Vector, 'x', 'y', 'z') + + # Access the 'yaw', 'pitch' fields as a Direction. + look = multi_attribute_alias(Direction, 'yaw', 'pitch') + + # Access the 'x', 'y', 'z', 'pitch', 'yaw' fields 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. + position_and_look = multi_attribute_alias( + PositionAndLook, x='x', y='y', z='z', yaw='yaw', pitch='pitch') + + # Access the 'velocity_{x,y,z}' fields as a Vector. + velocity = multi_attribute_alias( + Vector, 'velocity_x', 'velocity_y', 'velocity_z') + + # This alias is retained for backward compatibility. + objectUUID = attribute_alias('object_uuid') + + # Access the entity type as a string, according to the EntityType enum. + @property + def type(self): + if self.context is None: + raise ValueError('This packet must have a non-None "context" ' + 'in order to read the "type" property.') + # pylint: disable=no-member + return self.EntityType.name_from_value(self.type_id) + + @type.setter + def type(self, type_name): + if self.context is None: + raise ValueError('This packet must have a non-None "context" ' + 'in order to set the "type" property.') + self.type_id = getattr(self.EntityType, type_name) + + @type.deleter + def type(self): + del self.type_id + diff --git a/minecraft/networking/packets/clientbound/play/spawn_object_packet.py b/minecraft/networking/packets/clientbound/play/spawn_object_packet.py index d856f3fa..05b74598 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_object_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_object_packet.py @@ -124,8 +124,8 @@ def write_fields(self, 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: - Angle.send(coord, packet_buffer) + for angle in self.pitch, self.yaw: + Angle.send(angle, packet_buffer) Integer.send(self.data, packet_buffer) if self.context.protocol_version >= 49 or self.data > 0: From 484464dd0431af5807eec8d950c11f2f7e7e87c6 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Fri, 30 Aug 2019 17:00:21 +0200 Subject: [PATCH 05/13] Implement UseEntityPacket Serverbound --- .../packets/serverbound/play/__init__.py | 48 +++++++++++++++++-- minecraft/networking/types/enum.py | 10 +++- 2 files changed, 54 insertions(+), 4 deletions(-) diff --git a/minecraft/networking/packets/serverbound/play/__init__.py b/minecraft/networking/packets/serverbound/play/__init__.py index ff5dacb1..118ab7e1 100644 --- a/minecraft/networking/packets/serverbound/play/__init__.py +++ b/minecraft/networking/packets/serverbound/play/__init__.py @@ -3,9 +3,9 @@ ) from minecraft.networking.types import ( - Double, Float, Boolean, VarInt, String, Byte, Position, Enum, - RelativeHand, BlockFace, Vector, Direction, PositionAndLook, - multi_attribute_alias + Double, Float, Boolean, VarInt, String, Byte, UnsignedByte, + Position, Enum, RelativeHand, BlockFace, ClickType, Vector, + Direction, PositionAndLook, multi_attribute_alias ) from .client_settings_packet import ClientSettingsPacket @@ -22,6 +22,7 @@ def get_packets(context): ClientSettingsPacket, PluginMessagePacket, PlayerBlockPlacementPacket, + UseEntityPacket } if context.protocol_version >= 69: packets |= { @@ -257,6 +258,47 @@ def get_id(context): Hand = RelativeHand +class UseEntityPacket(Packet): + @staticmethod + def get_id(context): + return 0x0E if context.protocol_version >= 464 else \ + 0x0D if context.protocol_version >= 389 else \ + 0x0B if context.protocol_version >= 386 else \ + 0x0A if context.protocol_version >= 345 else \ + 0x09 if context.protocol_version >= 343 else \ + 0x0A if context.protocol_version >= 336 else \ + 0x0B if context.protocol_version >= 318 else \ + 0x0A if context.protocol_version >= 94 else \ + 0x09 if context.protocol_version >= 70 else \ + 0x02 + + packet_name = "use entity" + + def read(self, file_object): + self.target = VarInt.read(file_object) + self.type = VarInt.read(file_object) + if self.type is ClickType.INTERACT_AT: + self.target_x =Float.read(file_object) + self.target_y = Float.read(file_object) + self.target_z = Float.read(file_object) + if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + self.hand = VarInt.read(file_object) + + def write_fields(self, packet_buffer): + VarInt.send(self.target, packet_buffer) + VarInt.send(self.type, packet_buffer) + if self.type is ClickType.INTERACT_AT: + Float.send(self.target_x, packet_buffer) + Float.send(self.target_y, packet_buffer) + Float.send(self.target_z, packet_buffer) + if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + VarInt.send(self.hand) + + ClickType = ClickType + + Hand = RelativeHand + + class VehicleMovePacket(Packet): @staticmethod def get_id(context): diff --git a/minecraft/networking/types/enum.py b/minecraft/networking/types/enum.py index 61aa2384..98451a98 100644 --- a/minecraft/networking/types/enum.py +++ b/minecraft/networking/types/enum.py @@ -12,7 +12,7 @@ __all__ = ( 'Enum', 'BitFieldEnum', 'AbsoluteHand', 'RelativeHand', 'BlockFace', - 'Difficulty', 'Dimension', 'GameMode', 'OriginPoint' + 'Difficulty', 'Dimension', 'GameMode', 'OriginPoint', 'ClickType' ) @@ -113,3 +113,11 @@ class GameMode(Enum): class OriginPoint(Enum): FEET = 0 EYES = 1 + + +# Designation of a player's click action. +# Used in Use Entity Packet +class ClickType(Enum): + INTERACT = 0 + ATTACK = 1 + INTERACT_AT = 2 From 7640cea2d71097bdd1ff624840b1a712072f0933 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Fri, 30 Aug 2019 17:05:42 +0200 Subject: [PATCH 06/13] Partial Implementation of BlockActionPacket Clientbound --- .../packets/clientbound/play/__init__.py | 25 +++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index 3c968336..9fa76e0c 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -4,8 +4,9 @@ from minecraft.networking.types import ( Integer, FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID, - Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension, - GameMode, Vector, Direction, PositionAndLook, multi_attribute_alias, + Short, VarInt, Double, Float, String, Position, Enum, Difficulty, + Dimension, GameMode, Vector, Direction, PositionAndLook, + multi_attribute_alias, ) from .combat_event_packet import CombatEventPacket @@ -47,6 +48,7 @@ def get_packets(context): EntityPacket, DestroyEntitiesPacket, SpawnMobPacket, + BlockActionPacket, } if context.protocol_version <= 47: packets |= { @@ -375,3 +377,22 @@ def get_id(context): # the packet; it can only be changed by attribute or property assignment. position_and_look = multi_attribute_alias( PositionAndLook, 'x', 'y', 'z', 'yaw', 'pitch') + + +class BlockActionPacket(Packet): + @staticmethod + def get_id(context): + return 0x0A if context.protocol_version >= 332 else \ + 0x0B if context.protocol_version >= 318 else \ + 0x0A if context.protocol_version >= 70 else \ + 0x25 if context.protocol_version >= 69 else \ + 0x24 + + packet_name = "block action" + get_definition = staticmethod(lambda context: [ + {'location': Position}, + {'block_type': VarInt} if context.protocol_version == 347 else {}, + {'action_id': UnsignedByte}, # TODO Interpret action_id and + {'action_param': UnsignedByte}, # action_param fields. + {'block_type': VarInt} if context.protocol_version != 347 else {}, + ]) From 8ee1a60f6135f3ce36dfec72f1b3fc0b647fbe33 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Fri, 30 Aug 2019 17:36:14 +0200 Subject: [PATCH 07/13] Fix flake8 & pylint errors --- .../packets/clientbound/play/__init__.py | 2 +- .../play/destroy_entities_packet.py | 3 +- .../clientbound/play/spawn_mob_packet.py | 36 +++++++++---------- tox.ini | 1 + 4 files changed, 20 insertions(+), 22 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index 9fa76e0c..bc4043fb 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -355,7 +355,7 @@ def get_id(context): 0x2A if context.protocol_version >= 345 else \ 0x29 if context.protocol_version >= 332 else \ 0x2A if context.protocol_version >= 318 else \ - 0x29 # Note: Packet added in protocol version 94 + 0x29 # Note: Packet added in protocol version 94 packet_name = "vehicle move clientbound" definition = [ diff --git a/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py index e0d07492..7d9b2bfe 100644 --- a/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py +++ b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py @@ -3,7 +3,6 @@ from minecraft.networking.types import VarInt - class DestroyEntitiesPacket(Packet): @staticmethod def get_id(context): @@ -27,4 +26,4 @@ def write(self, packet_buffer): count = len(self.entity_ids) VarInt.send(count, packet_buffer) for entity_id in self.entity_ids: - VarInt.send(entity_id, packet_buffer) \ No newline at end of file + VarInt.send(entity_id, packet_buffer) diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py index b1af084f..f4441c04 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -1,5 +1,4 @@ from minecraft.networking.packets import Packet -from minecraft.networking.types.utility import descriptor from minecraft.networking.types import ( VarInt, UUID, Double, Integer, Angle, Short, UnsignedByte, @@ -30,18 +29,18 @@ def field_enum(cls, field, context): return getattr(cls, name) class EntityType(Enum): - BAT = 3 if pv >= 393 else \ + BAT = 3 if pv >= 393 else \ 65 - BLAZE = 4 if pv >= 393 else \ + BLAZE = 4 if pv >= 393 else \ 61 - CAVE_SPIDER = 7 if pv >= 447 else \ - 6 if pv >= 393 else \ + CAVE_SPIDER = 7 if pv >= 447 else \ + 6 if pv >= 393 else \ 59 - CHICKEN = 8 if pv >= 447 else \ - 7 if pv >= 393 else \ + CHICKEN = 8 if pv >= 447 else \ + 7 if pv >= 393 else \ 93 COW = 10 if pv >= 447 else \ - 9 if pv >= 393 else \ + 9 if pv >= 393 else \ 92 CREEPER = 11 if pv >= 447 else \ 10 if pv >= 393 else \ @@ -66,13 +65,13 @@ class EntityType(Enum): 68 MAGMA_CUBE = 40 if pv >= 447 else \ 38 if pv >= 393 else \ - 62 # Lava Slime + 62 # Lava Slime MOOSHROOM = 49 if pv >= 447 else \ 47 if pv >= 393 else \ - 96 # Mushroom Cow + 96 # Mushroom Cow OCELOT = 50 if pv >= 447 else \ 48 if pv >= 393 else \ - 98 # Ozelot + 98 # Ozelot PIG = 54 if pv >= 447 else \ 51 if pv >= 393 else \ 90 @@ -96,7 +95,7 @@ class EntityType(Enum): 55 SNOW_GOLEM = 69 if pv >= 447 else \ 66 if pv >= 393 else \ - 97 # Snow Man + 97 # Snow Man SPIDER = 72 if pv >= 447 else \ 69 if pv >= 393 else \ 52 @@ -108,13 +107,13 @@ class EntityType(Enum): 120 IRON_GOLEM = 85 if pv >= 447 else \ 80 if pv >= 393 else \ - 99 # Villager Golem + 99 # Villager Golem WITCH = 89 if pv >= 447 else \ 82 if pv >= 393 else \ 66 WITHER = 90 if pv >= 447 else \ 83 if pv >= 393 else \ - 64 # Wither Boss + 64 # Wither Boss # Issue with Wither Skeletons in PrismarineJS? # Not present in some protocol versions so # only 99% certain this enum is 100% accurate. @@ -130,8 +129,8 @@ class EntityType(Enum): if pv >= 447: TRADER_LLAMA = 75 if pv >= 393: - COD = 9 if pv >= 447 else \ - 8 + COD = 9 if pv >= 447 else \ + 8 DOLPHIN = 13 if pv >= 447 else \ 12 DROWNED = 15 if pv >= 447 else \ @@ -149,7 +148,7 @@ class EntityType(Enum): if pv >= 335: ILLUSIONER = 33 if pv >= 447 else \ 31 if pv >= 393 else \ - 37 # Illusion Illager + 37 # Illusion Illager PARROT = 53 if pv >= 447 else \ 50 if pv >= 393 else \ 105 @@ -189,7 +188,7 @@ class EntityType(Enum): 35 VINDICATOR = 86 if pv >= 447 else \ 81 if pv >= 393 else \ - 36 # Vindication Illager + 36 # Vindication Illager ZOMBIE_HORSE = 95 if pv >= 447 else \ 88 if pv >= 393 else \ 29 @@ -306,4 +305,3 @@ def type(self, type_name): @type.deleter def type(self): del self.type_id - diff --git a/tox.ini b/tox.ini index c1ffb046..46e321fa 100644 --- a/tox.ini +++ b/tox.ini @@ -56,6 +56,7 @@ deps = [flake8] per-file-ignores = */clientbound/play/spawn_object_packet.py:E221,E222,E271,E272 + */clientbound/play/spawn_mob_packet.py:E127,E128,E221,E222,E271,E272 [testenv:pylint-errors] basepython = python3.6 From 45afa54f39e2b3825b2dec45ef83b32bf5d2fb6b Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Fri, 30 Aug 2019 17:43:14 +0200 Subject: [PATCH 08/13] Add missed serverbound.play.__init__.py file --- .../networking/packets/serverbound/play/__init__.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/minecraft/networking/packets/serverbound/play/__init__.py b/minecraft/networking/packets/serverbound/play/__init__.py index 118ab7e1..e52d1ce0 100644 --- a/minecraft/networking/packets/serverbound/play/__init__.py +++ b/minecraft/networking/packets/serverbound/play/__init__.py @@ -3,9 +3,9 @@ ) from minecraft.networking.types import ( - Double, Float, Boolean, VarInt, String, Byte, UnsignedByte, - Position, Enum, RelativeHand, BlockFace, ClickType, Vector, - Direction, PositionAndLook, multi_attribute_alias + Double, Float, Boolean, VarInt, String, Byte, Position, Enum, + RelativeHand, BlockFace, ClickType, Vector, Direction, + PositionAndLook, multi_attribute_alias ) from .client_settings_packet import ClientSettingsPacket @@ -278,7 +278,7 @@ def read(self, file_object): self.target = VarInt.read(file_object) self.type = VarInt.read(file_object) if self.type is ClickType.INTERACT_AT: - self.target_x =Float.read(file_object) + self.target_x = Float.read(file_object) self.target_y = Float.read(file_object) self.target_z = Float.read(file_object) if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: @@ -309,7 +309,7 @@ def get_id(context): 0x0F if context.protocol_version >= 343 else \ 0x10 if context.protocol_version >= 336 else \ 0x11 if context.protocol_version >= 318 else \ - 0x10 # Note: Packet added in protocol version 94 + 0x10 # Note: Packet added in protocol version 94 packet_name = "vehicle move serverbound" definition = [ From bfcfa6497db7fcb4cb6d6436af1ad4059795af2d Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Sat, 31 Aug 2019 08:37:40 +0200 Subject: [PATCH 09/13] Add SpawnMobPacket tests, add new multiattributes and fix bugs --- .../play/destroy_entities_packet.py | 6 +- .../clientbound/play/spawn_mob_packet.py | 43 ++++----- .../packets/serverbound/play/__init__.py | 46 +-------- .../serverbound/play/use_entity_packet.py | 50 ++++++++++ minecraft/networking/types/utility.py | 25 ++++- tests/test_packets.py | 96 ++++++++++++++++++- tests/test_utility_types.py | 26 ++++- 7 files changed, 217 insertions(+), 75 deletions(-) create mode 100644 minecraft/networking/packets/serverbound/play/use_entity_packet.py diff --git a/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py index 7d9b2bfe..6536f8b3 100644 --- a/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py +++ b/minecraft/networking/packets/clientbound/play/destroy_entities_packet.py @@ -18,11 +18,15 @@ def get_id(context): 0x30 if context.protocol_version >= 70 else \ 0x13 + packet_name = 'destroy entities' + + fields = 'entity_ids', + def read(self, file_object): self.entity_ids = [VarInt.read(file_object) for i in range(VarInt.read(file_object))] - def write(self, packet_buffer): + def write_fields(self, packet_buffer): count = len(self.entity_ids) VarInt.send(count, packet_buffer) for entity_id in self.entity_ids: diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py index f4441c04..017f0888 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -1,8 +1,8 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, UUID, Double, Integer, Angle, Short, UnsignedByte, - Enum, Vector, Direction, PositionAndLook, attribute_alias, + VarInt, UUID, Double, Integer, Angle, Short, UnsignedByte, Enum, Vector, + Direction, PositionAndLook, LookAndDirection, PositionLookAndDirection, multi_attribute_alias ) @@ -226,15 +226,16 @@ def read(self, file_object): # TODO: read entity metadata def write_fields(self, packet_buffer): - VarInt.read(self.entity_id, packet_buffer) + VarInt.send(self.entity_id, packet_buffer) if self.context.protocol_version >= 69: - UUID.send(self.entity_id, packet_buffer) + UUID.send(self.entity_uuid, packet_buffer) if self.context.protocol_version >= 301: VarInt.send(self.type_id, packet_buffer) else: UnsignedByte.send(self.type_id, packet_buffer) + # pylint: disable=no-member xyz_type = Double if self.context.protocol_version >= 97 else Integer for coord in self.x, self.y, self.z: xyz_type.send(coord, packet_buffer) @@ -261,6 +262,7 @@ def type(self, type_name): if self.context is None: raise ValueError('This packet must have a non-None "context" ' 'in order to set the "type" property.') + # pylint: disable=no-member self.type_id = getattr(self.EntityType, type_name) @type.deleter @@ -273,35 +275,22 @@ def type(self): # Access the 'yaw', 'pitch' fields as a Direction. look = multi_attribute_alias(Direction, 'yaw', 'pitch') + # Access the 'yaw', 'pitch' 'head_pitch' fields as a LookAndDirection. + look_and_direction = multi_attribute_alias(LookAndDirection, + 'yaw', 'pitch', 'head_pitch') + # Access the 'x', 'y', 'z', 'pitch', 'yaw' fields 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. position_and_look = multi_attribute_alias( PositionAndLook, x='x', y='y', z='z', yaw='yaw', pitch='pitch') + # Access the 'x', 'y', 'z', 'pitch', 'yaw', 'headpitch' fields as a + # PositionLookAndDirection + position_look_and_direction = multi_attribute_alias( + PositionLookAndDirection, x='x', y='y', z='z', yaw='yaw', + pitch='pitch', head_pitch='head_pitch') + # Access the 'velocity_{x,y,z}' fields as a Vector. velocity = multi_attribute_alias( Vector, 'velocity_x', 'velocity_y', 'velocity_z') - - # This alias is retained for backward compatibility. - objectUUID = attribute_alias('object_uuid') - - # Access the entity type as a string, according to the EntityType enum. - @property - def type(self): - if self.context is None: - raise ValueError('This packet must have a non-None "context" ' - 'in order to read the "type" property.') - # pylint: disable=no-member - return self.EntityType.name_from_value(self.type_id) - - @type.setter - def type(self, type_name): - if self.context is None: - raise ValueError('This packet must have a non-None "context" ' - 'in order to set the "type" property.') - self.type_id = getattr(self.EntityType, type_name) - - @type.deleter - def type(self): - del self.type_id diff --git a/minecraft/networking/packets/serverbound/play/__init__.py b/minecraft/networking/packets/serverbound/play/__init__.py index e52d1ce0..dc93ec29 100644 --- a/minecraft/networking/packets/serverbound/play/__init__.py +++ b/minecraft/networking/packets/serverbound/play/__init__.py @@ -4,11 +4,12 @@ from minecraft.networking.types import ( Double, Float, Boolean, VarInt, String, Byte, Position, Enum, - RelativeHand, BlockFace, ClickType, Vector, Direction, - PositionAndLook, multi_attribute_alias + RelativeHand, BlockFace, Vector, Direction, PositionAndLook, + multi_attribute_alias ) from .client_settings_packet import ClientSettingsPacket +from .use_entity_packet import UseEntityPacket # Formerly known as state_playing_serverbound. @@ -258,47 +259,6 @@ def get_id(context): Hand = RelativeHand -class UseEntityPacket(Packet): - @staticmethod - def get_id(context): - return 0x0E if context.protocol_version >= 464 else \ - 0x0D if context.protocol_version >= 389 else \ - 0x0B if context.protocol_version >= 386 else \ - 0x0A if context.protocol_version >= 345 else \ - 0x09 if context.protocol_version >= 343 else \ - 0x0A if context.protocol_version >= 336 else \ - 0x0B if context.protocol_version >= 318 else \ - 0x0A if context.protocol_version >= 94 else \ - 0x09 if context.protocol_version >= 70 else \ - 0x02 - - packet_name = "use entity" - - def read(self, file_object): - self.target = VarInt.read(file_object) - self.type = VarInt.read(file_object) - if self.type is ClickType.INTERACT_AT: - self.target_x = Float.read(file_object) - self.target_y = Float.read(file_object) - self.target_z = Float.read(file_object) - if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: - self.hand = VarInt.read(file_object) - - def write_fields(self, packet_buffer): - VarInt.send(self.target, packet_buffer) - VarInt.send(self.type, packet_buffer) - if self.type is ClickType.INTERACT_AT: - Float.send(self.target_x, packet_buffer) - Float.send(self.target_y, packet_buffer) - Float.send(self.target_z, packet_buffer) - if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: - VarInt.send(self.hand) - - ClickType = ClickType - - Hand = RelativeHand - - class VehicleMovePacket(Packet): @staticmethod def get_id(context): diff --git a/minecraft/networking/packets/serverbound/play/use_entity_packet.py b/minecraft/networking/packets/serverbound/play/use_entity_packet.py new file mode 100644 index 00000000..5e86df89 --- /dev/null +++ b/minecraft/networking/packets/serverbound/play/use_entity_packet.py @@ -0,0 +1,50 @@ +from minecraft.networking.packets import Packet +from minecraft.networking.types import ( + VarInt, Float, RelativeHand, ClickType +) + + +class UseEntityPacket(Packet): + @staticmethod + def get_id(context): + return 0x0E if context.protocol_version >= 464 else \ + 0x0D if context.protocol_version >= 389 else \ + 0x0B if context.protocol_version >= 386 else \ + 0x0A if context.protocol_version >= 345 else \ + 0x09 if context.protocol_version >= 343 else \ + 0x0A if context.protocol_version >= 336 else \ + 0x0B if context.protocol_version >= 318 else \ + 0x0A if context.protocol_version >= 94 else \ + 0x09 if context.protocol_version >= 70 else \ + 0x02 + + packet_name = "use entity" + + fields = 'target', 'type', 'target_x', 'target_y', 'target_z', 'hand' + + def read(self, file_object): + self.target = VarInt.read(file_object) + self.type = VarInt.read(file_object) + + if self.type is ClickType.INTERACT_AT: + for attr in 'target_x', 'target_y', 'target_z': + setattr(self, attr, Float.read(file_object)) + + if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + self.hand = VarInt.read(file_object) + + def write_fields(self, packet_buffer): + # pylint: disable=no-member + VarInt.send(self.target, packet_buffer) + VarInt.send(self.type, packet_buffer) + + if self.type is ClickType.INTERACT_AT: + for attr in self.target_x, self.target_y, self.target_z: + Float.send(attr, packet_buffer) + + if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + VarInt.send(self.hand, packet_buffer) + + ClickType = ClickType + + Hand = RelativeHand diff --git a/minecraft/networking/types/utility.py b/minecraft/networking/types/utility.py index 29164379..4d536160 100644 --- a/minecraft/networking/types/utility.py +++ b/minecraft/networking/types/utility.py @@ -8,7 +8,8 @@ __all__ = ( - 'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor', + 'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', + 'LookAndDirection', 'PositionLookAndDirection', 'descriptor', 'attribute_alias', 'multi_attribute_alias', ) @@ -196,10 +197,30 @@ def __delete__(self, instance): class PositionAndLook(MutableRecord): """A mutable record containing 3 spatial position coordinates - and 2 rotational coordinates for a look direction. + and 2 rotational components for a look direction. """ __slots__ = 'x', 'y', 'z', 'yaw', 'pitch' position = multi_attribute_alias(Vector, 'x', 'y', 'z') look = multi_attribute_alias(Direction, 'yaw', 'pitch') + + +LookAndDirection = namedtuple('LookAndDirection', + ('yaw', 'pitch', 'head_pitch')) + + +class PositionLookAndDirection(MutableRecord): + """ + A mutable record containing 3 spation position coordinates, + 2 rotational components and an additional pitch component for + the head of the object. + """ + __slots__ = 'x', 'y', 'z', 'yaw', 'pitch', 'head_pitch' + + position = multi_attribute_alias(Vector, 'x', 'y', 'z') + + look = multi_attribute_alias(Direction, 'yaw', 'pitch') + + look_and_direction = multi_attribute_alias(LookAndDirection, + 'yaw', 'pitch', 'head_pitch') diff --git a/tests/test_packets.py b/tests/test_packets.py index a0044887..6d56388c 100644 --- a/tests/test_packets.py +++ b/tests/test_packets.py @@ -9,7 +9,8 @@ from minecraft import SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS from minecraft.networking.connection import ConnectionContext from minecraft.networking.types import ( - VarInt, Enum, Vector, PositionAndLook, OriginPoint, + VarInt, Enum, Vector, PositionAndLook, PositionLookAndDirection, + LookAndDirection, OriginPoint, ) from minecraft.networking.packets import ( Packet, PacketBuffer, PacketListener, KeepAlivePacket, serverbound, @@ -226,6 +227,99 @@ def test_multi_block_change_packet(self): self._test_read_write_packet(packet) + def test_spawn_mob_packet(self): + for protocol_version in TEST_VERSIONS: + logging.debug('protocol_version = %r' % protocol_version) + context = ConnectionContext(protocol_version=protocol_version) + + EntityType = clientbound.play.SpawnMobPacket.field_enum( + 'type_id', context) + + pos_look_dir = PositionLookAndDirection( + position=(Vector(48.35, 82.0, 11.28) if protocol_version >= 97 + else Vector(48, 82, 11)), + look_and_direction=(LookAndDirection( + yaw=87.9, pitch=90, head_pitch=19.07))) + + velocity = Vector(100, 98, 2) + entity_id, type_name, type_id = 573, 'CREEPER', EntityType.CREEPER + + packet = clientbound.play.SpawnMobPacket( + context=context, + x=pos_look_dir.x, y=pos_look_dir.y, z=pos_look_dir.z, + yaw=pos_look_dir.yaw, pitch=pos_look_dir.pitch, + head_pitch=pos_look_dir.head_pitch, + velocity_x=velocity.x, velocity_y=velocity.y, + velocity_z=velocity.z, + entity_id=entity_id, type_id=type_id) + + if protocol_version >= 69: + entity_uuid = 'd9568851-85bc-4a10-8d6a-261d130626fa' + packet.entity_uuid = entity_uuid + self.assertEqual(packet.entity_uuid, entity_uuid) + self.assertEqual(packet.position_look_and_direction, pos_look_dir) + self.assertEqual(packet.position, pos_look_dir.position) + self.assertEqual(packet.look_and_direction, + pos_look_dir.look_and_direction) + self.assertEqual(packet.look, pos_look_dir.look) + self.assertEqual(packet.velocity, velocity) + self.assertEqual(packet.field_enum( + 'type_id', context).name_from_value(type_id), type_name) + + self.assertEqual( + str(packet), + "0x%02X SpawnMobPacket(entity_id=573, " + "entity_uuid='d9568851-85bc-4a10-8d6a-261d130626fa', " + "type_id=CREEPER, x=48.35, y=82.0, z=11.28, " + "pitch=90, yaw=87.9, head_pitch=19.07, " + "velocity_x=100, velocity_y=98, velocity_z=2)" + % packet.id if protocol_version >= 97 else + "0x%02X SpawnMobPacket(entity_id=573, " + "entity_uuid='d9568851-85bc-4a10-8d6a-261d130626fa', " + "type_id=CREEPER, x=48, y=82, z=11, " + "pitch=90, yaw=87.9, head_pitch=19.07, " + "velocity_x=100, velocity_y=98, velocity_z=2)" + % packet.id if protocol_version >= 69 else + "0x%02X SpawnMobPacket(entity_id=573, " + "type_id=CREEPER, x=48, y=82, z=11, " + "pitch=90, yaw=87.9, head_pitch=19.07, " + "velocity_x=100, velocity_y=98, velocity_z=2)" % packet.id) + + # Assert no repeating values / names for each protocol_version + # in EntityType Enum + names = [] + values = [] + for name, name_value in packet.field_enum( + 'type_id', context).__dict__.items(): + if name.isupper(): + names.append(name) + values.append(name_value) + + # Remove duplicates + no_repeats_names = list(dict.fromkeys(names)) + no_repeats_values = list(dict.fromkeys(values)) + self.assertEqual(names, no_repeats_names) + self.assertEqual(values, no_repeats_values) + + packet2 = clientbound.play.SpawnMobPacket( + context=context, + position_look_and_direction=pos_look_dir, + velocity=velocity, + entity_id=entity_id, type_id=type_id) + + if protocol_version >= 69: + packet2.entity_uuid = entity_uuid + self.assertEqual(packet.__dict__, packet2.__dict__) + packet2.position = pos_look_dir.position + self.assertEqual(packet2.position, pos_look_dir.position) + + self._test_read_write_packet(packet, context, + yaw=360/256, pitch=360/256, + head_pitch=360/256) + self._test_read_write_packet(packet2, context, + yaw=360/256, pitch=360/256, + head_pitch=360/256) + def test_spawn_object_packet(self): for protocol_version in TEST_VERSIONS: logging.debug('protocol_version = %r' % protocol_version) diff --git a/tests/test_utility_types.py b/tests/test_utility_types.py index 3f8cc357..794353c1 100644 --- a/tests/test_utility_types.py +++ b/tests/test_utility_types.py @@ -1,7 +1,8 @@ import unittest from minecraft.networking.types import ( - Enum, BitFieldEnum, Vector, Position, PositionAndLook + Enum, BitFieldEnum, Vector, Position, PositionAndLook, + PositionLookAndDirection ) @@ -73,3 +74,26 @@ def test_properties(self): self.assertFalse(pos_look_1 != pos_look_2) pos_look_1.position += Vector(1, 1, 1) self.assertTrue(pos_look_1 != pos_look_2) + + +class PositionLookAndDirectionTest(unittest.TestCase): + """ This also tests the MutableRecord base type. """ + def test_properties(self): + pos_look_1 = PositionLookAndDirection(position=(1, 2, 3), + look_and_direction=(4, 5, 6)) + pos_look_2 = PositionLookAndDirection(x=1, y=2, z=3, + yaw=4, pitch=5,head_pitch=6) + string_repr = ('PositionLookAndDirection(x=1, y=2, z=3, '\ + 'yaw=4, pitch=5, head_pitch=6)') + + self.assertEqual(pos_look_1, pos_look_2) + self.assertEqual(pos_look_1.position, pos_look_1.position) + self.assertEqual(pos_look_1.look, pos_look_2.look) + self.assertEqual(hash(pos_look_1), hash(pos_look_2)) + self.assertEqual(pos_look_1.look_and_direction, + pos_look_2.look_and_direction) + self.assertEqual(str(pos_look_1), string_repr) + + self.assertFalse(pos_look_1 != pos_look_2) + pos_look_1.position += Vector(1, 1, 1) + self.assertTrue(pos_look_1 != pos_look_2) \ No newline at end of file From 4e217a1522cbe271cdee41e0e41b2e3bbbce3f22 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Sat, 31 Aug 2019 09:14:22 +0200 Subject: [PATCH 10/13] Fix spawn_mob_packet test and flake8 --- .../packets/clientbound/play/spawn_mob_packet.py | 2 +- minecraft/networking/types/utility.py | 2 +- tests/test_packets.py | 7 ++++--- tests/test_utility_types.py | 6 +++--- 4 files changed, 9 insertions(+), 8 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py index 017f0888..a78e55b8 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -289,7 +289,7 @@ def type(self): # PositionLookAndDirection position_look_and_direction = multi_attribute_alias( PositionLookAndDirection, x='x', y='y', z='z', yaw='yaw', - pitch='pitch', head_pitch='head_pitch') + pitch='pitch', head_pitch='head_pitch') # Access the 'velocity_{x,y,z}' fields as a Vector. velocity = multi_attribute_alias( diff --git a/minecraft/networking/types/utility.py b/minecraft/networking/types/utility.py index 4d536160..1356ebe3 100644 --- a/minecraft/networking/types/utility.py +++ b/minecraft/networking/types/utility.py @@ -223,4 +223,4 @@ class PositionLookAndDirection(MutableRecord): look = multi_attribute_alias(Direction, 'yaw', 'pitch') look_and_direction = multi_attribute_alias(LookAndDirection, - 'yaw', 'pitch', 'head_pitch') + 'yaw', 'pitch', 'head_pitch') diff --git a/tests/test_packets.py b/tests/test_packets.py index 6d56388c..cd857482 100644 --- a/tests/test_packets.py +++ b/tests/test_packets.py @@ -5,6 +5,7 @@ import struct from zlib import decompress from random import choice +from collections import OrderedDict from minecraft import SUPPORTED_PROTOCOL_VERSIONS, RELEASE_PROTOCOL_VERSIONS from minecraft.networking.connection import ConnectionContext @@ -290,14 +291,14 @@ def test_spawn_mob_packet(self): names = [] values = [] for name, name_value in packet.field_enum( - 'type_id', context).__dict__.items(): + 'type_id', context).__dict__.items(): if name.isupper(): names.append(name) values.append(name_value) # Remove duplicates - no_repeats_names = list(dict.fromkeys(names)) - no_repeats_values = list(dict.fromkeys(values)) + no_repeats_names = list(OrderedDict.fromkeys(names)) + no_repeats_values = list(OrderedDict.fromkeys(values)) self.assertEqual(names, no_repeats_names) self.assertEqual(values, no_repeats_values) diff --git a/tests/test_utility_types.py b/tests/test_utility_types.py index 794353c1..e2dd2b07 100644 --- a/tests/test_utility_types.py +++ b/tests/test_utility_types.py @@ -82,8 +82,8 @@ def test_properties(self): pos_look_1 = PositionLookAndDirection(position=(1, 2, 3), look_and_direction=(4, 5, 6)) pos_look_2 = PositionLookAndDirection(x=1, y=2, z=3, - yaw=4, pitch=5,head_pitch=6) - string_repr = ('PositionLookAndDirection(x=1, y=2, z=3, '\ + yaw=4, pitch=5, head_pitch=6) + string_repr = ('PositionLookAndDirection(x=1, y=2, z=3, ' 'yaw=4, pitch=5, head_pitch=6)') self.assertEqual(pos_look_1, pos_look_2) @@ -96,4 +96,4 @@ def test_properties(self): self.assertFalse(pos_look_1 != pos_look_2) pos_look_1.position += Vector(1, 1, 1) - self.assertTrue(pos_look_1 != pos_look_2) \ No newline at end of file + self.assertTrue(pos_look_1 != pos_look_2) From 283b39e7bc6d39862620a1497dd69b788cd6721d Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Sat, 31 Aug 2019 16:00:30 +0200 Subject: [PATCH 11/13] Implement Clientbound EntityHeadLookPacket --- .../packets/clientbound/play/__init__.py | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index bc4043fb..cb83a88c 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -49,6 +49,7 @@ def get_packets(context): DestroyEntitiesPacket, SpawnMobPacket, BlockActionPacket, + EntityHeadLookPacket, } if context.protocol_version <= 47: packets |= { @@ -396,3 +397,25 @@ def get_id(context): {'action_param': UnsignedByte}, # action_param fields. {'block_type': VarInt} if context.protocol_version != 347 else {}, ]) + + +class EntityHeadLookPacket(Packet): + @staticmethod + def get_id(context): + return 0x3B if context.protocol_version >= 471 else \ + 0x39 if context.protocol_version >= 461 else \ + 0x3A if context.protocol_version >= 451 else \ + 0x39 if context.protocol_version >= 389 else \ + 0x38 if context.protocol_version >= 352 else \ + 0x37 if context.protocol_version >= 345 else \ + 0x36 if context.protocol_version >= 336 else \ + 0x35 if context.protocol_version >= 332 else \ + 0x36 if context.protocol_version >= 318 else \ + 0x34 if context.protocol_version >= 70 else \ + 0x19 + + packet_name = 'entity look' + definition = [ + {'entity_id': VarInt}, + {'head_yaw': Angle}, + ] From 256c61c5dc5b8917a60e9e37523b3aa81e241917 Mon Sep 17 00:00:00 2001 From: Zachy24 Date: Sat, 31 Aug 2019 18:00:56 +0100 Subject: [PATCH 12/13] Write tests for new packets and fix issues it revealed --- .../packets/clientbound/play/__init__.py | 5 +- .../clientbound/play/spawn_mob_packet.py | 20 +++++++- .../serverbound/play/use_entity_packet.py | 24 +++++---- tests/test_packets.py | 49 +++++++++++++++++-- 4 files changed, 83 insertions(+), 15 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/__init__.py b/minecraft/networking/packets/clientbound/play/__init__.py index cb83a88c..547eaccf 100644 --- a/minecraft/networking/packets/clientbound/play/__init__.py +++ b/minecraft/networking/packets/clientbound/play/__init__.py @@ -414,7 +414,10 @@ def get_id(context): 0x34 if context.protocol_version >= 70 else \ 0x19 - packet_name = 'entity look' + packet_name = 'entity head look' + + fields = 'entity_id', 'head_yaw' + definition = [ {'entity_id': VarInt}, {'head_yaw': Angle}, diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py index a78e55b8..0beaa36d 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -3,7 +3,7 @@ from minecraft.networking.types import ( VarInt, UUID, Double, Integer, Angle, Short, UnsignedByte, Enum, Vector, Direction, PositionAndLook, LookAndDirection, PositionLookAndDirection, - multi_attribute_alias + multi_attribute_alias, descriptor ) @@ -18,6 +18,24 @@ def get_id(context): fields = ('entity_id', 'entity_uuid', 'type_id', 'x', 'y', 'z', 'pitch', 'yaw', 'head_pitch', 'velocity_x', 'velocity_y', 'velocity_z') + @descriptor + def EntityType(desc, self, cls): # pylint: disable=no-self-argument + if self is None: + # EntityType is being accessed as a class attribute. + raise AttributeError( + '"SpawnObjectPacket.EntityType" cannot be accessed as a ' + 'class attribute, because it depends on the protocol version. ' + 'There are two ways to access the correct version of the ' + 'class:\n\n' + '1. Access the "EntityType" attribute of a ' + '"SpawnObjectPacket" instance with its "context" property ' + 'set.\n\n' + '2. Call "SpawnObjectPacket.field_enum(\'type_id\', ' + 'context)".') + else: + # EntityType is being accessed as an instance attribute. + return self.field_enum('type_id', self.context) + @classmethod def field_enum(cls, field, context): if field != 'type_id' or context is None: diff --git a/minecraft/networking/packets/serverbound/play/use_entity_packet.py b/minecraft/networking/packets/serverbound/play/use_entity_packet.py index 5e86df89..6566159e 100644 --- a/minecraft/networking/packets/serverbound/play/use_entity_packet.py +++ b/minecraft/networking/packets/serverbound/play/use_entity_packet.py @@ -1,6 +1,6 @@ from minecraft.networking.packets import Packet from minecraft.networking.types import ( - VarInt, Float, RelativeHand, ClickType + VarInt, Float, RelativeHand, ClickType, multi_attribute_alias, Vector ) @@ -20,31 +20,35 @@ def get_id(context): packet_name = "use entity" - fields = 'target', 'type', 'target_x', 'target_y', 'target_z', 'hand' + fields = ('entity_id', 'click_type', 'target_x', 'target_y', 'target_z', + 'hand') def read(self, file_object): - self.target = VarInt.read(file_object) - self.type = VarInt.read(file_object) + self.entity_id = VarInt.read(file_object) + self.click_type = VarInt.read(file_object) - if self.type is ClickType.INTERACT_AT: + if self.click_type is ClickType.INTERACT_AT: for attr in 'target_x', 'target_y', 'target_z': setattr(self, attr, Float.read(file_object)) - if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + if self.click_type in [ClickType.INTERACT_AT, ClickType.INTERACT]: self.hand = VarInt.read(file_object) def write_fields(self, packet_buffer): # pylint: disable=no-member - VarInt.send(self.target, packet_buffer) - VarInt.send(self.type, packet_buffer) + VarInt.send(self.entity_id, packet_buffer) + VarInt.send(self.click_type, packet_buffer) - if self.type is ClickType.INTERACT_AT: + if self.click_type is ClickType.INTERACT_AT: for attr in self.target_x, self.target_y, self.target_z: Float.send(attr, packet_buffer) - if self.type in [ClickType.INTERACT_AT, ClickType.INTERACT]: + if self.click_type in [ClickType.INTERACT_AT, ClickType.INTERACT]: VarInt.send(self.hand, packet_buffer) ClickType = ClickType Hand = RelativeHand + + # Access the 'target_{x,y,z}' fields as a Vector. + target = multi_attribute_alias(Vector, 'target_x', 'target_y', 'target_z') diff --git a/tests/test_packets.py b/tests/test_packets.py index cd857482..cb4765ce 100644 --- a/tests/test_packets.py +++ b/tests/test_packets.py @@ -11,7 +11,7 @@ from minecraft.networking.connection import ConnectionContext from minecraft.networking.types import ( VarInt, Enum, Vector, PositionAndLook, PositionLookAndDirection, - LookAndDirection, OriginPoint, + LookAndDirection, OriginPoint, ClickType, RelativeHand ) from minecraft.networking.packets import ( Packet, PacketBuffer, PacketListener, KeepAlivePacket, serverbound, @@ -158,6 +158,50 @@ class Beta(Enum): class TestReadWritePackets(unittest.TestCase): maxDiff = None + def test_entity_head_look_packet(self): + for protocol_version in TEST_VERSIONS: + logging.debug('protocol_version = %r' % protocol_version) + context = ConnectionContext(protocol_version=protocol_version) + packet = clientbound.play.EntityHeadLookPacket( + entity_id=897, head_yaw=93.87) + self.assertEqual( + str(packet), + 'EntityHeadLookPacket(entity_id=897, head_yaw=93.87)' + ) + self._test_read_write_packet(packet, context, head_yaw=360/256) + + def test_destroy_entities_packet(self): + for protocol_version in TEST_VERSIONS: + logging.debug('protocol_version = %r' % protocol_version) + context = ConnectionContext(protocol_version=protocol_version) + packet = clientbound.play.DestroyEntitiesPacket( + entity_ids=[593, 388, 1856]) + self.assertEqual( + str(packet), + 'DestroyEntitiesPacket(entity_ids=[593, 388, 1856])' + ) + self._test_read_write_packet(packet, context) + + def test_use_entity_packet(self): + for protocol_version in TEST_VERSIONS: + logging.debug('protocol_version = %r' % protocol_version) + context = ConnectionContext(protocol_version=protocol_version) + packet = serverbound.play.UseEntityPacket(context) + packet.entity_id = 495 + packet.click_type = ClickType.INTERACT_AT + packet.target = 51.0, 2.0, 50.0 + packet.hand = RelativeHand.MAIN + + self.assertEqual( + str(packet), + '0x%02X UseEntityPacket(entity_id=495, ' + 'click_type=INTERACT_AT, ' + 'target_x=51.0, target_y=2.0, target_z=50.0, hand=MAIN)' % + packet.id + ) + + self._test_read_write_packet(packet, context) + def test_explosion_packet(self): Record = clientbound.play.ExplosionPacket.Record packet = clientbound.play.ExplosionPacket( @@ -264,8 +308,7 @@ def test_spawn_mob_packet(self): pos_look_dir.look_and_direction) self.assertEqual(packet.look, pos_look_dir.look) self.assertEqual(packet.velocity, velocity) - self.assertEqual(packet.field_enum( - 'type_id', context).name_from_value(type_id), type_name) + self.assertEqual(packet.type, type_name) self.assertEqual( str(packet), From 6a7c6653849a7ebbc6717621638e49b8a325b79c Mon Sep 17 00:00:00 2001 From: Zachy Date: Mon, 2 Sep 2019 19:17:08 +0100 Subject: [PATCH 13/13] Correct spelling typo's. --- .../packets/clientbound/play/spawn_mob_packet.py | 8 ++++---- minecraft/networking/types/utility.py | 4 ++-- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py index 0beaa36d..660dac2a 100644 --- a/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py +++ b/minecraft/networking/packets/clientbound/play/spawn_mob_packet.py @@ -23,14 +23,14 @@ def EntityType(desc, self, cls): # pylint: disable=no-self-argument if self is None: # EntityType is being accessed as a class attribute. raise AttributeError( - '"SpawnObjectPacket.EntityType" cannot be accessed as a ' + '"SpawnMobPacket.EntityType" cannot be accessed as a ' 'class attribute, because it depends on the protocol version. ' 'There are two ways to access the correct version of the ' 'class:\n\n' '1. Access the "EntityType" attribute of a ' - '"SpawnObjectPacket" instance with its "context" property ' + '"SpawnMobPacket" instance with its "context" property ' 'set.\n\n' - '2. Call "SpawnObjectPacket.field_enum(\'type_id\', ' + '2. Call "SpawnMobPacket.field_enum(\'type_id\', ' 'context)".') else: # EntityType is being accessed as an instance attribute. @@ -303,7 +303,7 @@ def type(self): position_and_look = multi_attribute_alias( PositionAndLook, x='x', y='y', z='z', yaw='yaw', pitch='pitch') - # Access the 'x', 'y', 'z', 'pitch', 'yaw', 'headpitch' fields as a + # Access the 'x', 'y', 'z', 'pitch', 'yaw', 'head_pitch' fields as a # PositionLookAndDirection position_look_and_direction = multi_attribute_alias( PositionLookAndDirection, x='x', y='y', z='z', yaw='yaw', diff --git a/minecraft/networking/types/utility.py b/minecraft/networking/types/utility.py index 1356ebe3..a4a6b96a 100644 --- a/minecraft/networking/types/utility.py +++ b/minecraft/networking/types/utility.py @@ -212,8 +212,8 @@ class PositionAndLook(MutableRecord): class PositionLookAndDirection(MutableRecord): """ - A mutable record containing 3 spation position coordinates, - 2 rotational components and an additional pitch component for + A mutable record containing 3 spatial position coordinates, + 2 rotational components and an additional rotational component for the head of the object. """ __slots__ = 'x', 'y', 'z', 'yaw', 'pitch', 'head_pitch'