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,408 +1,408 @@
1
- import os
2
- from typing import Optional, Tuple, Iterable, TYPE_CHECKING, BinaryIO, Dict, List, Union
3
- import numpy
4
- import copy
5
-
6
- from amulet_nbt import (
7
- IntTag,
8
- StringTag,
9
- ListTag,
10
- CompoundTag,
11
- load as load_nbt,
12
- utf8_escape_decoder,
13
- utf8_escape_encoder,
14
- )
15
-
16
- from amulet.api.data_types import (
17
- VersionNumberAny,
18
- VersionNumberTuple,
19
- ChunkCoordinates,
20
- AnyNDArray,
21
- Dimension,
22
- PlatformType,
23
- )
24
- from amulet.api.wrapper import StructureFormatWrapper
25
- from amulet.api.chunk import Chunk
26
- from amulet.api.selection import SelectionGroup, SelectionBox
27
- from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError
28
- from amulet.utils.numpy_helpers import brute_sort_objects_no_hash
29
-
30
- from .chunk import MCStructureChunk
31
- from .interface import MCStructureInterface
32
-
33
- if TYPE_CHECKING:
34
- from amulet.api.wrapper import Translator, Interface
35
-
36
- mcstructure_interface = MCStructureInterface()
37
-
38
-
39
- class MCStructureFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
40
- """
41
- This FormatWrapper class exists to interface with the Bedrock mcstructure structure block format.
42
- """
43
-
44
- def __init__(self, path: str):
45
- """
46
- Construct a new instance of :class:`MCStructureFormatWrapper`.
47
-
48
- This should not be used directly. You should instead use :func:`amulet.load_format`.
49
-
50
- :param path: The file path to the serialised data.
51
- """
52
- super().__init__(path)
53
- self._chunks: Dict[
54
- ChunkCoordinates,
55
- Tuple[
56
- SelectionBox,
57
- numpy.ndarray,
58
- AnyNDArray,
59
- List[CompoundTag],
60
- List[CompoundTag],
61
- ],
62
- ] = {}
63
-
64
- def _create(
65
- self,
66
- overwrite: bool,
67
- bounds: Union[
68
- SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
69
- ] = None,
70
- **kwargs,
71
- ):
72
- if not overwrite and os.path.isfile(self.path):
73
- raise ObjectWriteError(f"There is already a file at {self.path}")
74
- translator_version = self.translation_manager.get_version(
75
- "bedrock", (999, 999, 999)
76
- )
77
- self._platform = translator_version.platform
78
- self._version = translator_version.version_number
79
- self._chunks = {}
80
- self._set_selection(bounds)
81
- self._is_open = True
82
- self._has_lock = True
83
-
84
- def open_from(self, f: BinaryIO):
85
- mcstructure = load_nbt(
86
- f, little_endian=True, string_decoder=utf8_escape_decoder
87
- ).compound
88
- if mcstructure.get_int("format_version").py_int == 1:
89
- min_point = numpy.array(
90
- tuple(c.py_int for c in mcstructure.get_list("structure_world_origin"))
91
- )
92
- max_point = min_point + tuple(
93
- c.py_int for c in mcstructure.get_list("size")
94
- )
95
- selection = SelectionBox(min_point, max_point)
96
- self._bounds[self.dimensions[0]] = SelectionGroup(selection)
97
- translator_version = self.translation_manager.get_version(
98
- "bedrock", (999, 999, 999)
99
- )
100
- self._platform = translator_version.platform
101
- self._version = translator_version.version_number
102
- structure = mcstructure.get_compound("structure")
103
- indices = structure.get_list("block_indices")
104
- blocks_array: numpy.ndarray = numpy.array(
105
- [[b.py_int for b in layer] for layer in indices],
106
- dtype=numpy.int32,
107
- ).reshape((len(indices), *selection.shape))
108
-
109
- palette_tag = structure.get_compound("palette")
110
- palette_key = next(iter(palette_tag))
111
- sub_palette_tag = palette_tag.get_compound(palette_key)
112
- block_palette = list(sub_palette_tag.get_list("block_palette"))
113
-
114
- if -1 in blocks_array[0]:
115
- blocks_array[0][blocks_array[0] == -1] = len(block_palette)
116
- block_palette.append(
117
- CompoundTag(
118
- {
119
- "name": StringTag("minecraft:structure_void"),
120
- "states": CompoundTag(),
121
- "version": IntTag(17694723), # 1.13.0
122
- }
123
- )
124
- )
125
-
126
- for cx, cz in selection.chunk_locations():
127
- chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection(
128
- selection
129
- )
130
- array_slice = (slice(None),) + chunk_box.create_moved_box(
131
- selection.min, subtract=True
132
- ).slice
133
- chunk_blocks_: numpy.ndarray = blocks_array[array_slice]
134
- chunk_palette_indexes, chunk_blocks = numpy.unique(
135
- chunk_blocks_.reshape((chunk_blocks_.shape[0], -1)).T,
136
- return_inverse=True,
137
- axis=0,
138
- )
139
- chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:])
140
-
141
- chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object)
142
- for palette_index, indexes in enumerate(chunk_palette_indexes):
143
- chunk_palette[palette_index] = tuple(
144
- block_palette[index] for index in indexes if index >= 0
145
- )
146
-
147
- self._chunks[(cx, cz)] = (
148
- chunk_box,
149
- chunk_blocks,
150
- chunk_palette,
151
- [],
152
- [],
153
- )
154
-
155
- block_entities = {
156
- int(key): val["block_entity_data"]
157
- for key, val in sub_palette_tag.get_compound(
158
- "block_position_data"
159
- ).items()
160
- if "block_entity_data" in val
161
- }
162
- for location, block_entity in block_entities.items():
163
- if all(key in block_entity for key in "xyz"):
164
- x, y, z = (
165
- block_entity["x"].py_int,
166
- block_entity["y"].py_int,
167
- block_entity["z"].py_int,
168
- )
169
- cx, cz = x >> 4, z >> 4
170
- if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][
171
- 0
172
- ]:
173
- self._chunks[(cx, cz)][3].append(block_entity)
174
-
175
- for entity in structure.get_list("entities"):
176
- if "Pos" in entity:
177
- pos = entity.get_list("Pos")
178
- x = pos[0].py_float
179
- y = pos[1].py_float
180
- z = pos[2].py_float
181
- cx, cz = numpy.floor([x, z]).astype(int) >> 4
182
- if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][
183
- 0
184
- ]:
185
- self._chunks[(cx, cz)][4].append(entity)
186
-
187
- else:
188
- raise Exception(
189
- f"mcstructure file with format_version=={mcstructure.get_int('format_version').py_int} cannot be read"
190
- )
191
-
192
- @staticmethod
193
- def is_valid(path: str) -> bool:
194
- return os.path.isfile(path) and path.endswith(".mcstructure")
195
-
196
- @property
197
- def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
198
- return {"bedrock": (False, True)}
199
-
200
- @property
201
- def extensions(self) -> Tuple[str, ...]:
202
- return (".mcstructure",)
203
-
204
- def _get_interface(self, raw_chunk_data=None) -> "MCStructureInterface":
205
- return mcstructure_interface
206
-
207
- def _get_interface_and_translator(
208
- self, raw_chunk_data=None
209
- ) -> Tuple["Interface", "Translator", VersionNumberAny]:
210
- interface = self._get_interface(raw_chunk_data)
211
- translator, version_identifier = interface.get_translator(
212
- self.max_world_version, raw_chunk_data, self.translation_manager
213
- )
214
- return interface, translator, version_identifier
215
-
216
- def save_to(self, f: BinaryIO):
217
- selection = self._bounds[self.dimensions[0]].selection_boxes[0]
218
- mcstructure = CompoundTag(
219
- {
220
- "format_version": IntTag(1),
221
- "structure_world_origin": ListTag(
222
- [
223
- IntTag(selection.min_x),
224
- IntTag(selection.min_y),
225
- IntTag(selection.min_z),
226
- ]
227
- ),
228
- "size": ListTag(
229
- [
230
- IntTag(selection.size_x),
231
- IntTag(selection.size_y),
232
- IntTag(selection.size_z),
233
- ]
234
- ),
235
- }
236
- )
237
-
238
- entities = []
239
- block_entities = []
240
- blocks = numpy.zeros(selection.shape, dtype=numpy.uint32)
241
- palette: List[AnyNDArray] = []
242
- if self.version < (1, 13, 0):
243
- raise Exception(
244
- "Writing to mcstructre files in pre-1.13 format is not currently supported."
245
- )
246
- else:
247
- arr = numpy.empty(1, dtype=object)
248
- arr[0] = [
249
- CompoundTag(
250
- {
251
- "name": StringTag("minecraft:air"),
252
- "states": CompoundTag(),
253
- "version": IntTag(17694723),
254
- }
255
- )
256
- ]
257
- palette.append(arr)
258
-
259
- palette_len = 1
260
-
261
- for (
262
- selection_,
263
- blocks_,
264
- palette_,
265
- block_entities_,
266
- entities_,
267
- ) in self._chunks.values():
268
- if selection_.intersects(selection):
269
- box = selection_.create_moved_box(selection.min, subtract=True)
270
- blocks[box.slice] = blocks_ + palette_len
271
- palette.append(palette_)
272
- palette_len += len(palette_)
273
- block_entities += block_entities_
274
- entities += entities_
275
-
276
- compact_palette, lut = brute_sort_objects_no_hash(numpy.concatenate(palette))
277
- blocks = lut[blocks].ravel()
278
- block_palette = []
279
- block_palette_indices = []
280
- for block_list in compact_palette:
281
- indexed_block = [-1] * 2
282
- for block_layer, block in enumerate(block_list):
283
- if block_layer >= 2:
284
- break
285
- if block["name"] != StringTag("minecraft:structure_void"):
286
- if block in block_palette:
287
- indexed_block[block_layer] = block_palette.index(block)
288
- else:
289
- indexed_block[block_layer] = len(block_palette)
290
- block_palette.append(block)
291
- block_palette_indices.append(indexed_block)
292
-
293
- block_indices = numpy.array(block_palette_indices, dtype=numpy.int32)[blocks].T
294
-
295
- mcstructure["structure"] = CompoundTag(
296
- {
297
- "block_indices": ListTag(
298
- [ # a list of tag ints that index into the block_palette. One list per block layer
299
- ListTag([IntTag(block) for block in layer])
300
- for layer in block_indices
301
- ]
302
- ),
303
- "entities": ListTag(entities),
304
- "palette": CompoundTag(
305
- {
306
- "default": CompoundTag(
307
- {
308
- "block_palette": ListTag(block_palette),
309
- "block_position_data": CompoundTag(
310
- {
311
- str(
312
- (
313
- (
314
- block_entity["x"].py_int
315
- - selection.min_x
316
- )
317
- * selection.size_y
318
- + (
319
- block_entity["y"].py_int
320
- - selection.min_y
321
- )
322
- )
323
- * selection.size_z
324
- + block_entity["z"].py_int
325
- - selection.min_z
326
- ): CompoundTag(
327
- {"block_entity_data": block_entity}
328
- )
329
- for block_entity in block_entities
330
- }
331
- ),
332
- }
333
- )
334
- }
335
- ),
336
- }
337
- )
338
- mcstructure.save_to(
339
- f, compressed=False, little_endian=True, string_encoder=utf8_escape_encoder
340
- )
341
-
342
- def _close(self):
343
- """Close the disk database"""
344
- self._chunks.clear()
345
-
346
- def unload(self):
347
- pass
348
-
349
- def all_chunk_coords(
350
- self, dimension: Optional[Dimension] = None
351
- ) -> Iterable[ChunkCoordinates]:
352
- yield from self._chunks.keys()
353
-
354
- def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
355
- return (cx, cz) in self._chunks
356
-
357
- def _encode(
358
- self,
359
- interface: MCStructureInterface,
360
- chunk: Chunk,
361
- dimension: Dimension,
362
- chunk_palette: AnyNDArray,
363
- ):
364
- return interface.encode(
365
- chunk,
366
- chunk_palette,
367
- self.max_world_version,
368
- SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
369
- self._bounds[dimension].to_box()
370
- ),
371
- )
372
-
373
- def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
374
- if (cx, cz) in self._chunks:
375
- del self._chunks[(cx, cz)]
376
-
377
- def _put_raw_chunk_data(
378
- self,
379
- cx: int,
380
- cz: int,
381
- section: MCStructureChunk,
382
- dimension: Optional[Dimension] = None,
383
- ):
384
- self._chunks[(cx, cz)] = copy.deepcopy(
385
- (
386
- section.selection,
387
- section.blocks,
388
- section.palette,
389
- section.block_entities,
390
- section.entities,
391
- )
392
- )
393
-
394
- def _get_raw_chunk_data(
395
- self, cx: int, cz: int, dimension: Optional[Dimension] = None
396
- ) -> MCStructureChunk:
397
- """
398
- Return the raw data as loaded from disk.
399
-
400
- :param cx: The x coordinate of the chunk.
401
- :param cz: The z coordinate of the chunk.
402
- :param dimension: The dimension to load the data from.
403
- :return: The raw chunk data.
404
- """
405
- if (cx, cz) in self._chunks:
406
- return MCStructureChunk(*copy.deepcopy(self._chunks[(cx, cz)]))
407
- else:
408
- raise ChunkDoesNotExist
1
+ import os
2
+ from typing import Optional, Tuple, Iterable, TYPE_CHECKING, BinaryIO, Dict, List, Union
3
+ import numpy
4
+ import copy
5
+
6
+ from amulet_nbt import (
7
+ IntTag,
8
+ StringTag,
9
+ ListTag,
10
+ CompoundTag,
11
+ load as load_nbt,
12
+ utf8_escape_decoder,
13
+ utf8_escape_encoder,
14
+ )
15
+
16
+ from amulet.api.data_types import (
17
+ VersionNumberAny,
18
+ VersionNumberTuple,
19
+ ChunkCoordinates,
20
+ AnyNDArray,
21
+ Dimension,
22
+ PlatformType,
23
+ )
24
+ from amulet.api.wrapper import StructureFormatWrapper
25
+ from amulet.api.chunk import Chunk
26
+ from amulet.api.selection import SelectionGroup, SelectionBox
27
+ from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError
28
+ from amulet.utils.numpy_helpers import brute_sort_objects_no_hash
29
+
30
+ from .chunk import MCStructureChunk
31
+ from .interface import MCStructureInterface
32
+
33
+ if TYPE_CHECKING:
34
+ from amulet.api.wrapper import Translator, Interface
35
+
36
+ mcstructure_interface = MCStructureInterface()
37
+
38
+
39
+ class MCStructureFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
40
+ """
41
+ This FormatWrapper class exists to interface with the Bedrock mcstructure structure block format.
42
+ """
43
+
44
+ def __init__(self, path: str):
45
+ """
46
+ Construct a new instance of :class:`MCStructureFormatWrapper`.
47
+
48
+ This should not be used directly. You should instead use :func:`amulet.load_format`.
49
+
50
+ :param path: The file path to the serialised data.
51
+ """
52
+ super().__init__(path)
53
+ self._chunks: Dict[
54
+ ChunkCoordinates,
55
+ Tuple[
56
+ SelectionBox,
57
+ numpy.ndarray,
58
+ AnyNDArray,
59
+ List[CompoundTag],
60
+ List[CompoundTag],
61
+ ],
62
+ ] = {}
63
+
64
+ def _create(
65
+ self,
66
+ overwrite: bool,
67
+ bounds: Union[
68
+ SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
69
+ ] = None,
70
+ **kwargs,
71
+ ):
72
+ if not overwrite and os.path.isfile(self.path):
73
+ raise ObjectWriteError(f"There is already a file at {self.path}")
74
+ translator_version = self.translation_manager.get_version(
75
+ "bedrock", (999, 999, 999)
76
+ )
77
+ self._platform = translator_version.platform
78
+ self._version = translator_version.version_number
79
+ self._chunks = {}
80
+ self._set_selection(bounds)
81
+ self._is_open = True
82
+ self._has_lock = True
83
+
84
+ def open_from(self, f: BinaryIO):
85
+ mcstructure = load_nbt(
86
+ f, little_endian=True, string_decoder=utf8_escape_decoder
87
+ ).compound
88
+ if mcstructure.get_int("format_version").py_int == 1:
89
+ min_point = numpy.array(
90
+ tuple(c.py_int for c in mcstructure.get_list("structure_world_origin"))
91
+ )
92
+ max_point = min_point + tuple(
93
+ c.py_int for c in mcstructure.get_list("size")
94
+ )
95
+ selection = SelectionBox(min_point, max_point)
96
+ self._bounds[self.dimensions[0]] = SelectionGroup(selection)
97
+ translator_version = self.translation_manager.get_version(
98
+ "bedrock", (999, 999, 999)
99
+ )
100
+ self._platform = translator_version.platform
101
+ self._version = translator_version.version_number
102
+ structure = mcstructure.get_compound("structure")
103
+ indices = structure.get_list("block_indices")
104
+ blocks_array: numpy.ndarray = numpy.array(
105
+ [[b.py_int for b in layer] for layer in indices],
106
+ dtype=numpy.int32,
107
+ ).reshape((len(indices), *selection.shape))
108
+
109
+ palette_tag = structure.get_compound("palette")
110
+ palette_key = next(iter(palette_tag))
111
+ sub_palette_tag = palette_tag.get_compound(palette_key)
112
+ block_palette = list(sub_palette_tag.get_list("block_palette"))
113
+
114
+ if -1 in blocks_array[0]:
115
+ blocks_array[0][blocks_array[0] == -1] = len(block_palette)
116
+ block_palette.append(
117
+ CompoundTag(
118
+ {
119
+ "name": StringTag("minecraft:structure_void"),
120
+ "states": CompoundTag(),
121
+ "version": IntTag(17694723), # 1.13.0
122
+ }
123
+ )
124
+ )
125
+
126
+ for cx, cz in selection.chunk_locations():
127
+ chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection(
128
+ selection
129
+ )
130
+ array_slice = (slice(None),) + chunk_box.create_moved_box(
131
+ selection.min, subtract=True
132
+ ).slice
133
+ chunk_blocks_: numpy.ndarray = blocks_array[array_slice]
134
+ chunk_palette_indexes, chunk_blocks = numpy.unique(
135
+ chunk_blocks_.reshape((chunk_blocks_.shape[0], -1)).T,
136
+ return_inverse=True,
137
+ axis=0,
138
+ )
139
+ chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape[1:])
140
+
141
+ chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object)
142
+ for palette_index, indexes in enumerate(chunk_palette_indexes):
143
+ chunk_palette[palette_index] = tuple(
144
+ block_palette[index] for index in indexes if index >= 0
145
+ )
146
+
147
+ self._chunks[(cx, cz)] = (
148
+ chunk_box,
149
+ chunk_blocks,
150
+ chunk_palette,
151
+ [],
152
+ [],
153
+ )
154
+
155
+ block_entities = {
156
+ int(key): val["block_entity_data"]
157
+ for key, val in sub_palette_tag.get_compound(
158
+ "block_position_data"
159
+ ).items()
160
+ if "block_entity_data" in val
161
+ }
162
+ for location, block_entity in block_entities.items():
163
+ if all(key in block_entity for key in "xyz"):
164
+ x, y, z = (
165
+ block_entity["x"].py_int,
166
+ block_entity["y"].py_int,
167
+ block_entity["z"].py_int,
168
+ )
169
+ cx, cz = x >> 4, z >> 4
170
+ if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][
171
+ 0
172
+ ]:
173
+ self._chunks[(cx, cz)][3].append(block_entity)
174
+
175
+ for entity in structure.get_list("entities"):
176
+ if "Pos" in entity:
177
+ pos = entity.get_list("Pos")
178
+ x = pos[0].py_float
179
+ y = pos[1].py_float
180
+ z = pos[2].py_float
181
+ cx, cz = numpy.floor([x, z]).astype(int) >> 4
182
+ if (cx, cz) in self._chunks and (x, y, z) in self._chunks[(cx, cz)][
183
+ 0
184
+ ]:
185
+ self._chunks[(cx, cz)][4].append(entity)
186
+
187
+ else:
188
+ raise Exception(
189
+ f"mcstructure file with format_version=={mcstructure.get_int('format_version').py_int} cannot be read"
190
+ )
191
+
192
+ @staticmethod
193
+ def is_valid(path: str) -> bool:
194
+ return os.path.isfile(path) and path.endswith(".mcstructure")
195
+
196
+ @property
197
+ def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
198
+ return {"bedrock": (False, True)}
199
+
200
+ @property
201
+ def extensions(self) -> Tuple[str, ...]:
202
+ return (".mcstructure",)
203
+
204
+ def _get_interface(self, raw_chunk_data=None) -> "MCStructureInterface":
205
+ return mcstructure_interface
206
+
207
+ def _get_interface_and_translator(
208
+ self, raw_chunk_data=None
209
+ ) -> Tuple["Interface", "Translator", VersionNumberAny]:
210
+ interface = self._get_interface(raw_chunk_data)
211
+ translator, version_identifier = interface.get_translator(
212
+ self.max_world_version, raw_chunk_data, self.translation_manager
213
+ )
214
+ return interface, translator, version_identifier
215
+
216
+ def save_to(self, f: BinaryIO):
217
+ selection = self._bounds[self.dimensions[0]].selection_boxes[0]
218
+ mcstructure = CompoundTag(
219
+ {
220
+ "format_version": IntTag(1),
221
+ "structure_world_origin": ListTag(
222
+ [
223
+ IntTag(selection.min_x),
224
+ IntTag(selection.min_y),
225
+ IntTag(selection.min_z),
226
+ ]
227
+ ),
228
+ "size": ListTag(
229
+ [
230
+ IntTag(selection.size_x),
231
+ IntTag(selection.size_y),
232
+ IntTag(selection.size_z),
233
+ ]
234
+ ),
235
+ }
236
+ )
237
+
238
+ entities = []
239
+ block_entities = []
240
+ blocks = numpy.zeros(selection.shape, dtype=numpy.uint32)
241
+ palette: List[AnyNDArray] = []
242
+ if self.version < (1, 13, 0):
243
+ raise Exception(
244
+ "Writing to mcstructre files in pre-1.13 format is not currently supported."
245
+ )
246
+ else:
247
+ arr = numpy.empty(1, dtype=object)
248
+ arr[0] = [
249
+ CompoundTag(
250
+ {
251
+ "name": StringTag("minecraft:air"),
252
+ "states": CompoundTag(),
253
+ "version": IntTag(17694723),
254
+ }
255
+ )
256
+ ]
257
+ palette.append(arr)
258
+
259
+ palette_len = 1
260
+
261
+ for (
262
+ selection_,
263
+ blocks_,
264
+ palette_,
265
+ block_entities_,
266
+ entities_,
267
+ ) in self._chunks.values():
268
+ if selection_.intersects(selection):
269
+ box = selection_.create_moved_box(selection.min, subtract=True)
270
+ blocks[box.slice] = blocks_ + palette_len
271
+ palette.append(palette_)
272
+ palette_len += len(palette_)
273
+ block_entities += block_entities_
274
+ entities += entities_
275
+
276
+ compact_palette, lut = brute_sort_objects_no_hash(numpy.concatenate(palette))
277
+ blocks = lut[blocks].ravel()
278
+ block_palette = []
279
+ block_palette_indices = []
280
+ for block_list in compact_palette:
281
+ indexed_block = [-1] * 2
282
+ for block_layer, block in enumerate(block_list):
283
+ if block_layer >= 2:
284
+ break
285
+ if block["name"] != StringTag("minecraft:structure_void"):
286
+ if block in block_palette:
287
+ indexed_block[block_layer] = block_palette.index(block)
288
+ else:
289
+ indexed_block[block_layer] = len(block_palette)
290
+ block_palette.append(block)
291
+ block_palette_indices.append(indexed_block)
292
+
293
+ block_indices = numpy.array(block_palette_indices, dtype=numpy.int32)[blocks].T
294
+
295
+ mcstructure["structure"] = CompoundTag(
296
+ {
297
+ "block_indices": ListTag(
298
+ [ # a list of tag ints that index into the block_palette. One list per block layer
299
+ ListTag([IntTag(block) for block in layer])
300
+ for layer in block_indices
301
+ ]
302
+ ),
303
+ "entities": ListTag(entities),
304
+ "palette": CompoundTag(
305
+ {
306
+ "default": CompoundTag(
307
+ {
308
+ "block_palette": ListTag(block_palette),
309
+ "block_position_data": CompoundTag(
310
+ {
311
+ str(
312
+ (
313
+ (
314
+ block_entity["x"].py_int
315
+ - selection.min_x
316
+ )
317
+ * selection.size_y
318
+ + (
319
+ block_entity["y"].py_int
320
+ - selection.min_y
321
+ )
322
+ )
323
+ * selection.size_z
324
+ + block_entity["z"].py_int
325
+ - selection.min_z
326
+ ): CompoundTag(
327
+ {"block_entity_data": block_entity}
328
+ )
329
+ for block_entity in block_entities
330
+ }
331
+ ),
332
+ }
333
+ )
334
+ }
335
+ ),
336
+ }
337
+ )
338
+ mcstructure.save_to(
339
+ f, compressed=False, little_endian=True, string_encoder=utf8_escape_encoder
340
+ )
341
+
342
+ def _close(self):
343
+ """Close the disk database"""
344
+ self._chunks.clear()
345
+
346
+ def unload(self):
347
+ pass
348
+
349
+ def all_chunk_coords(
350
+ self, dimension: Optional[Dimension] = None
351
+ ) -> Iterable[ChunkCoordinates]:
352
+ yield from self._chunks.keys()
353
+
354
+ def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
355
+ return (cx, cz) in self._chunks
356
+
357
+ def _encode(
358
+ self,
359
+ interface: MCStructureInterface,
360
+ chunk: Chunk,
361
+ dimension: Dimension,
362
+ chunk_palette: AnyNDArray,
363
+ ):
364
+ return interface.encode(
365
+ chunk,
366
+ chunk_palette,
367
+ self.max_world_version,
368
+ SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
369
+ self._bounds[dimension].to_box()
370
+ ),
371
+ )
372
+
373
+ def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
374
+ if (cx, cz) in self._chunks:
375
+ del self._chunks[(cx, cz)]
376
+
377
+ def _put_raw_chunk_data(
378
+ self,
379
+ cx: int,
380
+ cz: int,
381
+ section: MCStructureChunk,
382
+ dimension: Optional[Dimension] = None,
383
+ ):
384
+ self._chunks[(cx, cz)] = copy.deepcopy(
385
+ (
386
+ section.selection,
387
+ section.blocks,
388
+ section.palette,
389
+ section.block_entities,
390
+ section.entities,
391
+ )
392
+ )
393
+
394
+ def _get_raw_chunk_data(
395
+ self, cx: int, cz: int, dimension: Optional[Dimension] = None
396
+ ) -> MCStructureChunk:
397
+ """
398
+ Return the raw data as loaded from disk.
399
+
400
+ :param cx: The x coordinate of the chunk.
401
+ :param cz: The z coordinate of the chunk.
402
+ :param dimension: The dimension to load the data from.
403
+ :return: The raw chunk data.
404
+ """
405
+ if (cx, cz) in self._chunks:
406
+ return MCStructureChunk(*copy.deepcopy(self._chunks[(cx, cz)]))
407
+ else:
408
+ raise ChunkDoesNotExist