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,373 +1,373 @@
1
- import os
2
- from typing import (
3
- Optional,
4
- Tuple,
5
- Iterable,
6
- TYPE_CHECKING,
7
- BinaryIO,
8
- Dict,
9
- Union,
10
- List,
11
- NamedTuple,
12
- )
13
- import numpy
14
- import copy
15
-
16
- from amulet_nbt import (
17
- ShortTag,
18
- StringTag,
19
- ListTag,
20
- CompoundTag,
21
- ByteArrayTag,
22
- NamedTag,
23
- load as load_nbt,
24
- )
25
-
26
- from amulet.api.data_types import (
27
- AnyNDArray,
28
- VersionNumberAny,
29
- VersionNumberTuple,
30
- Dimension,
31
- PlatformType,
32
- PointCoordinates,
33
- ChunkCoordinates,
34
- )
35
- from amulet.api.registry import BlockManager
36
- from amulet.api.wrapper import StructureFormatWrapper
37
- from amulet.api.chunk import Chunk
38
- from amulet.api.selection import SelectionBox, SelectionGroup
39
- from amulet.api.errors import ObjectReadError, ObjectWriteError, ChunkDoesNotExist
40
- from .interface import (
41
- JavaSchematicInterface,
42
- BedrockSchematicInterface,
43
- SchematicInterface,
44
- )
45
- from .chunk import SchematicChunk
46
-
47
- if TYPE_CHECKING:
48
- from amulet.api.wrapper import Translator, Interface
49
-
50
- java_interface = JavaSchematicInterface()
51
- bedrock_interface = BedrockSchematicInterface()
52
-
53
-
54
- def _is_schematic(path: str):
55
- """Check if a file is actually a sponge schematic file."""
56
- try:
57
- return "Blocks" in load_nbt(path).compound
58
- except:
59
- return False
60
-
61
-
62
- class SchematicFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
63
- """
64
- This FormatWrapper class exists to interface with the legacy schematic structure format.
65
- """
66
-
67
- def __init__(self, path: str):
68
- """
69
- Construct a new instance of :class:`SchematicFormatWrapper`.
70
-
71
- This should not be used directly. You should instead use :func:`amulet.load_format`.
72
-
73
- :param path: The file path to the serialised data.
74
- """
75
- super().__init__(path)
76
- self._chunks: Dict[
77
- ChunkCoordinates,
78
- SchematicChunk,
79
- ] = {}
80
-
81
- def _create(
82
- self,
83
- overwrite: bool,
84
- bounds: Union[
85
- SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
86
- ] = None,
87
- **kwargs,
88
- ):
89
- if not overwrite and os.path.isfile(self.path):
90
- raise ObjectWriteError(f"There is already a file at {self.path}")
91
- if self._platform == "bedrock":
92
- self._version = (1, 12, 0)
93
- else:
94
- self._platform = "java"
95
- self._version = (1, 12, 2)
96
- self._chunks = {}
97
- self._set_selection(bounds)
98
- self._is_open = True
99
- self._has_lock = True
100
-
101
- def open_from(self, f: BinaryIO):
102
- schematic = load_nbt(f).compound
103
- if "BlockData" in schematic:
104
- raise ObjectReadError("This file is not a legacy schematic file.")
105
- materials = schematic.get_string("Materials", StringTag("Alpha")).py_str
106
- if materials == "Alpha":
107
- self._platform = "java"
108
- self._version = (1, 12, 2)
109
- elif materials == "Pocket":
110
- self._platform = "bedrock"
111
- self._version = (1, 12, 0)
112
- else:
113
- raise Exception(
114
- f'"{materials}" is not a supported platform for a schematic file.'
115
- )
116
- self._chunks = {}
117
- selection_box = SelectionBox(
118
- (0, 0, 0),
119
- (
120
- schematic.get_short("Width").py_int,
121
- schematic.get_short("Height").py_int,
122
- schematic.get_short("Length").py_int,
123
- ),
124
- )
125
- self._bounds[self.dimensions[0]] = SelectionGroup(selection_box)
126
- entities: ListTag = schematic.get_list("Entities", ListTag())
127
- block_entities: ListTag = schematic.get_list("TileEntities", ListTag())
128
- blocks: numpy.ndarray = (
129
- schematic.get_byte_array("Blocks")
130
- .np_array.astype(numpy.uint8)
131
- .astype(numpy.uint16)
132
- )
133
- if "AddBlocks" in schematic:
134
- add_blocks = schematic.get_byte_array("AddBlocks").np_array
135
- blocks = (
136
- blocks
137
- + (
138
- numpy.concatenate([[(add_blocks & 0xF0) >> 4], [add_blocks & 0xF]])
139
- .T.ravel()
140
- .astype(numpy.uint16)
141
- << 8
142
- )[: blocks.size]
143
- )
144
- max_point = selection_box.max
145
- temp_shape = (max_point[1], max_point[2], max_point[0])
146
- blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ
147
- data = numpy.transpose(
148
- schematic.get_byte_array("Data").np_array.reshape(temp_shape), (2, 0, 1)
149
- ).astype(numpy.uint8)
150
- for cx, cz in selection_box.chunk_locations():
151
- box = SelectionBox(
152
- (cx * self.sub_chunk_size, 0, cz * self.sub_chunk_size),
153
- (
154
- min((cx + 1) * self.sub_chunk_size, selection_box.size_x),
155
- selection_box.size_y,
156
- min((cz + 1) * self.sub_chunk_size, selection_box.size_z),
157
- ),
158
- )
159
- self._chunks[(cx, cz)] = SchematicChunk(
160
- box, blocks[box.slice], data[box.slice], [], []
161
- )
162
- for e in block_entities:
163
- if isinstance(e, CompoundTag) and all(key in e for key in ("x", "y", "z")):
164
- x = e.get_int("x").py_int
165
- y = e.get_int("y").py_int
166
- z = e.get_int("z").py_int
167
- if (x, y, z) in selection_box:
168
- cx = x >> 4
169
- cz = z >> 4
170
- self._chunks[(cx, cz)].block_entities.append(e)
171
- for e in entities:
172
- if isinstance(e, CompoundTag) and "Pos" in e:
173
- pos: PointCoordinates = tuple(map(float, e.get_list("Pos", ListTag())))
174
- if len(pos) == 3:
175
- if pos in selection_box:
176
- cx = int(pos[0]) >> 4
177
- cz = int(pos[2]) >> 4
178
- self._chunks[(cx, cz)].entities.append(e)
179
-
180
- @staticmethod
181
- def is_valid(path: str) -> bool:
182
- return (
183
- os.path.isfile(path) and path.endswith(".schematic") and _is_schematic(path)
184
- )
185
-
186
- @property
187
- def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
188
- return {"java": (True, True), "bedrock": (True, True)}
189
-
190
- @property
191
- def extensions(self) -> Tuple[str, ...]:
192
- return (".schematic",)
193
-
194
- def _get_interface(self, raw_chunk_data=None) -> SchematicInterface:
195
- if self._platform == "java":
196
- return java_interface
197
- elif self._platform == "bedrock":
198
- return bedrock_interface
199
- else:
200
- raise Exception(f"{self._platform} is not a supported platform")
201
-
202
- def _get_interface_and_translator(
203
- self, raw_chunk_data=None
204
- ) -> Tuple["Interface", "Translator", VersionNumberAny]:
205
- interface = self._get_interface(raw_chunk_data)
206
- translator, version_identifier = interface.get_translator(
207
- self.max_world_version, raw_chunk_data, self.translation_manager
208
- )
209
- return interface, translator, version_identifier
210
-
211
- def save_to(self, f: BinaryIO):
212
- if self._platform == "java":
213
- materials = "Alpha"
214
- elif self._platform == "bedrock":
215
- materials = "Pocket"
216
- else:
217
- raise ObjectWriteError(
218
- f'"{self._platform}" is not a supported platform for a schematic file.'
219
- )
220
-
221
- selection = self._bounds[self.dimensions[0]].selection_boxes[0]
222
-
223
- tag = CompoundTag(
224
- {
225
- "TileTicks": ListTag(),
226
- "Width": ShortTag(selection.size_x),
227
- "Height": ShortTag(selection.size_y),
228
- "Length": ShortTag(selection.size_z),
229
- "Materials": StringTag(materials),
230
- }
231
- )
232
-
233
- entities = []
234
- block_entities = []
235
- blocks = numpy.zeros(
236
- selection.shape, dtype=numpy.uint16
237
- ) # only 12 bits are actually used at most
238
- block_data = numpy.zeros(
239
- selection.shape, dtype=numpy.uint8
240
- ) # only 4 bits are used
241
-
242
- for chunk in self._chunks.values():
243
- if chunk.selection.intersects(selection):
244
- box = chunk.selection.create_moved_box(selection.min, subtract=True)
245
- blocks[box.slice] = chunk.blocks
246
- block_data[box.slice] = chunk.data
247
- for be in chunk.block_entities:
248
- coord_type = be["x"].__class__
249
- be["x"] = coord_type(be["x"].py_int - selection.min_x)
250
- be["y"] = coord_type(be["y"].py_int - selection.min_y)
251
- be["z"] = coord_type(be["z"].py_int - selection.min_z)
252
- block_entities.append(be)
253
- for e in chunk.entities:
254
- coord_type = e["Pos"][0].__class__
255
- e["Pos"][0] = coord_type(e["Pos"][0] - selection.min_x)
256
- e["Pos"][1] = coord_type(e["Pos"][1] - selection.min_y)
257
- e["Pos"][2] = coord_type(e["Pos"][2] - selection.min_z)
258
- entities.append(e)
259
-
260
- tag["Entities"] = ListTag(entities)
261
- tag["TileEntities"] = ListTag(block_entities)
262
- tag["Data"] = ByteArrayTag(numpy.transpose(block_data, (1, 2, 0))) # XYZ => YZX
263
- tag["Blocks"] = ByteArrayTag(
264
- numpy.transpose((blocks & 0xFF).astype(numpy.uint8), (1, 2, 0))
265
- )
266
- if numpy.max(blocks) > 0xFF:
267
- add_blocks = (numpy.transpose(blocks & 0xF00, (1, 2, 0)) >> 8).ravel()
268
- tag["AddBlocks"] = ByteArrayTag((add_blocks[::2] << 4) + add_blocks[1::2])
269
- NamedTag(tag, "Schematic").save_to(f)
270
-
271
- def _close(self):
272
- """Close the disk database"""
273
- self._chunks.clear()
274
-
275
- def unload(self):
276
- pass
277
-
278
- def all_chunk_coords(
279
- self, dimension: Optional[Dimension] = None
280
- ) -> Iterable[ChunkCoordinates]:
281
- yield from self._chunks.keys()
282
-
283
- def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
284
- return (cx, cz) in self._chunks
285
-
286
- def _pack(
287
- self,
288
- chunk: "Chunk",
289
- translator: "Translator",
290
- chunk_version: VersionNumberAny,
291
- ) -> Tuple["Chunk", AnyNDArray]:
292
- version = self.translation_manager.get_version(
293
- *translator.translator_key(chunk_version)
294
- )
295
- palette = []
296
- for entry in chunk.block_palette.blocks:
297
- b = version.block.block_to_ints(entry)
298
- if b is None:
299
- b = (0, 0)
300
- palette.append(b)
301
- return (
302
- chunk,
303
- numpy.array(palette),
304
- )
305
-
306
- def _encode(
307
- self,
308
- interface: SchematicInterface,
309
- chunk: Chunk,
310
- dimension: Dimension,
311
- chunk_palette: AnyNDArray,
312
- ):
313
- return interface.encode(
314
- chunk,
315
- chunk_palette,
316
- self.max_world_version,
317
- SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
318
- self._bounds[dimension].to_box()
319
- ),
320
- )
321
-
322
- def _unpack(
323
- self,
324
- translator: "Translator",
325
- game_version: VersionNumberAny,
326
- chunk: "Chunk",
327
- chunk_palette: AnyNDArray,
328
- ) -> "Chunk":
329
- version = self.translation_manager.get_version(
330
- *translator.translator_key(game_version)
331
- )
332
- palette = chunk._block_palette = BlockManager()
333
- lut = numpy.array(
334
- [
335
- palette.get_add_block(version.block.ints_to_block(block, data))
336
- for block, data in chunk_palette
337
- ]
338
- )
339
- if len(palette.blocks) != len(chunk_palette):
340
- # if a blockstate was defined twice
341
- for cy in chunk.blocks.sub_chunks:
342
- chunk.blocks.add_sub_chunk(cy, lut[chunk.blocks.get_sub_chunk(cy)])
343
-
344
- return chunk
345
-
346
- def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
347
- if (cx, cz) in self._chunks:
348
- del self._chunks[(cx, cz)]
349
-
350
- def _put_raw_chunk_data(
351
- self,
352
- cx: int,
353
- cz: int,
354
- section: SchematicChunk,
355
- dimension: Optional[Dimension] = None,
356
- ):
357
- self._chunks[(cx, cz)] = copy.deepcopy(section)
358
-
359
- def _get_raw_chunk_data(
360
- self, cx: int, cz: int, dimension: Optional[Dimension] = None
361
- ) -> SchematicChunk:
362
- """
363
- Return the raw data as loaded from disk.
364
-
365
- :param cx: The x coordinate of the chunk.
366
- :param cz: The z coordinate of the chunk.
367
- :param dimension: The dimension to load the data from.
368
- :return: The raw chunk data.
369
- """
370
- if (cx, cz) in self._chunks:
371
- return copy.deepcopy(self._chunks[(cx, cz)])
372
- else:
373
- raise ChunkDoesNotExist
1
+ import os
2
+ from typing import (
3
+ Optional,
4
+ Tuple,
5
+ Iterable,
6
+ TYPE_CHECKING,
7
+ BinaryIO,
8
+ Dict,
9
+ Union,
10
+ List,
11
+ NamedTuple,
12
+ )
13
+ import numpy
14
+ import copy
15
+
16
+ from amulet_nbt import (
17
+ ShortTag,
18
+ StringTag,
19
+ ListTag,
20
+ CompoundTag,
21
+ ByteArrayTag,
22
+ NamedTag,
23
+ load as load_nbt,
24
+ )
25
+
26
+ from amulet.api.data_types import (
27
+ AnyNDArray,
28
+ VersionNumberAny,
29
+ VersionNumberTuple,
30
+ Dimension,
31
+ PlatformType,
32
+ PointCoordinates,
33
+ ChunkCoordinates,
34
+ )
35
+ from amulet.api.registry import BlockManager
36
+ from amulet.api.wrapper import StructureFormatWrapper
37
+ from amulet.api.chunk import Chunk
38
+ from amulet.api.selection import SelectionBox, SelectionGroup
39
+ from amulet.api.errors import ObjectReadError, ObjectWriteError, ChunkDoesNotExist
40
+ from .interface import (
41
+ JavaSchematicInterface,
42
+ BedrockSchematicInterface,
43
+ SchematicInterface,
44
+ )
45
+ from .chunk import SchematicChunk
46
+
47
+ if TYPE_CHECKING:
48
+ from amulet.api.wrapper import Translator, Interface
49
+
50
+ java_interface = JavaSchematicInterface()
51
+ bedrock_interface = BedrockSchematicInterface()
52
+
53
+
54
+ def _is_schematic(path: str):
55
+ """Check if a file is actually a sponge schematic file."""
56
+ try:
57
+ return "Blocks" in load_nbt(path).compound
58
+ except:
59
+ return False
60
+
61
+
62
+ class SchematicFormatWrapper(StructureFormatWrapper[VersionNumberTuple]):
63
+ """
64
+ This FormatWrapper class exists to interface with the legacy schematic structure format.
65
+ """
66
+
67
+ def __init__(self, path: str):
68
+ """
69
+ Construct a new instance of :class:`SchematicFormatWrapper`.
70
+
71
+ This should not be used directly. You should instead use :func:`amulet.load_format`.
72
+
73
+ :param path: The file path to the serialised data.
74
+ """
75
+ super().__init__(path)
76
+ self._chunks: Dict[
77
+ ChunkCoordinates,
78
+ SchematicChunk,
79
+ ] = {}
80
+
81
+ def _create(
82
+ self,
83
+ overwrite: bool,
84
+ bounds: Union[
85
+ SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
86
+ ] = None,
87
+ **kwargs,
88
+ ):
89
+ if not overwrite and os.path.isfile(self.path):
90
+ raise ObjectWriteError(f"There is already a file at {self.path}")
91
+ if self._platform == "bedrock":
92
+ self._version = (1, 12, 0)
93
+ else:
94
+ self._platform = "java"
95
+ self._version = (1, 12, 2)
96
+ self._chunks = {}
97
+ self._set_selection(bounds)
98
+ self._is_open = True
99
+ self._has_lock = True
100
+
101
+ def open_from(self, f: BinaryIO):
102
+ schematic = load_nbt(f).compound
103
+ if "BlockData" in schematic:
104
+ raise ObjectReadError("This file is not a legacy schematic file.")
105
+ materials = schematic.get_string("Materials", StringTag("Alpha")).py_str
106
+ if materials == "Alpha":
107
+ self._platform = "java"
108
+ self._version = (1, 12, 2)
109
+ elif materials == "Pocket":
110
+ self._platform = "bedrock"
111
+ self._version = (1, 12, 0)
112
+ else:
113
+ raise Exception(
114
+ f'"{materials}" is not a supported platform for a schematic file.'
115
+ )
116
+ self._chunks = {}
117
+ selection_box = SelectionBox(
118
+ (0, 0, 0),
119
+ (
120
+ schematic.get_short("Width").py_int,
121
+ schematic.get_short("Height").py_int,
122
+ schematic.get_short("Length").py_int,
123
+ ),
124
+ )
125
+ self._bounds[self.dimensions[0]] = SelectionGroup(selection_box)
126
+ entities: ListTag = schematic.get_list("Entities", ListTag())
127
+ block_entities: ListTag = schematic.get_list("TileEntities", ListTag())
128
+ blocks: numpy.ndarray = (
129
+ schematic.get_byte_array("Blocks")
130
+ .np_array.astype(numpy.uint8)
131
+ .astype(numpy.uint16)
132
+ )
133
+ if "AddBlocks" in schematic:
134
+ add_blocks = schematic.get_byte_array("AddBlocks").np_array
135
+ blocks = (
136
+ blocks
137
+ + (
138
+ numpy.concatenate([[(add_blocks & 0xF0) >> 4], [add_blocks & 0xF]])
139
+ .T.ravel()
140
+ .astype(numpy.uint16)
141
+ << 8
142
+ )[: blocks.size]
143
+ )
144
+ max_point = selection_box.max
145
+ temp_shape = (max_point[1], max_point[2], max_point[0])
146
+ blocks = numpy.transpose(blocks.reshape(temp_shape), (2, 0, 1)) # YZX => XYZ
147
+ data = numpy.transpose(
148
+ schematic.get_byte_array("Data").np_array.reshape(temp_shape), (2, 0, 1)
149
+ ).astype(numpy.uint8)
150
+ for cx, cz in selection_box.chunk_locations():
151
+ box = SelectionBox(
152
+ (cx * self.sub_chunk_size, 0, cz * self.sub_chunk_size),
153
+ (
154
+ min((cx + 1) * self.sub_chunk_size, selection_box.size_x),
155
+ selection_box.size_y,
156
+ min((cz + 1) * self.sub_chunk_size, selection_box.size_z),
157
+ ),
158
+ )
159
+ self._chunks[(cx, cz)] = SchematicChunk(
160
+ box, blocks[box.slice], data[box.slice], [], []
161
+ )
162
+ for e in block_entities:
163
+ if isinstance(e, CompoundTag) and all(key in e for key in ("x", "y", "z")):
164
+ x = e.get_int("x").py_int
165
+ y = e.get_int("y").py_int
166
+ z = e.get_int("z").py_int
167
+ if (x, y, z) in selection_box:
168
+ cx = x >> 4
169
+ cz = z >> 4
170
+ self._chunks[(cx, cz)].block_entities.append(e)
171
+ for e in entities:
172
+ if isinstance(e, CompoundTag) and "Pos" in e:
173
+ pos: PointCoordinates = tuple(map(float, e.get_list("Pos", ListTag())))
174
+ if len(pos) == 3:
175
+ if pos in selection_box:
176
+ cx = int(pos[0]) >> 4
177
+ cz = int(pos[2]) >> 4
178
+ self._chunks[(cx, cz)].entities.append(e)
179
+
180
+ @staticmethod
181
+ def is_valid(path: str) -> bool:
182
+ return (
183
+ os.path.isfile(path) and path.endswith(".schematic") and _is_schematic(path)
184
+ )
185
+
186
+ @property
187
+ def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
188
+ return {"java": (True, True), "bedrock": (True, True)}
189
+
190
+ @property
191
+ def extensions(self) -> Tuple[str, ...]:
192
+ return (".schematic",)
193
+
194
+ def _get_interface(self, raw_chunk_data=None) -> SchematicInterface:
195
+ if self._platform == "java":
196
+ return java_interface
197
+ elif self._platform == "bedrock":
198
+ return bedrock_interface
199
+ else:
200
+ raise Exception(f"{self._platform} is not a supported platform")
201
+
202
+ def _get_interface_and_translator(
203
+ self, raw_chunk_data=None
204
+ ) -> Tuple["Interface", "Translator", VersionNumberAny]:
205
+ interface = self._get_interface(raw_chunk_data)
206
+ translator, version_identifier = interface.get_translator(
207
+ self.max_world_version, raw_chunk_data, self.translation_manager
208
+ )
209
+ return interface, translator, version_identifier
210
+
211
+ def save_to(self, f: BinaryIO):
212
+ if self._platform == "java":
213
+ materials = "Alpha"
214
+ elif self._platform == "bedrock":
215
+ materials = "Pocket"
216
+ else:
217
+ raise ObjectWriteError(
218
+ f'"{self._platform}" is not a supported platform for a schematic file.'
219
+ )
220
+
221
+ selection = self._bounds[self.dimensions[0]].selection_boxes[0]
222
+
223
+ tag = CompoundTag(
224
+ {
225
+ "TileTicks": ListTag(),
226
+ "Width": ShortTag(selection.size_x),
227
+ "Height": ShortTag(selection.size_y),
228
+ "Length": ShortTag(selection.size_z),
229
+ "Materials": StringTag(materials),
230
+ }
231
+ )
232
+
233
+ entities = []
234
+ block_entities = []
235
+ blocks = numpy.zeros(
236
+ selection.shape, dtype=numpy.uint16
237
+ ) # only 12 bits are actually used at most
238
+ block_data = numpy.zeros(
239
+ selection.shape, dtype=numpy.uint8
240
+ ) # only 4 bits are used
241
+
242
+ for chunk in self._chunks.values():
243
+ if chunk.selection.intersects(selection):
244
+ box = chunk.selection.create_moved_box(selection.min, subtract=True)
245
+ blocks[box.slice] = chunk.blocks
246
+ block_data[box.slice] = chunk.data
247
+ for be in chunk.block_entities:
248
+ coord_type = be["x"].__class__
249
+ be["x"] = coord_type(be["x"].py_int - selection.min_x)
250
+ be["y"] = coord_type(be["y"].py_int - selection.min_y)
251
+ be["z"] = coord_type(be["z"].py_int - selection.min_z)
252
+ block_entities.append(be)
253
+ for e in chunk.entities:
254
+ coord_type = e["Pos"][0].__class__
255
+ e["Pos"][0] = coord_type(e["Pos"][0] - selection.min_x)
256
+ e["Pos"][1] = coord_type(e["Pos"][1] - selection.min_y)
257
+ e["Pos"][2] = coord_type(e["Pos"][2] - selection.min_z)
258
+ entities.append(e)
259
+
260
+ tag["Entities"] = ListTag(entities)
261
+ tag["TileEntities"] = ListTag(block_entities)
262
+ tag["Data"] = ByteArrayTag(numpy.transpose(block_data, (1, 2, 0))) # XYZ => YZX
263
+ tag["Blocks"] = ByteArrayTag(
264
+ numpy.transpose((blocks & 0xFF).astype(numpy.uint8), (1, 2, 0))
265
+ )
266
+ if numpy.max(blocks) > 0xFF:
267
+ add_blocks = (numpy.transpose(blocks & 0xF00, (1, 2, 0)) >> 8).ravel()
268
+ tag["AddBlocks"] = ByteArrayTag((add_blocks[::2] << 4) + add_blocks[1::2])
269
+ NamedTag(tag, "Schematic").save_to(f)
270
+
271
+ def _close(self):
272
+ """Close the disk database"""
273
+ self._chunks.clear()
274
+
275
+ def unload(self):
276
+ pass
277
+
278
+ def all_chunk_coords(
279
+ self, dimension: Optional[Dimension] = None
280
+ ) -> Iterable[ChunkCoordinates]:
281
+ yield from self._chunks.keys()
282
+
283
+ def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
284
+ return (cx, cz) in self._chunks
285
+
286
+ def _pack(
287
+ self,
288
+ chunk: "Chunk",
289
+ translator: "Translator",
290
+ chunk_version: VersionNumberAny,
291
+ ) -> Tuple["Chunk", AnyNDArray]:
292
+ version = self.translation_manager.get_version(
293
+ *translator.translator_key(chunk_version)
294
+ )
295
+ palette = []
296
+ for entry in chunk.block_palette.blocks:
297
+ b = version.block.block_to_ints(entry)
298
+ if b is None:
299
+ b = (0, 0)
300
+ palette.append(b)
301
+ return (
302
+ chunk,
303
+ numpy.array(palette),
304
+ )
305
+
306
+ def _encode(
307
+ self,
308
+ interface: SchematicInterface,
309
+ chunk: Chunk,
310
+ dimension: Dimension,
311
+ chunk_palette: AnyNDArray,
312
+ ):
313
+ return interface.encode(
314
+ chunk,
315
+ chunk_palette,
316
+ self.max_world_version,
317
+ SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
318
+ self._bounds[dimension].to_box()
319
+ ),
320
+ )
321
+
322
+ def _unpack(
323
+ self,
324
+ translator: "Translator",
325
+ game_version: VersionNumberAny,
326
+ chunk: "Chunk",
327
+ chunk_palette: AnyNDArray,
328
+ ) -> "Chunk":
329
+ version = self.translation_manager.get_version(
330
+ *translator.translator_key(game_version)
331
+ )
332
+ palette = chunk._block_palette = BlockManager()
333
+ lut = numpy.array(
334
+ [
335
+ palette.get_add_block(version.block.ints_to_block(block, data))
336
+ for block, data in chunk_palette
337
+ ]
338
+ )
339
+ if len(palette.blocks) != len(chunk_palette):
340
+ # if a blockstate was defined twice
341
+ for cy in chunk.blocks.sub_chunks:
342
+ chunk.blocks.add_sub_chunk(cy, lut[chunk.blocks.get_sub_chunk(cy)])
343
+
344
+ return chunk
345
+
346
+ def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
347
+ if (cx, cz) in self._chunks:
348
+ del self._chunks[(cx, cz)]
349
+
350
+ def _put_raw_chunk_data(
351
+ self,
352
+ cx: int,
353
+ cz: int,
354
+ section: SchematicChunk,
355
+ dimension: Optional[Dimension] = None,
356
+ ):
357
+ self._chunks[(cx, cz)] = copy.deepcopy(section)
358
+
359
+ def _get_raw_chunk_data(
360
+ self, cx: int, cz: int, dimension: Optional[Dimension] = None
361
+ ) -> SchematicChunk:
362
+ """
363
+ Return the raw data as loaded from disk.
364
+
365
+ :param cx: The x coordinate of the chunk.
366
+ :param cz: The z coordinate of the chunk.
367
+ :param dimension: The dimension to load the data from.
368
+ :return: The raw chunk data.
369
+ """
370
+ if (cx, cz) in self._chunks:
371
+ return copy.deepcopy(self._chunks[(cx, cz)])
372
+ else:
373
+ raise ChunkDoesNotExist