Skip to content

Commit

Permalink
Add support for Minecraft 18w43a to 1.14 (protocols 441 to 477):
Browse files Browse the repository at this point in the history
This commit introduces a change to `SpawnObjectPacket' which may break existing
code: because the nested enum class `SpawnObjectPacket.EntityType' (which gives
the mapping between integer values of the `type_id' property and their string
representations) has different members depending on the protocol version, it is
now necessary to provide a `ConnectionContext' instance to obtain the correct
version of this enum class. See the text of the `AttributeError' raised from
`clientbound.play.SpawnObjectPacket.EntityType' for further instructions.
  • Loading branch information
joodicator committed May 9, 2019
1 parent 9b43d6f commit 24004c2
Show file tree
Hide file tree
Showing 18 changed files with 288 additions and 70 deletions.
38 changes: 38 additions & 0 deletions minecraft/__init__.py
Expand Up @@ -132,6 +132,44 @@
'1.13.2-pre1': 402,
'1.13.2-pre2': 403,
'1.13.2': 404,
'18w43a': 441,
'18w43b': 441,
'18w43c': 442,
'18w44a': 443,
'18w45a': 444,
'18w46a': 445,
'18w47a': 446,
'18w47b': 447,
'18w48a': 448,
'18w48b': 449,
'18w49a': 450,
'18w50a': 451,
'19w02a': 452,
'19w03a': 453,
'19w03b': 454,
'19w03c': 455,
'19w04a': 456,
'19w04b': 457,
'19w05a': 458,
'19w06a': 459,
'19w07a': 460,
'19w08a': 461,
'19w08b': 462,
'19w09a': 463,
'19w11a': 464,
'19w11b': 465,
'19w12a': 466,
'19w12b': 467,
'19w13a': 468,
'19w13b': 469,
'19w14a': 470,
'19w14b': 471,
'1.14-pre1': 472,
'1.14-pre2': 473,
'1.14-pre3': 474,
'1.14-pre4': 475,
'1.14-pre5': 476,
'1.14': 477,
}

SUPPORTED_PROTOCOL_VERSIONS = \
Expand Down
28 changes: 21 additions & 7 deletions minecraft/networking/packets/clientbound/play/__init__.py
Expand Up @@ -47,7 +47,8 @@ def get_packets(context):
class KeepAlivePacket(AbstractKeepAlivePacket):
@staticmethod
def get_id(context):
return 0x21 if context.protocol_version >= 389 else \
return 0x20 if context.protocol_version >= 471 else \
0x21 if context.protocol_version >= 389 else \
0x20 if context.protocol_version >= 345 else \
0x1F if context.protocol_version >= 332 else \
0x20 if context.protocol_version >= 318 else \
Expand All @@ -70,9 +71,10 @@ def get_id(context):
{'entity_id': Integer},
{'game_mode': UnsignedByte},
{'dimension': Integer if context.protocol_version >= 108 else Byte},
{'difficulty': UnsignedByte},
{'difficulty': UnsignedByte} if context.protocol_version < 464 else {},
{'max_players': UnsignedByte},
{'level_type': String},
{'render_distance': VarInt} if context.protocol_version >= 468 else {},
{'reduced_debug_info': Boolean}])


