amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of amulet-core might be problematic. Click here for more details.
- amulet/__init__.py +27 -27
- amulet/__pyinstaller/__init__.py +2 -2
- amulet/__pyinstaller/hook-amulet.py +4 -4
- amulet/_version.py +21 -21
- amulet/api/__init__.py +2 -2
- amulet/api/abstract_base_entity.py +128 -128
- amulet/api/block.py +630 -630
- amulet/api/block_entity.py +71 -71
- amulet/api/cache.py +107 -107
- amulet/api/chunk/__init__.py +6 -6
- amulet/api/chunk/biomes.py +207 -207
- amulet/api/chunk/block_entity_dict.py +175 -175
- amulet/api/chunk/blocks.py +46 -46
- amulet/api/chunk/chunk.py +389 -389
- amulet/api/chunk/entity_list.py +75 -75
- amulet/api/chunk/status.py +167 -167
- amulet/api/data_types/__init__.py +4 -4
- amulet/api/data_types/generic_types.py +4 -4
- amulet/api/data_types/operation_types.py +16 -16
- amulet/api/data_types/world_types.py +49 -49
- amulet/api/data_types/wrapper_types.py +71 -71
- amulet/api/entity.py +74 -74
- amulet/api/errors.py +119 -119
- amulet/api/history/__init__.py +36 -36
- amulet/api/history/base/__init__.py +3 -3
- amulet/api/history/base/base_history.py +26 -26
- amulet/api/history/base/history_manager.py +63 -63
- amulet/api/history/base/revision_manager.py +73 -73
- amulet/api/history/changeable.py +15 -15
- amulet/api/history/data_types.py +7 -7
- amulet/api/history/history_manager/__init__.py +3 -3
- amulet/api/history/history_manager/container.py +102 -102
- amulet/api/history/history_manager/database.py +279 -279
- amulet/api/history/history_manager/meta.py +93 -93
- amulet/api/history/history_manager/object.py +116 -116
- amulet/api/history/revision_manager/__init__.py +2 -2
- amulet/api/history/revision_manager/disk.py +33 -33
- amulet/api/history/revision_manager/ram.py +12 -12
- amulet/api/item.py +75 -75
- amulet/api/level/__init__.py +4 -4
- amulet/api/level/base_level/__init__.py +1 -1
- amulet/api/level/base_level/base_level.py +1035 -1026
- amulet/api/level/base_level/chunk_manager.py +227 -227
- amulet/api/level/base_level/clone.py +389 -389
- amulet/api/level/base_level/player_manager.py +101 -101
- amulet/api/level/immutable_structure/__init__.py +1 -1
- amulet/api/level/immutable_structure/immutable_structure.py +94 -94
- amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
- amulet/api/level/structure.py +22 -22
- amulet/api/level/world.py +19 -19
- amulet/api/partial_3d_array/__init__.py +2 -2
- amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
- amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
- amulet/api/partial_3d_array/data_types.py +15 -15
- amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
- amulet/api/partial_3d_array/util.py +152 -152
- amulet/api/player.py +65 -65
- amulet/api/registry/__init__.py +2 -2
- amulet/api/registry/base_registry.py +34 -34
- amulet/api/registry/biome_manager.py +153 -153
- amulet/api/registry/block_manager.py +156 -156
- amulet/api/selection/__init__.py +2 -2
- amulet/api/selection/abstract_selection.py +315 -315
- amulet/api/selection/box.py +805 -805
- amulet/api/selection/group.py +488 -488
- amulet/api/structure.py +37 -37
- amulet/api/wrapper/__init__.py +8 -8
- amulet/api/wrapper/chunk/interface.py +441 -441
- amulet/api/wrapper/chunk/translator.py +567 -567
- amulet/api/wrapper/format_wrapper.py +772 -772
- amulet/api/wrapper/structure_format_wrapper.py +116 -116
- amulet/api/wrapper/world_format_wrapper.py +63 -63
- amulet/level/__init__.py +1 -1
- amulet/level/formats/anvil_forge_world.py +40 -40
- amulet/level/formats/anvil_world/__init__.py +3 -3
- amulet/level/formats/anvil_world/_sector_manager.py +291 -384
- amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
- amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
- amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
- amulet/level/formats/anvil_world/dimension.py +177 -177
- amulet/level/formats/anvil_world/format.py +769 -769
- amulet/level/formats/anvil_world/region.py +384 -384
- amulet/level/formats/construction/__init__.py +3 -3
- amulet/level/formats/construction/format_wrapper.py +515 -515
- amulet/level/formats/construction/interface.py +134 -134
- amulet/level/formats/construction/section.py +60 -60
- amulet/level/formats/construction/util.py +165 -165
- amulet/level/formats/leveldb_world/__init__.py +3 -3
- amulet/level/formats/leveldb_world/chunk.py +33 -33
- amulet/level/formats/leveldb_world/dimension.py +385 -419
- amulet/level/formats/leveldb_world/format.py +659 -641
- amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
- amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
- amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
- amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
- amulet/level/formats/mcstructure/__init__.py +3 -3
- amulet/level/formats/mcstructure/chunk.py +50 -50
- amulet/level/formats/mcstructure/format_wrapper.py +408 -408
- amulet/level/formats/mcstructure/interface.py +175 -175
- amulet/level/formats/schematic/__init__.py +3 -3
- amulet/level/formats/schematic/chunk.py +55 -55
- amulet/level/formats/schematic/data_types.py +4 -4
- amulet/level/formats/schematic/format_wrapper.py +373 -373
- amulet/level/formats/schematic/interface.py +142 -142
- amulet/level/formats/sponge_schem/__init__.py +4 -4
- amulet/level/formats/sponge_schem/chunk.py +62 -62
- amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
- amulet/level/formats/sponge_schem/interface.py +118 -118
- amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
- amulet/level/formats/sponge_schem/varint/varint.py +87 -87
- amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
- amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
- amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
- amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
- amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
- amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
- amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
- amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
- amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
- amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
- amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
- amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
- amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
- amulet/level/load.py +59 -59
- amulet/level/loader.py +95 -95
- amulet/level/translators/chunk/bedrock/__init__.py +267 -267
- amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
- amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
- amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
- amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
- amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
- amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
- amulet/libs/leveldb/__init__.py +7 -7
- amulet/operations/__init__.py +5 -5
- amulet/operations/clone.py +18 -18
- amulet/operations/delete_chunk.py +32 -32
- amulet/operations/fill.py +30 -30
- amulet/operations/paste.py +65 -65
- amulet/operations/replace.py +58 -58
- amulet/utils/__init__.py +14 -14
- amulet/utils/format_utils.py +41 -41
- amulet/utils/generator.py +15 -15
- amulet/utils/matrix.py +243 -243
- amulet/utils/numpy_helpers.py +46 -46
- amulet/utils/world_utils.py +349 -349
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
- amulet_core-1.9.20.dist-info/RECORD +208 -0
- amulet_core-1.9.19.dist-info/RECORD +0 -208
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
- {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
|
@@ -1,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
|