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.

Files changed (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {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