Expand All @@ -99,7 +101,8 @@ class Position(Enum):
class DisconnectPacket(Packet):
@staticmethod
def get_id(context):
return 0x1B if context.protocol_version >= 345 else \
return 0x1A if context.protocol_version >= 471 else \
0x1B if context.protocol_version >= 345 else \
0x1A if context.protocol_version >= 332 else \
0x1B if context.protocol_version >= 318 else \
0x1A if context.protocol_version >= 107 else \
Expand Down Expand Up @@ -145,7 +148,10 @@ def get_id(context):
class EntityVelocityPacket(Packet):
@staticmethod
def get_id(context):
return 0x41 if context.protocol_version >= 389 else \
return 0x45 if context.protocol_version >= 471 else \
0x41 if context.protocol_version >= 461 else \
0x42 if context.protocol_version >= 451 else \
0x41 if context.protocol_version >= 389 else \
0x40 if context.protocol_version >= 352 else \
0x3F if context.protocol_version >= 345 else \
0x3E if context.protocol_version >= 336 else \
Expand All @@ -167,7 +173,10 @@ def get_id(context):
class UpdateHealthPacket(Packet):
@staticmethod
def get_id(context):
return 0x44 if context.protocol_version >= 389 else \
return 0x48 if context.protocol_version >= 471 else \
0x44 if context.protocol_version >= 461 else \
0x45 if context.protocol_version >= 451 else \
0x44 if context.protocol_version >= 389 else \
0x43 if context.protocol_version >= 352 else \
0x42 if context.protocol_version >= 345 else \
0x41 if context.protocol_version >= 336 else \
Expand All @@ -188,7 +197,8 @@ def get_id(context):
class PluginMessagePacket(AbstractPluginMessagePacket):
@staticmethod
def get_id(context):
return 0x19 if context.protocol_version >= 345 else \
return 0x18 if context.protocol_version >= 471 else \
0x19 if context.protocol_version >= 345 else \
0x18 if context.protocol_version >= 332 else \
0x19 if context.protocol_version >= 318 else \
0x18 if context.protocol_version >= 70 else \
Expand All @@ -198,7 +208,11 @@ def get_id(context):
class PlayerListHeaderAndFooterPacket(Packet):
@staticmethod
def get_id(context):
return 0x4E if context.protocol_version >= 393 else \
return 0x53 if context.protocol_version >= 471 else \
0x5F if context.protocol_version >= 461 else \
0x50 if context.protocol_version >= 451 else \
0x4F if context.protocol_version >= 441 else \
0x4E if context.protocol_version >= 393 else \
0x4A if context.protocol_version >= 338 else \
0x49 if context.protocol_version >= 335 else \
0x47 if context.protocol_version >= 110 else \
Expand Down
Expand Up @@ -8,7 +8,9 @@
class CombatEventPacket(Packet):
@staticmethod
def get_id(context):
return 0x2F if context.protocol_version >= 389 else \
return 0x32 if context.protocol_version >= 471 else \
0x30 if context.protocol_version >= 451 else \
0x2F if context.protocol_version >= 389 else \
0x2E if context.protocol_version >= 345 else \
0x2D if context.protocol_version >= 336 else \
0x2C if context.protocol_version >= 332 else \
Expand Down
Expand Up @@ -5,7 +5,8 @@
class ExplosionPacket(Packet):
@staticmethod
def get_id(context):
return 0x1E if context.protocol_version >= 389 else \
return 0x1C if context.protocol_version >= 471 else \
0x1E if context.protocol_version >= 389 else \
0x1D if context.protocol_version >= 345 else \
0x1C if context.protocol_version >= 332 else \
0x1D if context.protocol_version >= 318 else \
Expand Down
9 changes: 8 additions & 1 deletion minecraft/networking/packets/clientbound/play/map_packet.py
Expand Up @@ -28,7 +28,7 @@ def __init__(self, type, direction, location, display_name=None):

class Map(MutableRecord):
__slots__ = ('id', 'scale', 'icons', 'pixels', 'width', 'height',
'is_tracking_position')
'is_tracking_position', 'is_locked')

def __init__(self, id=None, scale=None, width=128, height=128):
self.id = id
Expand All @@ -38,6 +38,7 @@ def __init__(self, id=None, scale=None, width=128, height=128):
self.height = height
self.pixels = bytearray(0 for i in range(width*height))
self.is_tracking_position = True
self.is_locked = False

class MapSet(object):
__slots__ = 'maps_by_id'
Expand All @@ -58,6 +59,11 @@ def read(self, file_object):
else:
self.is_tracking_position = True

if self.context.protocol_version >= 452:
self.is_locked = Boolean.read(file_object)
else:
self.is_locked = False

icon_count = VarInt.read(file_object)
self.icons = []
for i in range(icon_count):
Expand Down Expand Up @@ -99,6 +105,7 @@ def apply_to_map(self, map):
z = self.offset[1] + i // self.width
map.pixels[x + map.width * z] = self.pixels[i]
map.is_tracking_position = self.is_tracking_position
map.is_locked = self.is_locked

def apply_to_map_set(self, map_set):
map = map_set.maps_by_id.get(self.map_id)
Expand Down
Expand Up @@ -8,7 +8,9 @@
class PlayerListItemPacket(Packet):
@staticmethod
def get_id(context):
return 0x30 if context.protocol_version >= 389 else \
return 0x33 if context.protocol_version >= 471 else \
0x31 if context.protocol_version >= 451 else \
0x30 if context.protocol_version >= 389 else \
0x2F if context.protocol_version >= 345 else \
0x2E if context.protocol_version >= 336 else \
0x2D if context.protocol_version >= 332 else \
Expand Down
Expand Up @@ -8,7 +8,9 @@
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
@staticmethod
def get_id(context):
return 0x32 if context.protocol_version >= 389 else \
return 0x35 if context.protocol_version >= 471 else \
0x33 if context.protocol_version >= 451 else \
0x32 if context.protocol_version >= 389 else \
0x31 if context.protocol_version >= 352 else \
0x30 if context.protocol_version >= 345 else \
0x2F if context.protocol_version >= 336 else \
Expand All @@ -29,7 +31,7 @@ def get_id(context):
])

field_enum = classmethod(
lambda cls, field: cls if field == 'flags' else None)
lambda cls, field, context: cls if field == 'flags' else None)

FLAG_REL_X = 0x01
FLAG_REL_Y = 0x02
Expand Down
127 changes: 96 additions & 31 deletions minecraft/networking/packets/clientbound/play/spawn_object_packet.py
@@ -1,4 +1,5 @@
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,
Expand All @@ -14,40 +15,89 @@ def get_id(context):

packet_name = 'spawn object'

