import abc
from .. import nbt
from .. import util
from ..versions import VersionSwitcher
from .type import Type
from .string import Identifier
[docs]class NBT(Type):
[docs] class Specialization(Type):
tag = None
root_name = ""
@classmethod
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if isinstance(cls.tag, dict):
cls.tag = VersionSwitcher(cls.tag)
[docs] @classmethod
@abc.abstractmethod
def from_nbt(cls, data, *, ctx=None):
raise NotImplementedError
[docs] @classmethod
@abc.abstractmethod
def to_nbt(cls, value, *, ctx=None):
raise NotImplementedError
@classmethod
def _unpack(cls, buf, *, ctx=None):
data = nbt.load(buf)
if isinstance(cls.tag, VersionSwitcher):
tag = cls.tag[ctx.version]
else:
tag = cls.tag
if not isinstance(data, tag):
raise ValueError(f"Expected {tag}, got {type(data)}")
if data.root_name != cls.root_name:
raise ValueError(f"Mismatched root names; expected {repr(cls.root_name)}, got {repr(data.root_name)}")
return cls.from_nbt(data, ctx=ctx)
@classmethod
def _pack(cls, value, *, ctx=None):
data = cls.to_nbt(value, ctx=ctx)
data.root_name = cls.root_name
return nbt.dump(data)
@classmethod
def _call(cls, *, root_name=""):
return cls.make_type(cls.__name__,
root_name = root_name,
)
[docs] class VersionSwitched(Specialization):
[docs] @classmethod
def real_tag(cls, *, ctx=None):
tag = cls.tag[ctx.version]
if tag is None:
return NBT.Empty
return tag
@classmethod
def _default(cls, *, ctx=None):
tag = cls.real_tag(ctx=ctx)
if issubclass(tag, NBT.Specialization):
return tag.default(ctx=ctx)
return tag().value
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
tag = cls.real_tag(ctx=ctx)
if issubclass(tag, NBT.Specialization):
return tag.from_nbt(data, ctx=ctx)
return data.value
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
tag = cls.real_tag(ctx=ctx)
if issubclass(tag, NBT.Specialization):
return tag.to_nbt(value, ctx=ctx)
return tag(value)
@classmethod
def _call(cls, switcher, *, root_name=""):
if isinstance(switcher, dict):
switcher = VersionSwitcher(switcher)
return cls.make_type(cls.__name__,
root_name = root_name,
tag = switcher,
)
[docs] class Defaulted(Specialization):
elem_tag = None
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
if issubclass(cls.elem_tag, NBT.Specialization):
return cls.elem_tag.from_nbt(data)
return data.value
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
if issubclass(cls.elem_tag, NBT.Specialization):
return cls.elem_tag.to_nbt(value)
return cls.elem_tag(value)
@classmethod
def _call(cls, elem_tag, default, *, root_name=""):
return cls.make_type(f"{cls.__name__}{elem_tag.__name__}",
root_name = root_name,
tag = elem_tag.tag if issubclass(elem_tag, NBT.Specialization) else elem_tag,
elem_tag = elem_tag,
_default = default,
)
[docs] class Boolean(Specialization):
tag = nbt.Byte
_default = False
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
return bool(data.value)
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
return cls.tag(int(value))
[docs] class Identifier(Specialization):
tag = nbt.String
_default = Identifier.Identifier()
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
return Identifier.Identifier(data.value)
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
return cls.tag(str(value))
[docs] class List(Specialization):
tag = nbt.List
list_tag = None
_default = []
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
if issubclass(cls.list_tag, NBT.Specialization):
return [cls.list_tag.from_nbt(cls.list_tag.tag(x), ctx=ctx) for x in data.value]
return data.value
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
if issubclass(cls.list_tag, NBT.Specialization):
return nbt.List(cls.list_tag.tag)([cls.list_tag.to_nbt(x, ctx=ctx).value for x in value])
return nbt.List(cls.list_tag)(value)
@classmethod
def _call(cls, tag, *, root_name=""):
return cls.make_type(f"{cls.__name__}({tag.__name__})",
root_name = root_name,
list_tag = tag,
)
[docs] class Empty(Specialization):
"""Used for marking fields in an NBT.Compound as non-existent"""
[docs] class Optional(Specialization):
"""Used for marking fields in an NBT.Compound as optional"""
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
if issubclass(cls.tag, NBT.Specialization):
return cls.tag.from_nbt(data, ctx=ctx)
return data.value
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
if issubclass(cls.tag, NBT.Specialization):
return cls.tag.to_nbt(value, ctx=ctx)
return cls.tag(value)
@classmethod
def _call(cls, tag, *, root_name=""):
return cls.make_type(f"{cls.__name__}{tag.__name__}",
root_name = root_name,
tag = tag,
)
[docs] class Compound(Specialization):
tag = nbt.Compound
elems = None
value_type = None
def __set__(self, instance, value):
if isinstance(value, dict):
value = self.value_type(value)
super().__set__(instance, value)
[docs] @classmethod
def handle_tag(cls, tag, *, ctx=None):
if issubclass(tag, NBT.VersionSwitched):
return tag.real_tag(ctx=ctx)
return tag
@classmethod
def _default(cls, *, ctx=None):
defaults = {}
for name, tag in cls.elems.items():
tag = cls.handle_tag(tag, ctx=ctx)
if issubclass(tag, (NBT.Empty, NBT.Optional)):
continue
elif issubclass(tag, NBT.Specialization):
defaults[name] = tag.default(ctx=ctx)
else:
defaults[name] = tag().value
return cls.value_type(defaults)
[docs] @classmethod
def from_nbt(cls, data, *, ctx=None):
values = {}
for name, tag in cls.elems.items():
tag = cls.handle_tag(tag, ctx=ctx)
if issubclass(tag, NBT.Empty):
continue
field = data.value.get(name)
if field is None and issubclass(tag, NBT.Optional):
continue
elif issubclass(tag, NBT.Specialization):
values[name] = tag.from_nbt(field, ctx=ctx)
else:
if not isinstance(field, tag):
raise ValueError(f"Expected {tag}, got {type(field)}")
values[name] = field.value
return cls.value_type(values)
[docs] @classmethod
def to_nbt(cls, value, *, ctx=None):
data = cls.tag()
for name, tag in cls.elems.items():
tag = cls.handle_tag(tag, ctx=ctx)
if issubclass(tag, NBT.Empty):
continue
field = value.get(name)
if field is None and issubclass(tag, NBT.Optional):
continue
elif issubclass(tag, NBT.Specialization):
data[name] = tag.to_nbt(field, ctx=ctx)
else:
data[name] = tag(field)
return data
@classmethod
def _call(cls, type_name=None, elems=None, *, root_name="", **kwargs):
if type_name is None:
type_name = cls.__name__
if elems is None:
elems = {}
# Use fancy 3.9+ |= operator?
elems.update(kwargs)
to_change = {}
for name, tag in elems.items():
if isinstance(tag, dict):
to_change[name] = NBT.VersionSwitched(tag)
elems.update(to_change)
return cls.make_type(type_name,
root_name = root_name,
elems = elems,
value_type = util.AttrDict(type_name)
)
@classmethod
def _default(cls, *, ctx=None):
return nbt.Compound(root_name="")
@classmethod
def _unpack(cls, buf, *, ctx=None):
return nbt.load(buf)
@classmethod
def _pack(cls, value, *, ctx=None):
return nbt.dump(value)