amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.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__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,515 +1,515 @@
|
|
|
1
|
-
import os
|
|
2
|
-
from typing import (
|
|
3
|
-
Optional,
|
|
4
|
-
List,
|
|
5
|
-
Tuple,
|
|
6
|
-
Dict,
|
|
7
|
-
Iterable,
|
|
8
|
-
TYPE_CHECKING,
|
|
9
|
-
BinaryIO,
|
|
10
|
-
Any,
|
|
11
|
-
Union,
|
|
12
|
-
)
|
|
13
|
-
import numpy
|
|
14
|
-
from io import BytesIO
|
|
15
|
-
import struct
|
|
16
|
-
import copy
|
|
17
|
-
import logging
|
|
18
|
-
|
|
19
|
-
from amulet_nbt import (
|
|
20
|
-
ByteTag,
|
|
21
|
-
IntTag,
|
|
22
|
-
StringTag,
|
|
23
|
-
ListTag,
|
|
24
|
-
CompoundTag,
|
|
25
|
-
ByteArrayTag,
|
|
26
|
-
IntArrayTag,
|
|
27
|
-
NamedTag,
|
|
28
|
-
load as load_nbt,
|
|
29
|
-
)
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
from amulet.api.data_types import (
|
|
33
|
-
AnyNDArray,
|
|
34
|
-
VersionNumberAny,
|
|
35
|
-
VersionNumberTuple,
|
|
36
|
-
Dimension,
|
|
37
|
-
PlatformType,
|
|
38
|
-
ChunkCoordinates,
|
|
39
|
-
)
|
|
40
|
-
from amulet.api.registry import BlockManager
|
|
41
|
-
from amulet.api.wrapper import StructureFormatWrapper
|
|
42
|
-
from amulet.api.chunk import Chunk
|
|
43
|
-
from amulet.api.selection import SelectionGroup, SelectionBox
|
|
44
|
-
from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError
|
|
45
|
-
|
|
46
|
-
from .section import ConstructionSection
|
|
47
|
-
from .interface import Construction0Interface, ConstructionInterface
|
|
48
|
-
from .util import (
|
|
49
|
-
unpack_palette,
|
|
50
|
-
parse_entities,
|
|
51
|
-
parse_block_entities,
|
|
52
|
-
serialise_entities,
|
|
53
|
-
serialise_block_entities,
|
|
54
|
-
find_fitting_array_type,
|
|
55
|
-
pack_palette,
|
|
56
|
-
)
|
|
57
|
-
|
|
58
|
-
if TYPE_CHECKING:
|
|
59
|
-
from amulet.api.wrapper import Translator, Interface
|
|
60
|
-
|
|
61
|
-
log = logging.getLogger(__name__)
|
|
62
|
-
|
|
63
|
-
construction_0_interface = Construction0Interface()
|
|
64
|
-
|
|
65
|
-
INT_STRUCT = struct.Struct(">I")
|
|
66
|
-
SECTION_ENTRY_TYPE = numpy.dtype(
|
|
67
|
-
[
|
|
68
|
-
("sx", "i4"),
|
|
69
|
-
("sy", "i4"),
|
|
70
|
-
("sz", "i4"),
|
|
71
|
-
("shapex", "i1"),
|
|
72
|
-
("shapey", "i1"),
|
|
73
|
-
("shapez", "i1"),
|
|
74
|
-
("position", "i4"),
|
|
75
|
-
("length", "i4"),
|
|
76
|
-
]
|
|
77
|
-
)
|
|
78
|
-
|
|
79
|
-
magic_num = b"constrct"
|
|
80
|
-
magic_num_len = len(magic_num)
|
|
81
|
-
|
|
82
|
-
max_format_version = 0
|
|
83
|
-
max_section_version = 0
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
class ConstructionFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
|
|
87
|
-
"""
|
|
88
|
-
This FormatWrapper class exists to interface with the construction format.
|
|
89
|
-
"""
|
|
90
|
-
|
|
91
|
-
_format_version: int
|
|
92
|
-
_section_version: int
|
|
93
|
-
_chunk_to_section: Dict[Tuple[int, int], List[ConstructionSection]]
|
|
94
|
-
_selection_boxes: List[SelectionBox]
|
|
95
|
-
_chunk_to_box: Dict[Tuple[int, int], List[SelectionBox]]
|
|
96
|
-
|
|
97
|
-
def __init__(self, path: str):
|
|
98
|
-
"""
|
|
99
|
-
Construct a new instance of :class:`ConstructionFormatWrapper`.
|
|
100
|
-
|
|
101
|
-
This should not be used directly. You should instead use :func:`amulet.load_format`.
|
|
102
|
-
|
|
103
|
-
:param path: The file path to the serialised data.
|
|
104
|
-
"""
|
|
105
|
-
super().__init__(path)
|
|
106
|
-
|
|
107
|
-
self._format_version = max_format_version
|
|
108
|
-
self._section_version = max_section_version
|
|
109
|
-
|
|
110
|
-
# which sections are in a given chunk
|
|
111
|
-
self._chunk_to_section = {}
|
|
112
|
-
|
|
113
|
-
self._selection_boxes = []
|
|
114
|
-
|
|
115
|
-
# which selection boxes intersect a given chunk (boxes are clipped to the size of the chunk)
|
|
116
|
-
self._chunk_to_box = {}
|
|
117
|
-
|
|
118
|
-
self._shallow_load()
|
|
119
|
-
|
|
120
|
-
def _shallow_load(self):
|
|
121
|
-
if os.path.isfile(self.path):
|
|
122
|
-
with open(self.path, "rb") as f:
|
|
123
|
-
magic_num_1 = f.read(magic_num_len)
|
|
124
|
-
if magic_num_1 == magic_num:
|
|
125
|
-
format_version = struct.unpack(">B", f.read(1))[0]
|
|
126
|
-
if format_version == 0:
|
|
127
|
-
f.seek(-magic_num_len, os.SEEK_END)
|
|
128
|
-
magic_num_2 = f.read(magic_num_len)
|
|
129
|
-
if magic_num_2 == magic_num:
|
|
130
|
-
f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END)
|
|
131
|
-
metadata_end = f.tell()
|
|
132
|
-
metadata_start = INT_STRUCT.unpack(f.read(INT_STRUCT.size))[
|
|
133
|
-
0
|
|
134
|
-
]
|
|
135
|
-
f.seek(metadata_start)
|
|
136
|
-
|
|
137
|
-
metadata = load_nbt(
|
|
138
|
-
f.read(metadata_end - metadata_start),
|
|
139
|
-
compressed=True,
|
|
140
|
-
).compound
|
|
141
|
-
|
|
142
|
-
export_version = metadata.get_compound("export_version")
|
|
143
|
-
|
|
144
|
-
self._platform = export_version.get_string("edition").py_str
|
|
145
|
-
self._version = tuple(
|
|
146
|
-
map(
|
|
147
|
-
lambda v: v.py_int,
|
|
148
|
-
export_version.get_list("version"),
|
|
149
|
-
)
|
|
150
|
-
)
|
|
151
|
-
|
|
152
|
-
selection_boxes = (
|
|
153
|
-
metadata.get_int_array("selection_boxes")
|
|
154
|
-
.np_array.reshape(-1, 6)
|
|
155
|
-
.tolist()
|
|
156
|
-
)
|
|
157
|
-
self._bounds[self.dimensions[0]] = SelectionGroup(
|
|
158
|
-
[
|
|
159
|
-
SelectionBox((minx, miny, minz), (maxx, maxy, maxz))
|
|
160
|
-
for minx, miny, minz, maxx, maxy, maxz in selection_boxes
|
|
161
|
-
]
|
|
162
|
-
)
|
|
163
|
-
|
|
164
|
-
def _create(
|
|
165
|
-
self,
|
|
166
|
-
overwrite: bool,
|
|
167
|
-
bounds: Union[
|
|
168
|
-
SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
|
|
169
|
-
] = None,
|
|
170
|
-
format_version=max_format_version,
|
|
171
|
-
section_version=max_section_version,
|
|
172
|
-
**kwargs,
|
|
173
|
-
):
|
|
174
|
-
if not overwrite and os.path.isfile(self.path):
|
|
175
|
-
raise ObjectWriteError(f"There is already a file at {self.path}")
|
|
176
|
-
self._format_version = format_version
|
|
177
|
-
self._section_version = section_version
|
|
178
|
-
translator_version = self.translation_manager.get_version(
|
|
179
|
-
self.platform, self.version
|
|
180
|
-
)
|
|
181
|
-
self._platform = translator_version.platform
|
|
182
|
-
self._version = translator_version.version_number
|
|
183
|
-
self._chunk_to_section = {}
|
|
184
|
-
self._chunk_to_box = {}
|
|
185
|
-
self._set_selection(bounds)
|
|
186
|
-
self._populate_chunk_to_box()
|
|
187
|
-
self._is_open = True
|
|
188
|
-
self._has_lock = True
|
|
189
|
-
|
|
190
|
-
def _populate_chunk_to_box(self):
|
|
191
|
-
for box in self._bounds[self.dimensions[0]].selection_boxes:
|
|
192
|
-
for cx, cz in box.chunk_locations():
|
|
193
|
-
self._chunk_to_box.setdefault((cx, cz), []).append(
|
|
194
|
-
SelectionBox.create_chunk_box(cx, cz).intersection(box)
|
|
195
|
-
)
|
|
196
|
-
|
|
197
|
-
def open_from(self, f: BinaryIO):
|
|
198
|
-
f = BytesIO(f.read())
|
|
199
|
-
magic_num_1 = f.read(magic_num_len)
|
|
200
|
-
assert magic_num_1 == magic_num, f"This file is not a construction file."
|
|
201
|
-
self._format_version = struct.unpack(">B", f.read(1))[0]
|
|
202
|
-
if self._format_version == 0:
|
|
203
|
-
f.seek(-magic_num_len, os.SEEK_END)
|
|
204
|
-
magic_num_2 = f.read(magic_num_len)
|
|
205
|
-
assert (
|
|
206
|
-
magic_num_2 == magic_num
|
|
207
|
-
), "It looks like this file is corrupt. It probably wasn't saved properly"
|
|
208
|
-
|
|
209
|
-
f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END)
|
|
210
|
-
metadata_end = f.tell()
|
|
211
|
-
metadata_start = INT_STRUCT.unpack(f.read(INT_STRUCT.size))[0]
|
|
212
|
-
f.seek(metadata_start)
|
|
213
|
-
|
|
214
|
-
metadata = load_nbt(
|
|
215
|
-
f.read(metadata_end - metadata_start),
|
|
216
|
-
compressed=True,
|
|
217
|
-
).compound
|
|
218
|
-
|
|
219
|
-
try:
|
|
220
|
-
export_version = metadata.get_compound("export_version")
|
|
221
|
-
self._platform = export_version.get_string("edition").py_str
|
|
222
|
-
self._version = tuple(
|
|
223
|
-
map(lambda v: v.py_int, export_version.get_list("version"))
|
|
224
|
-
)
|
|
225
|
-
except KeyError as e:
|
|
226
|
-
raise KeyError(f'Missing export version identifying key "{e.args[0]}"')
|
|
227
|
-
|
|
228
|
-
self._section_version = metadata.get_byte("section_version").py_int
|
|
229
|
-
|
|
230
|
-
palette = unpack_palette(metadata.get_list("block_palette"))
|
|
231
|
-
|
|
232
|
-
selection_boxes = (
|
|
233
|
-
metadata.get_int_array("selection_boxes")
|
|
234
|
-
.np_array.reshape(-1, 6)
|
|
235
|
-
.tolist()
|
|
236
|
-
)
|
|
237
|
-
|
|
238
|
-
self._bounds[self.dimensions[0]] = SelectionGroup(
|
|
239
|
-
[
|
|
240
|
-
SelectionBox((minx, miny, minz), (maxx, maxy, maxz))
|
|
241
|
-
for minx, miny, minz, maxx, maxy, maxz in selection_boxes
|
|
242
|
-
]
|
|
243
|
-
)
|
|
244
|
-
|
|
245
|
-
self._populate_chunk_to_box()
|
|
246
|
-
|
|
247
|
-
section_index_table = (
|
|
248
|
-
metadata.get_byte_array("section_index_table")
|
|
249
|
-
.np_array.view(SECTION_ENTRY_TYPE)
|
|
250
|
-
.tolist()
|
|
251
|
-
)
|
|
252
|
-
|
|
253
|
-
if self._section_version == 0:
|
|
254
|
-
for (
|
|
255
|
-
start_x,
|
|
256
|
-
start_y,
|
|
257
|
-
start_z,
|
|
258
|
-
shape_x,
|
|
259
|
-
shape_y,
|
|
260
|
-
shape_z,
|
|
261
|
-
position,
|
|
262
|
-
length,
|
|
263
|
-
) in section_index_table:
|
|
264
|
-
f.seek(position)
|
|
265
|
-
nbt_obj = load_nbt(f.read(length)).compound
|
|
266
|
-
blocks_array_type = nbt_obj.get_byte("blocks_array_type").py_int
|
|
267
|
-
if blocks_array_type == -1:
|
|
268
|
-
blocks = None
|
|
269
|
-
block_entities = None
|
|
270
|
-
else:
|
|
271
|
-
if blocks_array_type == 7:
|
|
272
|
-
block_array = nbt_obj.get_byte_array("blocks").np_array
|
|
273
|
-
elif blocks_array_type == 11:
|
|
274
|
-
block_array = nbt_obj.get_int_array("blocks").np_array
|
|
275
|
-
elif blocks_array_type == 12:
|
|
276
|
-
block_array = nbt_obj.get_long_array("blocks").np_array
|
|
277
|
-
else:
|
|
278
|
-
raise TypeError
|
|
279
|
-
|
|
280
|
-
blocks = block_array.reshape((shape_x, shape_y, shape_z))
|
|
281
|
-
block_entities = parse_block_entities(
|
|
282
|
-
nbt_obj.get_list("block_entities")
|
|
283
|
-
)
|
|
284
|
-
|
|
285
|
-
start = numpy.array([start_x, start_y, start_z])
|
|
286
|
-
chunk_index: numpy.ndarray = start // self.sub_chunk_size
|
|
287
|
-
shape = numpy.array([shape_x, shape_y, shape_z])
|
|
288
|
-
if numpy.any(shape <= 0):
|
|
289
|
-
continue # skip sections with zero size
|
|
290
|
-
if numpy.any(
|
|
291
|
-
start + shape > (chunk_index + 1) * self.sub_chunk_size
|
|
292
|
-
):
|
|
293
|
-
log.error(
|
|
294
|
-
f"section in construction file did not fit in one sub-chunk. Start: {start}, Shape: {shape}"
|
|
295
|
-
)
|
|
296
|
-
cx, cy, cz = chunk_index.tolist()
|
|
297
|
-
self._chunk_to_section.setdefault((cx, cz), []).append(
|
|
298
|
-
ConstructionSection(
|
|
299
|
-
(start_x, start_y, start_z),
|
|
300
|
-
(shape_x, shape_y, shape_z),
|
|
301
|
-
blocks,
|
|
302
|
-
palette,
|
|
303
|
-
parse_entities(nbt_obj.get_list("entities")),
|
|
304
|
-
block_entities,
|
|
305
|
-
)
|
|
306
|
-
)
|
|
307
|
-
else:
|
|
308
|
-
raise Exception(
|
|
309
|
-
f"This wrapper does not support any construction section version higher than {max_section_version}"
|
|
310
|
-
)
|
|
311
|
-
|
|
312
|
-
else:
|
|
313
|
-
raise Exception(
|
|
314
|
-
f"This wrapper does not support any construction format version higher than {max_format_version}"
|
|
315
|
-
)
|
|
316
|
-
|
|
317
|
-
@property
|
|
318
|
-
def multi_selection(self) -> bool:
|
|
319
|
-
return True
|
|
320
|
-
|
|
321
|
-
@staticmethod
|
|
322
|
-
def is_valid(path: str) -> bool:
|
|
323
|
-
return os.path.isfile(path) and path.endswith(".construction")
|
|
324
|
-
|
|
325
|
-
@property
|
|
326
|
-
def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
|
|
327
|
-
return {
|
|
328
|
-
"bedrock": (True, True),
|
|
329
|
-
"java": (True, True),
|
|
330
|
-
}
|
|
331
|
-
|
|
332
|
-
@property
|
|
333
|
-
def extensions(self) -> Tuple[str, ...]:
|
|
334
|
-
return (".construction",)
|
|
335
|
-
|
|
336
|
-
def _get_interface(
|
|
337
|
-
self, raw_chunk_data: Optional[Any] = None
|
|
338
|
-
) -> Construction0Interface:
|
|
339
|
-
return construction_0_interface
|
|
340
|
-
|
|
341
|
-
def _get_interface_and_translator(
|
|
342
|
-
self, raw_chunk_data=None
|
|
343
|
-
) -> Tuple["Interface", "Translator", VersionNumberAny]:
|
|
344
|
-
interface = self._get_interface(raw_chunk_data)
|
|
345
|
-
translator, version_identifier = interface.get_translator(
|
|
346
|
-
self.max_world_version, raw_chunk_data, self.translation_manager
|
|
347
|
-
)
|
|
348
|
-
return interface, translator, version_identifier
|
|
349
|
-
|
|
350
|
-
def save_to(self, f: BinaryIO):
|
|
351
|
-
palette: BlockManager = BlockManager()
|
|
352
|
-
f.write(magic_num)
|
|
353
|
-
f.write(struct.pack(">B", self._format_version))
|
|
354
|
-
if self._format_version == 0:
|
|
355
|
-
metadata = CompoundTag(
|
|
356
|
-
{
|
|
357
|
-
"created_with": StringTag("amulet_python_wrapper_v2"),
|
|
358
|
-
"selection_boxes": IntArrayTag(
|
|
359
|
-
[
|
|
360
|
-
c
|
|
361
|
-
for box in self._bounds[self.dimensions[0]].selection_boxes
|
|
362
|
-
for c in (*box.min, *box.max)
|
|
363
|
-
]
|
|
364
|
-
),
|
|
365
|
-
"section_version": ByteTag(self._section_version),
|
|
366
|
-
"export_version": CompoundTag(
|
|
367
|
-
{
|
|
368
|
-
"edition": StringTag(self._platform),
|
|
369
|
-
"version": ListTag([IntTag(v) for v in self._version]),
|
|
370
|
-
}
|
|
371
|
-
),
|
|
372
|
-
}
|
|
373
|
-
)
|
|
374
|
-
section_index_table: List[
|
|
375
|
-
Tuple[int, int, int, int, int, int, int, int]
|
|
376
|
-
] = []
|
|
377
|
-
if self._section_version == 0:
|
|
378
|
-
for section_list in self._chunk_to_section.values():
|
|
379
|
-
for section in section_list:
|
|
380
|
-
sx, sy, sz = section.location
|
|
381
|
-
shapex, shapey, shapez = section.shape
|
|
382
|
-
blocks = section.blocks
|
|
383
|
-
entities = section.entities
|
|
384
|
-
block_entities = section.block_entities
|
|
385
|
-
section_palette = section.palette
|
|
386
|
-
position = f.tell()
|
|
387
|
-
|
|
388
|
-
_tag = CompoundTag({"entities": serialise_entities(entities)})
|
|
389
|
-
|
|
390
|
-
if blocks is None:
|
|
391
|
-
_tag["blocks_array_type"] = ByteTag(-1)
|
|
392
|
-
else:
|
|
393
|
-
flattened_array = blocks.ravel()
|
|
394
|
-
index, flattened_array = numpy.unique(
|
|
395
|
-
flattened_array, return_inverse=True
|
|
396
|
-
)
|
|
397
|
-
section_palette = numpy.array(
|
|
398
|
-
section_palette, dtype=object
|
|
399
|
-
)[index]
|
|
400
|
-
lut = numpy.vectorize(palette.get_add_block)(
|
|
401
|
-
section_palette
|
|
402
|
-
)
|
|
403
|
-
flattened_array = lut[flattened_array]
|
|
404
|
-
array_type = find_fitting_array_type(flattened_array)
|
|
405
|
-
_tag["blocks_array_type"] = ByteTag(array_type.tag_id)
|
|
406
|
-
_tag["blocks"] = array_type(flattened_array)
|
|
407
|
-
_tag["block_entities"] = serialise_block_entities(
|
|
408
|
-
block_entities or []
|
|
409
|
-
)
|
|
410
|
-
|
|
411
|
-
NamedTag(_tag).save_to(f)
|
|
412
|
-
|
|
413
|
-
length = f.tell() - position
|
|
414
|
-
section_index_table.append(
|
|
415
|
-
(sx, sy, sz, shapex, shapey, shapez, position, length)
|
|
416
|
-
)
|
|
417
|
-
else:
|
|
418
|
-
raise Exception(
|
|
419
|
-
f"This wrapper doesn't support any section version higher than {max_section_version}"
|
|
420
|
-
)
|
|
421
|
-
metadata_start = f.tell()
|
|
422
|
-
metadata["section_index_table"] = ByteArrayTag(
|
|
423
|
-
numpy.array(section_index_table, dtype=SECTION_ENTRY_TYPE).view(
|
|
424
|
-
numpy.int8
|
|
425
|
-
)
|
|
426
|
-
)
|
|
427
|
-
metadata["block_palette"] = pack_palette(palette)
|
|
428
|
-
metadata.save_to(f)
|
|
429
|
-
f.write(INT_STRUCT.pack(metadata_start))
|
|
430
|
-
f.write(magic_num)
|
|
431
|
-
else:
|
|
432
|
-
raise Exception(
|
|
433
|
-
f"This wrapper doesn't support any construction version higher than {max_format_version}"
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
def _close(self):
|
|
437
|
-
"""Close the disk database"""
|
|
438
|
-
pass
|
|
439
|
-
|
|
440
|
-
def unload(self):
|
|
441
|
-
pass
|
|
442
|
-
|
|
443
|
-
def all_chunk_coords(
|
|
444
|
-
self, dimension: Optional[Dimension] = None
|
|
445
|
-
) -> Iterable[ChunkCoordinates]:
|
|
446
|
-
yield from self._chunk_to_section.keys()
|
|
447
|
-
|
|
448
|
-
def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
|
|
449
|
-
return (cx, cz) in self._chunk_to_section
|
|
450
|
-
|
|
451
|
-
def _pack(
|
|
452
|
-
self,
|
|
453
|
-
chunk: "Chunk",
|
|
454
|
-
translator: "Translator",
|
|
455
|
-
chunk_version: VersionNumberAny,
|
|
456
|
-
) -> Tuple["Chunk", AnyNDArray]:
|
|
457
|
-
return chunk, numpy.array(chunk.block_palette.blocks)
|
|
458
|
-
|
|
459
|
-
def _encode(
|
|
460
|
-
self,
|
|
461
|
-
interface: ConstructionInterface,
|
|
462
|
-
chunk: Chunk,
|
|
463
|
-
dimension: Dimension,
|
|
464
|
-
chunk_palette: AnyNDArray,
|
|
465
|
-
):
|
|
466
|
-
return interface.encode(
|
|
467
|
-
chunk,
|
|
468
|
-
chunk_palette,
|
|
469
|
-
self.max_world_version,
|
|
470
|
-
self._chunk_to_box.get((chunk.cx, chunk.cz)),
|
|
471
|
-
)
|
|
472
|
-
|
|
473
|
-
def _unpack(
|
|
474
|
-
self,
|
|
475
|
-
translator: "Translator",
|
|
476
|
-
game_version: VersionNumberAny,
|
|
477
|
-
chunk: "Chunk",
|
|
478
|
-
chunk_palette: AnyNDArray,
|
|
479
|
-
) -> "Chunk":
|
|
480
|
-
palette = chunk._block_palette = BlockManager()
|
|
481
|
-
lut = numpy.array([palette.get_add_block(block) for block in chunk_palette])
|
|
482
|
-
if len(palette.blocks) != len(chunk_palette):
|
|
483
|
-
# if a blockstate was defined twice
|
|
484
|
-
for cy in chunk.blocks.sub_chunks:
|
|
485
|
-
chunk.blocks.add_sub_chunk(cy, lut[chunk.blocks.get_sub_chunk(cy)])
|
|
486
|
-
return chunk
|
|
487
|
-
|
|
488
|
-
def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
|
|
489
|
-
if (cx, cz) in self._chunk_to_section:
|
|
490
|
-
del self._chunk_to_section[(cx, cz)]
|
|
491
|
-
|
|
492
|
-
def _put_raw_chunk_data(
|
|
493
|
-
self,
|
|
494
|
-
cx: int,
|
|
495
|
-
cz: int,
|
|
496
|
-
data: List[ConstructionSection],
|
|
497
|
-
dimension: Optional[Dimension] = None,
|
|
498
|
-
):
|
|
499
|
-
self._chunk_to_section[cx, cz] = copy.deepcopy(data)
|
|
500
|
-
|
|
501
|
-
def _get_raw_chunk_data(
|
|
502
|
-
self, cx: int, cz: int, dimension: Optional[Dimension] = None
|
|
503
|
-
) -> List[ConstructionSection]:
|
|
504
|
-
"""
|
|
505
|
-
Return the raw data as loaded from disk.
|
|
506
|
-
|
|
507
|
-
:param cx: The x coordinate of the chunk.
|
|
508
|
-
:param cz: The z coordinate of the chunk.
|
|
509
|
-
:param dimension: The dimension to load the data from.
|
|
510
|
-
:return: The raw chunk data.
|
|
511
|
-
"""
|
|
512
|
-
if (cx, cz) in self._chunk_to_section:
|
|
513
|
-
return copy.deepcopy(self._chunk_to_section[(cx, cz)])
|
|
514
|
-
else:
|
|
515
|
-
raise ChunkDoesNotExist
|
|
1
|
+
import os
|
|
2
|
+
from typing import (
|
|
3
|
+
Optional,
|
|
4
|
+
List,
|
|
5
|
+
Tuple,
|
|
6
|
+
Dict,
|
|
7
|
+
Iterable,
|
|
8
|
+
TYPE_CHECKING,
|
|
9
|
+
BinaryIO,
|
|
10
|
+
Any,
|
|
11
|
+
Union,
|
|
12
|
+
)
|
|
13
|
+
import numpy
|
|
14
|
+
from io import BytesIO
|
|
15
|
+
import struct
|
|
16
|
+
import copy
|
|
17
|
+
import logging
|
|
18
|
+
|
|
19
|
+
from amulet_nbt import (
|
|
20
|
+
ByteTag,
|
|
21
|
+
IntTag,
|
|
22
|
+
StringTag,
|
|
23
|
+
ListTag,
|
|
24
|
+
CompoundTag,
|
|
25
|
+
ByteArrayTag,
|
|
26
|
+
IntArrayTag,
|
|
27
|
+
NamedTag,
|
|
28
|
+
load as load_nbt,
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
from amulet.api.data_types import (
|
|
33
|
+
AnyNDArray,
|
|
34
|
+
VersionNumberAny,
|
|
35
|
+
VersionNumberTuple,
|
|
36
|
+
Dimension,
|
|
37
|
+
PlatformType,
|
|
38
|
+
ChunkCoordinates,
|
|
39
|
+
)
|
|
40
|
+
from amulet.api.registry import BlockManager
|
|
41
|
+
from amulet.api.wrapper import StructureFormatWrapper
|
|
42
|
+
from amulet.api.chunk import Chunk
|
|
43
|
+
from amulet.api.selection import SelectionGroup, SelectionBox
|
|
44
|
+
from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError
|
|
45
|
+
|
|
46
|
+
from .section import ConstructionSection
|
|
47
|
+
from .interface import Construction0Interface, ConstructionInterface
|
|
48
|
+
from .util import (
|
|
49
|
+
unpack_palette,
|
|
50
|
+
parse_entities,
|
|
51
|
+
parse_block_entities,
|
|
52
|
+
serialise_entities,
|
|
53
|
+
serialise_block_entities,
|
|
54
|
+
find_fitting_array_type,
|
|
55
|
+
pack_palette,
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
if TYPE_CHECKING:
|
|
59
|
+
from amulet.api.wrapper import Translator, Interface
|
|
60
|
+
|
|
61
|
+
log = logging.getLogger(__name__)
|
|
62
|
+
|
|
63
|
+
construction_0_interface = Construction0Interface()
|
|
64
|
+
|
|
65
|
+
INT_STRUCT = struct.Struct(">I")
|
|
66
|
+
SECTION_ENTRY_TYPE = numpy.dtype(
|
|
67
|
+
[
|
|
68
|
+
("sx", "i4"),
|
|
69
|
+
("sy", "i4"),
|
|
70
|
+
("sz", "i4"),
|
|
71
|
+
("shapex", "i1"),
|
|
72
|
+
("shapey", "i1"),
|
|
73
|
+
("shapez", "i1"),
|
|
74
|
+
("position", "i4"),
|
|
75
|
+
("length", "i4"),
|
|
76
|
+
]
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
magic_num = b"constrct"
|
|
80
|
+
magic_num_len = len(magic_num)
|
|
81
|
+
|
|
82
|
+
max_format_version = 0
|
|
83
|
+
max_section_version = 0
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ConstructionFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
|
|
87
|
+
"""
|
|
88
|
+
This FormatWrapper class exists to interface with the construction format.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
_format_version: int
|
|
92
|
+
_section_version: int
|
|
93
|
+
_chunk_to_section: Dict[Tuple[int, int], List[ConstructionSection]]
|
|
94
|
+
_selection_boxes: List[SelectionBox]
|
|
95
|
+
_chunk_to_box: Dict[Tuple[int, int], List[SelectionBox]]
|
|
96
|
+
|
|
97
|
+
def __init__(self, path: str):
|
|
98
|
+
"""
|
|
99
|
+
Construct a new instance of :class:`ConstructionFormatWrapper`.
|
|
100
|
+
|
|
101
|
+
This should not be used directly. You should instead use :func:`amulet.load_format`.
|
|
102
|
+
|
|
103
|
+
:param path: The file path to the serialised data.
|
|
104
|
+
"""
|
|
105
|
+
super().__init__(path)
|
|
106
|
+
|
|
107
|
+
self._format_version = max_format_version
|
|
108
|
+
self._section_version = max_section_version
|
|
109
|
+
|
|
110
|
+
# which sections are in a given chunk
|
|
111
|
+
self._chunk_to_section = {}
|
|
112
|
+
|
|
113
|
+
self._selection_boxes = []
|
|
114
|
+
|
|
115
|
+
# which selection boxes intersect a given chunk (boxes are clipped to the size of the chunk)
|
|
116
|
+
self._chunk_to_box = {}
|
|
117
|
+
|
|
118
|
+
self._shallow_load()
|
|
119
|
+
|
|
120
|
+
def _shallow_load(self):
|
|
121
|
+
if os.path.isfile(self.path):
|
|
122
|
+
with open(self.path, "rb") as f:
|
|
123
|
+
magic_num_1 = f.read(magic_num_len)
|
|
124
|
+
if magic_num_1 == magic_num:
|
|
125
|
+
format_version = struct.unpack(">B", f.read(1))[0]
|
|
126
|
+
if format_version == 0:
|
|
127
|
+
f.seek(-magic_num_len, os.SEEK_END)
|
|
128
|
+
magic_num_2 = f.read(magic_num_len)
|
|
129
|
+
if magic_num_2 == magic_num:
|
|
130
|
+
f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END)
|
|
131
|
+
metadata_end = f.tell()
|
|
132
|
+
metadata_start = INT_STRUCT.unpack(f.read(INT_STRUCT.size))[
|
|
133
|
+
0
|
|
134
|
+
]
|
|
135
|
+
f.seek(metadata_start)
|
|
136
|
+
|
|
137
|
+
metadata = load_nbt(
|
|
138
|
+
f.read(metadata_end - metadata_start),
|
|
139
|
+
compressed=True,
|
|
140
|
+
).compound
|
|
141
|
+
|
|
142
|
+
export_version = metadata.get_compound("export_version")
|
|
143
|
+
|
|
144
|
+
self._platform = export_version.get_string("edition").py_str
|
|
145
|
+
self._version = tuple(
|
|
146
|
+
map(
|
|
147
|
+
lambda v: v.py_int,
|
|
148
|
+
export_version.get_list("version"),
|
|
149
|
+
)
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
selection_boxes = (
|
|
153
|
+
metadata.get_int_array("selection_boxes")
|
|
154
|
+
.np_array.reshape(-1, 6)
|
|
155
|
+
.tolist()
|
|
156
|
+
)
|
|
157
|
+
self._bounds[self.dimensions[0]] = SelectionGroup(
|
|
158
|
+
[
|
|
159
|
+
SelectionBox((minx, miny, minz), (maxx, maxy, maxz))
|
|
160
|
+
for minx, miny, minz, maxx, maxy, maxz in selection_boxes
|
|
161
|
+
]
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
def _create(
|
|
165
|
+
self,
|
|
166
|
+
overwrite: bool,
|
|
167
|
+
bounds: Union[
|
|
168
|
+
SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
|
|
169
|
+
] = None,
|
|
170
|
+
format_version=max_format_version,
|
|
171
|
+
section_version=max_section_version,
|
|
172
|
+
**kwargs,
|
|
173
|
+
):
|
|
174
|
+
if not overwrite and os.path.isfile(self.path):
|
|
175
|
+
raise ObjectWriteError(f"There is already a file at {self.path}")
|
|
176
|
+
self._format_version = format_version
|
|
177
|
+
self._section_version = section_version
|
|
178
|
+
translator_version = self.translation_manager.get_version(
|
|
179
|
+
self.platform, self.version
|
|
180
|
+
)
|
|
181
|
+
self._platform = translator_version.platform
|
|
182
|
+
self._version = translator_version.version_number
|
|
183
|
+
self._chunk_to_section = {}
|
|
184
|
+
self._chunk_to_box = {}
|
|
185
|
+
self._set_selection(bounds)
|
|
186
|
+
self._populate_chunk_to_box()
|
|
187
|
+
self._is_open = True
|
|
188
|
+
self._has_lock = True
|
|
189
|
+
|
|
190
|
+
def _populate_chunk_to_box(self):
|
|
191
|
+
for box in self._bounds[self.dimensions[0]].selection_boxes:
|
|
192
|
+
for cx, cz in box.chunk_locations():
|
|
193
|
+
self._chunk_to_box.setdefault((cx, cz), []).append(
|
|
194
|
+
SelectionBox.create_chunk_box(cx, cz).intersection(box)
|
|
195
|
+
)
|
|
196
|
+
|
|
197
|
+
def open_from(self, f: BinaryIO):
|
|
198
|
+
f = BytesIO(f.read())
|
|
199
|
+
magic_num_1 = f.read(magic_num_len)
|
|
200
|
+
assert magic_num_1 == magic_num, f"This file is not a construction file."
|
|
201
|
+
self._format_version = struct.unpack(">B", f.read(1))[0]
|
|
202
|
+
if self._format_version == 0:
|
|
203
|
+
f.seek(-magic_num_len, os.SEEK_END)
|
|
204
|
+
magic_num_2 = f.read(magic_num_len)
|
|
205
|
+
assert (
|
|
206
|
+
magic_num_2 == magic_num
|
|
207
|
+
), "It looks like this file is corrupt. It probably wasn't saved properly"
|
|
208
|
+
|
|
209
|
+
f.seek(-magic_num_len - INT_STRUCT.size, os.SEEK_END)
|
|
210
|
+
metadata_end = f.tell()
|
|
211
|
+
metadata_start = INT_STRUCT.unpack(f.read(INT_STRUCT.size))[0]
|
|
212
|
+
f.seek(metadata_start)
|
|
213
|
+
|
|
214
|
+
metadata = load_nbt(
|
|
215
|
+
f.read(metadata_end - metadata_start),
|
|
216
|
+
compressed=True,
|
|
217
|
+
).compound
|
|
218
|
+
|
|
219
|
+
try:
|
|
220
|
+
export_version = metadata.get_compound("export_version")
|
|
221
|
+
self._platform = export_version.get_string("edition").py_str
|
|
222
|
+
self._version = tuple(
|
|
223
|
+
map(lambda v: v.py_int, export_version.get_list("version"))
|
|
224
|
+
)
|
|
225
|
+
except KeyError as e:
|
|
226
|
+
raise KeyError(f'Missing export version identifying key "{e.args[0]}"')
|
|
227
|
+
|
|
228
|
+
self._section_version = metadata.get_byte("section_version").py_int
|
|
229
|
+
|
|
230
|
+
palette = unpack_palette(metadata.get_list("block_palette"))
|
|
231
|
+
|
|
232
|
+
selection_boxes = (
|
|
233
|
+
metadata.get_int_array("selection_boxes")
|
|
234
|
+
.np_array.reshape(-1, 6)
|
|
235
|
+
.tolist()
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
self._bounds[self.dimensions[0]] = SelectionGroup(
|
|
239
|
+
[
|
|
240
|
+
SelectionBox((minx, miny, minz), (maxx, maxy, maxz))
|
|
241
|
+
for minx, miny, minz, maxx, maxy, maxz in selection_boxes
|
|
242
|
+
]
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
self._populate_chunk_to_box()
|
|
246
|
+
|
|
247
|
+
section_index_table = (
|
|
248
|
+
metadata.get_byte_array("section_index_table")
|
|
249
|
+
.np_array.view(SECTION_ENTRY_TYPE)
|
|
250
|
+
.tolist()
|
|
251
|
+
)
|
|
252
|
+
|
|
253
|
+
if self._section_version == 0:
|
|
254
|
+
for (
|
|
255
|
+
start_x,
|
|
256
|
+
start_y,
|
|
257
|
+
start_z,
|
|
258
|
+
shape_x,
|
|
259
|
+
shape_y,
|
|
260
|
+
shape_z,
|
|
261
|
+
position,
|
|
262
|
+
length,
|
|
263
|
+
) in section_index_table:
|
|
264
|
+
f.seek(position)
|
|
265
|
+
nbt_obj = load_nbt(f.read(length)).compound
|
|
266
|
+
blocks_array_type = nbt_obj.get_byte("blocks_array_type").py_int
|
|
267
|
+
if blocks_array_type == -1:
|
|
268
|
+
blocks = None
|
|
269
|
+
block_entities = None
|
|
270
|
+
else:
|
|
271
|
+
if blocks_array_type == 7:
|
|
272
|
+
block_array = nbt_obj.get_byte_array("blocks").np_array
|
|
273
|
+
elif blocks_array_type == 11:
|
|
274
|
+
block_array = nbt_obj.get_int_array("blocks").np_array
|
|
275
|
+
elif blocks_array_type == 12:
|
|
276
|
+
block_array = nbt_obj.get_long_array("blocks").np_array
|
|
277
|
+
else:
|
|
278
|
+
raise TypeError
|
|
279
|
+
|
|
280
|
+
blocks = block_array.reshape((shape_x, shape_y, shape_z))
|
|
281
|
+
block_entities = parse_block_entities(
|
|
282
|
+
nbt_obj.get_list("block_entities")
|
|
283
|
+
)
|
|
284
|
+
|
|
285
|
+
start = numpy.array([start_x, start_y, start_z])
|
|
286
|
+
chunk_index: numpy.ndarray = start // self.sub_chunk_size
|
|
287
|
+
shape = numpy.array([shape_x, shape_y, shape_z])
|
|
288
|
+
if numpy.any(shape <= 0):
|
|
289
|
+
continue # skip sections with zero size
|
|
290
|
+
if numpy.any(
|
|
291
|
+
start + shape > (chunk_index + 1) * self.sub_chunk_size
|
|
292
|
+
):
|
|
293
|
+
log.error(
|
|
294
|
+
f"section in construction file did not fit in one sub-chunk. Start: {start}, Shape: {shape}"
|
|
295
|
+
)
|
|
296
|
+
cx, cy, cz = chunk_index.tolist()
|
|
297
|
+
self._chunk_to_section.setdefault((cx, cz), []).append(
|
|
298
|
+
ConstructionSection(
|
|
299
|
+
(start_x, start_y, start_z),
|
|
300
|
+
(shape_x, shape_y, shape_z),
|
|
301
|
+
blocks,
|
|
302
|
+
palette,
|
|
303
|
+
parse_entities(nbt_obj.get_list("entities")),
|
|
304
|
+
block_entities,
|
|
305
|
+
)
|
|
306
|
+
)
|
|
307
|
+
else:
|
|
308
|
+
raise Exception(
|
|
309
|
+
f"This wrapper does not support any construction section version higher than {max_section_version}"
|
|
310
|
+
)
|
|
311
|
+
|
|
312
|
+
else:
|
|
313
|
+
raise Exception(
|
|
314
|
+
f"This wrapper does not support any construction format version higher than {max_format_version}"
|
|
315
|
+
)
|
|
316
|
+
|
|
317
|
+
@property
|
|
318
|
+
def multi_selection(self) -> bool:
|
|
319
|
+
return True
|
|
320
|
+
|
|
321
|
+
@staticmethod
|
|
322
|
+
def is_valid(path: str) -> bool:
|
|
323
|
+
return os.path.isfile(path) and path.endswith(".construction")
|
|
324
|
+
|
|
325
|
+
@property
|
|
326
|
+
def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
|
|
327
|
+
return {
|
|
328
|
+
"bedrock": (True, True),
|
|
329
|
+
"java": (True, True),
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
@property
|
|
333
|
+
def extensions(self) -> Tuple[str, ...]:
|
|
334
|
+
return (".construction",)
|
|
335
|
+
|
|
336
|
+
def _get_interface(
|
|
337
|
+
self, raw_chunk_data: Optional[Any] = None
|
|
338
|
+
) -> Construction0Interface:
|
|
339
|
+
return construction_0_interface
|
|
340
|
+
|
|
341
|
+
def _get_interface_and_translator(
|
|
342
|
+
self, raw_chunk_data=None
|
|
343
|
+
) -> Tuple["Interface", "Translator", VersionNumberAny]:
|
|
344
|
+
interface = self._get_interface(raw_chunk_data)
|
|
345
|
+
translator, version_identifier = interface.get_translator(
|
|
346
|
+
self.max_world_version, raw_chunk_data, self.translation_manager
|
|
347
|
+
)
|
|
348
|
+
return interface, translator, version_identifier
|
|
349
|
+
|
|
350
|
+
def save_to(self, f: BinaryIO):
|
|
351
|
+
palette: BlockManager = BlockManager()
|
|
352
|
+
f.write(magic_num)
|
|
353
|
+
f.write(struct.pack(">B", self._format_version))
|
|
354
|
+
if self._format_version == 0:
|
|
355
|
+
metadata = CompoundTag(
|
|
356
|
+
{
|
|
357
|
+
"created_with": StringTag("amulet_python_wrapper_v2"),
|
|
358
|
+
"selection_boxes": IntArrayTag(
|
|
359
|
+
[
|
|
360
|
+
c
|
|
361
|
+
for box in self._bounds[self.dimensions[0]].selection_boxes
|
|
362
|
+
for c in (*box.min, *box.max)
|
|
363
|
+
]
|
|
364
|
+
),
|
|
365
|
+
"section_version": ByteTag(self._section_version),
|
|
366
|
+
"export_version": CompoundTag(
|
|
367
|
+
{
|
|
368
|
+
"edition": StringTag(self._platform),
|
|
369
|
+
"version": ListTag([IntTag(v) for v in self._version]),
|
|
370
|
+
}
|
|
371
|
+
),
|
|
372
|
+
}
|
|
373
|
+
)
|
|
374
|
+
section_index_table: List[
|
|
375
|
+
Tuple[int, int, int, int, int, int, int, int]
|
|
376
|
+
] = []
|
|
377
|
+
if self._section_version == 0:
|
|
378
|
+
for section_list in self._chunk_to_section.values():
|
|
379
|
+
for section in section_list:
|
|
380
|
+
sx, sy, sz = section.location
|
|
381
|
+
shapex, shapey, shapez = section.shape
|
|
382
|
+
blocks = section.blocks
|
|
383
|
+
entities = section.entities
|
|
384
|
+
block_entities = section.block_entities
|
|
385
|
+
section_palette = section.palette
|
|
386
|
+
position = f.tell()
|
|
387
|
+
|
|
388
|
+
_tag = CompoundTag({"entities": serialise_entities(entities)})
|
|
389
|
+
|
|
390
|
+
if blocks is None:
|
|
391
|
+
_tag["blocks_array_type"] = ByteTag(-1)
|
|
392
|
+
else:
|
|
393
|
+
flattened_array = blocks.ravel()
|
|
394
|
+
index, flattened_array = numpy.unique(
|
|
395
|
+
flattened_array, return_inverse=True
|
|
396
|
+
)
|
|
397
|
+
section_palette = numpy.array(
|
|
398
|
+
section_palette, dtype=object
|
|
399
|
+
)[index]
|
|
400
|
+
lut = numpy.vectorize(palette.get_add_block)(
|
|
401
|
+
section_palette
|
|
402
|
+
)
|
|
403
|
+
flattened_array = lut[flattened_array]
|
|
404
|
+
array_type = find_fitting_array_type(flattened_array)
|
|
405
|
+
_tag["blocks_array_type"] = ByteTag(array_type.tag_id)
|
|
406
|
+
_tag["blocks"] = array_type(flattened_array)
|
|
407
|
+
_tag["block_entities"] = serialise_block_entities(
|
|
408
|
+
block_entities or []
|
|
409
|
+
)
|
|
410
|
+
|
|
411
|
+
NamedTag(_tag).save_to(f)
|
|
412
|
+
|
|
413
|
+
length = f.tell() - position
|
|
414
|
+
section_index_table.append(
|
|
415
|
+
(sx, sy, sz, shapex, shapey, shapez, position, length)
|
|
416
|
+
)
|
|
417
|
+
else:
|
|
418
|
+
raise Exception(
|
|
419
|
+
f"This wrapper doesn't support any section version higher than {max_section_version}"
|
|
420
|
+
)
|
|
421
|
+
metadata_start = f.tell()
|
|
422
|
+
metadata["section_index_table"] = ByteArrayTag(
|
|
423
|
+
numpy.array(section_index_table, dtype=SECTION_ENTRY_TYPE).view(
|
|
424
|
+
numpy.int8
|
|
425
|
+
)
|
|
426
|
+
)
|
|
427
|
+
metadata["block_palette"] = pack_palette(palette)
|
|
428
|
+
metadata.save_to(f)
|
|
429
|
+
f.write(INT_STRUCT.pack(metadata_start))
|
|
430
|
+
f.write(magic_num)
|
|
431
|
+
else:
|
|
432
|
+
raise Exception(
|
|
433
|
+
f"This wrapper doesn't support any construction version higher than {max_format_version}"
|
|
434
|
+
)
|
|
435
|
+
|
|
436
|
+
def _close(self):
|
|
437
|
+
"""Close the disk database"""
|
|
438
|
+
pass
|
|
439
|
+
|
|
440
|
+
def unload(self):
|
|
441
|
+
pass
|
|
442
|
+
|
|
443
|
+
def all_chunk_coords(
|
|
444
|
+
self, dimension: Optional[Dimension] = None
|
|
445
|
+
) -> Iterable[ChunkCoordinates]:
|
|
446
|
+
yield from self._chunk_to_section.keys()
|
|
447
|
+
|
|
448
|
+
def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
|
|
449
|
+
return (cx, cz) in self._chunk_to_section
|
|
450
|
+
|
|
451
|
+
def _pack(
|
|
452
|
+
self,
|
|
453
|
+
chunk: "Chunk",
|
|
454
|
+
translator: "Translator",
|
|
455
|
+
chunk_version: VersionNumberAny,
|
|
456
|
+
) -> Tuple["Chunk", AnyNDArray]:
|
|
457
|
+
return chunk, numpy.array(chunk.block_palette.blocks)
|
|
458
|
+
|
|
459
|
+
def _encode(
|
|
460
|
+
self,
|
|
461
|
+
interface: ConstructionInterface,
|
|
462
|
+
chunk: Chunk,
|
|
463
|
+
dimension: Dimension,
|
|
464
|
+
chunk_palette: AnyNDArray,
|
|
465
|
+
):
|
|
466
|
+
return interface.encode(
|
|
467
|
+
chunk,
|
|
468
|
+
chunk_palette,
|
|
469
|
+
self.max_world_version,
|
|
470
|
+
self._chunk_to_box.get((chunk.cx, chunk.cz)),
|
|
471
|
+
)
|
|
472
|
+
|
|
473
|
+
def _unpack(
|
|
474
|
+
self,
|
|
475
|
+
translator: "Translator",
|
|
476
|
+
game_version: VersionNumberAny,
|
|
477
|
+
chunk: "Chunk",
|
|
478
|
+
chunk_palette: AnyNDArray,
|
|
479
|
+
) -> "Chunk":
|
|
480
|
+
palette = chunk._block_palette = BlockManager()
|
|
481
|
+
lut = numpy.array([palette.get_add_block(block) for block in chunk_palette])
|
|
482
|
+
if len(palette.blocks) != len(chunk_palette):
|
|
483
|
+
# if a blockstate was defined twice
|
|
484
|
+
for cy in chunk.blocks.sub_chunks:
|
|
485
|
+
chunk.blocks.add_sub_chunk(cy, lut[chunk.blocks.get_sub_chunk(cy)])
|
|
486
|
+
return chunk
|
|
487
|
+
|
|
488
|
+
def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
|
|
489
|
+
if (cx, cz) in self._chunk_to_section:
|
|
490
|
+
del self._chunk_to_section[(cx, cz)]
|
|
491
|
+
|
|
492
|
+
def _put_raw_chunk_data(
|
|
493
|
+
self,
|
|
494
|
+
cx: int,
|
|
495
|
+
cz: int,
|
|
496
|
+
data: List[ConstructionSection],
|
|
497
|
+
dimension: Optional[Dimension] = None,
|
|
498
|
+
):
|
|
499
|
+
self._chunk_to_section[cx, cz] = copy.deepcopy(data)
|
|
500
|
+
|
|
501
|
+
def _get_raw_chunk_data(
|
|
502
|
+
self, cx: int, cz: int, dimension: Optional[Dimension] = None
|
|
503
|
+
) -> List[ConstructionSection]:
|
|
504
|
+
"""
|
|
505
|
+
Return the raw data as loaded from disk.
|
|
506
|
+
|
|
507
|
+
:param cx: The x coordinate of the chunk.
|
|
508
|
+
:param cz: The z coordinate of the chunk.
|
|
509
|
+
:param dimension: The dimension to load the data from.
|
|
510
|
+
:return: The raw chunk data.
|
|
511
|
+
"""
|
|
512
|
+
if (cx, cz) in self._chunk_to_section:
|
|
513
|
+
return copy.deepcopy(self._chunk_to_section[(cx, cz)])
|
|
514
|
+
else:
|
|
515
|
+
raise ChunkDoesNotExist
|