mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-25 11:46:54 +01:00
Improve metadata and auxiliary methods of existing packets.
* Add multi-attribute aliases to some packets, for user convenience. * Add support for writing PlayerListItemPacket. * Add 'fields' attributes to manually-read/written packet classes, implementing 'field_string' where appropriate to allow enable the default __repr__ implementation. * Modify data constructors where appropriate so that __repr__ implementations match their constructor protocols. * Improve comments on type aliases within packet classes. * Add/modify tests to cover the new functionality.
This commit is contained in:
parent
234e57716c
commit
e3d2b1a368
@ -4,7 +4,8 @@ from minecraft.networking.packets import (
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Integer, FixedPointInteger, Angle, UnsignedByte, Byte, Boolean, UUID,
|
||||
Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension, GameMode
|
||||
Short, VarInt, Double, Float, String, Enum, Difficulty, Dimension,
|
||||
GameMode, Vector, Direction, PositionAndLook, multi_attribute_alias,
|
||||
)
|
||||
|
||||
from .combat_event_packet import CombatEventPacket
|
||||
@ -91,13 +92,9 @@ class JoinGamePacket(Packet):
|
||||
{'reduced_debug_info': Boolean},
|
||||
])
|
||||
|
||||
# JoinGamePacket.Difficulty is an alias for Difficulty
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
|
||||
# JoinGamePacket.Gamemode is an alias for Gamemode
|
||||
GameMode = GameMode
|
||||
|
||||
# JoinGamePacket.Dimension is an alias for Dimension
|
||||
Dimension = Dimension
|
||||
|
||||
|
||||
@ -115,7 +112,7 @@ class ServerDifficultyPacket(Packet):
|
||||
{'is_locked': Boolean} if context.protocol_version >= 464 else {},
|
||||
])
|
||||
|
||||
# ServerDifficultyPacket.Difficulty is an alias for Difficulty
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
|
||||
|
||||
@ -181,10 +178,22 @@ class SpawnPlayerPacket(Packet):
|
||||
else {'z': FixedPointInteger},
|
||||
{'yaw': Angle},
|
||||
{'pitch': Angle},
|
||||
{'current_item': Short} if context.protocol_version <= 49 else {},
|
||||
# TODO: read entity metadata
|
||||
{'current_item': Short} if context.protocol_version <= 49 else {}
|
||||
])
|
||||
|
||||
# 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')
|
||||
|
||||
|
||||
class EntityVelocityPacket(Packet):
|
||||
@staticmethod
|
||||
@ -258,13 +267,9 @@ class RespawnPacket(Packet):
|
||||
{'level_type': String},
|
||||
])
|
||||
|
||||
# RespawnPacket.Difficulty is an alias for Difficulty.
|
||||
# These aliases declare the Enum type corresponding to each field:
|
||||
Difficulty = Difficulty
|
||||
|
||||
# RespawnPacket.Dimension is an alias for Dimension.
|
||||
Dimension = Dimension
|
||||
|
||||
# RespawnPacket.Gamemode is an alias for Gamemode.
|
||||
GameMode = GameMode
|
||||
|
||||
|
||||
|
@ -53,6 +53,11 @@ class MultiBlockChangePacket(Packet):
|
||||
|
||||
packet_name = 'multi block change'
|
||||
|
||||
fields = 'chunk_x', 'chunk_z', 'records'
|
||||
|
||||
# Access the 'chunk_x' and 'chunk_z' fields as a tuple.
|
||||
chunk_pos = multi_attribute_alias(tuple, 'chunk_x', 'chunk_z')
|
||||
|
||||
class Record(MutableRecord):
|
||||
__slots__ = 'x', 'y', 'z', 'block_state_id'
|
||||
|
||||
|
@ -22,6 +22,8 @@ class CombatEventPacket(Packet):
|
||||
|
||||
packet_name = 'combat event'
|
||||
|
||||
fields = 'event',
|
||||
|
||||
# The abstract type of the 'event' field of this packet.
|
||||
class EventType(MutableRecord):
|
||||
__slots__ = ()
|
||||
|
@ -18,14 +18,19 @@ class ExplosionPacket(Packet):
|
||||
|
||||
packet_name = 'explosion'
|
||||
|
||||
class Record(Vector):
|
||||
__slots__ = ()
|
||||
fields = 'x', 'y', 'z', 'radius', 'records', \
|
||||
'player_motion_x', 'player_motion_y', 'player_motion_z'
|
||||
|
||||
# Access the 'x', 'y', 'z' fields as a Vector tuple.
|
||||
position = multi_attribute_alias(Vector, 'x', 'y', 'z')
|
||||
|
||||
# Access the 'player_motion_{x,y,z}' fields as a Vector tuple.
|
||||
player_motion = multi_attribute_alias(
|
||||
Vector, 'player_motion_x', 'player_motion_y', 'player_motion_z')
|
||||
|
||||
class Record(Vector):
|
||||
__slots__ = ()
|
||||
|
||||
def read(self, file_object):
|
||||
self.x = Float.read(file_object)
|
||||
self.y = Float.read(file_object)
|
||||
|
@ -15,7 +15,13 @@ class FacePlayerPacket(Packet):
|
||||
|
||||
packet_name = 'face player'
|
||||
|
||||
# Access the 'x', 'y', 'z' fields as a Vector.
|
||||
@property
|
||||
def fields(self):
|
||||
return ('origin', 'x', 'y', 'z', 'entity_id', 'entity_origin') \
|
||||
if self.context.protocol_version >= 353 else \
|
||||
('entity_id', 'x', 'y', 'z')
|
||||
|
||||
# Access the 'x', 'y', 'z' fields as a Vector tuple.
|
||||
target = multi_attribute_alias(Vector, 'x', 'y', 'z')
|
||||
|
||||
def read(self, file_object):
|
||||
|
@ -17,6 +17,20 @@ class MapPacket(Packet):
|
||||
|
||||
packet_name = 'map'
|
||||
|
||||
@property
|
||||
def fields(self):
|
||||
fields = 'id', 'scale', 'icons', 'width', 'height', 'pixels'
|
||||
if self.context.protocol_version >= 107:
|
||||
fields += 'is_tracking_position',
|
||||
if self.context.protocol_version >= 452:
|
||||
fields += 'is_locked',
|
||||
return fields
|
||||
|
||||
def field_string(self, field):
|
||||
if field == 'pixels' and isinstance(self.pixels, bytearray):
|
||||
return 'bytearray(...)'
|
||||
return super(MapPacket, self).field_string(field)
|
||||
|
||||
class MapIcon(MutableRecord):
|
||||
__slots__ = 'type', 'direction', 'location', 'display_name'
|
||||
|
||||
@ -43,11 +57,11 @@ class MapPacket(Packet):
|
||||
class MapSet(object):
|
||||
__slots__ = 'maps_by_id'
|
||||
|
||||
def __init__(self):
|
||||
self.maps_by_id = dict()
|
||||
def __init__(self, *maps):
|
||||
self.maps_by_id = {map.id: map for map in maps}
|
||||
|
||||
def __repr__(self):
|
||||
maps = (str(map) for map in self.maps_by_id.values())
|
||||
maps = (repr(map) for map in self.maps_by_id.values())
|
||||
return 'MapSet(%s)' % ', '.join(maps)
|
||||
|
||||
def read(self, file_object):
|
||||
@ -143,9 +157,3 @@ class MapPacket(Packet):
|
||||
UnsignedByte.send(self.offset[0], packet_buffer) # x
|
||||
UnsignedByte.send(self.offset[1], packet_buffer) # z
|
||||
VarIntPrefixedByteArray.send(self.pixels, packet_buffer)
|
||||
|
||||
def __repr__(self):
|
||||
return '%sMapPacket(%s)' % (
|
||||
('0x%02X ' % self.id) if self.id is not None else '',
|
||||
', '.join('%s=%r' % (k, v) for (k, v) in self.__dict__.items()
|
||||
if k not in ('pixels', '_context', 'id', 'definition')))
|
||||
|
@ -1,7 +1,7 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
String, Boolean, UUID, VarInt,
|
||||
String, Boolean, UUID, VarInt, MutableRecord,
|
||||
)
|
||||
|
||||
|
||||
@ -20,21 +20,24 @@ class PlayerListItemPacket(Packet):
|
||||
|
||||
packet_name = "player list item"
|
||||
|
||||
fields = 'action_type', 'actions'
|
||||
|
||||
def field_string(self, field):
|
||||
if field == 'action_type':
|
||||
return self.action_type.__name__
|
||||
return super(PlayerListItemPacket, self).field_string(field)
|
||||
|
||||
class PlayerList(object):
|
||||
__slots__ = 'players_by_uuid'
|
||||
|
||||
def __init__(self):
|
||||
self.players_by_uuid = dict()
|
||||
def __init__(self, *items):
|
||||
self.players_by_uuid = {item.uuid: item for item in items}
|
||||
|
||||
class PlayerListItem(object):
|
||||
class PlayerListItem(MutableRecord):
|
||||
__slots__ = (
|
||||
'uuid', 'name', 'properties', 'gamemode', 'ping', 'display_name')
|
||||
|
||||
def __init__(self, **kwds):
|
||||
for key, val in kwds.items():
|
||||
setattr(self, key, val)
|
||||
|
||||
class PlayerProperty(object):
|
||||
class PlayerProperty(MutableRecord):
|
||||
__slots__ = 'name', 'value', 'signature'
|
||||
|
||||
def read(self, file_object):
|
||||
@ -46,33 +49,44 @@ class PlayerListItemPacket(Packet):
|
||||
else:
|
||||
self.signature = None
|
||||
|
||||
class Action(object):
|
||||
__slots__ = 'uuid'
|
||||
def send(self, packet_buffer):
|
||||
String.send(self.name, packet_buffer)
|
||||
String.send(self.value, packet_buffer)
|
||||
if self.signature is not None:
|
||||
Boolean.send(True, packet_buffer)
|
||||
String.send(self.signature, packet_buffer)
|
||||
else:
|
||||
Boolean.send(False, packet_buffer)
|
||||
|
||||
class Action(MutableRecord):
|
||||
__slots__ = 'uuid',
|
||||
|
||||
def read(self, file_object):
|
||||
self.uuid = UUID.read(file_object)
|
||||
self._read(file_object)
|
||||
|
||||
def send(self, packet_buffer):
|
||||
UUID.send(self.uuid, packet_buffer)
|
||||
self._send(packet_buffer)
|
||||
|
||||
def _read(self, file_object):
|
||||
raise NotImplementedError(
|
||||
'This abstract method must be overridden in a subclass.')
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
raise NotImplementedError(
|
||||
'This abstract method must be overridden in a subclass.')
|
||||
|
||||
@classmethod
|
||||
def type_from_id(cls, action_id):
|
||||
subcls = {
|
||||
0: PlayerListItemPacket.AddPlayerAction,
|
||||
1: PlayerListItemPacket.UpdateGameModeAction,
|
||||
2: PlayerListItemPacket.UpdateLatencyAction,
|
||||
3: PlayerListItemPacket.UpdateDisplayNameAction,
|
||||
4: PlayerListItemPacket.RemovePlayerAction
|
||||
}.get(action_id)
|
||||
if subcls is None:
|
||||
raise ValueError("Unknown player list action ID: %s."
|
||||
% action_id)
|
||||
for subcls in cls.__subclasses__():
|
||||
if subcls.action_id == action_id:
|
||||
return subcls
|
||||
raise ValueError("Unknown player list action ID: %s." % action_id)
|
||||
|
||||
class AddPlayerAction(Action):
|
||||
__slots__ = 'name', 'properties', 'gamemode', 'ping', 'display_name'
|
||||
action_id = 0
|
||||
|
||||
def _read(self, file_object):
|
||||
self.name = String.read(file_object)
|
||||
@ -90,6 +104,19 @@ class PlayerListItemPacket(Packet):
|
||||
else:
|
||||
self.display_name = None
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
String.send(self.name, packet_buffer)
|
||||
VarInt.send(len(self.properties), packet_buffer)
|
||||
for property in self.properties:
|
||||
property.send(packet_buffer)
|
||||
VarInt.send(self.gamemode, packet_buffer)
|
||||
VarInt.send(self.ping, packet_buffer)
|
||||
if self.display_name is not None:
|
||||
Boolean.send(True, packet_buffer)
|
||||
String.send(self.display_name, packet_buffer)
|
||||
else:
|
||||
Boolean.send(False, packet_buffer)
|
||||
|
||||
def apply(self, player_list):
|
||||
player = PlayerListItemPacket.PlayerListItem(
|
||||
uuid=self.uuid,
|
||||
@ -102,10 +129,14 @@ class PlayerListItemPacket(Packet):
|
||||
|
||||
class UpdateGameModeAction(Action):
|
||||
__slots__ = 'gamemode'
|
||||
action_id = 1
|
||||
|
||||
def _read(self, file_object):
|
||||
self.gamemode = VarInt.read(file_object)
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
VarInt.send(self.gamemode, packet_buffer)
|
||||
|
||||
def apply(self, player_list):
|
||||
player = player_list.players_by_uuid.get(self.uuid)
|
||||
if player:
|
||||
@ -113,10 +144,14 @@ class PlayerListItemPacket(Packet):
|
||||
|
||||
class UpdateLatencyAction(Action):
|
||||
__slots__ = 'ping'
|
||||
action_id = 2
|
||||
|
||||
def _read(self, file_object):
|
||||
self.ping = VarInt.read(file_object)
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
VarInt.send(self.ping, packet_buffer)
|
||||
|
||||
def apply(self, player_list):
|
||||
player = player_list.players_by_uuid.get(self.uuid)
|
||||
if player:
|
||||
@ -124,6 +159,7 @@ class PlayerListItemPacket(Packet):
|
||||
|
||||
class UpdateDisplayNameAction(Action):
|
||||
__slots__ = 'display_name'
|
||||
action_id = 3
|
||||
|
||||
def _read(self, file_object):
|
||||
has_display_name = Boolean.read(file_object)
|
||||
@ -132,15 +168,27 @@ class PlayerListItemPacket(Packet):
|
||||
else:
|
||||
self.display_name = None
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
if self.display_name is not None:
|
||||
Boolean.send(True, packet_buffer)
|
||||
String.send(self.display_name, packet_buffer)
|
||||
else:
|
||||
Boolean.send(False, packet_buffer)
|
||||
|
||||
def apply(self, player_list):
|
||||
player = player_list.players_by_uuid.get(self.uuid)
|
||||
if player:
|
||||
player.display_name = self.display_name
|
||||
|
||||
class RemovePlayerAction(Action):
|
||||
action_id = 4
|
||||
|
||||
def _read(self, file_object):
|
||||
pass
|
||||
|
||||
def _send(self, packet_buffer):
|
||||
pass
|
||||
|
||||
def apply(self, player_list):
|
||||
if self.uuid in player_list.players_by_uuid:
|
||||
del player_list.players_by_uuid[self.uuid]
|
||||
@ -155,9 +203,12 @@ class PlayerListItemPacket(Packet):
|
||||
action.read(file_object)
|
||||
self.actions.append(action)
|
||||
|
||||
def write_fields(self, packet_buffer):
|
||||
VarInt.send(self.action_type.action_id, packet_buffer)
|
||||
VarInt.send(len(self.actions), packet_buffer)
|
||||
for action in self.actions:
|
||||
action.send(packet_buffer)
|
||||
|
||||
def apply(self, player_list):
|
||||
for action in self.actions:
|
||||
action.apply(player_list)
|
||||
|
||||
def write_fields(self, packet_buffer):
|
||||
raise NotImplementedError
|
||||
|
@ -1,7 +1,8 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Double, Float, Byte, VarInt, BitFieldEnum, PositionAndLook
|
||||
Double, Float, Byte, VarInt, BitFieldEnum, Vector, Direction,
|
||||
PositionAndLook, multi_attribute_alias,
|
||||
)
|
||||
|
||||
|
||||
@ -30,6 +31,18 @@ class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
{'teleport_id': VarInt} if context.protocol_version >= 107 else {},
|
||||
])
|
||||
|
||||
# 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')
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field, context: cls if field == 'flags' else None)
|
||||
|
||||
|
@ -3,7 +3,7 @@ from minecraft.networking.types.utility import descriptor
|
||||
|
||||
from minecraft.networking.types import (
|
||||
VarInt, UUID, Byte, Double, Integer, Angle, Short, Enum, Vector,
|
||||
PositionAndLook, attribute_alias, multi_attribute_alias,
|
||||
Direction, PositionAndLook, attribute_alias, multi_attribute_alias,
|
||||
)
|
||||
|
||||
|
||||
@ -15,8 +15,8 @@ class SpawnObjectPacket(Packet):
|
||||
|
||||
packet_name = 'spawn object'
|
||||
|
||||
fields = ('entity_id', 'object_uuid', 'type_id',
|
||||
'x', 'y', 'z', 'pitch', 'yaw')
|
||||
fields = ('entity_id', 'object_uuid', 'type_id', 'x', 'y', 'z', 'pitch',
|
||||
'yaw', 'data', 'velocity_x', 'velocity_y', 'velocity_z')
|
||||
|
||||
@descriptor
|
||||
def EntityType(desc, self, cls): # pylint: disable=no-self-argument
|
||||
@ -152,13 +152,19 @@ class SpawnObjectPacket(Packet):
|
||||
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')
|
||||
|
||||
|
@ -112,8 +112,9 @@ class Packet(object):
|
||||
str = '0x%02X %s' % (self.id, str)
|
||||
fields = self.fields
|
||||
if fields is not None:
|
||||
str = '%s(%s)' % (str, ', '.join('%s=%s' %
|
||||
(a, self.field_string(a)) for a in fields))
|
||||
inner_str = ', '.join('%s=%s' % (a, self.field_string(a))
|
||||
for a in fields if hasattr(self, a))
|
||||
str = '%s(%s)' % (str, inner_str)
|
||||
return str
|
||||
|
||||
@property
|
||||
|
@ -4,7 +4,8 @@ from minecraft.networking.packets import (
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Double, Float, Boolean, VarInt, String, Byte, Position, Enum,
|
||||
RelativeHand, BlockFace
|
||||
RelativeHand, BlockFace, Vector, Direction, PositionAndLook,
|
||||
multi_attribute_alias
|
||||
)
|
||||
|
||||
from .client_settings_packet import ClientSettingsPacket
|
||||
@ -98,6 +99,19 @@ class PositionAndLookPacket(Packet):
|
||||
{'pitch': Float},
|
||||
{'on_ground': Boolean}]
|
||||
|
||||
# Access the 'x', 'feet_y', 'z' fields as a Vector tuple.
|
||||
position = multi_attribute_alias(Vector, 'x', 'feet_y', 'z')
|
||||
|
||||
# Access the 'yaw', 'pitch' fields as a Direction tuple.
|
||||
look = multi_attribute_alias(Direction, 'yaw', 'pitch')
|
||||
|
||||
# Access the 'x', 'feet_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', 'feet_y', 'z', 'yaw', 'pitch')
|
||||
|
||||
|
||||
class TeleportConfirmPacket(Packet):
|
||||
# Note: added between protocol versions 47 and 107.
|
||||
|
@ -8,7 +8,7 @@ from itertools import chain
|
||||
|
||||
|
||||
__all__ = (
|
||||
'Vector', 'MutableRecord', 'PositionAndLook', 'descriptor',
|
||||
'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor',
|
||||
'attribute_alias', 'multi_attribute_alias',
|
||||
)
|
||||
|
||||
@ -53,8 +53,9 @@ class Vector(namedtuple('BaseVector', ('x', 'y', 'z'))):
|
||||
|
||||
|
||||
class MutableRecord(object):
|
||||
"""An abstract base class providing namedtuple-like repr(), ==, and hash()
|
||||
implementations for types containing mutable fields given by __slots__.
|
||||
"""An abstract base class providing namedtuple-like repr(), ==, hash(), and
|
||||
iter(), implementations for types containing mutable fields given by
|
||||
__slots__.
|
||||
"""
|
||||
__slots__ = ()
|
||||
|
||||
@ -64,19 +65,27 @@ class MutableRecord(object):
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%s)' % (type(self).__name__, ', '.join(
|
||||
'%s=%r' % (a, getattr(self, a)) for a in self.__slots__))
|
||||
'%s=%r' % (a, getattr(self, a)) for a in self._all_slots()))
|
||||
|
||||
def __eq__(self, other):
|
||||
return type(self) is type(other) and \
|
||||
all(getattr(self, a) == getattr(other, a) for a in self.__slots__)
|
||||
return type(self) is type(other) and all(
|
||||
getattr(self, a) == getattr(other, a) for a in self._all_slots())
|
||||
|
||||
def __ne__(self, other):
|
||||
return not (self == other)
|
||||
|
||||
def __hash__(self):
|
||||
values = tuple(getattr(self, a, None) for a in self.__slots__)
|
||||
values = tuple(getattr(self, a, None) for a in self._all_slots())
|
||||
return hash((type(self), values))
|
||||
|
||||
def __iter__(self):
|
||||
return iter(getattr(self, a) for a in self._all_slots())
|
||||
|
||||
@classmethod
|
||||
def _all_slots(cls):
|
||||
return tuple(f for c in reversed(cls.__mro__)
|
||||
for f in getattr(c, '__slots__', ()))
|
||||
|
||||
|
||||
def attribute_alias(name):
|
||||
"""An attribute descriptor that redirects access to a different attribute
|
||||
@ -97,10 +106,18 @@ def multi_attribute_alias(container, *arg_names, **kwd_names):
|
||||
argument to its constructor, and accessible as its 'n'th iterable
|
||||
element.
|
||||
|
||||
As a special case, 'tuple' may be given as the 'container' when there
|
||||
are positional arguments, and (even though the tuple constructor does
|
||||
not take positional arguments), the arguments will be aliased to the
|
||||
corresponding positions in a tuple.
|
||||
|
||||
The name in 'kwd_names' mapped to by the key 'k' is the parent attribute
|
||||
that will be aliased to the field of 'container' settable by the keyword
|
||||
argument 'k' to its constructor, and accessible as its 'k' attribute.
|
||||
"""
|
||||
if container is tuple:
|
||||
container = lambda *args: args # noqa: E731
|
||||
|
||||
@property
|
||||
def alias(self):
|
||||
return container(
|
||||
|
@ -149,7 +149,8 @@ class PacketEnumTest(unittest.TestCase):
|
||||
|
||||
self.assertEqual(
|
||||
str(ExamplePacket(ConnectionContext(), alpha=0, beta=0, gamma=0)),
|
||||
'0x00 ExamplePacket(alpha=ZERO, beta=0, gamma=0)')
|
||||
'0x00 ExamplePacket(alpha=ZERO, beta=0, gamma=0)'
|
||||
)
|
||||
|
||||
|
||||
class TestReadWritePackets(unittest.TestCase):
|
||||
@ -163,16 +164,43 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
Record(-35, -127, 95), Record(11, 113, -8)],
|
||||
player_motion=Vector(4, 5, 0))
|
||||
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'ExplosionPacket(x=787, y=-37, z=0, radius=15, records=['
|
||||
'Record(-14, -116, -5), Record(-77, 34, -36), '
|
||||
'Record(-35, -127, 95), Record(11, 113, -8)], '
|
||||
'player_motion_x=4, player_motion_y=5, player_motion_z=0)'
|
||||
)
|
||||
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
def test_combat_event_packet(self):
|
||||
packet = clientbound.play.CombatEventPacket()
|
||||
for event in (
|
||||
packet.EnterCombatEvent(),
|
||||
packet.EndCombatEvent(duration=415, entity_id=91063502),
|
||||
packet.EntityDeadEvent(player_id=178, entity_id=36, message='RIP'),
|
||||
):
|
||||
packet.event = event
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EnterCombatEvent())
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'CombatEventPacket(event=EnterCombatEvent())'
|
||||
)
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EndCombatEvent(
|
||||
duration=415, entity_id=91063502))
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'CombatEventPacket(event=EndCombatEvent('
|
||||
'duration=415, entity_id=91063502))'
|
||||
)
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
packet = clientbound.play.CombatEventPacket(
|
||||
event=clientbound.play.CombatEventPacket.EntityDeadEvent(
|
||||
player_id=178, entity_id=36, message='RIP'))
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
"CombatEventPacket(event=EntityDeadEvent("
|
||||
"player_id=178, entity_id=36, message='RIP'))"
|
||||
)
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
def test_multi_block_change_packet(self):
|
||||
@ -186,8 +214,16 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
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.assertEqual(packet.chunk_pos, (packet.chunk_x, packet.chunk_z))
|
||||
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
'MultiBlockChangePacket(chunk_x=167, chunk_z=15, records=['
|
||||
'Record(x=1, y=2, z=3, block_state_id=909), '
|
||||
'Record(x=1, y=2, z=3, block_state_id=909), '
|
||||
'Record(x=1, y=2, z=3, block_state_id=909)])'
|
||||
)
|
||||
|
||||
self._test_read_write_packet(packet)
|
||||
|
||||
def test_spawn_object_packet(self):
|
||||
@ -221,6 +257,23 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
self.assertEqual(packet.velocity, velocity)
|
||||
self.assertEqual(packet.type, type_name)
|
||||
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
"0x%02X SpawnObjectPacket(entity_id=49846, "
|
||||
"object_uuid='d9568851-85bc-4a10-8d6a-261d130626fa', "
|
||||
"type_id=EGG, x=68.0, y=38.0, z=76.0, pitch=180, yaw=263.494, "
|
||||
"data=1, velocity_x=21, velocity_y=55, velocity_z=41)"
|
||||
% packet.id if protocol_version >= 100 else
|
||||
"0x%02X SpawnObjectPacket(entity_id=49846, "
|
||||
"object_uuid='d9568851-85bc-4a10-8d6a-261d130626fa', "
|
||||
"type_id=EGG, x=68, y=38, z=76, pitch=180, yaw=263.494, "
|
||||
"data=1, velocity_x=21, velocity_y=55, velocity_z=41)"
|
||||
% packet.id if protocol_version >= 49 else
|
||||
"0x%02X SpawnObjectPacket(entity_id=49846, type_id=EGG, "
|
||||
"x=68, y=38, z=76, pitch=180, yaw=263.494, data=1, "
|
||||
"velocity_x=21, velocity_y=55, velocity_z=41)" % packet.id
|
||||
)
|
||||
|
||||
packet2 = clientbound.play.SpawnObjectPacket(
|
||||
context=context, position_and_look=pos_look,
|
||||
velocity=velocity, type=type_name,
|
||||
@ -254,17 +307,25 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
if context.protocol_version >= 95:
|
||||
packet.sound_category = \
|
||||
clientbound.play.SoundEffectPacket.SoundCategory.NEUTRAL
|
||||
|
||||
self._test_read_write_packet(packet, context)
|
||||
|
||||
def test_face_player_packet(self):
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
context = ConnectionContext(protocol_version=protocol_version)
|
||||
|
||||
packet = clientbound.play.FacePlayerPacket()
|
||||
packet = clientbound.play.FacePlayerPacket(context)
|
||||
packet.target = 1.0, -2.0, 3.5
|
||||
packet.entity_id = None
|
||||
if protocol_version >= 353:
|
||||
packet.origin = OriginPoint.EYES
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
"0x%02X FacePlayerPacket(origin=EYES, x=1.0, y=-2.0, z=3.5, "
|
||||
"entity_id=None)" % packet.id if protocol_version >= 353 else
|
||||
"0x%02X FacePlayerPacket(entity_id=None, x=1.0, y=-2.0, z=3.5)"
|
||||
% packet.id
|
||||
)
|
||||
self._test_read_write_packet(packet, context)
|
||||
|
||||
packet.entity_id = 123
|
||||
@ -272,6 +333,13 @@ class TestReadWritePackets(unittest.TestCase):
|
||||
packet.entity_origin = OriginPoint.FEET
|
||||
else:
|
||||
del packet.target
|
||||
self.assertEqual(
|
||||
str(packet),
|
||||
"0x%02X FacePlayerPacket(origin=EYES, x=1.0, y=-2.0, z=3.5, "
|
||||
"entity_id=123, entity_origin=FEET)" % packet.id
|
||||
if protocol_version >= 353 else
|
||||
"0x%02X FacePlayerPacket(entity_id=123)" % packet.id
|
||||
)
|
||||
self._test_read_write_packet(packet, context)
|
||||
|
||||
def _test_read_write_packet(self, packet_in, context=None, **kwargs):
|
||||
|
@ -1,11 +1,13 @@
|
||||
import unittest
|
||||
from minecraft.networking.types import (UUID, VarInt, String, Boolean)
|
||||
from minecraft.networking.types import (UUID, VarInt)
|
||||
from minecraft.networking.packets import PacketBuffer
|
||||
from minecraft.networking.packets.clientbound.play import (
|
||||
PlayerPositionAndLookPacket, PlayerListItemPacket, MapPacket
|
||||
)
|
||||
from minecraft.networking.connection import ConnectionContext
|
||||
|
||||
from tests.test_packets import TEST_VERSIONS
|
||||
|
||||
|
||||
class PlayerPositionAndLookTest(unittest.TestCase):
|
||||
|
||||
@ -141,36 +143,35 @@ class PlayerListItemTest(unittest.TestCase):
|
||||
PlayerListItemPacket().read(packet_buffer)
|
||||
|
||||
@staticmethod
|
||||
def make_add_player_packet(display_name=True):
|
||||
def make_add_player_packet(context, display_name=True):
|
||||
packet_buffer = PacketBuffer()
|
||||
|
||||
VarInt.send(0, packet_buffer) # action_id
|
||||
VarInt.send(1, packet_buffer) # action count
|
||||
UUID.send(fake_uuid, packet_buffer) # uuid
|
||||
String.send("player", packet_buffer) # player name
|
||||
|
||||
VarInt.send(2, packet_buffer) # number of properties
|
||||
String.send("property1", packet_buffer)
|
||||
String.send("value1", packet_buffer)
|
||||
Boolean.send(False, packet_buffer) # is signed
|
||||
String.send("property2", packet_buffer)
|
||||
String.send("value2", packet_buffer)
|
||||
Boolean.send(True, packet_buffer) # is signed
|
||||
String.send("signature", packet_buffer)
|
||||
|
||||
VarInt.send(42, packet_buffer) # game mode
|
||||
VarInt.send(69, packet_buffer) # ping
|
||||
Boolean.send(display_name, packet_buffer) # has display name
|
||||
if display_name:
|
||||
String.send("display", packet_buffer) # display name
|
||||
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.AddPlayerAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.AddPlayerAction(
|
||||
uuid=fake_uuid,
|
||||
name='goodmonson',
|
||||
properties=[
|
||||
PlayerListItemPacket.PlayerProperty(
|
||||
name='property1', value='value1', signature=None),
|
||||
PlayerListItemPacket.PlayerProperty(
|
||||
name='property2', value='value2', signature='gm')
|
||||
],
|
||||
gamemode=42,
|
||||
ping=69,
|
||||
display_name='Goodmonson' if display_name else None
|
||||
),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
packet_buffer.reset_cursor()
|
||||
return packet_buffer
|
||||
|
||||
def test_add_player_action(self):
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
context = ConnectionContext(protocol_version=protocol_version)
|
||||
player_list = PlayerListItemPacket.PlayerList()
|
||||
|
||||
packet_buffer = self.make_add_player_packet()
|
||||
packet_buffer = self.make_add_player_packet(context)
|
||||
|
||||
packet = PlayerListItemPacket()
|
||||
packet.read(packet_buffer)
|
||||
@ -179,66 +180,95 @@ class PlayerListItemTest(unittest.TestCase):
|
||||
self.assertIn(fake_uuid, player_list.players_by_uuid)
|
||||
player = player_list.players_by_uuid[fake_uuid]
|
||||
|
||||
self.assertEqual(player.name, "player")
|
||||
self.assertEqual(player.properties[0].name, "property1")
|
||||
self.assertEqual(player.name, 'goodmonson')
|
||||
self.assertEqual(player.properties[0].name, 'property1')
|
||||
self.assertIsNone(player.properties[0].signature)
|
||||
self.assertEqual(player.properties[1].value, "value2")
|
||||
self.assertEqual(player.properties[1].signature, "signature")
|
||||
self.assertEqual(player.properties[1].value, 'value2')
|
||||
self.assertEqual(player.properties[1].signature, 'gm')
|
||||
self.assertEqual(player.gamemode, 42)
|
||||
self.assertEqual(player.ping, 69)
|
||||
self.assertEqual(player.display_name, "display")
|
||||
self.assertEqual(player.display_name, 'Goodmonson')
|
||||
|
||||
@staticmethod
|
||||
def make_action_base(action_id):
|
||||
packet_buffer = PacketBuffer()
|
||||
VarInt.send(action_id, packet_buffer)
|
||||
VarInt.send(1, packet_buffer) # action count
|
||||
UUID.send(fake_uuid, packet_buffer)
|
||||
|
||||
return packet_buffer
|
||||
|
||||
def read_and_apply(self, packet_buffer, player_list):
|
||||
def read_and_apply(self, context, packet_buffer, player_list):
|
||||
packet_buffer.reset_cursor()
|
||||
packet = PlayerListItemPacket()
|
||||
packet = PlayerListItemPacket(context)
|
||||
packet.read(packet_buffer)
|
||||
packet.apply(player_list)
|
||||
|
||||
def test_add_and_others(self):
|
||||
for protocol_version in TEST_VERSIONS:
|
||||
context = ConnectionContext(protocol_version=protocol_version)
|
||||
|
||||
player_list = PlayerListItemPacket.PlayerList()
|
||||
by_uuid = player_list.players_by_uuid
|
||||
|
||||
packet_buffer = self.make_add_player_packet(display_name=False)
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
packet_buffer = self.make_add_player_packet(
|
||||
context, display_name=False)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertEqual(by_uuid[fake_uuid].gamemode, 42)
|
||||
self.assertEqual(by_uuid[fake_uuid].ping, 69)
|
||||
self.assertIsNone(by_uuid[fake_uuid].display_name)
|
||||
|
||||
# Change the game mode
|
||||
packet_buffer = self.make_action_base(1)
|
||||
VarInt.send(43, packet_buffer) # gamemode
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
packet_buffer = PacketBuffer()
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.UpdateGameModeAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.UpdateGameModeAction(
|
||||
uuid=fake_uuid, gamemode=43),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertEqual(by_uuid[fake_uuid].gamemode, 43)
|
||||
|
||||
# Change the ping
|
||||
packet_buffer = self.make_action_base(2)
|
||||
VarInt.send(70, packet_buffer) # ping
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
packet_buffer = PacketBuffer()
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.UpdateLatencyAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.UpdateLatencyAction(
|
||||
uuid=fake_uuid, ping=70),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertEqual(by_uuid[fake_uuid].ping, 70)
|
||||
|
||||
# Change the display name
|
||||
packet_buffer = self.make_action_base(3)
|
||||
Boolean.send(True, packet_buffer)
|
||||
String.send("display2", packet_buffer)
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
self.assertEqual(by_uuid[fake_uuid].display_name, "display2")
|
||||
packet_buffer = PacketBuffer()
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.UpdateDisplayNameAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.UpdateDisplayNameAction(
|
||||
uuid=fake_uuid, display_name='Badmonson'),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertEqual(by_uuid[fake_uuid].display_name, 'Badmonson')
|
||||
|
||||
# Remove the display name
|
||||
packet_buffer = self.make_action_base(3)
|
||||
Boolean.send(False, packet_buffer)
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
packet_buffer = PacketBuffer()
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.UpdateDisplayNameAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.UpdateDisplayNameAction(
|
||||
uuid=fake_uuid, display_name=None),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertIsNone(by_uuid[fake_uuid].display_name)
|
||||
|
||||
# Remove the player
|
||||
packet_buffer = self.make_action_base(4)
|
||||
self.read_and_apply(packet_buffer, player_list)
|
||||
packet_buffer = PacketBuffer()
|
||||
PlayerListItemPacket(
|
||||
context=context,
|
||||
action_type=PlayerListItemPacket.RemovePlayerAction,
|
||||
actions=[
|
||||
PlayerListItemPacket.RemovePlayerAction(uuid=fake_uuid),
|
||||
],
|
||||
).write_fields(packet_buffer)
|
||||
self.read_and_apply(context, packet_buffer, player_list)
|
||||
self.assertNotIn(fake_uuid, player_list.players_by_uuid)
|
||||
|
Loading…
Reference in New Issue
Block a user