From 5e92631b615fc67128c212fe85a91d7ad9ae83f7 Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 4 Jul 2024 22:26:39 +0800 Subject: [PATCH 1/8] Support parsing block_states with version greater than 1.18 --- mcworldlib/chunk.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index 4135014..ffe9c2f 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -51,7 +51,7 @@ def get_blocks(self): Palette, Indexes: See get_section_blocks() """ blocks = {} - for section in self.data_root['Sections']: + for section in self.data_root['sections']: # noinspection PyPep8Naming Y = int(section['Y']) palette, indexes = self.get_section_blocks(Y, _section=section) @@ -70,17 +70,23 @@ def get_section_blocks(self, Y: int, _section=None): """ section = _section if not section: - for section in self.data_root.get('Sections', []): + for section in self.data_root.get('sections', []): if section.get('Y') == Y: break else: return None, None - if 'Palette' not in section or 'BlockStates' not in section: + if 'block_states' not in section: return None, None - palette = section['Palette'] - indexes = self._decode_blockstates(section['BlockStates'], palette) + states = section['block_states'] + + palette = states['palette'] + + if 'data' not in states: + return palette, numpy.zeros((u.SECTION_HEIGHT, *reversed(u.CHUNK_SIZE))) + + indexes = self._decode_blockstates(states['data'], palette) return palette, indexes.reshape((u.SECTION_HEIGHT, *reversed(u.CHUNK_SIZE))) def _decode_blockstates(self, data, palette=None): From 0f2c7fd6f342146fdf7b14faf8fb6b198d783eae Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 09:22:11 +0800 Subject: [PATCH 2/8] Support auto version detection and use correct method on parsing chunk --- mcworldlib/chunk.py | 82 +++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 75 insertions(+), 7 deletions(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index ffe9c2f..98bdac9 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -44,11 +44,19 @@ def entities(self): def entities(self, value: nbt.List[nbt.Compound]): self.data_root['Entities'] = value - def get_blocks(self): - """Yield a (Y, Palette, BlockState Indexes Array) tuple for every chunk section. + + def is_version_1_21(self): + if "sections" in self.data_root: + return True + else: + return False + + + def get_blocks_1_21(self): + """Yield a (Y, palette, block_states Indexes Array) tuple for every chunk section. Y: Y "level" of the section, the section "index" (NOT the NBT section index!) - Palette, Indexes: See get_section_blocks() + palette, Indexes: See get_section_blocks() """ blocks = {} for section in self.data_root['sections']: @@ -61,12 +69,47 @@ def get_blocks(self): # noinspection PyRedundantParentheses yield (Y, *blocks[Y]) - # noinspection PyPep8Naming + + def get_blocks_old(self): + """For version lower than 1.21.1 + + Yield a (Y, Palette, BlockState Indexes Array) tuple for every chunk section. + + Y: Y "level" of the section, the section "index" (NOT the NBT section index!) + Palette, Indexes: See get_section_blocks() + """ + blocks = {} + for section in self.data_root['Sections']: + # noinspection PyPep8Naming + Y = int(section['Y']) + palette, indexes = self.get_section_blocks(Y, _section=section) + if palette: + blocks[Y] = palette, indexes + for Y in sorted(blocks): + # noinspection PyRedundantParentheses + yield (Y, *blocks[Y]) + + + def get_blocks(self, Y: int, _section=None): + if self.is_version_1_21(): + return self.get_blocks_1_21(Y, _section) + else: + return self.get_blocks_old(Y, _section) + + def get_section_blocks(self, Y: int, _section=None): - """Return a (Palette, BlockState Indexes Array) tuple for a chunk section. + if self.is_version_1_21(): + return self.get_section_blocks_1_21(Y, _section) + else: + return self.get_section_blocks_old(Y, _section) - Palette: NBT List of Block States, straight from NBT data - Indexes: 16 x 16 x 16 numpy.ndarray, in YZX order, of indexes matching Palette's + + # noinspection PyPep8Naming + def get_section_blocks_1_21(self, Y: int, _section=None): + """Return a (palette, block_state Indexes Array) tuple for a chunk section. + + palette: NBT List of Block States, straight from NBT data + Indexes: 16 x 16 x 16 numpy.ndarray, in YZX order, of indexes matching palette's """ section = _section if not section: @@ -88,6 +131,31 @@ def get_section_blocks(self, Y: int, _section=None): indexes = self._decode_blockstates(states['data'], palette) return palette, indexes.reshape((u.SECTION_HEIGHT, *reversed(u.CHUNK_SIZE))) + + + def get_section_blocks_old(self, Y: int, _section=None): + """For version lower than 1.21.1 + + Return a (Palette, BlockState Indexes Array) tuple for a chunk section. + + Palette: NBT List of Block States, straight from NBT data + Indexes: 16 x 16 x 16 numpy.ndarray, in YZX order, of indexes matching Palette's + """ + section = _section + if not section: + for section in self.data_root.get('Sections', []): + if section.get('Y') == Y: + break + else: + return None, None + + if 'Palette' not in section or 'BlockStates' not in section: + return None, None + + palette = section['Palette'] + indexes = self._decode_blockstates(section['BlockStates'], palette) + return palette, indexes.reshape((u.SECTION_HEIGHT, *reversed(u.CHUNK_SIZE))) + def _decode_blockstates(self, data, palette=None): """Decode an NBT BlockStates LongArray to a block state indexes array""" From bf5bc45d96268adf0f27b2dfddbda7ee1e206de4 Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 09:36:38 +0800 Subject: [PATCH 3/8] Prevent from running the test script due to the need to switch directories and installation. --- mcworldlib/tree.py | 4 ++-- tests/tests.py | 9 ++++++--- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/mcworldlib/tree.py b/mcworldlib/tree.py index d9cba1b..0197efa 100644 --- a/mcworldlib/tree.py +++ b/mcworldlib/tree.py @@ -209,9 +209,9 @@ def is_nbt_container(tag: 'nbt.AnyTag') -> bool: def tests(): import json for data in ( - json.load(open("../data/New World/advancements/" + json.load(open("data/New World/advancements/" "8b4accb8-d952-4050-97f2-e00c4423ba92.json")), - nbt.load_dat("../data/New World/level.dat"), + nbt.load_dat("data/New World/level.dat"), [{"x": 1, "y": 2}, "a", ((4, {"z": 5}, "b"), 6, "c")], {"name": ["Rodrigo", ("E", "S"), "Silva"], "alias": "MestreLion"} ): diff --git a/tests/tests.py b/tests/tests.py index 92358d8..c084c14 100755 --- a/tests/tests.py +++ b/tests/tests.py @@ -1,13 +1,16 @@ #!/usr/bin/env python3 +""" +Run tests by `python tests/tests.py` +""" import io import logging import os.path import sys +sys.path.append(os.path.abspath(".")) import mcworldlib as mc - logging.basicConfig(level=logging.DEBUG, format='%(levelname)-6s: %(message)s') @@ -80,7 +83,7 @@ def diamonds(): def new_diamonds(): - world = mc.load('New World') + world = mc.load('data/New World') for item in world.level['Data']['Player']['Inventory']: item['id'] = mc.String('minecraft:diamond_block') @@ -128,7 +131,7 @@ def player_pos(): def blocks(w=None): if w is None: - w = mc.load('New World') + w = mc.load('data/New World') for chunk in [w.player.get_chunk()]: # w.get_chunks(False): print(chunk) From a373585c9bbd69a8646d1af7212485ec5398b16d Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 09:58:39 +0800 Subject: [PATCH 4/8] Fix: 'chunk.get_blocks' function using 'yield' instead of 'return' --- mcworldlib/chunk.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index 98bdac9..ea1e4c5 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -90,11 +90,11 @@ def get_blocks_old(self): yield (Y, *blocks[Y]) - def get_blocks(self, Y: int, _section=None): + def get_blocks(self): if self.is_version_1_21(): - return self.get_blocks_1_21(Y, _section) + yield self.get_blocks_1_21() else: - return self.get_blocks_old(Y, _section) + yield self.get_blocks_old() def get_section_blocks(self, Y: int, _section=None): From 2bdfc4c4aa8319ccd7312b2d7a6da89eed715660 Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 10:27:12 +0800 Subject: [PATCH 5/8] Fix chunk.py: '_decode_blockstates' will failed if palette number greater than 16 --- mcworldlib/chunk.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index ea1e4c5..dcfeb8a 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -167,23 +167,24 @@ def bits_from_data(): return len(data) * pack_bits // self.BS_INDEXES if not palette: # Infer from data length (not the way described by Wiki!) return bits_from_data() - bit_length = max(self.BS_MIN_BITS, (len(palette) - 1).bit_length()) + bit_length = max(self.BS_MIN_BITS, (len(palette)).bit_length()) assert bit_length == bits_from_data(), \ f"BlockState bits mismatch: {bit_length} != {bits_from_data()}" return bit_length bits = bits_per_index() + padding_per_long = 64 % bits # Adapted from Amulet-Core's decode_long_array() # https://github.com/Amulet-Team/Amulet-Core/blob/develop/amulet/utils/world_utils.py indexes = numpy.packbits( numpy.pad( numpy.unpackbits( - data[::-1].astype(f">i{pack_bits//8}").view(f"uint{pack_bits//8}") - ).reshape(-1, bits), + data[::-1].astype(f">i{pack_bits//8}").view(f"uint{pack_bits//8}").unpack() + ).reshape(-1, 64)[:, padding_per_long:64].reshape(-1, bits), [(0, 0), (pack_bits - bits, 0)], "constant", ) - ).view(dtype=">q")[::-1] + ).view(dtype=">q")[::-1][:4096] return indexes # noinspection PyMethodMayBeStatic From e17e0b80a98b8bdf4f0d61e2da280dfb0a0a74fe Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 10:50:21 +0800 Subject: [PATCH 6/8] Revert commit a373585c9bbd69a8646d1af7212485ec5398b16d --- mcworldlib/chunk.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index dcfeb8a..73f0daf 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -92,9 +92,9 @@ def get_blocks_old(self): def get_blocks(self): if self.is_version_1_21(): - yield self.get_blocks_1_21() + return self.get_blocks_1_21() else: - yield self.get_blocks_old() + return self.get_blocks_old() def get_section_blocks(self, Y: int, _section=None): From 8a3c0b351636ddfcaadf08d96b0d47437434227d Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Thu, 10 Oct 2024 10:51:34 +0800 Subject: [PATCH 7/8] Fix #10 - Upgrade supportting parsing version to 1.21 - Fix '_decode_blockstates' if palette number greater than 16 From d7e376d78dd8c6952e9450d59dc831daffe134f9 Mon Sep 17 00:00:00 2001 From: suzakuwcx Date: Fri, 15 Nov 2024 11:30:59 +0800 Subject: [PATCH 8/8] Fix #10 Because that the index of palette array begins from 0, so the maximux index is 1 less than palette length --- mcworldlib/chunk.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mcworldlib/chunk.py b/mcworldlib/chunk.py index 73f0daf..e5dcab6 100644 --- a/mcworldlib/chunk.py +++ b/mcworldlib/chunk.py @@ -167,7 +167,7 @@ def bits_from_data(): return len(data) * pack_bits // self.BS_INDEXES if not palette: # Infer from data length (not the way described by Wiki!) return bits_from_data() - bit_length = max(self.BS_MIN_BITS, (len(palette)).bit_length()) + bit_length = max(self.BS_MIN_BITS, (len(palette) - 1).bit_length()) assert bit_length == bits_from_data(), \ f"BlockState bits mismatch: {bit_length} != {bits_from_data()}" return bit_length