class EntityType(Enum):
BOAT = 1
ITEM_STACK = 2
AREA_EFFECT_CLOUD = 3
MINECART = 10
ACTIVATED_TNT = 50
ENDERCRYSTAL = 51
ARROW = 60
SNOWBALL = 61
EGG = 62
FIREBALL = 63
FIRECHARGE = 64
ENDERPERL = 65
WITHER_SKULL = 66
SHULKER_BULLET = 67
LLAMA_SPIT = 68
FALLING_OBJECT = 70
ITEM_FRAMES = 71
EYE_OF_ENDER = 72
POTION = 73
EXP_BOTTLE = 75
FIREWORK_ROCKET = 76
LEASH_KNOT = 77
ARMORSTAND = 78
EVOCATION_FANGS = 79
FISHING_HOOK = 90
SPECTRAL_ARROW = 91
DRAGON_FIREBALL = 93
fields = ('entity_id', 'object_uuid', 'type_id',
'x', 'y', 'z', 'pitch', 'yaw')

@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(
'This interface is deprecated:\n\n'
'As of pyCraft\'s support for Minecraft 1.14, the nested '
'class "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:
return

pv = context.protocol_version
name = 'EntityType_%d' % pv
if hasattr(cls, name):
return getattr(cls, name)

class EntityType(Enum):
ACTIVATED_TNT = 50 if pv < 458 else 55 # PrimedTnt
AREA_EFFECT_CLOUD = 3 if pv < 458 else 0
ARMORSTAND = 78 if pv < 458 else 1
ARROW = 60 if pv < 458 else 2
BOAT = 1 if pv < 458 else 5
DRAGON_FIREBALL = 93 if pv < 458 else 13
EGG = 62 if pv < 458 else 74 # ThrownEgg
ENDERCRYSTAL = 51 if pv < 458 else 16
ENDERPEARL = 65 if pv < 458 else 75 # ThrownEnderpearl
EVOCATION_FANGS = 79 if pv < 458 else 20
EXP_BOTTLE = 75 if pv < 458 else 76 # ThrownExpBottle
EYE_OF_ENDER = 72 if pv < 458 else 23 # EyeOfEnderSignal
FALLING_OBJECT = 70 if pv < 458 else 24 # FallingSand
FIREBALL = 63 if pv < 458 else 34 # Fireball (ghast)
FIRECHARGE = 64 if pv < 458 else 65 # SmallFireball (blaze)
FIREWORK_ROCKET = 76 if pv < 458 else 25 # FireworksRocketEntity
FISHING_HOOK = 90 if pv < 458 else 93 # Fishing bobber
ITEM_FRAMES = 71 if pv < 458 else 33 # ItemFrame
ITEM_STACK = 2 if pv < 458 else 32 # Item
LEASH_KNOT = 77 if pv < 458 else 35
LLAMA_SPIT = 68 if pv < 458 else 37
MINECART = 10 if pv < 458 else 39 # MinecartRideable
POTION = 73 if pv < 458 else 77 # ThrownPotion
SHULKER_BULLET = 67 if pv < 458 else 60
SNOWBALL = 61 if pv < 458 else 67
SPECTRAL_ARROW = 91 if pv < 458 else 68
WITHER_SKULL = 66 if pv < 458 else 85
if pv >= 393:
TRIDENT = 94
if pv >= 458:
MINECART_CHEST = 40
MINECART_COMMAND_BLOCK = 41
MINECART_FURNACE = 42
MINECART_HOPPER = 43
MINECART_SPAWNER = 44
MINECART_TNT = 45

setattr(cls, name, EntityType)
return EntityType

def read(self, file_object):
self.entity_id = VarInt.read(file_object)
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 >= 458:
self.type_id = VarInt.read(file_object)
else:
self.type_id = Byte.read(file_object)

xyz_type = Double if self.context.protocol_version >= 100 else Integer
for attr in 'x', 'y', 'z':
Expand All @@ -64,7 +114,11 @@ def write_fields(self, packet_buffer):
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)

if self.context.protocol_version >= 458:
VarInt.send(self.type_id, packet_buffer)
else:
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:
Expand All @@ -78,9 +132,20 @@ def write_fields(self, packet_buffer):
Short.send(coord, packet_buffer)

# 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 = 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):
Expand Down
4 changes: 2 additions & 2 deletions minecraft/networking/packets/packet.py
Expand Up @@ -129,7 +129,7 @@ def field_string(self, field):
"""
value = getattr(self, field, None)

enum_class = self.field_enum(field)
enum_class = self.field_enum(field, self.context)
if enum_class is not None:
name = enum_class.name_from_value(value)
if name is not None:
Expand All @@ -138,7 +138,7 @@ def field_string(self, field):
return repr(value)

@classmethod
def field_enum(cls, field):
def field_enum(cls, field, context=None):
""" The subclass of 'minecraft.networking.types.Enum' associated with
this field, or None if there is no such class.
"""
Expand Down

0 comments on commit 24004c2

Please sign in to comment.