from .. import util
from ..versions import Version, VersionSwitcher
from ..types import TypeContext, VarInt, RawByte, prepare_type
[docs]class PacketContext:
def __init__(self, version=None):
self.version = Version(version)
[docs]class Packet:
id = None
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if hasattr(cls, "__annotations__"):
to_change = {}
for attr, attr_type in cls.__annotations__.items():
new_type = prepare_type(attr_type)
if new_type != attr_type:
to_change[attr] = new_type
# Change this so type's constructor calls __set_name__?
# Note: Names are set by type's constructor before
# __init_subclass__ is called, so a metaclass would be needed.
setattr(cls, attr, new_type.descriptor(attr))
cls.__annotations__.update(to_change)
else:
cls.__annotations__ = {}
if isinstance(cls.id, dict):
cls.id = VersionSwitcher(cls.id)
def __init__(self, *, buf=None, ctx=None, **kwargs):
if buf is not None:
buf = util.file_object(buf)
self._fields = {}
for attr, attr_type in self.enumerate_fields():
if buf is None:
if attr in kwargs:
setattr(self, attr, kwargs[attr])
else:
setattr(self, attr, attr_type.default(ctx=self.type_ctx(ctx)))
else:
setattr(self, attr, attr_type.unpack(buf, ctx=self.type_ctx(ctx)))
[docs] def type_ctx(self, ctx):
return TypeContext(self, ctx)
[docs] def pack(self, *, ctx=None):
return VarInt.pack(self.get_id(ctx=ctx), ctx=self.type_ctx(ctx)) + b"".join(y.pack(getattr(self, x), ctx=self.type_ctx(ctx)) for x, y in self.enumerate_fields())
def _get_field(self, attr):
return self._fields[attr]
def _set_field(self, attr, value):
self._fields[attr] = value
def __repr__(self):
ret = f"{type(self).__name__}("
ret += ", ".join(f"{x}={repr(getattr(self, x))}" for x, _ in self.enumerate_fields())
ret += ")"
return ret
[docs] @classmethod
def enumerate_fields(cls):
for attr, attr_type in cls.__annotations__.items():
yield attr, attr_type
[docs] @classmethod
def unpack(cls, buf, *, ctx=None):
return cls(buf=buf, ctx=ctx)
[docs] @classmethod
def get_id(cls, *, ctx=None):
if isinstance(cls.id, VersionSwitcher):
return cls.id[ctx.version]
return cls.id
[docs]class GenericPacket(Packet, metaclass=GenericPacketMeta):
data: RawByte[None]
def __new__(cls, id=None, **kwargs):
if id is None:
if cls.id is None:
raise TypeError("Use of GenericPacket without setting its id")
return super().__new__(cls)
return type(f"{cls.__name__}({id:#x})", (cls,), dict(
id = id,
))
# Classes used for inheritance to know where a packet is bound and what state it's used in
[docs]class ServerboundPacket(Packet):
pass
[docs]class ClientboundPacket(Packet):
pass
[docs]class HandshakingPacket(Packet):
pass
[docs]class StatusPacket(Packet):
pass
[docs]class LoginPacket(Packet):
pass
[docs]class PlayPacket(Packet):
pass