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,463 +1,463 @@
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
- ShortTag,
8
- IntTag,
9
- ListTag,
10
- CompoundTag,
11
- ByteArrayTag,
12
- IntArrayTag,
13
- NamedTag,
14
- load as load_nbt,
15
- )
16
-
17
- from amulet.api.data_types import (
18
- VersionNumberAny,
19
- VersionNumberInt,
20
- ChunkCoordinates,
21
- AnyNDArray,
22
- Dimension,
23
- PlatformType,
24
- )
25
- from amulet.api.wrapper import StructureFormatWrapper
26
- from amulet.api.chunk import Chunk
27
- from amulet.api.selection import SelectionGroup, SelectionBox
28
- from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError, ObjectReadError
29
- from amulet.api.block import Block
30
- from amulet.utils.numpy_helpers import brute_sort_objects_no_hash
31
-
32
- from .chunk import SpongeSchemChunk
33
- from .interface import SpongeSchemInterface
34
- from .varint import decode_byte_array, encode_array
35
-
36
- if TYPE_CHECKING:
37
- from amulet.api.wrapper import Translator, Interface
38
-
39
-
40
- class SpongeSchemReadError(ObjectReadError):
41
- pass
42
-
43
-
44
- class SpongeSchemWriteError(ObjectWriteError):
45
- pass
46
-
47
-
48
- sponge_schem_interface = SpongeSchemInterface()
49
-
50
- max_schem_version = 2
51
-
52
-
53
- def _is_sponge(path: str):
54
- """Check if a file is actually a sponge schematic file."""
55
- try:
56
- return "BlockData" in load_nbt(path).compound
57
- except:
58
- return False
59
-
60
-
61
- class SpongeSchemFormatWrapper(StructureFormatWrapper[VersionNumberInt]):
62
- """
63
- This FormatWrapper class exists to interface with the sponge schematic structure format.
64
- """
65
-
66
- def __init__(self, path: str):
67
- """
68
- Construct a new instance of :class:`SpongeSchemFormatWrapper`.
69
-
70
- This should not be used directly. You should instead use :func:`amulet.load_format`.
71
-
72
- :param path: The file path to the serialised data.
73
- """
74
- super().__init__(path)
75
- self._chunks: Dict[
76
- ChunkCoordinates,
77
- SpongeSchemChunk,
78
- ] = {}
79
- self._schem_version: int = max_schem_version
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 SpongeSchemWriteError(f"There is already a file at {self.path}")
91
- translator_version = self.translation_manager.get_version("java", self._version)
92
- self._platform = translator_version.platform
93
- self._version = translator_version.data_version
94
- self._chunks = {}
95
- self._set_selection(bounds)
96
- self._is_open = True
97
- self._has_lock = True
98
-
99
- def open_from(self, f: BinaryIO):
100
- sponge_schem = load_nbt(f).compound
101
- version_tag = sponge_schem.get("Version")
102
- if not isinstance(version_tag, IntTag):
103
- raise SpongeSchemReadError("Version key must exist and be an integer.")
104
- version = version_tag.py_int
105
- if version == 1:
106
- raise SpongeSchemReadError(
107
- "Sponge Schematic Version 1 is not supported currently."
108
- )
109
- elif version == 2:
110
- offset = sponge_schem.get("Offset")
111
- if isinstance(offset, IntArrayTag) and len(offset) == 3:
112
- min_point = numpy.array(offset)
113
- else:
114
- min_point = numpy.array([0, 0, 0], dtype=numpy.int32)
115
-
116
- size = []
117
- for key in ("Width", "Height", "Length"):
118
- val = sponge_schem.get(key)
119
- if not isinstance(val, ShortTag):
120
- raise SpongeSchemReadError(
121
- f"Key {key} must exist and be a ShortTag."
122
- )
123
- # convert to an unsigned short
124
- val = val.py_int
125
- if val < 0:
126
- val += 2**16
127
- size.append(val)
128
-
129
- max_point = min_point + size
130
- selection = SelectionBox(min_point, max_point)
131
- self._bounds[self.dimensions[0]] = SelectionGroup(selection)
132
- data_version = sponge_schem.get("DataVersion")
133
- if not isinstance(data_version, IntTag):
134
- raise SpongeSchemReadError("DataVersion must be a IntTag.")
135
- translator_version = self.translation_manager.get_version(
136
- "java", int(data_version)
137
- )
138
- self._platform = translator_version.platform
139
- self._version = translator_version.data_version
140
-
141
- packed_block_data = sponge_schem.get("BlockData")
142
- if not isinstance(packed_block_data, ByteArrayTag):
143
- raise SpongeSchemReadError("BlockData must be a ByteArrayTag")
144
-
145
- unpacked_block_data = decode_byte_array(
146
- numpy.array(packed_block_data, dtype=numpy.uint8)
147
- )
148
- if len(unpacked_block_data) != numpy.prod(size):
149
- raise SpongeSchemReadError(
150
- "The data contained in BlockData does not match the size of the schematic."
151
- )
152
- dx, dy, dz = selection.shape
153
- blocks_array: numpy.ndarray = numpy.transpose(
154
- numpy.array(
155
- unpacked_block_data,
156
- dtype=numpy.uint32,
157
- ).reshape((dy, dz, dx)),
158
- (2, 0, 1), # YZX => XYZ
159
- )
160
-
161
- if "Palette" not in sponge_schem:
162
- raise SpongeSchemReadError(
163
- "Amulet is not able to read Sponge Schem files with no block palette."
164
- )
165
-
166
- palette_data = sponge_schem.get("Palette")
167
- if not isinstance(palette_data, CompoundTag):
168
- raise SpongeSchemReadError("Palette must be a CompoundTag.")
169
-
170
- block_palette: Dict[int, Block] = {}
171
- for blockstate, index_tag in palette_data.items():
172
- index = index_tag.py_int
173
- if index in block_palette:
174
- raise SpongeSchemReadError(
175
- f"Duplicate block index {index} found in the palette."
176
- )
177
- block_palette[index] = Block.from_string_blockstate(blockstate)
178
-
179
- if not numpy.all(numpy.isin(blocks_array, list(block_palette))):
180
- raise SpongeSchemReadError(
181
- "Some values in BlockData were not present in Palette"
182
- )
183
-
184
- for cx, cz in selection.chunk_locations():
185
- chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection(
186
- selection
187
- )
188
- array_slice = chunk_box.create_moved_box(
189
- selection.min, subtract=True
190
- ).slice
191
- chunk_blocks_: numpy.ndarray = blocks_array[array_slice]
192
- chunk_palette_indexes, chunk_blocks = numpy.unique(
193
- chunk_blocks_,
194
- return_inverse=True,
195
- )
196
- chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape)
197
-
198
- chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object)
199
- for palette_index, index in enumerate(chunk_palette_indexes):
200
- chunk_palette[palette_index] = block_palette[index]
201
-
202
- self._chunks[(cx, cz)] = SpongeSchemChunk(
203
- chunk_box,
204
- chunk_blocks,
205
- chunk_palette,
206
- [],
207
- [],
208
- )
209
-
210
- if "BlockEntities" in sponge_schem:
211
- block_entities = sponge_schem["BlockEntities"]
212
- if not (
213
- isinstance(block_entities, ListTag)
214
- and (
215
- len(block_entities) == 0 or block_entities.list_data_type == 10
216
- ) # CompoundTag.tag_id
217
- ):
218
- raise SpongeSchemReadError(
219
- "BlockEntities must be a ListTag of compound tags."
220
- )
221
-
222
- for block_entity in block_entities:
223
- if "Pos" not in block_entity:
224
- continue
225
-
226
- pos_tag = block_entity["Pos"]
227
- if not (isinstance(pos_tag, IntArrayTag) and len(pos_tag) == 3):
228
- continue
229
-
230
- pos = pos_tag.np_array + min_point
231
- x, y, z = pos
232
- block_entity["Pos"] = IntArrayTag(pos)
233
- cx, cz = x >> 4, z >> 4
234
- if (cx, cz) in self._chunks and (x, y, z) in self._chunks[
235
- (cx, cz)
236
- ].selection:
237
- self._chunks[(cx, cz)].block_entities.append(block_entity)
238
-
239
- if "Entities" in sponge_schem:
240
- entities = sponge_schem["Entities"]
241
- if not (
242
- isinstance(entities, ListTag)
243
- and (
244
- len(entities) == 0 or entities.list_data_type == 10
245
- ) # CompoundTag.tag_id
246
- ):
247
- raise SpongeSchemReadError(
248
- "Entities must be a ListTag of compound tags."
249
- )
250
-
251
- for entity in entities:
252
- if "Pos" not in entity:
253
- continue
254
-
255
- pos = entity["Pos"]
256
- if not (
257
- isinstance(pos, ListTag)
258
- and len(pos) == 3
259
- and pos.list_data_type == 6
260
- ): # DoubleTag.tag_id:
261
- continue
262
-
263
- x, y, z = (
264
- pos[0].py_float + offset[0],
265
- pos[1].py_float + offset[0],
266
- pos[2].py_float + offset[0],
267
- )
268
- entity["Pos"] = ListTag(
269
- [
270
- IntTag(x),
271
- IntTag(y),
272
- IntTag(z),
273
- ]
274
- )
275
- cx, cz = numpy.floor([x, z]).astype(int) >> 4
276
- if (cx, cz) in self._chunks and (x, y, z) in self._chunks[
277
- (cx, cz)
278
- ].selection:
279
- self._chunks[(cx, cz)].entities.append(entity)
280
-
281
- else:
282
- raise SpongeSchemReadError(
283
- f"Sponge Schematic Version {version} is not supported currently."
284
- )
285
-
286
- @staticmethod
287
- def is_valid(path: str) -> bool:
288
- return (
289
- os.path.isfile(path)
290
- and path.endswith((".schem", ".schematic"))
291
- and _is_sponge(path)
292
- )
293
-
294
- @property
295
- def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
296
- return {"java": (False, True)}
297
-
298
- @property
299
- def extensions(self) -> Tuple[str, ...]:
300
- return (".schem", ".schematic")
301
-
302
- def _get_interface(self, raw_chunk_data=None) -> "SpongeSchemInterface":
303
- return sponge_schem_interface
304
-
305
- def _get_interface_and_translator(
306
- self, raw_chunk_data=None
307
- ) -> Tuple["Interface", "Translator", VersionNumberAny]:
308
- interface = self._get_interface(raw_chunk_data)
309
- translator, version_identifier = interface.get_translator(
310
- self.max_world_version, raw_chunk_data, self.translation_manager
311
- )
312
- return interface, translator, version_identifier
313
-
314
- def save_to(self, f: BinaryIO):
315
- if self._schem_version == 1:
316
- raise SpongeSchemReadError(
317
- "Sponge Schematic Version 1 is not supported currently."
318
- )
319
- elif self._schem_version == 2:
320
- selection = self._bounds[self.dimensions[0]].selection_boxes[0]
321
- if any(s > 2**16 - 1 for s in selection.shape):
322
- raise SpongeSchemWriteError(
323
- "The structure is too large to be exported to a Sponge Schematic file. It must be 2^16 - 1 at most in each dimension."
324
- )
325
- overflowed_shape = [
326
- s if s < 2**15 else s - 2**16 for s in selection.shape
327
- ]
328
- tag = CompoundTag(
329
- {
330
- "Version": IntTag(2),
331
- "DataVersion": IntTag(self._version),
332
- "Width": ShortTag(overflowed_shape[0]),
333
- "Height": ShortTag(overflowed_shape[1]),
334
- "Length": ShortTag(overflowed_shape[2]),
335
- "Offset": IntArrayTag(selection.min),
336
- }
337
- )
338
-
339
- entities = []
340
- block_entities = []
341
- blocks = numpy.zeros(selection.shape, dtype=numpy.uint32)
342
- palette: List[AnyNDArray] = []
343
- if self._version < 1500:
344
- raise Exception(
345
- "Writing to Sponge Schematic files in pre-1.13 format is not currently supported."
346
- )
347
- else:
348
- arr = numpy.empty(1, dtype=object)
349
- arr[0] = Block("minecraft", "air")
350
- palette.append(arr)
351
-
352
- palette_len = 1
353
-
354
- for chunk in self._chunks.values():
355
- if chunk.selection.intersects(selection):
356
- box = chunk.selection.create_moved_box(selection.min, subtract=True)
357
- blocks[box.slice] = chunk.blocks + palette_len
358
- palette.append(chunk.palette)
359
- palette_len += len(chunk.palette)
360
- for be in chunk.block_entities:
361
- be = copy.deepcopy(be)
362
- be["Pos"] = IntArrayTag(be["Pos"].np_array - selection.min)
363
- block_entities.append(be)
364
-
365
- for e in chunk.entities:
366
- e = copy.deepcopy(e)
367
- x, y, z = e["Pos"]
368
- e["Pos"] = ListTag(
369
- [
370
- IntTag(x - selection.min_x),
371
- IntTag(y - selection.min_y),
372
- IntTag(z - selection.min_z),
373
- ]
374
- )
375
- entities.append(e)
376
-
377
- compact_palette, lut = brute_sort_objects_no_hash(
378
- numpy.concatenate(palette)
379
- )
380
- blocks = numpy.transpose(lut[blocks], (1, 2, 0)).ravel() # XYZ => YZX
381
- block_palette = []
382
- for index, block in enumerate(compact_palette):
383
- block: Block
384
- block_palette.append(block.blockstate)
385
-
386
- tag["PaletteMax"] = IntTag(len(compact_palette))
387
- tag["Palette"] = CompoundTag(
388
- {
389
- blockstate: IntTag(index)
390
- for index, blockstate in enumerate(block_palette)
391
- }
392
- )
393
- tag["BlockData"] = ByteArrayTag(list(encode_array(blocks)))
394
- if block_entities:
395
- tag["BlockEntities"] = ListTag(block_entities)
396
- if entities:
397
- tag["Entities"] = ListTag(entities)
398
-
399
- NamedTag(tag, "Schematic").save_to(f)
400
- else:
401
- raise SpongeSchemReadError(
402
- f"Sponge Schematic Version {self._schem_version} is not supported currently."
403
- )
404
-
405
- def _close(self):
406
- """Close the disk database"""
407
- self._chunks.clear()
408
-
409
- def unload(self):
410
- pass
411
-
412
- def all_chunk_coords(
413
- self, dimension: Optional[Dimension] = None
414
- ) -> Iterable[ChunkCoordinates]:
415
- yield from self._chunks.keys()
416
-
417
- def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
418
- return (cx, cz) in self._chunks
419
-
420
- def _encode(
421
- self,
422
- interface: SpongeSchemInterface,
423
- chunk: Chunk,
424
- dimension: Dimension,
425
- chunk_palette: AnyNDArray,
426
- ):
427
- return interface.encode(
428
- chunk,
429
- chunk_palette,
430
- self.max_world_version,
431
- SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
432
- self._bounds[dimension].to_box()
433
- ),
434
- )
435
-
436
- def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
437
- if (cx, cz) in self._chunks:
438
- del self._chunks[(cx, cz)]
439
-
440
- def _put_raw_chunk_data(
441
- self,
442
- cx: int,
443
- cz: int,
444
- section: SpongeSchemChunk,
445
- dimension: Optional[Dimension] = None,
446
- ):
447
- self._chunks[(cx, cz)] = copy.deepcopy(section)
448
-
449
- def _get_raw_chunk_data(
450
- self, cx: int, cz: int, dimension: Optional[Dimension] = None
451
- ) -> SpongeSchemChunk:
452
- """
453
- Return the raw data as loaded from disk.
454
-
455
- :param cx: The x coordinate of the chunk.
456
- :param cz: The z coordinate of the chunk.
457
- :param dimension: The dimension to load the data from.
458
- :return: The raw chunk data.
459
- """
460
- if (cx, cz) in self._chunks:
461
- return copy.deepcopy(self._chunks[(cx, cz)])
462
- else:
463
- 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
+ ShortTag,
8
+ IntTag,
9
+ ListTag,
10
+ CompoundTag,
11
+ ByteArrayTag,
12
+ IntArrayTag,
13
+ NamedTag,
14
+ load as load_nbt,
15
+ )
16
+
17
+ from amulet.api.data_types import (
18
+ VersionNumberAny,
19
+ VersionNumberInt,
20
+ ChunkCoordinates,
21
+ AnyNDArray,
22
+ Dimension,
23
+ PlatformType,
24
+ )
25
+ from amulet.api.wrapper import StructureFormatWrapper
26
+ from amulet.api.chunk import Chunk
27
+ from amulet.api.selection import SelectionGroup, SelectionBox
28
+ from amulet.api.errors import ChunkDoesNotExist, ObjectWriteError, ObjectReadError
29
+ from amulet.api.block import Block
30
+ from amulet.utils.numpy_helpers import brute_sort_objects_no_hash
31
+
32
+ from .chunk import SpongeSchemChunk
33
+ from .interface import SpongeSchemInterface
34
+ from .varint import decode_byte_array, encode_array
35
+
36
+ if TYPE_CHECKING:
37
+ from amulet.api.wrapper import Translator, Interface
38
+
39
+
40
+ class SpongeSchemReadError(ObjectReadError):
41
+ pass
42
+
43
+
44
+ class SpongeSchemWriteError(ObjectWriteError):
45
+ pass
46
+
47
+
48
+ sponge_schem_interface = SpongeSchemInterface()
49
+
50
+ max_schem_version = 2
51
+
52
+
53
+ def _is_sponge(path: str):
54
+ """Check if a file is actually a sponge schematic file."""
55
+ try:
56
+ return "BlockData" in load_nbt(path).compound
57
+ except:
58
+ return False
59
+
60
+
61
+ class SpongeSchemFormatWrapper(StructureFormatWrapper[VersionNumberInt]):
62
+ """
63
+ This FormatWrapper class exists to interface with the sponge schematic structure format.
64
+ """
65
+
66
+ def __init__(self, path: str):
67
+ """
68
+ Construct a new instance of :class:`SpongeSchemFormatWrapper`.
69
+
70
+ This should not be used directly. You should instead use :func:`amulet.load_format`.
71
+
72
+ :param path: The file path to the serialised data.
73
+ """
74
+ super().__init__(path)
75
+ self._chunks: Dict[
76
+ ChunkCoordinates,
77
+ SpongeSchemChunk,
78
+ ] = {}
79
+ self._schem_version: int = max_schem_version
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 SpongeSchemWriteError(f"There is already a file at {self.path}")
91
+ translator_version = self.translation_manager.get_version("java", self._version)
92
+ self._platform = translator_version.platform
93
+ self._version = translator_version.data_version
94
+ self._chunks = {}
95
+ self._set_selection(bounds)
96
+ self._is_open = True
97
+ self._has_lock = True
98
+
99
+ def open_from(self, f: BinaryIO):
100
+ sponge_schem = load_nbt(f).compound
101
+ version_tag = sponge_schem.get("Version")
102
+ if not isinstance(version_tag, IntTag):
103
+ raise SpongeSchemReadError("Version key must exist and be an integer.")
104
+ version = version_tag.py_int
105
+ if version == 1:
106
+ raise SpongeSchemReadError(
107
+ "Sponge Schematic Version 1 is not supported currently."
108
+ )
109
+ elif version == 2:
110
+ offset = sponge_schem.get("Offset")
111
+ if isinstance(offset, IntArrayTag) and len(offset) == 3:
112
+ min_point = numpy.array(offset)
113
+ else:
114
+ min_point = numpy.array([0, 0, 0], dtype=numpy.int32)
115
+
116
+ size = []
117
+ for key in ("Width", "Height", "Length"):
118
+ val = sponge_schem.get(key)
119
+ if not isinstance(val, ShortTag):
120
+ raise SpongeSchemReadError(
121
+ f"Key {key} must exist and be a ShortTag."
122
+ )
123
+ # convert to an unsigned short
124
+ val = val.py_int
125
+ if val < 0:
126
+ val += 2**16
127
+ size.append(val)
128
+
129
+ max_point = min_point + size
130
+ selection = SelectionBox(min_point, max_point)
131
+ self._bounds[self.dimensions[0]] = SelectionGroup(selection)
132
+ data_version = sponge_schem.get("DataVersion")
133
+ if not isinstance(data_version, IntTag):
134
+ raise SpongeSchemReadError("DataVersion must be a IntTag.")
135
+ translator_version = self.translation_manager.get_version(
136
+ "java", int(data_version)
137
+ )
138
+ self._platform = translator_version.platform
139
+ self._version = translator_version.data_version
140
+
141
+ packed_block_data = sponge_schem.get("BlockData")
142
+ if not isinstance(packed_block_data, ByteArrayTag):
143
+ raise SpongeSchemReadError("BlockData must be a ByteArrayTag")
144
+
145
+ unpacked_block_data = decode_byte_array(
146
+ numpy.array(packed_block_data, dtype=numpy.uint8)
147
+ )
148
+ if len(unpacked_block_data) != numpy.prod(size):
149
+ raise SpongeSchemReadError(
150
+ "The data contained in BlockData does not match the size of the schematic."
151
+ )
152
+ dx, dy, dz = selection.shape
153
+ blocks_array: numpy.ndarray = numpy.transpose(
154
+ numpy.array(
155
+ unpacked_block_data,
156
+ dtype=numpy.uint32,
157
+ ).reshape((dy, dz, dx)),
158
+ (2, 0, 1), # YZX => XYZ
159
+ )
160
+
161
+ if "Palette" not in sponge_schem:
162
+ raise SpongeSchemReadError(
163
+ "Amulet is not able to read Sponge Schem files with no block palette."
164
+ )
165
+
166
+ palette_data = sponge_schem.get("Palette")
167
+ if not isinstance(palette_data, CompoundTag):
168
+ raise SpongeSchemReadError("Palette must be a CompoundTag.")
169
+
170
+ block_palette: Dict[int, Block] = {}
171
+ for blockstate, index_tag in palette_data.items():
172
+ index = index_tag.py_int
173
+ if index in block_palette:
174
+ raise SpongeSchemReadError(
175
+ f"Duplicate block index {index} found in the palette."
176
+ )
177
+ block_palette[index] = Block.from_string_blockstate(blockstate)
178
+
179
+ if not numpy.all(numpy.isin(blocks_array, list(block_palette))):
180
+ raise SpongeSchemReadError(
181
+ "Some values in BlockData were not present in Palette"
182
+ )
183
+
184
+ for cx, cz in selection.chunk_locations():
185
+ chunk_box = SelectionBox.create_chunk_box(cx, cz).intersection(
186
+ selection
187
+ )
188
+ array_slice = chunk_box.create_moved_box(
189
+ selection.min, subtract=True
190
+ ).slice
191
+ chunk_blocks_: numpy.ndarray = blocks_array[array_slice]
192
+ chunk_palette_indexes, chunk_blocks = numpy.unique(
193
+ chunk_blocks_,
194
+ return_inverse=True,
195
+ )
196
+ chunk_blocks = chunk_blocks.reshape(chunk_blocks_.shape)
197
+
198
+ chunk_palette = numpy.empty(len(chunk_palette_indexes), dtype=object)
199
+ for palette_index, index in enumerate(chunk_palette_indexes):
200
+ chunk_palette[palette_index] = block_palette[index]
201
+
202
+ self._chunks[(cx, cz)] = SpongeSchemChunk(
203
+ chunk_box,
204
+ chunk_blocks,
205
+ chunk_palette,
206
+ [],
207
+ [],
208
+ )
209
+
210
+ if "BlockEntities" in sponge_schem:
211
+ block_entities = sponge_schem["BlockEntities"]
212
+ if not (
213
+ isinstance(block_entities, ListTag)
214
+ and (
215
+ len(block_entities) == 0 or block_entities.list_data_type == 10
216
+ ) # CompoundTag.tag_id
217
+ ):
218
+ raise SpongeSchemReadError(
219
+ "BlockEntities must be a ListTag of compound tags."
220
+ )
221
+
222
+ for block_entity in block_entities:
223
+ if "Pos" not in block_entity:
224
+ continue
225
+
226
+ pos_tag = block_entity["Pos"]
227
+ if not (isinstance(pos_tag, IntArrayTag) and len(pos_tag) == 3):
228
+ continue
229
+
230
+ pos = pos_tag.np_array + min_point
231
+ x, y, z = pos
232
+ block_entity["Pos"] = IntArrayTag(pos)
233
+ cx, cz = x >> 4, z >> 4
234
+ if (cx, cz) in self._chunks and (x, y, z) in self._chunks[
235
+ (cx, cz)
236
+ ].selection:
237
+ self._chunks[(cx, cz)].block_entities.append(block_entity)
238
+
239
+ if "Entities" in sponge_schem:
240
+ entities = sponge_schem["Entities"]
241
+ if not (
242
+ isinstance(entities, ListTag)
243
+ and (
244
+ len(entities) == 0 or entities.list_data_type == 10
245
+ ) # CompoundTag.tag_id
246
+ ):
247
+ raise SpongeSchemReadError(
248
+ "Entities must be a ListTag of compound tags."
249
+ )
250
+
251
+ for entity in entities:
252
+ if "Pos" not in entity:
253
+ continue
254
+
255
+ pos = entity["Pos"]
256
+ if not (
257
+ isinstance(pos, ListTag)
258
+ and len(pos) == 3
259
+ and pos.list_data_type == 6
260
+ ): # DoubleTag.tag_id:
261
+ continue
262
+
263
+ x, y, z = (
264
+ pos[0].py_float + offset[0],
265
+ pos[1].py_float + offset[0],
266
+ pos[2].py_float + offset[0],
267
+ )
268
+ entity["Pos"] = ListTag(
269
+ [
270
+ IntTag(x),
271
+ IntTag(y),
272
+ IntTag(z),
273
+ ]
274
+ )
275
+ cx, cz = numpy.floor([x, z]).astype(int) >> 4
276
+ if (cx, cz) in self._chunks and (x, y, z) in self._chunks[
277
+ (cx, cz)
278
+ ].selection:
279
+ self._chunks[(cx, cz)].entities.append(entity)
280
+
281
+ else:
282
+ raise SpongeSchemReadError(
283
+ f"Sponge Schematic Version {version} is not supported currently."
284
+ )
285
+
286
+ @staticmethod
287
+ def is_valid(path: str) -> bool:
288
+ return (
289
+ os.path.isfile(path)
290
+ and path.endswith((".schem", ".schematic"))
291
+ and _is_sponge(path)
292
+ )
293
+
294
+ @property
295
+ def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
296
+ return {"java": (False, True)}
297
+
298
+ @property
299
+ def extensions(self) -> Tuple[str, ...]:
300
+ return (".schem", ".schematic")
301
+
302
+ def _get_interface(self, raw_chunk_data=None) -> "SpongeSchemInterface":
303
+ return sponge_schem_interface
304
+
305
+ def _get_interface_and_translator(
306
+ self, raw_chunk_data=None
307
+ ) -> Tuple["Interface", "Translator", VersionNumberAny]:
308
+ interface = self._get_interface(raw_chunk_data)
309
+ translator, version_identifier = interface.get_translator(
310
+ self.max_world_version, raw_chunk_data, self.translation_manager
311
+ )
312
+ return interface, translator, version_identifier
313
+
314
+ def save_to(self, f: BinaryIO):
315
+ if self._schem_version == 1:
316
+ raise SpongeSchemReadError(
317
+ "Sponge Schematic Version 1 is not supported currently."
318
+ )
319
+ elif self._schem_version == 2:
320
+ selection = self._bounds[self.dimensions[0]].selection_boxes[0]
321
+ if any(s > 2**16 - 1 for s in selection.shape):
322
+ raise SpongeSchemWriteError(
323
+ "The structure is too large to be exported to a Sponge Schematic file. It must be 2^16 - 1 at most in each dimension."
324
+ )
325
+ overflowed_shape = [
326
+ s if s < 2**15 else s - 2**16 for s in selection.shape
327
+ ]
328
+ tag = CompoundTag(
329
+ {
330
+ "Version": IntTag(2),
331
+ "DataVersion": IntTag(self._version),
332
+ "Width": ShortTag(overflowed_shape[0]),
333
+ "Height": ShortTag(overflowed_shape[1]),
334
+ "Length": ShortTag(overflowed_shape[2]),
335
+ "Offset": IntArrayTag(selection.min),
336
+ }
337
+ )
338
+
339
+ entities = []
340
+ block_entities = []
341
+ blocks = numpy.zeros(selection.shape, dtype=numpy.uint32)
342
+ palette: List[AnyNDArray] = []
343
+ if self._version < 1500:
344
+ raise Exception(
345
+ "Writing to Sponge Schematic files in pre-1.13 format is not currently supported."
346
+ )
347
+ else:
348
+ arr = numpy.empty(1, dtype=object)
349
+ arr[0] = Block("minecraft", "air")
350
+ palette.append(arr)
351
+
352
+ palette_len = 1
353
+
354
+ for chunk in self._chunks.values():
355
+ if chunk.selection.intersects(selection):
356
+ box = chunk.selection.create_moved_box(selection.min, subtract=True)
357
+ blocks[box.slice] = chunk.blocks + palette_len
358
+ palette.append(chunk.palette)
359
+ palette_len += len(chunk.palette)
360
+ for be in chunk.block_entities:
361
+ be = copy.deepcopy(be)
362
+ be["Pos"] = IntArrayTag(be["Pos"].np_array - selection.min)
363
+ block_entities.append(be)
364
+
365
+ for e in chunk.entities:
366
+ e = copy.deepcopy(e)
367
+ x, y, z = e["Pos"]
368
+ e["Pos"] = ListTag(
369
+ [
370
+ IntTag(x - selection.min_x),
371
+ IntTag(y - selection.min_y),
372
+ IntTag(z - selection.min_z),
373
+ ]
374
+ )
375
+ entities.append(e)
376
+
377
+ compact_palette, lut = brute_sort_objects_no_hash(
378
+ numpy.concatenate(palette)
379
+ )
380
+ blocks = numpy.transpose(lut[blocks], (1, 2, 0)).ravel() # XYZ => YZX
381
+ block_palette = []
382
+ for index, block in enumerate(compact_palette):
383
+ block: Block
384
+ block_palette.append(block.blockstate)
385
+
386
+ tag["PaletteMax"] = IntTag(len(compact_palette))
387
+ tag["Palette"] = CompoundTag(
388
+ {
389
+ blockstate: IntTag(index)
390
+ for index, blockstate in enumerate(block_palette)
391
+ }
392
+ )
393
+ tag["BlockData"] = ByteArrayTag(list(encode_array(blocks)))
394
+ if block_entities:
395
+ tag["BlockEntities"] = ListTag(block_entities)
396
+ if entities:
397
+ tag["Entities"] = ListTag(entities)
398
+
399
+ NamedTag(tag, "Schematic").save_to(f)
400
+ else:
401
+ raise SpongeSchemReadError(
402
+ f"Sponge Schematic Version {self._schem_version} is not supported currently."
403
+ )
404
+
405
+ def _close(self):
406
+ """Close the disk database"""
407
+ self._chunks.clear()
408
+
409
+ def unload(self):
410
+ pass
411
+
412
+ def all_chunk_coords(
413
+ self, dimension: Optional[Dimension] = None
414
+ ) -> Iterable[ChunkCoordinates]:
415
+ yield from self._chunks.keys()
416
+
417
+ def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
418
+ return (cx, cz) in self._chunks
419
+
420
+ def _encode(
421
+ self,
422
+ interface: SpongeSchemInterface,
423
+ chunk: Chunk,
424
+ dimension: Dimension,
425
+ chunk_palette: AnyNDArray,
426
+ ):
427
+ return interface.encode(
428
+ chunk,
429
+ chunk_palette,
430
+ self.max_world_version,
431
+ SelectionBox.create_chunk_box(chunk.cx, chunk.cz).intersection(
432
+ self._bounds[dimension].to_box()
433
+ ),
434
+ )
435
+
436
+ def _delete_chunk(self, cx: int, cz: int, dimension: Optional[Dimension] = None):
437
+ if (cx, cz) in self._chunks:
438
+ del self._chunks[(cx, cz)]
439
+
440
+ def _put_raw_chunk_data(
441
+ self,
442
+ cx: int,
443
+ cz: int,
444
+ section: SpongeSchemChunk,
445
+ dimension: Optional[Dimension] = None,
446
+ ):
447
+ self._chunks[(cx, cz)] = copy.deepcopy(section)
448
+
449
+ def _get_raw_chunk_data(
450
+ self, cx: int, cz: int, dimension: Optional[Dimension] = None
451
+ ) -> SpongeSchemChunk:
452
+ """
453
+ Return the raw data as loaded from disk.
454
+
455
+ :param cx: The x coordinate of the chunk.
456
+ :param cz: The z coordinate of the chunk.
457
+ :param dimension: The dimension to load the data from.
458
+ :return: The raw chunk data.
459
+ """
460
+ if (cx, cz) in self._chunks:
461
+ return copy.deepcopy(self._chunks[(cx, cz)])
462
+ else:
463
+ raise ChunkDoesNotExist