Skip to content

Commit

Permalink
remove Root.root and its spurious extra Compound. See vberlier/nbtlib…
Browse files Browse the repository at this point in the history
  • Loading branch information
MestreLion committed Oct 19, 2021
1 parent 271ae96 commit 9e6412e
Show file tree
Hide file tree
Showing 4 changed files with 79 additions and 74 deletions.
64 changes: 30 additions & 34 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,25 +42,23 @@ You can open a Minecraft World by several ways:
>>> world = mc.load('data/New World')
>>> mc.pretty(world) # Most classes have a pretty print. In many cases, their NBT data.
{
"": {
Data: {
WanderingTraderSpawnChance: 25,
BorderCenterZ: 0.0d,
Difficulty: 2b,
...
SpawnAngle: 0.0f,
version: 19133,
BorderSafeZone: 5.0d,
LastPlayed: 1633981265600L,
BorderWarningTime: 15.0d,
ScheduledEvents: [],
LevelName: "New World",
BorderSize: 59999968.0d,
DataVersion: 2730,
DataPacks: {
Enabled: ["vanilla"],
Disabled: ["Fabric Mods"]
}
Data: {
WanderingTraderSpawnChance: 25,
BorderCenterZ: 0.0d,
Difficulty: 2b,
...
SpawnAngle: 0.0f,
version: 19133,
BorderSafeZone: 5.0d,
LastPlayed: 1633981265600L,
BorderWarningTime: 15.0d,
ScheduledEvents: [],
LevelName: "New World",
BorderSize: 59999968.0d,
DataVersion: 2730,
DataPacks: {
Enabled: ["vanilla"],
Disabled: ["Fabric Mods"]
}
}
}
Expand Down Expand Up @@ -139,20 +137,18 @@ A `RegionFile` is a dictionary of chunks, and each `Chunk` contains its NBT data
>>> chunk = region[30, 31]
>>> mc.pretty(chunk) # alternatively, print(chunk.pretty())
{
"": {
Level: {
Status: "structure_starts",
zPos: 31,
LastUpdate: 4959L,
InhabitedTime: 0L,
xPos: -34,
Heightmaps: {},
TileEntities: [],
Entities: [],
...
},
DataVersion: 2730
}
Level: {
Status: "structure_starts",
zPos: 31,
LastUpdate: 4959L,
InhabitedTime: 0L,
xPos: -34,
Heightmaps: {},
TileEntities: [],
Entities: [],
...
},
DataVersion: 2730
}

```
Expand Down Expand Up @@ -246,7 +242,7 @@ Reading and modifying the Player's inventory is quite easy:
import mcworldlib as mc
world = mc.load('New World')
inventory = world.player.inventory
# The above is a shortcut for world.root['Data']['Player']['Inventory']
# The above is a shortcut for world['Data']['Player']['Inventory']

# Easily loop each item as if the inventory were a list. In fact, it *is*!
for item in inventory:
Expand Down
7 changes: 1 addition & 6 deletions mcworldlib/level.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,11 +22,6 @@ class Level(nbt.File):
'player',
)

@property
def root_name(self):
"""The name of the root nbt tag."""
return nbt.Path("''.Data")

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.player = None
Expand All @@ -41,7 +36,7 @@ def parse(cls, buff, *args, **kwargs):

# Player
name = 'Player'
self.player = player.Player(self.root[name])
self.player = player.Player(self.data_root[name])
self.player.name = name
self.player.world = self

Expand Down
76 changes: 45 additions & 31 deletions mcworldlib/nbt.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,19 +11,24 @@
# and all the ones defined here

import io as _io
import logging as _logging
import zlib as _zlib

# TODO: (and suggest to nbtlib)
# - class Root(Compound): transparently handle the unnamed [''] root tag
# - improve upon nbtlib.File.root property: self['x'] -> self['']['x']
# - nbtlib.File, chunk.Chunk and level.Level would inherit from it
# - completely hide the root from outside, re-add it only on .write()
# - Auto-casting value to tag on assignment based on current type
# - compound['string'] = 'foo' -> compound['string'] = String('foo')
# - maybe this is only meant for nbtlib.Schema?
# - String.__str__() should not quote or escape

# not in nbtlib.tag.__all__ and only used here
# noinspection PyProtectedMember
from nbtlib.tag import Base as _Base # Not in nbtlib.tag.__all__
from nbtlib.tag import (
Base as _Base,
BYTE as _BYTE,
read_numeric as _read_numeric,
read_string as _read_string,
write_numeric as _write_numeric,
write_string as _write_string,
)
from nbtlib.tag import *
# noinspection PyUnresolvedReferences
from nbtlib.nbt import File as _File, load as load_dat # could be File.load
Expand All @@ -32,55 +37,64 @@
from nbtlib.literal.serializer import serialize_tag as _serialize_tag


_log = _logging.getLogger(__name__)


class Root(Compound):
"""Unnamed Compound tag, used as root tag in files and chunks"""
# Should contain the following from nbtlib_File:
# root_name()
# root_name.setter()
# root()
# root.setter()
# __repr__()

__slots__ = ()
__slots__ = (
'root_name',
)

def __init__(self, root_name: str = "", *args, **kwargs):
super().__init__(*args, **kwargs)
self.root_name: str = root_name

@property
def data_root(self):
"""The sole root child tag containing all data apart from DataVersion.
If there is no such tag, return root itself
"""
root = self.root
tags = set(root.keys()) - {'DataVersion'}
tags = set(self.keys()) - {'DataVersion'}

# 'Data' in level.dat (with DataVersion *inside* it)
# 'data' in <dim>/data/*.dat files (idcounts, map_*, raids*)
# 'Level' in chunks from <dim>/region/*.mca anvil files, 'region' category
if len(tags) == 1:
return root[next(iter(tags))]
return self[next(iter(tags))]

# No sole child, data is at root along with with DataVersion
# - chunks from <dim>/*/*.mca anvil files, 'entities' and 'poi' categories
return root

# The following are copy-pasted from nbtlib.File

@property
def root_name(self):
"""The name of the root nbt tag."""
return next(iter(self), None)

@root_name.setter
def root_name(self, value):
self[value] = self.pop(self.root_name)
return self

@property
def root(self):
"""The root nbt tag of the file."""
return self[self.root_name]
"""Deprecated, just use self directly."""
_log.warning(".root is deprecated, just access its contents directly")
return self

@root.setter
def root(self, value):
self[self.root_name] = value
@classmethod
def parse(cls, buff, byteorder='big'):
tag_id = _read_numeric(_BYTE, buff, byteorder)
if not tag_id == cls.tag_id:
# Possible issues with a non-Compound root:
# - root_name might not be in __slots__(), and thus not assignable
# - not returning a superclass might be surprising and have side-effects
raise TypeError("Non-Compound root tags is not supported:"
f"{cls.get_tag(tag_id)}")
name = _read_string(buff, byteorder)
self = super().parse(buff, byteorder)
self.root_name = name
return self

def write(self, buff, byteorder='big'):
_write_numeric(_BYTE, self.tag_id, buff, byteorder)
_write_string(getattr(self, 'root_name', "") or "", buff, byteorder)
super().write(buff, byteorder)


# Overrides and extensions
Expand Down
6 changes: 3 additions & 3 deletions mcworldlib/world.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,16 +50,16 @@ def __init__(self, *args, **kwargs):

@property
def name(self):
return str(self.root.get('LevelName', os.path.basename(self.path)))
return str(self.data_root.get('LevelName', os.path.basename(self.path)))

@name.setter
def name(self, value):
self.root['LevelName'] = nbt.String(value)
self.data_root['LevelName'] = nbt.String(value)

@property
def level(self):
"""Somewhat redundant API shortcut, as for now World *is* a Level"""
return self.root
return self

def _category_dict(self, category):
return {k: v.get(category, {}) for k, v in self.dimensions.items()}
Expand Down

0 comments on commit 9e6412e

Please sign in to comment.