amulet-core 2.0a5__cp311-cp311-macosx_10_9_universal2.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.cpython-311-darwin.so +0 -0
- amulet/__init__.pyi +30 -0
- amulet/__pyinstaller/__init__.py +2 -0
- amulet/__pyinstaller/hook-amulet.py +4 -0
- amulet/_init.py +28 -0
- amulet/_version.py +21 -0
- amulet/biome.cpp +36 -0
- amulet/biome.hpp +43 -0
- amulet/biome.pyi +77 -0
- amulet/block.cpp +435 -0
- amulet/block.hpp +119 -0
- amulet/block.pyi +273 -0
- amulet/block_entity.cpp +12 -0
- amulet/block_entity.hpp +56 -0
- amulet/block_entity.pyi +80 -0
- amulet/chunk.cpp +16 -0
- amulet/chunk.hpp +99 -0
- amulet/chunk.pyi +30 -0
- amulet/chunk_/components/biome.py +155 -0
- amulet/chunk_/components/block_entity.py +117 -0
- amulet/chunk_/components/entity.py +64 -0
- amulet/chunk_/components/height_2d.py +16 -0
- amulet/chunk_components.pyi +95 -0
- amulet/collections.pyi +37 -0
- amulet/data_types.py +29 -0
- amulet/entity.py +180 -0
- amulet/errors.py +63 -0
- amulet/game/__init__.py +7 -0
- amulet/game/_game.py +152 -0
- amulet/game/_universal/__init__.py +1 -0
- amulet/game/_universal/_biome.py +17 -0
- amulet/game/_universal/_block.py +47 -0
- amulet/game/_universal/_version.py +68 -0
- amulet/game/abc/__init__.py +22 -0
- amulet/game/abc/_block_specification.py +150 -0
- amulet/game/abc/biome.py +213 -0
- amulet/game/abc/block.py +331 -0
- amulet/game/abc/game_version_container.py +25 -0
- amulet/game/abc/json_interface.py +27 -0
- amulet/game/abc/version.py +44 -0
- amulet/game/bedrock/__init__.py +1 -0
- amulet/game/bedrock/_biome.py +35 -0
- amulet/game/bedrock/_block.py +42 -0
- amulet/game/bedrock/_version.py +165 -0
- amulet/game/java/__init__.py +2 -0
- amulet/game/java/_biome.py +35 -0
- amulet/game/java/_block.py +60 -0
- amulet/game/java/_version.py +176 -0
- amulet/game/translate/__init__.py +12 -0
- amulet/game/translate/_functions/__init__.py +15 -0
- amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
- amulet/game/translate/_functions/_code_functions/_text.py +553 -0
- amulet/game/translate/_functions/_code_functions/banner_pattern.py +67 -0
- amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +152 -0
- amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +88 -0
- amulet/game/translate/_functions/_code_functions/bedrock_sign.py +152 -0
- amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +16 -0
- amulet/game/translate/_functions/_code_functions/custom_name.py +146 -0
- amulet/game/translate/_functions/_frozen.py +66 -0
- amulet/game/translate/_functions/_state.py +54 -0
- amulet/game/translate/_functions/_typing.py +98 -0
- amulet/game/translate/_functions/abc.py +116 -0
- amulet/game/translate/_functions/carry_nbt.py +160 -0
- amulet/game/translate/_functions/carry_properties.py +80 -0
- amulet/game/translate/_functions/code.py +143 -0
- amulet/game/translate/_functions/map_block_name.py +66 -0
- amulet/game/translate/_functions/map_nbt.py +111 -0
- amulet/game/translate/_functions/map_properties.py +93 -0
- amulet/game/translate/_functions/multiblock.py +112 -0
- amulet/game/translate/_functions/new_block.py +42 -0
- amulet/game/translate/_functions/new_entity.py +43 -0
- amulet/game/translate/_functions/new_nbt.py +206 -0
- amulet/game/translate/_functions/new_properties.py +64 -0
- amulet/game/translate/_functions/sequence.py +51 -0
- amulet/game/translate/_functions/walk_input_nbt.py +331 -0
- amulet/game/translate/_translator.py +433 -0
- amulet/item.py +75 -0
- amulet/level/__init__.pyi +27 -0
- amulet/level/_load.py +100 -0
- amulet/level/abc/__init__.py +12 -0
- amulet/level/abc/_chunk_handle.py +335 -0
- amulet/level/abc/_dimension.py +86 -0
- amulet/level/abc/_history/__init__.py +1 -0
- amulet/level/abc/_history/_cache.py +224 -0
- amulet/level/abc/_history/_history_manager.py +291 -0
- amulet/level/abc/_level/__init__.py +5 -0
- amulet/level/abc/_level/_compactable_level.py +10 -0
- amulet/level/abc/_level/_creatable_level.py +29 -0
- amulet/level/abc/_level/_disk_level.py +17 -0
- amulet/level/abc/_level/_level.py +453 -0
- amulet/level/abc/_level/_loadable_level.py +42 -0
- amulet/level/abc/_player_storage.py +7 -0
- amulet/level/abc/_raw_level.py +187 -0
- amulet/level/abc/_registry.py +40 -0
- amulet/level/bedrock/__init__.py +2 -0
- amulet/level/bedrock/_chunk_handle.py +19 -0
- amulet/level/bedrock/_dimension.py +22 -0
- amulet/level/bedrock/_level.py +187 -0
- amulet/level/bedrock/_raw/__init__.py +5 -0
- amulet/level/bedrock/_raw/_actor_counter.py +53 -0
- amulet/level/bedrock/_raw/_chunk.py +54 -0
- amulet/level/bedrock/_raw/_chunk_decode.py +668 -0
- amulet/level/bedrock/_raw/_chunk_encode.py +602 -0
- amulet/level/bedrock/_raw/_constant.py +9 -0
- amulet/level/bedrock/_raw/_dimension.py +343 -0
- amulet/level/bedrock/_raw/_level.py +463 -0
- amulet/level/bedrock/_raw/_level_dat.py +90 -0
- amulet/level/bedrock/_raw/_typing.py +6 -0
- amulet/level/bedrock/_raw/leveldb_chunk_versions.py +83 -0
- amulet/level/bedrock/chunk/__init__.py +1 -0
- amulet/level/bedrock/chunk/_chunk.py +126 -0
- amulet/level/bedrock/chunk/components/__init__.py +0 -0
- amulet/level/bedrock/chunk/components/chunk_version.py +12 -0
- amulet/level/bedrock/chunk/components/finalised_state.py +13 -0
- amulet/level/bedrock/chunk/components/raw_chunk.py +15 -0
- amulet/level/construction/__init__.py +0 -0
- amulet/level/java/__init__.pyi +21 -0
- amulet/level/java/_chunk_handle.py +17 -0
- amulet/level/java/_chunk_handle.pyi +15 -0
- amulet/level/java/_dimension.py +20 -0
- amulet/level/java/_dimension.pyi +13 -0
- amulet/level/java/_level.py +184 -0
- amulet/level/java/_level.pyi +120 -0
- amulet/level/java/_raw/__init__.pyi +19 -0
- amulet/level/java/_raw/_chunk.pyi +23 -0
- amulet/level/java/_raw/_chunk_decode.py +561 -0
- amulet/level/java/_raw/_chunk_encode.py +463 -0
- amulet/level/java/_raw/_constant.py +9 -0
- amulet/level/java/_raw/_constant.pyi +20 -0
- amulet/level/java/_raw/_data_pack/__init__.py +2 -0
- amulet/level/java/_raw/_data_pack/__init__.pyi +8 -0
- amulet/level/java/_raw/_data_pack/data_pack.py +241 -0
- amulet/level/java/_raw/_data_pack/data_pack.pyi +197 -0
- amulet/level/java/_raw/_data_pack/data_pack_manager.py +77 -0
- amulet/level/java/_raw/_data_pack/data_pack_manager.pyi +75 -0
- amulet/level/java/_raw/_dimension.py +86 -0
- amulet/level/java/_raw/_dimension.pyi +72 -0
- amulet/level/java/_raw/_level.py +507 -0
- amulet/level/java/_raw/_level.pyi +238 -0
- amulet/level/java/_raw/_typing.py +3 -0
- amulet/level/java/_raw/_typing.pyi +5 -0
- amulet/level/java/anvil/__init__.py +2 -0
- amulet/level/java/anvil/__init__.pyi +11 -0
- amulet/level/java/anvil/_dimension.py +170 -0
- amulet/level/java/anvil/_dimension.pyi +109 -0
- amulet/level/java/anvil/_region.py +421 -0
- amulet/level/java/anvil/_region.pyi +197 -0
- amulet/level/java/anvil/_sector_manager.py +223 -0
- amulet/level/java/anvil/_sector_manager.pyi +142 -0
- amulet/level/java/chunk.pyi +81 -0
- amulet/level/java/chunk_/_chunk.py +260 -0
- amulet/level/java/chunk_/components/inhabited_time.py +12 -0
- amulet/level/java/chunk_/components/last_update.py +12 -0
- amulet/level/java/chunk_/components/legacy_version.py +12 -0
- amulet/level/java/chunk_/components/light_populated.py +12 -0
- amulet/level/java/chunk_/components/named_height_2d.py +37 -0
- amulet/level/java/chunk_/components/status.py +11 -0
- amulet/level/java/chunk_/components/terrain_populated.py +12 -0
- amulet/level/java/chunk_components.pyi +22 -0
- amulet/level/java/long_array.pyi +38 -0
- amulet/level/java_forge/__init__.py +0 -0
- amulet/level/mcstructure/__init__.py +0 -0
- amulet/level/nbt/__init__.py +0 -0
- amulet/level/schematic/__init__.py +0 -0
- amulet/level/sponge_schematic/__init__.py +0 -0
- amulet/level/temporary_level/__init__.py +1 -0
- amulet/level/temporary_level/_level.py +16 -0
- amulet/palette/__init__.pyi +8 -0
- amulet/palette/biome_palette.pyi +45 -0
- amulet/palette/block_palette.pyi +45 -0
- amulet/player.py +64 -0
- amulet/py.typed +0 -0
- amulet/selection/__init__.py +2 -0
- amulet/selection/abstract_selection.py +342 -0
- amulet/selection/box.py +852 -0
- amulet/selection/group.py +481 -0
- amulet/utils/__init__.pyi +28 -0
- amulet/utils/call_spec/__init__.py +24 -0
- amulet/utils/call_spec/__init__.pyi +53 -0
- amulet/utils/call_spec/_call_spec.py +262 -0
- amulet/utils/call_spec/_call_spec.pyi +272 -0
- amulet/utils/format_utils.py +41 -0
- amulet/utils/generator.py +18 -0
- amulet/utils/matrix.py +243 -0
- amulet/utils/matrix.pyi +177 -0
- amulet/utils/numpy.pyi +11 -0
- amulet/utils/numpy_helpers.py +19 -0
- amulet/utils/shareable_lock.py +335 -0
- amulet/utils/shareable_lock.pyi +190 -0
- amulet/utils/signal/__init__.py +10 -0
- amulet/utils/signal/__init__.pyi +25 -0
- amulet/utils/signal/_signal.py +228 -0
- amulet/utils/signal/_signal.pyi +84 -0
- amulet/utils/task_manager.py +235 -0
- amulet/utils/task_manager.pyi +168 -0
- amulet/utils/typed_property.py +111 -0
- amulet/utils/typing.py +4 -0
- amulet/utils/typing.pyi +6 -0
- amulet/utils/weakref.py +70 -0
- amulet/utils/weakref.pyi +50 -0
- amulet/utils/world_utils.py +102 -0
- amulet/utils/world_utils.pyi +109 -0
- amulet/version.cpp +136 -0
- amulet/version.hpp +142 -0
- amulet/version.pyi +94 -0
- amulet_core-2.0a5.dist-info/METADATA +103 -0
- amulet_core-2.0a5.dist-info/RECORD +210 -0
- amulet_core-2.0a5.dist-info/WHEEL +5 -0
- amulet_core-2.0a5.dist-info/entry_points.txt +2 -0
- amulet_core-2.0a5.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,668 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
import struct
|
|
3
|
+
from typing import Optional, TypeVar, TYPE_CHECKING, Callable
|
|
4
|
+
import logging
|
|
5
|
+
from functools import cache
|
|
6
|
+
|
|
7
|
+
import numpy
|
|
8
|
+
|
|
9
|
+
from amulet_nbt import (
|
|
10
|
+
CompoundTag,
|
|
11
|
+
IntTag,
|
|
12
|
+
FloatTag,
|
|
13
|
+
StringTag,
|
|
14
|
+
ListTag,
|
|
15
|
+
NamedTag,
|
|
16
|
+
ReadOffset,
|
|
17
|
+
load_array as load_nbt_array,
|
|
18
|
+
read_nbt,
|
|
19
|
+
utf8_escape_encoding,
|
|
20
|
+
)
|
|
21
|
+
|
|
22
|
+
from amulet.block import Block, BlockStack, PropertyValueType
|
|
23
|
+
from amulet.block_entity import BlockEntity
|
|
24
|
+
from amulet.entity import Entity
|
|
25
|
+
from amulet.biome import Biome
|
|
26
|
+
from amulet.palette import BlockPalette
|
|
27
|
+
from amulet.chunk import ComponentDataMapping
|
|
28
|
+
from amulet.chunk.components.sub_chunk_array import SubChunkArrayContainer
|
|
29
|
+
from amulet.chunk.components.block import BlockComponent, BlockComponentData
|
|
30
|
+
from amulet.chunk.components.block_entity import (
|
|
31
|
+
BlockEntityComponent,
|
|
32
|
+
BlockEntityComponentData,
|
|
33
|
+
)
|
|
34
|
+
from amulet.chunk.components.entity import EntityComponent, EntityComponentData
|
|
35
|
+
from amulet.chunk.components.height_2d import Height2DComponent
|
|
36
|
+
from amulet.chunk.components.biome import (
|
|
37
|
+
Biome2DComponent,
|
|
38
|
+
Biome2DComponentData,
|
|
39
|
+
Biome3DComponent,
|
|
40
|
+
Biome3DComponentData,
|
|
41
|
+
)
|
|
42
|
+
from amulet.game import get_game_version
|
|
43
|
+
from amulet.version import VersionNumber, VersionRange
|
|
44
|
+
|
|
45
|
+
from amulet.utils.world_utils import from_nibble_array
|
|
46
|
+
from amulet.utils.numpy import unique_inverse
|
|
47
|
+
|
|
48
|
+
from amulet.level.bedrock._raw import BedrockRawChunk
|
|
49
|
+
from amulet.level.bedrock.chunk import BedrockChunk, BedrockChunk0, BedrockChunk29
|
|
50
|
+
from amulet.level.bedrock.chunk.components.finalised_state import (
|
|
51
|
+
FinalisedStateComponent,
|
|
52
|
+
)
|
|
53
|
+
from amulet.level.bedrock.chunk.components.raw_chunk import RawChunkComponent
|
|
54
|
+
from amulet.level.bedrock.chunk.components.chunk_version import ChunkVersionComponent
|
|
55
|
+
|
|
56
|
+
if TYPE_CHECKING:
|
|
57
|
+
from ._level import BedrockRawLevel
|
|
58
|
+
from ._dimension import BedrockRawDimension
|
|
59
|
+
|
|
60
|
+
log = logging.getLogger(__name__)
|
|
61
|
+
|
|
62
|
+
SubChunkNDArray = numpy.ndarray
|
|
63
|
+
AnyNDArray = numpy.ndarray
|
|
64
|
+
T = TypeVar("T")
|
|
65
|
+
GetT = TypeVar("GetT")
|
|
66
|
+
SetT = TypeVar("SetT")
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
@cache
|
|
70
|
+
def unpack_block_version(block_version: int) -> VersionNumber:
|
|
71
|
+
return VersionNumber(*struct.unpack("4B", struct.pack(">i", block_version)))
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def raw_to_native(
|
|
75
|
+
raw_level: BedrockRawLevel,
|
|
76
|
+
dimension: BedrockRawDimension,
|
|
77
|
+
raw_chunk: BedrockRawChunk,
|
|
78
|
+
) -> BedrockChunk:
|
|
79
|
+
game_version = get_game_version("bedrock", raw_level.version)
|
|
80
|
+
max_version = game_version.max_version
|
|
81
|
+
|
|
82
|
+
floor_cy = dimension.bounds().min_y >> 4
|
|
83
|
+
|
|
84
|
+
chunk_data = raw_chunk.chunk_data
|
|
85
|
+
|
|
86
|
+
# Get the chunk format version
|
|
87
|
+
chunk_version_byte = chunk_data.pop(b",", None)
|
|
88
|
+
if chunk_version_byte is None:
|
|
89
|
+
chunk_version_byte = chunk_data.pop(b"v", None)
|
|
90
|
+
if chunk_version_byte is None:
|
|
91
|
+
raise RuntimeError
|
|
92
|
+
chunk_version = chunk_version_byte[0]
|
|
93
|
+
|
|
94
|
+
chunk_components: ComponentDataMapping = {} # type: ignore
|
|
95
|
+
chunk_class: type[BedrockChunk]
|
|
96
|
+
if chunk_version >= 29:
|
|
97
|
+
chunk_class = BedrockChunk29
|
|
98
|
+
else:
|
|
99
|
+
chunk_class = BedrockChunk0
|
|
100
|
+
|
|
101
|
+
version_range = VersionRange(
|
|
102
|
+
"bedrock",
|
|
103
|
+
VersionNumber(1, 0, 0),
|
|
104
|
+
max_version,
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
chunk_components[RawChunkComponent] = raw_chunk
|
|
108
|
+
chunk_components[ChunkVersionComponent] = chunk_version
|
|
109
|
+
|
|
110
|
+
# Parse finalised state
|
|
111
|
+
finalised_state = chunk_data.pop(b"\x36", None)
|
|
112
|
+
if finalised_state is None:
|
|
113
|
+
chunk_components[FinalisedStateComponent] = 2
|
|
114
|
+
elif len(finalised_state) == 1:
|
|
115
|
+
# old versions of the game store this as a byte
|
|
116
|
+
chunk_components[FinalisedStateComponent] = struct.unpack("b", finalised_state)[
|
|
117
|
+
0
|
|
118
|
+
]
|
|
119
|
+
elif len(finalised_state) == 4:
|
|
120
|
+
# newer versions store it as an int
|
|
121
|
+
chunk_components[FinalisedStateComponent] = struct.unpack(
|
|
122
|
+
"<i", finalised_state
|
|
123
|
+
)[0]
|
|
124
|
+
|
|
125
|
+
# Parse blocks
|
|
126
|
+
blocks: list[Block] = []
|
|
127
|
+
for block in dimension.default_block():
|
|
128
|
+
if version_range.contains(block.platform, block.version):
|
|
129
|
+
blocks.append(block)
|
|
130
|
+
else:
|
|
131
|
+
block_ = get_game_version(block.platform, block.version).block.translate(
|
|
132
|
+
"bedrock", max_version, block
|
|
133
|
+
)[0]
|
|
134
|
+
if isinstance(block_, Block):
|
|
135
|
+
blocks.append(block_)
|
|
136
|
+
chunk_components[BlockComponent] = block_component_data = BlockComponentData(
|
|
137
|
+
version_range, (16, 16, 16), BlockStack(*blocks)
|
|
138
|
+
)
|
|
139
|
+
if chunk_version >= 3:
|
|
140
|
+
subchunks = {}
|
|
141
|
+
for key in chunk_data.copy().keys():
|
|
142
|
+
if len(key) == 2 and key[0] == 0x2F:
|
|
143
|
+
cy = struct.unpack("b", key[1:2])[0]
|
|
144
|
+
if 25 <= chunk_version <= 28:
|
|
145
|
+
cy += floor_cy
|
|
146
|
+
subchunks[cy] = chunk_data.pop(key)
|
|
147
|
+
_load_subchunks(raw_level, subchunks, block_component_data)
|
|
148
|
+
else:
|
|
149
|
+
section_data = chunk_data.pop(b"\x30", None)
|
|
150
|
+
if section_data is not None:
|
|
151
|
+
block_ids = numpy.frombuffer(
|
|
152
|
+
section_data[: 2**15], dtype=numpy.uint8
|
|
153
|
+
).astype(numpy.uint32)
|
|
154
|
+
block_data = from_nibble_array(
|
|
155
|
+
numpy.frombuffer(section_data[2**15 : 2**15 + 2**14], dtype=numpy.uint8)
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
# there is other data here but we are going to skip over it
|
|
159
|
+
combined_palette, block_array = unique_inverse(
|
|
160
|
+
(block_ids << 4) + block_data
|
|
161
|
+
)
|
|
162
|
+
block_array = numpy.transpose(block_array.reshape(16, 16, 128), (0, 2, 1))
|
|
163
|
+
block_component_data.sections = {
|
|
164
|
+
i: block_array[:, i * 16 : (i + 1) * 16, :] for i in range(8)
|
|
165
|
+
}
|
|
166
|
+
palette: AnyNDArray = numpy.array(
|
|
167
|
+
[combined_palette >> 4, combined_palette & 15]
|
|
168
|
+
).T
|
|
169
|
+
chunk_palette = numpy.empty(len(palette), dtype=object)
|
|
170
|
+
for i, b in enumerate(palette):
|
|
171
|
+
chunk_palette[i] = ((None, tuple(b)),)
|
|
172
|
+
raise NotImplementedError("Need to set up the block palette")
|
|
173
|
+
|
|
174
|
+
# Parse block entities
|
|
175
|
+
block_entity_component_data = BlockEntityComponentData(version_range)
|
|
176
|
+
block_entities = _unpack_nbt_list(chunk_data.pop(b"\x31", b""))
|
|
177
|
+
block_entity_component_data.update(_decode_block_entity_list(block_entities))
|
|
178
|
+
chunk_components[BlockEntityComponent] = block_entity_component_data
|
|
179
|
+
|
|
180
|
+
# Parse entities
|
|
181
|
+
entity_component_data = EntityComponentData(version_range)
|
|
182
|
+
entities = _unpack_nbt_list(chunk_data.pop(b"\x32", b""))
|
|
183
|
+
entity_component_data |= set(
|
|
184
|
+
_decode_entity_list(entities) + _decode_entity_list(raw_chunk.entity_actor)
|
|
185
|
+
)
|
|
186
|
+
raw_chunk.entity_actor.clear()
|
|
187
|
+
chunk_components[EntityComponent] = entity_component_data
|
|
188
|
+
|
|
189
|
+
# Parse biome and height data
|
|
190
|
+
default_biome = dimension.default_biome()
|
|
191
|
+
if not version_range.contains(default_biome.platform, default_biome.version):
|
|
192
|
+
default_biome = get_game_version(
|
|
193
|
+
default_biome.platform, default_biome.version
|
|
194
|
+
).biome.translate("bedrock", max_version, default_biome)
|
|
195
|
+
if chunk_class.has_component(Biome3DComponent):
|
|
196
|
+
chunk_components[Biome3DComponent] = biome_3d = Biome3DComponentData(
|
|
197
|
+
version_range, (16, 16, 16), default_biome
|
|
198
|
+
)
|
|
199
|
+
if b"+" in chunk_data:
|
|
200
|
+
d2d = chunk_data[b"+"]
|
|
201
|
+
chunk_components[Height2DComponent] = (
|
|
202
|
+
numpy.frombuffer(d2d[:512], "<i2").reshape((16, 16)).astype(numpy.int64)
|
|
203
|
+
)
|
|
204
|
+
_decode_3d_biomes(
|
|
205
|
+
raw_level, biome_3d, d2d[512:], dimension.bounds().min_y >> 4
|
|
206
|
+
)
|
|
207
|
+
else:
|
|
208
|
+
chunk_components[Height2DComponent] = numpy.zeros((16, 16), numpy.int64)
|
|
209
|
+
elif chunk_class.has_component(Biome2DComponent):
|
|
210
|
+
chunk_components[Biome2DComponent] = biome_2d = Biome2DComponentData(
|
|
211
|
+
version_range, (16, 16), default_biome
|
|
212
|
+
)
|
|
213
|
+
if b"\x2D" in chunk_data:
|
|
214
|
+
d2d = chunk_data[b"\x2D"]
|
|
215
|
+
chunk_components[Height2DComponent] = (
|
|
216
|
+
numpy.frombuffer(d2d[:512], "<i2").reshape((16, 16)).astype(numpy.int64)
|
|
217
|
+
)
|
|
218
|
+
_decode_2d_biomes(
|
|
219
|
+
biome_2d,
|
|
220
|
+
raw_level.version,
|
|
221
|
+
numpy.frombuffer(d2d[512:], dtype="uint8"),
|
|
222
|
+
raw_level.biome_id_override.numerical_id_to_namespace_id,
|
|
223
|
+
game_version.biome.numerical_id_to_namespace_id,
|
|
224
|
+
)
|
|
225
|
+
else:
|
|
226
|
+
chunk_components[Height2DComponent] = numpy.zeros((16, 16), numpy.int64)
|
|
227
|
+
else:
|
|
228
|
+
raise RuntimeError
|
|
229
|
+
|
|
230
|
+
# TODO: implement key support
|
|
231
|
+
# \x33 ticks
|
|
232
|
+
# \x34 block extra data
|
|
233
|
+
# \x35 biome state
|
|
234
|
+
# \x39 7 ints and an end (03)? Honestly don't know what this is
|
|
235
|
+
# \x3A fire tick?
|
|
236
|
+
|
|
237
|
+
# \x2E 2d legacy
|
|
238
|
+
# \x30 legacy terrain
|
|
239
|
+
|
|
240
|
+
return chunk_class.from_component_data(chunk_components)
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def _load_palettized_subchunk(
|
|
244
|
+
data: bytes,
|
|
245
|
+
blocks: SubChunkArrayContainer,
|
|
246
|
+
block_palette: BlockPalette,
|
|
247
|
+
storage_count: int,
|
|
248
|
+
cy: int,
|
|
249
|
+
) -> None:
|
|
250
|
+
"""Load a sub-chunk stored in the palettized format."""
|
|
251
|
+
sub_chunk_blocks = numpy.zeros((16, 16, 16, storage_count), dtype=numpy.uint32)
|
|
252
|
+
sub_chunk_palette: list[list[Block]] = []
|
|
253
|
+
for storage_index in range(storage_count):
|
|
254
|
+
(
|
|
255
|
+
sub_chunk_blocks[:, :, :, storage_index],
|
|
256
|
+
palette_data,
|
|
257
|
+
data,
|
|
258
|
+
) = _load_palette_blocks(data)
|
|
259
|
+
palette_data_out: list[Block] = []
|
|
260
|
+
for block_nt in palette_data:
|
|
261
|
+
block = block_nt.compound
|
|
262
|
+
block_name = block.get_string("name")
|
|
263
|
+
assert block_name is not None
|
|
264
|
+
*namespace_, base_name = block_name.py_str.split(":", 1)
|
|
265
|
+
namespace = namespace_[0] if namespace_ else "minecraft"
|
|
266
|
+
|
|
267
|
+
properties: dict[str, PropertyValueType]
|
|
268
|
+
if "states" in block:
|
|
269
|
+
states = block.get_compound("states")
|
|
270
|
+
assert states is not None
|
|
271
|
+
properties = {
|
|
272
|
+
k: v
|
|
273
|
+
for k, v in states.items()
|
|
274
|
+
if isinstance(k, str) and isinstance(v, PropertyValueType)
|
|
275
|
+
}
|
|
276
|
+
version = unpack_block_version(
|
|
277
|
+
block.get_int("version", IntTag(17694720)).py_int
|
|
278
|
+
)
|
|
279
|
+
elif "val" in block:
|
|
280
|
+
val = block.get_int("val")
|
|
281
|
+
assert val is not None
|
|
282
|
+
properties = {"block_data": IntTag(val.py_int)}
|
|
283
|
+
version = VersionNumber(1, 12)
|
|
284
|
+
else:
|
|
285
|
+
properties = {}
|
|
286
|
+
version = unpack_block_version(
|
|
287
|
+
block.get_int("version", IntTag(17694720)).py_int
|
|
288
|
+
)
|
|
289
|
+
|
|
290
|
+
palette_data_out.append(
|
|
291
|
+
Block(
|
|
292
|
+
"bedrock",
|
|
293
|
+
version,
|
|
294
|
+
namespace=namespace,
|
|
295
|
+
base_name=base_name,
|
|
296
|
+
properties=properties,
|
|
297
|
+
)
|
|
298
|
+
)
|
|
299
|
+
sub_chunk_palette.append(palette_data_out)
|
|
300
|
+
|
|
301
|
+
if storage_count == 1:
|
|
302
|
+
block_lut = [
|
|
303
|
+
block_palette.block_stack_to_index(BlockStack(block))
|
|
304
|
+
for block in sub_chunk_palette[0]
|
|
305
|
+
]
|
|
306
|
+
blocks[cy] = numpy.array(block_lut, dtype=numpy.uint32)[
|
|
307
|
+
sub_chunk_blocks[:, :, :, 0]
|
|
308
|
+
]
|
|
309
|
+
elif storage_count > 1:
|
|
310
|
+
# we have two or more storages so need to find the unique block combinations and merge them together
|
|
311
|
+
sub_chunk_palette_, sub_chunk_blocks = numpy.unique(
|
|
312
|
+
sub_chunk_blocks.reshape(-1, storage_count),
|
|
313
|
+
return_inverse=True,
|
|
314
|
+
axis=0,
|
|
315
|
+
)
|
|
316
|
+
block_lut = [
|
|
317
|
+
block_palette.block_stack_to_index(
|
|
318
|
+
BlockStack(
|
|
319
|
+
*(
|
|
320
|
+
sub_chunk_palette[storage_index][index]
|
|
321
|
+
for storage_index, index in enumerate(palette_indexes)
|
|
322
|
+
if storage_index == 0
|
|
323
|
+
or sub_chunk_palette[storage_index][index].namespaced_name
|
|
324
|
+
!= "minecraft:air"
|
|
325
|
+
)
|
|
326
|
+
)
|
|
327
|
+
)
|
|
328
|
+
for palette_indexes in sub_chunk_palette_
|
|
329
|
+
]
|
|
330
|
+
blocks[cy] = numpy.array(block_lut, dtype=numpy.uint32)[
|
|
331
|
+
sub_chunk_blocks.reshape(16, 16, 16)
|
|
332
|
+
]
|
|
333
|
+
|
|
334
|
+
|
|
335
|
+
def _load_binary_subchunk(
|
|
336
|
+
data: bytes,
|
|
337
|
+
level: BedrockRawLevel,
|
|
338
|
+
blocks: SubChunkArrayContainer,
|
|
339
|
+
block_palette: BlockPalette,
|
|
340
|
+
cy: int,
|
|
341
|
+
) -> None:
|
|
342
|
+
block_ids = numpy.frombuffer(data[: 2**12], dtype=numpy.uint8).astype(numpy.uint32)
|
|
343
|
+
block_data = from_nibble_array(
|
|
344
|
+
numpy.frombuffer(data[2**12 : 2**12 + 2**11], dtype=numpy.uint8)
|
|
345
|
+
)
|
|
346
|
+
numerical_palette, block_array = unique_inverse((block_ids << 4) + block_data)
|
|
347
|
+
block_array = numpy.transpose(block_array.reshape(16, 16, 16), (0, 2, 1))
|
|
348
|
+
block_lut: list[int] = []
|
|
349
|
+
for block_id, block_data in numpy.array(
|
|
350
|
+
[numerical_palette >> 4, numerical_palette & 15]
|
|
351
|
+
).T:
|
|
352
|
+
try:
|
|
353
|
+
(
|
|
354
|
+
namespace,
|
|
355
|
+
base_name,
|
|
356
|
+
) = level.block_id_override.numerical_id_to_namespace_id(block_id)
|
|
357
|
+
except KeyError:
|
|
358
|
+
try:
|
|
359
|
+
namespace, base_name = get_game_version(
|
|
360
|
+
"bedrock", level.version
|
|
361
|
+
).block.numerical_id_to_namespace_id(block_id)
|
|
362
|
+
except KeyError:
|
|
363
|
+
namespace = "numerical"
|
|
364
|
+
base_name = str(block_id)
|
|
365
|
+
|
|
366
|
+
block_lut.append(
|
|
367
|
+
block_palette.block_stack_to_index(
|
|
368
|
+
BlockStack(
|
|
369
|
+
Block(
|
|
370
|
+
"bedrock",
|
|
371
|
+
VersionNumber(1, 12),
|
|
372
|
+
namespace=namespace,
|
|
373
|
+
base_name=base_name,
|
|
374
|
+
properties={"block_data": IntTag(block_data)},
|
|
375
|
+
)
|
|
376
|
+
)
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
|
|
380
|
+
blocks[cy] = numpy.array(block_lut, dtype=numpy.uint32)[block_array]
|
|
381
|
+
|
|
382
|
+
|
|
383
|
+
def _load_subchunks(
|
|
384
|
+
level: BedrockRawLevel, subchunks: dict[int, bytes], block_data: BlockComponentData
|
|
385
|
+
) -> None:
|
|
386
|
+
"""
|
|
387
|
+
Load a list of bytes objects which contain chunk data into the chunk.
|
|
388
|
+
This function should be able to load all sub-chunk formats (technically before it)
|
|
389
|
+
All sub-chunks will almost certainly all have the same sub-chunk version but
|
|
390
|
+
it should be able to handle a case where that is not true.
|
|
391
|
+
|
|
392
|
+
The newer formats allow multiple blocks to occupy the same space and the
|
|
393
|
+
newer versions also include a version ber block.
|
|
394
|
+
"""
|
|
395
|
+
blocks: SubChunkArrayContainer = block_data.sections
|
|
396
|
+
block_palette: BlockPalette = block_data.palette
|
|
397
|
+
for cy, data in subchunks.items():
|
|
398
|
+
sub_chunk_version, data = data[0], data[1:]
|
|
399
|
+
|
|
400
|
+
if sub_chunk_version == 9:
|
|
401
|
+
# There is an extra byte in this format storing the cy value
|
|
402
|
+
storage_count, cy, data = (
|
|
403
|
+
data[0],
|
|
404
|
+
struct.unpack("b", data[1:2])[0],
|
|
405
|
+
data[2:],
|
|
406
|
+
)
|
|
407
|
+
_load_palettized_subchunk(data, blocks, block_palette, storage_count, cy)
|
|
408
|
+
|
|
409
|
+
elif sub_chunk_version == 8:
|
|
410
|
+
storage_count, data = data[0], data[1:]
|
|
411
|
+
_load_palettized_subchunk(data, blocks, block_palette, storage_count, cy)
|
|
412
|
+
|
|
413
|
+
elif sub_chunk_version == 1:
|
|
414
|
+
_load_palettized_subchunk(data, blocks, block_palette, 1, cy)
|
|
415
|
+
|
|
416
|
+
elif 0 <= sub_chunk_version <= 7:
|
|
417
|
+
_load_binary_subchunk(data, level, blocks, block_palette, cy)
|
|
418
|
+
else:
|
|
419
|
+
raise Exception(f"sub-chunk version {sub_chunk_version} is not known.")
|
|
420
|
+
|
|
421
|
+
|
|
422
|
+
def _load_palette_blocks(
|
|
423
|
+
data: bytes,
|
|
424
|
+
) -> tuple[numpy.ndarray, list[NamedTag], bytes]:
|
|
425
|
+
data, _, blocks = _decode_packed_array(data)
|
|
426
|
+
if blocks is None:
|
|
427
|
+
blocks = numpy.zeros((16, 16, 16), dtype=numpy.int16)
|
|
428
|
+
palette_len = 1
|
|
429
|
+
else:
|
|
430
|
+
palette_len, data = struct.unpack("<I", data[:4])[0], data[4:]
|
|
431
|
+
|
|
432
|
+
if palette_len:
|
|
433
|
+
read_offset = ReadOffset()
|
|
434
|
+
palette = load_nbt_array(
|
|
435
|
+
data,
|
|
436
|
+
compressed=False,
|
|
437
|
+
count=palette_len,
|
|
438
|
+
little_endian=True,
|
|
439
|
+
read_offset=read_offset,
|
|
440
|
+
string_encoding=utf8_escape_encoding,
|
|
441
|
+
)
|
|
442
|
+
data = data[read_offset.offset :]
|
|
443
|
+
else:
|
|
444
|
+
palette = [
|
|
445
|
+
NamedTag(
|
|
446
|
+
CompoundTag(
|
|
447
|
+
{
|
|
448
|
+
"name": StringTag("minecraft:air"),
|
|
449
|
+
"states": CompoundTag(),
|
|
450
|
+
"version": IntTag(17694723),
|
|
451
|
+
}
|
|
452
|
+
)
|
|
453
|
+
)
|
|
454
|
+
]
|
|
455
|
+
|
|
456
|
+
return blocks, palette, data
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def _decode_2d_biomes(
|
|
460
|
+
biome_2d_data: Biome2DComponentData,
|
|
461
|
+
version: VersionNumber,
|
|
462
|
+
arr: numpy.ndarray,
|
|
463
|
+
numerical_id_to_namespace_id_override: Callable[[int], tuple[str, str]],
|
|
464
|
+
numerical_id_to_namespace_id: Callable[[int], tuple[str, str]],
|
|
465
|
+
) -> None:
|
|
466
|
+
numerical_ids, arr = numpy.unique(arr, return_inverse=True)
|
|
467
|
+
arr = arr.reshape(16, 16).T.astype(numpy.uint32)
|
|
468
|
+
lut = []
|
|
469
|
+
for numerical_id in numerical_ids:
|
|
470
|
+
try:
|
|
471
|
+
(
|
|
472
|
+
namespace,
|
|
473
|
+
base_name,
|
|
474
|
+
) = numerical_id_to_namespace_id_override(numerical_id)
|
|
475
|
+
except KeyError:
|
|
476
|
+
namespace, base_name = numerical_id_to_namespace_id(numerical_id)
|
|
477
|
+
biome = Biome("bedrock", version, namespace, base_name)
|
|
478
|
+
runtime_id = biome_2d_data.palette.biome_to_index(biome)
|
|
479
|
+
lut.append(runtime_id)
|
|
480
|
+
biome_2d_data.array[:, :] = numpy.array(lut, dtype=numpy.uint32)[arr]
|
|
481
|
+
|
|
482
|
+
|
|
483
|
+
def _decode_3d_biomes(
|
|
484
|
+
raw_level: BedrockRawLevel,
|
|
485
|
+
biome_3d_data: Biome3DComponentData,
|
|
486
|
+
data: bytes,
|
|
487
|
+
floor_cy: int,
|
|
488
|
+
) -> None:
|
|
489
|
+
# TODO: how does Bedrock store custom biomes?
|
|
490
|
+
# TODO: can I use one global lookup based on the max version or does it need to be done for the version the chunk was saved in?
|
|
491
|
+
|
|
492
|
+
# The 3D biome format consists of 25 16x arrays with the first array corresponding to the lowest sub-chunk in the world
|
|
493
|
+
# This is -64 in the overworld and 0 in the nether and end
|
|
494
|
+
cy = floor_cy
|
|
495
|
+
game_version = get_game_version("bedrock", raw_level.version)
|
|
496
|
+
while data:
|
|
497
|
+
data, bits_per_value, arr = _decode_packed_array(data)
|
|
498
|
+
if bits_per_value == 0:
|
|
499
|
+
numerical_id = struct.unpack(f"<I", data[:4])[0]
|
|
500
|
+
try:
|
|
501
|
+
(
|
|
502
|
+
namespace,
|
|
503
|
+
base_name,
|
|
504
|
+
) = raw_level.biome_id_override.numerical_id_to_namespace_id(
|
|
505
|
+
numerical_id
|
|
506
|
+
)
|
|
507
|
+
except KeyError:
|
|
508
|
+
namespace, base_name = game_version.biome.numerical_id_to_namespace_id(
|
|
509
|
+
numerical_id
|
|
510
|
+
)
|
|
511
|
+
# TODO: should this be based on the chunk version?
|
|
512
|
+
runtime_id = biome_3d_data.palette.biome_to_index(
|
|
513
|
+
Biome("bedrock", raw_level.version, namespace, base_name)
|
|
514
|
+
)
|
|
515
|
+
biome_3d_data.sections[cy] = numpy.full(
|
|
516
|
+
(16, 16, 16), runtime_id, dtype=numpy.uint32
|
|
517
|
+
)
|
|
518
|
+
data = data[4:]
|
|
519
|
+
elif bits_per_value > 0:
|
|
520
|
+
palette_len, data = struct.unpack("<I", data[:4])[0], data[4:]
|
|
521
|
+
numerical_palette = numpy.frombuffer(data, "<i4", palette_len)
|
|
522
|
+
lut = []
|
|
523
|
+
for numerical_id in numerical_palette:
|
|
524
|
+
try:
|
|
525
|
+
(
|
|
526
|
+
namespace,
|
|
527
|
+
base_name,
|
|
528
|
+
) = raw_level.biome_id_override.numerical_id_to_namespace_id(
|
|
529
|
+
numerical_id
|
|
530
|
+
)
|
|
531
|
+
except KeyError:
|
|
532
|
+
namespace, base_name = (
|
|
533
|
+
game_version.biome.numerical_id_to_namespace_id(numerical_id)
|
|
534
|
+
)
|
|
535
|
+
# TODO: should this be based on the chunk version?
|
|
536
|
+
runtime_id = biome_3d_data.palette.biome_to_index(
|
|
537
|
+
Biome("bedrock", raw_level.version, namespace, base_name)
|
|
538
|
+
)
|
|
539
|
+
lut.append(runtime_id)
|
|
540
|
+
biome_3d_data.sections[cy] = numpy.array(lut, dtype=numpy.uint32)[arr]
|
|
541
|
+
data = data[4 * palette_len :]
|
|
542
|
+
cy += 1
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
def _decode_packed_array(data: bytes) -> tuple[bytes, int, Optional[numpy.ndarray]]:
|
|
546
|
+
"""
|
|
547
|
+
Parse a packed array as documented here
|
|
548
|
+
https://gist.github.com/Tomcc/a96af509e275b1af483b25c543cfbf37
|
|
549
|
+
|
|
550
|
+
:param data: The data to parse
|
|
551
|
+
:return:
|
|
552
|
+
"""
|
|
553
|
+
# Ignore LSB of data (it is a flag) and get compacting level
|
|
554
|
+
bits_per_value, data = struct.unpack("b", data[0:1])[0] >> 1, data[1:]
|
|
555
|
+
if bits_per_value > 0:
|
|
556
|
+
values_per_word = 32 // bits_per_value # Word = 4 bytes, basis of compacting.
|
|
557
|
+
word_count = -(
|
|
558
|
+
-4096 // values_per_word
|
|
559
|
+
) # Ceiling divide is inverted floor divide
|
|
560
|
+
|
|
561
|
+
arr = numpy.packbits(
|
|
562
|
+
numpy.pad(
|
|
563
|
+
numpy.unpackbits(
|
|
564
|
+
numpy.frombuffer(
|
|
565
|
+
bytes(reversed(data[: 4 * word_count])), dtype="uint8"
|
|
566
|
+
)
|
|
567
|
+
)
|
|
568
|
+
.reshape(-1, 32)[:, -values_per_word * bits_per_value :]
|
|
569
|
+
.reshape(-1, bits_per_value)[-4096:, :],
|
|
570
|
+
[(0, 0), (16 - bits_per_value, 0)],
|
|
571
|
+
"constant",
|
|
572
|
+
)
|
|
573
|
+
).view(dtype=">i2")[::-1]
|
|
574
|
+
arr = arr.reshape((16, 16, 16)).swapaxes(1, 2)
|
|
575
|
+
data = data[4 * word_count :]
|
|
576
|
+
else:
|
|
577
|
+
arr = None
|
|
578
|
+
return data, bits_per_value, arr
|
|
579
|
+
|
|
580
|
+
|
|
581
|
+
def _unpack_nbt_list(raw_nbt: bytes) -> list[NamedTag]:
|
|
582
|
+
nbt_list = []
|
|
583
|
+
while raw_nbt:
|
|
584
|
+
read_offset = ReadOffset()
|
|
585
|
+
nbt = read_nbt(
|
|
586
|
+
raw_nbt,
|
|
587
|
+
little_endian=True,
|
|
588
|
+
read_offset=read_offset,
|
|
589
|
+
string_encoding=utf8_escape_encoding,
|
|
590
|
+
)
|
|
591
|
+
raw_nbt = raw_nbt[read_offset.offset :]
|
|
592
|
+
nbt_list.append(nbt)
|
|
593
|
+
return nbt_list
|
|
594
|
+
|
|
595
|
+
|
|
596
|
+
def pop_nbt(tag: CompoundTag, key: str, dtype: type[T]) -> T:
|
|
597
|
+
value = tag.pop(key)
|
|
598
|
+
if not isinstance(value, dtype):
|
|
599
|
+
raise TypeError
|
|
600
|
+
return value
|
|
601
|
+
|
|
602
|
+
|
|
603
|
+
def _decode_block_entity_list(
|
|
604
|
+
block_entities: list[NamedTag],
|
|
605
|
+
) -> list[tuple[tuple[int, int, int], BlockEntity]]:
|
|
606
|
+
return [ent for ent in map(_decode_block_entity, block_entities) if ent is not None]
|
|
607
|
+
|
|
608
|
+
|
|
609
|
+
def _decode_block_entity(
|
|
610
|
+
nbt: NamedTag,
|
|
611
|
+
) -> Optional[tuple[tuple[int, int, int], BlockEntity]]:
|
|
612
|
+
try:
|
|
613
|
+
tag = nbt.compound
|
|
614
|
+
namespace = ""
|
|
615
|
+
base_name = pop_nbt(tag, "id", StringTag).py_str
|
|
616
|
+
if not base_name:
|
|
617
|
+
raise Exception("entity id is empty")
|
|
618
|
+
x = pop_nbt(tag, "x", IntTag).py_int
|
|
619
|
+
y = pop_nbt(tag, "y", IntTag).py_int
|
|
620
|
+
z = pop_nbt(tag, "z", IntTag).py_int
|
|
621
|
+
return (x, y, z), BlockEntity(
|
|
622
|
+
platform="bedrock",
|
|
623
|
+
version=VersionNumber(1, 0, 0),
|
|
624
|
+
namespace=namespace,
|
|
625
|
+
base_name=base_name,
|
|
626
|
+
nbt=nbt,
|
|
627
|
+
)
|
|
628
|
+
except Exception as e:
|
|
629
|
+
log.exception(e)
|
|
630
|
+
return None
|
|
631
|
+
|
|
632
|
+
|
|
633
|
+
def _decode_entity_list(entities: list[NamedTag]) -> list[Entity]:
|
|
634
|
+
return [ent for ent in map(_decode_entity, entities) if ent is not None]
|
|
635
|
+
|
|
636
|
+
|
|
637
|
+
def _decode_entity(
|
|
638
|
+
nbt: NamedTag,
|
|
639
|
+
) -> Optional[Entity]:
|
|
640
|
+
try:
|
|
641
|
+
tag = nbt.compound
|
|
642
|
+
if "identifier" in tag:
|
|
643
|
+
namespace, base_name = pop_nbt(tag, "identifier", StringTag).py_str.split(
|
|
644
|
+
":", 1
|
|
645
|
+
)
|
|
646
|
+
elif "id" in tag:
|
|
647
|
+
# TODO: we should probably look up the real entity id
|
|
648
|
+
namespace = "numerical"
|
|
649
|
+
base_name = str(pop_nbt(tag, "id", IntTag).py_int)
|
|
650
|
+
else:
|
|
651
|
+
raise Exception("tag does not have identifier or id keys.")
|
|
652
|
+
|
|
653
|
+
pos = pop_nbt(tag, "Pos", ListTag)
|
|
654
|
+
x, y, z = (v.py_float for v in pos if isinstance(v, FloatTag))
|
|
655
|
+
|
|
656
|
+
return Entity(
|
|
657
|
+
platform="bedrock",
|
|
658
|
+
version=VersionNumber(1, 0, 0),
|
|
659
|
+
namespace=namespace,
|
|
660
|
+
base_name=base_name,
|
|
661
|
+
x=x,
|
|
662
|
+
y=y,
|
|
663
|
+
z=z,
|
|
664
|
+
nbt=nbt,
|
|
665
|
+
)
|
|
666
|
+
except Exception as e:
|
|
667
|
+
log.error(e)
|
|
668
|
+
return None
|