255 lines
8.8 KiB
Python
255 lines
8.8 KiB
Python
"""Minecraft data types that are used by packets, but don't have a specific
|
|
network representation.
|
|
"""
|
|
import types
|
|
|
|
from collections import namedtuple
|
|
from itertools import chain
|
|
|
|
|
|
__all__ = (
|
|
'Vector', 'MutableRecord', 'Direction', 'PositionAndLook', 'descriptor',
|
|
'overridable_descriptor', 'overridable_property', 'attribute_alias',
|
|
'multi_attribute_alias',
|
|
)
|
|
|
|
|
|
class Vector(namedtuple('BaseVector', ('x', 'y', 'z'))):
|
|
"""An immutable type usually used to represent 3D spatial coordinates,
|
|
supporting elementwise vector addition, subtraction, and negation; and
|
|
scalar multiplication and (right) division.
|
|
|
|
NOTE: subclasses of 'Vector' should have '__slots__ = ()' to avoid the
|
|
creation of a '__dict__' attribute, which would waste space.
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def __add__(self, other):
|
|
return NotImplemented if not isinstance(other, Vector) else \
|
|
type(self)(self.x + other.x, self.y + other.y, self.z + other.z)
|
|
|
|
def __sub__(self, other):
|
|
return NotImplemented if not isinstance(other, Vector) else \
|
|
type(self)(self.x - other.x, self.y - other.y, self.z - other.z)
|
|
|
|
def __neg__(self):
|
|
return type(self)(-self.x, -self.y, -self.z)
|
|
|
|
def __mul__(self, other):
|
|
return type(self)(self.x*other, self.y*other, self.z*other)
|
|
|
|
def __rmul__(self, other):
|
|
return type(self)(other*self.x, other*self.y, other*self.z)
|
|
|
|
def __truediv__(self, other):
|
|
return type(self)(self.x/other, self.y/other, self.z/other)
|
|
|
|
def __floordiv__(self, other):
|
|
return type(self)(self.x//other, self.y//other, self.z//other)
|
|
|
|
__div__ = __floordiv__
|
|
|
|
def __repr__(self):
|
|
return '%s(%r, %r, %r)' % (type(self).__name__, self.x, self.y, self.z)
|
|
|
|
|
|
class MutableRecord(object):
|
|
"""An abstract base class providing namedtuple-like repr(), ==, hash(), and
|
|
iter(), implementations for types containing mutable fields given by
|
|
__slots__.
|
|
"""
|
|
__slots__ = ()
|
|
|
|
def __init__(self, **kwds):
|
|
for attr, value in kwds.items():
|
|
setattr(self, attr, value)
|
|
|
|
def __repr__(self):
|
|
return '%s(%s)' % (type(self).__name__, ', '.join(
|
|
'%s=%r' % (a, getattr(self, a)) for a in self._all_slots()
|
|
if hasattr(self, a)))
|
|
|
|
def __eq__(self, other):
|
|
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._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):
|
|
for supcls in reversed(cls.__mro__):
|
|
slots = supcls.__dict__.get('__slots__', ())
|
|
slots = (slots,) if isinstance(slots, str) else slots
|
|
for slot in slots:
|
|
yield slot
|
|
|
|
|
|
def attribute_alias(name):
|
|
"""An attribute descriptor that redirects access to a different attribute
|
|
with a given name.
|
|
"""
|
|
return property(fget=(lambda self: getattr(self, name)),
|
|
fset=(lambda self, value: setattr(self, name, value)),
|
|
fdel=(lambda self: delattr(self, name)))
|
|
|
|
|
|
def multi_attribute_alias(container, *arg_names, **kwd_names):
|
|
"""A descriptor for an attribute whose value is a container of a given type
|
|
with several fields, each of which is aliased to a different attribute
|
|
of the parent object.
|
|
|
|
The 'n'th name in 'arg_names' is the parent attribute that will be
|
|
aliased to the field of 'container' settable by the 'n'th positional
|
|
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(
|
|
*(getattr(self, name) for name in arg_names),
|
|
**{kwd: getattr(self, name) for (kwd, name) in kwd_names.items()})
|
|
|
|
@alias.setter
|
|
def alias(self, values):
|
|
if arg_names:
|
|
for name, value in zip(arg_names, values):
|
|
setattr(self, name, value)
|
|
for kwd, name in kwd_names.items():
|
|
setattr(self, name, getattr(values, kwd))
|
|
|
|
@alias.deleter
|
|
def alias(self):
|
|
for name in chain(arg_names, kwd_names.values()):
|
|
delattr(self, name)
|
|
|
|
return alias
|
|
|
|
|
|
class overridable_descriptor:
|
|
"""As 'descriptor' (defined below), except that only a getter can be
|
|
defined, and the resulting descriptor has no '__set__' or '__delete__'
|
|
methods defined; hence, attributes defined via this class can be
|
|
overridden by attributes of instances of the class in which it occurs.
|
|
"""
|
|
__slots__ = '_fget',
|
|
|
|
def __init__(self, fget=None):
|
|
self._fget = fget if fget is not None else self._default_get
|
|
|
|
def getter(self, fget):
|
|
self._fget = fget
|
|
return self
|
|
|
|
@staticmethod
|
|
def _default_get(instance, owner):
|
|
raise AttributeError('unreadable attribute')
|
|
|
|
def __get__(self, instance, owner):
|
|
return self._fget(self, instance, owner)
|
|
|
|
|
|
class overridable_property(overridable_descriptor):
|
|
"""As the builtin 'property' decorator of Python, except that only
|
|
a getter is defined and the resulting descriptor is a non-data
|
|
descriptor, overridable by attributes of instances of the class
|
|
in which the property occurs. See also 'overridable_descriptor' above.
|
|
"""
|
|
def __get__(self, instance, _owner):
|
|
return self._fget(instance)
|
|
|
|
|
|
class descriptor(overridable_descriptor):
|
|
"""Behaves identically to the builtin 'property' decorator of Python,
|
|
except that the getter, setter and deleter functions given by the
|
|
user are used as the raw __get__, __set__ and __delete__ functions
|
|
as defined in Python's descriptor protocol.
|
|
|
|
Since an instance of this class always havs '__set__' and '__delete__'
|
|
defined, it is a "data descriptor", so its binding behaviour cannot be
|
|
overridden in instances of the class in which it occurs. See
|
|
https://docs.python.org/3/reference/datamodel.html#descriptor-invocation
|
|
for more information. See also 'overridable_descriptor' above.
|
|
"""
|
|
__slots__ = '_fset', '_fdel'
|
|
|
|
def __init__(self, fget=None, fset=None, fdel=None):
|
|
super(descriptor, self).__init__(fget=fget)
|
|
self._fset = fset if fset is not None else self._default_set
|
|
self._fdel = fdel if fdel is not None else self._default_del
|
|
|
|
def setter(self, fset):
|
|
self._fset = fset
|
|
return self
|
|
|
|
def deleter(self, fdel):
|
|
self._fdel = fdel
|
|
return self
|
|
|
|
@staticmethod
|
|
def _default_set(instance, value):
|
|
raise AttributeError("can't set attribute")
|
|
|
|
@staticmethod
|
|
def _default_del(instance):
|
|
raise AttributeError("can't delete attribute")
|
|
|
|
def __set__(self, instance, value):
|
|
return self._fset(self, instance, value)
|
|
|
|
def __delete__(self, instance):
|
|
return self._fdel(self, instance)
|
|
|
|
|
|
class class_and_instancemethod:
|
|
""" A decorator for functions defined in a class namespace which are to be
|
|
accessed as both class and instance methods: retrieving the method from
|
|
a class will return a bound class method (like the built-in
|
|
'classmethod' decorator), but retrieving the method from an instance
|
|
will return a bound instance method (as if the function were not
|
|
decorated). Therefore, the first argument of the decorated function may
|
|
be either a class or an instance, depending on how it was called.
|
|
"""
|
|
|
|
__slots__ = '_func',
|
|
|
|
def __init__(self, func):
|
|
self._func = func
|
|
|
|
def __get__(self, inst, owner=None):
|
|
bind_to = owner if inst is None else inst
|
|
return types.MethodType(self._func, bind_to)
|
|
|
|
|
|
Direction = namedtuple('Direction', ('yaw', 'pitch'))
|
|
|
|
|
|
class PositionAndLook(MutableRecord):
|
|
"""A mutable record containing 3 spatial position coordinates
|
|
and 2 rotational coordinates 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')
|