mirror of
https://github.com/ammaraskar/pyCraft.git
synced 2024-11-16 07:15:24 +01:00
Add ClientSettingsPacket and PluginMessagePacket.
Improve Packet string representation.
This commit is contained in:
parent
3269a022a8
commit
e9f095de42
@ -8,20 +8,22 @@ Use the packet classes under packets.clientbound.* and
|
||||
packets.serverbound.* instead.
|
||||
'''
|
||||
|
||||
from .packet import Packet
|
||||
# Packet-Related Utilities
|
||||
from .packet_buffer import PacketBuffer
|
||||
from .keep_alive_packet import KeepAlivePacket
|
||||
from .packet_listener import PacketListener
|
||||
|
||||
# For backward compatibility, re-export any old names from before the change:
|
||||
# Abstract Packet Classes
|
||||
from .packet import Packet
|
||||
from .keep_alive_packet import AbstractKeepAlivePacket
|
||||
from .plugin_message_packet import AbstractPluginMessagePacket
|
||||
|
||||
# Handshake State
|
||||
# ==============
|
||||
|
||||
# Legacy Packets (Handshake State)
|
||||
from .clientbound.handshake import get_packets as state_handshake_clientbound
|
||||
from .serverbound.handshake import HandShakePacket
|
||||
from .serverbound.handshake import get_packets as state_handshake_serverbound
|
||||
|
||||
# Status State
|
||||
# ==============
|
||||
# Legacy Packets (Status State)
|
||||
from .clientbound.status import ResponsePacket
|
||||
from .clientbound.status import PingResponsePacket as PingPacketResponse
|
||||
from .clientbound.status import get_packets as state_status_clientbound
|
||||
@ -29,8 +31,7 @@ from .serverbound.status import RequestPacket
|
||||
from .serverbound.status import PingPacket
|
||||
from .serverbound.status import get_packets as state_status_serverbound
|
||||
|
||||
# Login State
|
||||
# ==============
|
||||
# Legacy Packets (Login State)
|
||||
from .clientbound.login import DisconnectPacket
|
||||
from .clientbound.login import EncryptionRequestPacket
|
||||
from .clientbound.login import LoginSuccessPacket
|
||||
@ -40,8 +41,8 @@ from .serverbound.login import LoginStartPacket
|
||||
from .serverbound.login import EncryptionResponsePacket
|
||||
from .serverbound.login import get_packets as state_login_serverbound
|
||||
|
||||
# Playing State
|
||||
# ==============
|
||||
# Legacy Packets (Playing State)
|
||||
from .keep_alive_packet import KeepAlivePacket
|
||||
from .clientbound.play import KeepAlivePacket as KeepAlivePacketClientbound
|
||||
from .serverbound.play import KeepAlivePacket as KeepAlivePacketServerbound
|
||||
from .clientbound.play import JoinGamePacket
|
||||
@ -60,7 +61,7 @@ from .serverbound.play import TeleportConfirmPacket
|
||||
from .serverbound.play import AnimationPacket as AnimationPacketServerbound
|
||||
from .serverbound.play import get_packets as state_playing_serverbound
|
||||
|
||||
__all_legacy_packets__ = [
|
||||
__all_legacy_packets__ = (
|
||||
state_handshake_clientbound, HandShakePacket,
|
||||
state_handshake_serverbound, ResponsePacket,
|
||||
PingPacketResponse, state_status_clientbound,
|
||||
@ -75,19 +76,10 @@ __all_legacy_packets__ = [
|
||||
MapPacket, state_playing_clientbound, ChatPacket,
|
||||
PositionAndLookPacket, TeleportConfirmPacket,
|
||||
AnimationPacketServerbound, state_playing_serverbound,
|
||||
Packet, PacketBuffer, KeepAlivePacket
|
||||
]
|
||||
KeepAlivePacket,
|
||||
)
|
||||
|
||||
|
||||
class PacketListener(object):
|
||||
def __init__(self, callback, *args):
|
||||
self.callback = callback
|
||||
self.packets_to_listen = []
|
||||
for arg in args:
|
||||
if issubclass(arg, Packet):
|
||||
self.packets_to_listen.append(arg)
|
||||
|
||||
def call_packet(self, packet):
|
||||
for packet_type in self.packets_to_listen:
|
||||
if isinstance(packet, packet_type):
|
||||
self.callback(packet)
|
||||
__all_other__ = (
|
||||
Packet, PacketBuffer, PacketListener,
|
||||
AbstractKeepAlivePacket, AbstractPluginMessagePacket,
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
from minecraft.networking.packets import (
|
||||
Packet, PacketBuffer, KeepAlivePacket as AbstractKeepAlivePacket
|
||||
Packet, PacketBuffer, AbstractKeepAlivePacket, AbstractPluginMessagePacket
|
||||
)
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Integer, UnsignedByte, Byte, Boolean, UUID, Short, Position,
|
||||
VarInt, Double, Float, String
|
||||
VarInt, Double, Float, String, Enum,
|
||||
)
|
||||
|
||||
from .combat_event_packet import CombatEventPacket
|
||||
@ -32,6 +32,7 @@ def get_packets(context):
|
||||
SpawnObjectPacket,
|
||||
BlockChangePacket,
|
||||
MultiBlockChangePacket,
|
||||
PluginMessagePacket,
|
||||
}
|
||||
if context.protocol_version <= 47:
|
||||
packets |= {
|
||||
@ -81,6 +82,11 @@ class ChatMessagePacket(Packet):
|
||||
{'json_data': String},
|
||||
{'position': Byte}]
|
||||
|
||||
class Position(Enum):
|
||||
CHAT = 0 # A player-initiated chat message.
|
||||
SYSTEM = 1 # The result of running a command.
|
||||
GAME_INFO = 2 # Displayed above the hotbar in vanilla clients.
|
||||
|
||||
|
||||
class DisconnectPacket(Packet):
|
||||
@staticmethod
|
||||
@ -265,3 +271,12 @@ class MultiBlockChangePacket(Packet):
|
||||
|
||||
def write(self, socket, compression_threshold=None):
|
||||
raise NotImplementedError
|
||||
|
||||
|
||||
class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x18 if context.protocol_version >= 332 else \
|
||||
0x19 if context.protocol_version >= 318 else \
|
||||
0x18 if context.protocol_version >= 70 else \
|
||||
0x3F
|
||||
|
@ -1,11 +1,11 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Double, Float, Byte, VarInt
|
||||
Double, Float, Byte, VarInt, BitFieldEnum
|
||||
)
|
||||
|
||||
|
||||
class PlayerPositionAndLookPacket(Packet):
|
||||
class PlayerPositionAndLookPacket(Packet, BitFieldEnum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x2F if context.protocol_version >= 336 else \
|
||||
@ -25,6 +25,9 @@ class PlayerPositionAndLookPacket(Packet):
|
||||
{'teleport_id': VarInt} if context.protocol_version >= 107 else {},
|
||||
])
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: cls if field == 'flags' else None)
|
||||
|
||||
FLAG_REL_X = 0x01
|
||||
FLAG_REL_Y = 0x02
|
||||
FLAG_REL_Z = 0x04
|
||||
|
@ -1,7 +1,7 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
|
||||
from minecraft.networking.types import (
|
||||
VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short
|
||||
VarInt, UUID, Byte, Double, Integer, UnsignedByte, Short, Enum
|
||||
)
|
||||
|
||||
|
||||
@ -13,7 +13,7 @@ class SpawnObjectPacket(Packet):
|
||||
|
||||
packet_name = 'spawn object'
|
||||
|
||||
class EntityType:
|
||||
class EntityType(Enum):
|
||||
BOAT = 1
|
||||
ITEM_STACK = 2
|
||||
AREA_EFFECT_CLOUD = 3
|
||||
@ -42,19 +42,12 @@ class SpawnObjectPacket(Packet):
|
||||
SPECTRAL_ARROW = 91
|
||||
DRAGON_FIREBALL = 93
|
||||
|
||||
@classmethod
|
||||
def get_type_by_id(cls, type_id):
|
||||
by_id = {id: entity for (entity, id) in
|
||||
cls.__dict__.items() if entity.isupper()}
|
||||
|
||||
return by_id[type_id]
|
||||
|
||||
def read(self, file_object):
|
||||
self.entity_id = VarInt.read(file_object)
|
||||
if self._context.protocol_version >= 49:
|
||||
self.objectUUID = UUID.read(file_object)
|
||||
type_id = Byte.read(file_object)
|
||||
self.type = SpawnObjectPacket.EntityType.get_type_by_id(type_id)
|
||||
self.type = SpawnObjectPacket.EntityType.name_from_value(type_id)
|
||||
|
||||
if self._context.protocol_version >= 100:
|
||||
self.x = Double.read(file_object)
|
||||
|
@ -5,7 +5,11 @@ from minecraft.networking.types import (
|
||||
)
|
||||
|
||||
|
||||
class KeepAlivePacket(Packet):
|
||||
class AbstractKeepAlivePacket(Packet):
|
||||
packet_name = "keep alive"
|
||||
definition = [
|
||||
{'keep_alive_id': VarInt}]
|
||||
|
||||
|
||||
# This alias is retained for backward compatibility:
|
||||
KeepAlivePacket = AbstractKeepAlivePacket
|
||||
|
@ -1,7 +1,7 @@
|
||||
from .packet_buffer import PacketBuffer
|
||||
from zlib import compress
|
||||
from minecraft.networking.types import (
|
||||
VarInt
|
||||
VarInt, Enum
|
||||
)
|
||||
|
||||
|
||||
@ -107,6 +107,32 @@ class Packet(object):
|
||||
if self.id is not None:
|
||||
str = '0x%02X %s' % (self.id, str)
|
||||
if self.definition is not None:
|
||||
fields = {a: getattr(self, a) for d in self.definition for a in d}
|
||||
str = '%s %s' % (str, fields)
|
||||
str = '%s(%s)' % (str, ', '.join(
|
||||
'%s=%s' % (a, self.field_string(a))
|
||||
for d in self.definition for a in d))
|
||||
return str
|
||||
|
||||
def field_string(self, field):
|
||||
""" The string representation of the value of a the given named field
|
||||
of this packet. Override to customise field value representation.
|
||||
"""
|
||||
value = getattr(self, field, None)
|
||||
|
||||
enum_class = self.field_enum(field)
|
||||
if enum_class is not None:
|
||||
name = enum_class.name_from_value(value)
|
||||
if name is not None:
|
||||
return name
|
||||
|
||||
return repr(value)
|
||||
|
||||
@classmethod
|
||||
def field_enum(cls, field):
|
||||
""" The subclass of 'minecraft.networking.types.Enum' associated with
|
||||
this field, or None if there is no such class.
|
||||
"""
|
||||
enum_name = ''.join(s.capitalize() for s in field.split('_'))
|
||||
if hasattr(cls, enum_name):
|
||||
enum_class = getattr(cls, enum_name)
|
||||
if isinstance(enum_class, type) and issubclass(enum_class, Enum):
|
||||
return enum_class
|
||||
|
15
minecraft/networking/packets/packet_listener.py
Normal file
15
minecraft/networking/packets/packet_listener.py
Normal file
@ -0,0 +1,15 @@
|
||||
from .packet import Packet
|
||||
|
||||
|
||||
class PacketListener(object):
|
||||
def __init__(self, callback, *args):
|
||||
self.callback = callback
|
||||
self.packets_to_listen = []
|
||||
for arg in args:
|
||||
if issubclass(arg, Packet):
|
||||
self.packets_to_listen.append(arg)
|
||||
|
||||
def call_packet(self, packet):
|
||||
for packet_type in self.packets_to_listen:
|
||||
if isinstance(packet, packet_type):
|
||||
self.callback(packet)
|
8
minecraft/networking/packets/plugin_message_packet.py
Normal file
8
minecraft/networking/packets/plugin_message_packet.py
Normal file
@ -0,0 +1,8 @@
|
||||
from .packet import Packet
|
||||
from minecraft.networking.types import String, TrailingByteArray
|
||||
|
||||
|
||||
class AbstractPluginMessagePacket(Packet):
|
||||
definition = [
|
||||
{'channel': String},
|
||||
{'data': TrailingByteArray}]
|
@ -1,11 +1,13 @@
|
||||
from minecraft.networking.packets import (
|
||||
Packet, KeepAlivePacket as AbstractKeepAlivePacket
|
||||
Packet, AbstractKeepAlivePacket, AbstractPluginMessagePacket
|
||||
)
|
||||
|
||||
from minecraft.networking.types import (
|
||||
Double, Float, Boolean, VarInt, String
|
||||
Double, Float, Boolean, VarInt, String, Enum
|
||||
)
|
||||
|
||||
from .client_settings_packet import ClientSettingsPacket
|
||||
|
||||
|
||||
# Formerly known as state_playing_serverbound.
|
||||
def get_packets(context):
|
||||
@ -15,6 +17,8 @@ def get_packets(context):
|
||||
PositionAndLookPacket,
|
||||
AnimationPacket,
|
||||
ClientStatusPacket,
|
||||
ClientSettingsPacket,
|
||||
PluginMessagePacket,
|
||||
}
|
||||
if context.protocol_version >= 107:
|
||||
packets |= {
|
||||
@ -82,7 +86,7 @@ class TeleportConfirmPacket(Packet):
|
||||
{'teleport_id': VarInt}]
|
||||
|
||||
|
||||
class AnimationPacket(Packet):
|
||||
class AnimationPacket(Packet, Enum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x1D if context.protocol_version >= 332 else \
|
||||
@ -93,11 +97,14 @@ class AnimationPacket(Packet):
|
||||
packet_name = "animation"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'hand': VarInt} if context.protocol_version >= 107 else {}])
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: cls if field == 'hand' else None)
|
||||
|
||||
HAND_MAIN = 0
|
||||
HAND_OFF = 1
|
||||
|
||||
|
||||
class ClientStatusPacket(Packet):
|
||||
class ClientStatusPacket(Packet, Enum):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x03 if context.protocol_version >= 336 else \
|
||||
@ -110,8 +117,19 @@ class ClientStatusPacket(Packet):
|
||||
packet_name = "client status"
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'action_id': VarInt}])
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: cls if field == 'action_id' else None)
|
||||
|
||||
RESPAWN = 0
|
||||
REQUEST_STATS = 1
|
||||
# Note: Open Inventory (id 2) was removed in protocol version 319
|
||||
OPEN_INVENTORY = 2
|
||||
|
||||
|
||||
class PluginMessagePacket(AbstractPluginMessagePacket):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x09 if context.protocol_version >= 336 else \
|
||||
0x0A if context.protocol_version >= 317 else \
|
||||
0x09 if context.protocol_version >= 94 else \
|
||||
0x17
|
||||
|
@ -0,0 +1,51 @@
|
||||
from minecraft.networking.packets import Packet
|
||||
from minecraft.networking.types import (
|
||||
String, Byte, VarInt, Boolean, UnsignedByte, Enum, BitFieldEnum
|
||||
)
|
||||
|
||||
|
||||
class ClientSettingsPacket(Packet):
|
||||
@staticmethod
|
||||
def get_id(context):
|
||||
return 0x04 if context.protocol_version >= 336 else \
|
||||
0x05 if context.protocol_version >= 318 else \
|
||||
0x04 if context.protocol_version >= 94 else \
|
||||
0x15
|
||||
|
||||
packet_name = 'client settings'
|
||||
|
||||
get_definition = staticmethod(lambda context: [
|
||||
{'locale': String},
|
||||
{'view_distance': Byte},
|
||||
{'chat_mode': VarInt if context.protocol_version > 47 else Byte},
|
||||
{'chat_colors': Boolean},
|
||||
{'displayed_skin_parts': UnsignedByte},
|
||||
{'main_hand': VarInt}])
|
||||
|
||||
field_enum = classmethod(
|
||||
lambda cls, field: {
|
||||
'chat_mode': cls.ChatMode,
|
||||
'displayed_skin_parts': cls.SkinParts,
|
||||
'main_hand': cls.Hand,
|
||||
}.get(field))
|
||||
|
||||
class ChatMode(Enum):
|
||||
FULL = 0 # Receive all types of chat messages.
|
||||
SYSTEM = 1 # Receive only command results and game information.
|
||||
NONE = 2 # Receive only game information.
|
||||
|
||||
class SkinParts(BitFieldEnum):
|
||||
CAPE = 0x01
|
||||
JACKET = 0x02
|
||||
LEFT_SLEEVE = 0x04
|
||||
RIGHT_SLEEVE = 0x08
|
||||
LEFT_PANTS_LEG = 0x10
|
||||
RIGHT_PANTS_LEG = 0x20
|
||||
HAT = 0x40
|
||||
|
||||
ALL = 0x7F
|
||||
NONE = 0x00
|
||||
|
||||
class Hand(Enum):
|
||||
LEFT = 0
|
||||
RIGHT = 1
|
@ -205,6 +205,19 @@ class VarIntPrefixedByteArray(Type):
|
||||
socket.send(struct.pack(str(len(value)) + "s", value))
|
||||
|
||||
|
||||
class TrailingByteArray(Type):
|
||||
""" A byte array consisting of all remaining data. If present in a packet
|
||||
definition, this should only be the type of the last field. """
|
||||
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
return file_object.read()
|
||||
|
||||
@staticmethod
|
||||
def send(value, socket):
|
||||
socket.send(value)
|
||||
|
||||
|
||||
class String(Type):
|
||||
@staticmethod
|
||||
def read(file_object):
|
||||
@ -253,3 +266,30 @@ class Position(Type, namedtuple('Position', ('x', 'y', 'z'))):
|
||||
def send(x, y, z, socket):
|
||||
value = ((x & 0x3FFFFFF) << 38) | ((y & 0xFFF) << 26) | (z & 0x3FFFFFF)
|
||||
UnsignedLong.send(value, socket)
|
||||
|
||||
|
||||
class Enum(object):
|
||||
@classmethod
|
||||
def name_from_value(cls, value):
|
||||
for name, name_value in cls.__dict__.items():
|
||||
if name.isupper() and name_value == value:
|
||||
return name
|
||||
|
||||
|
||||
class BitFieldEnum(Enum):
|
||||
@classmethod
|
||||
def name_from_value(cls, value):
|
||||
if not isinstance(value, int):
|
||||
return
|
||||
ret_names = []
|
||||
ret_value = 0
|
||||
for cls_name, cls_value in sorted(
|
||||
[(n, v) for (n, v) in cls.__dict__.items()
|
||||
if isinstance(v, int) and n.isupper() and v | value == value],
|
||||
reverse=True, key=lambda p: p[1]
|
||||
):
|
||||
if ret_value | cls_value != ret_value or cls_value == value:
|
||||
ret_names.append(cls_name)
|
||||
ret_value |= cls_value
|
||||
if ret_value == value:
|
||||
return '|'.join(reversed(ret_names)) if ret_names else '0'
|
||||
|
@ -7,6 +7,9 @@ from minecraft.networking.packets import serverbound
|
||||
|
||||
class LegacyPacketNamesTest(unittest.TestCase):
|
||||
def test_legacy_packets_equal_current_packets(self):
|
||||
self.assertEqual(packets.KeepAlivePacket,
|
||||
packets.AbstractKeepAlivePacket)
|
||||
|
||||
self.assertEqual(packets.state_handshake_clientbound,
|
||||
clientbound.handshake.get_packets)
|
||||
self.assertEqual(packets.HandShakePacket,
|
||||
|
@ -6,9 +6,9 @@ from random import choice
|
||||
|
||||
from minecraft import SUPPORTED_PROTOCOL_VERSIONS
|
||||
from minecraft.networking.connection import ConnectionContext
|
||||
from minecraft.networking.types import VarInt
|
||||
from minecraft.networking.types import VarInt, Enum, BitFieldEnum
|
||||
from minecraft.networking.packets import (
|
||||
PacketBuffer, PacketListener, KeepAlivePacket, serverbound)
|
||||
Packet, PacketBuffer, PacketListener, KeepAlivePacket, serverbound)
|
||||
|
||||
|
||||
class PacketBufferTest(unittest.TestCase):
|
||||
@ -115,3 +115,61 @@ class PacketListenerTest(unittest.TestCase):
|
||||
|
||||
listener.call_packet(packet)
|
||||
listener.call_packet(uncalled_packet)
|
||||
|
||||
|
||||
class PacketEnumTest(unittest.TestCase):
|
||||
def test_packet_str(self):
|
||||
class ExamplePacket(Packet):
|
||||
id = 0x00
|
||||
packet_name = 'example'
|
||||
definition = [
|
||||
{'alpha': VarInt},
|
||||
{'beta': VarInt},
|
||||
{'gamma': VarInt}]
|
||||
|
||||
class Alpha(Enum):
|
||||
ZERO = 0
|
||||
|
||||
class Beta(Enum):
|
||||
ONE = 1
|
||||
|
||||
self.assertEqual(
|
||||
str(ExamplePacket(ConnectionContext(), alpha=0, beta=0, gamma=0)),
|
||||
'0x00 ExamplePacket(alpha=ZERO, beta=0, gamma=0)')
|
||||
|
||||
|
||||
class EnumTest(unittest.TestCase):
|
||||
def test_enum(self):
|
||||
class Example(Enum):
|
||||
ONE = 1
|
||||
TWO = 2
|
||||
THREE = 3
|
||||
|
||||
self.assertEqual(
|
||||
list(map(Example.name_from_value, range(5))),
|
||||
[None, 'ONE', 'TWO', 'THREE', None])
|
||||
|
||||
|
||||
class BitFieldEnumTest(unittest.TestCase):
|
||||
def test_name_from_value(self):
|
||||
class Example1(BitFieldEnum):
|
||||
ONE = 1
|
||||
TWO = 2
|
||||
FOUR = 4
|
||||
ALL = 7
|
||||
NONE = 0
|
||||
|
||||
self.assertEqual(
|
||||
list(map(Example1.name_from_value, range(9))),
|
||||
['NONE', 'ONE', 'TWO', 'ONE|TWO', 'FOUR',
|
||||
'ONE|FOUR', 'TWO|FOUR', 'ALL', None])
|
||||
|
||||
class Example2(BitFieldEnum):
|
||||
ONE = 1
|
||||
TWO = 2
|
||||
FOUR = 4
|
||||
|
||||
self.assertEqual(
|
||||
list(map(Example2.name_from_value, range(9))),
|
||||
['0', 'ONE', 'TWO', 'ONE|TWO', 'FOUR',
|
||||
'ONE|FOUR', 'TWO|FOUR', 'ONE|TWO|FOUR', None])
|
||||
|
Loading…
Reference in New Issue
Block a user