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,641 +1,659 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import os
|
|
4
|
-
import struct
|
|
5
|
-
import warnings
|
|
6
|
-
from typing import Tuple, Dict, Union, Optional, List, BinaryIO, Iterable, Any
|
|
7
|
-
from io import BytesIO
|
|
8
|
-
import shutil
|
|
9
|
-
import traceback
|
|
10
|
-
import time
|
|
11
|
-
|
|
12
|
-
from amulet_nbt import (
|
|
13
|
-
AbstractBaseTag,
|
|
14
|
-
load as load_nbt,
|
|
15
|
-
NamedTag,
|
|
16
|
-
CompoundTag,
|
|
17
|
-
StringTag,
|
|
18
|
-
ByteTag,
|
|
19
|
-
ShortTag,
|
|
20
|
-
IntTag,
|
|
21
|
-
ListTag,
|
|
22
|
-
LongTag,
|
|
23
|
-
FloatTag,
|
|
24
|
-
utf8_escape_decoder,
|
|
25
|
-
utf8_escape_encoder,
|
|
26
|
-
ReadContext,
|
|
27
|
-
)
|
|
28
|
-
from amulet.api.player import Player, LOCAL_PLAYER
|
|
29
|
-
from amulet.api.chunk import Chunk
|
|
30
|
-
from amulet.api.selection import SelectionBox, SelectionGroup
|
|
31
|
-
|
|
32
|
-
from leveldb import LevelDB, LevelDBException, LevelDBEncrypted
|
|
33
|
-
from amulet.utils.format_utils import check_all_exist
|
|
34
|
-
from amulet.api.data_types import (
|
|
35
|
-
ChunkCoordinates,
|
|
36
|
-
VersionNumberTuple,
|
|
37
|
-
PlatformType,
|
|
38
|
-
Dimension,
|
|
39
|
-
AnyNDArray,
|
|
40
|
-
)
|
|
41
|
-
from amulet.api.wrapper import WorldFormatWrapper
|
|
42
|
-
from amulet.api.errors import
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
self.
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
self.
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
buffer
|
|
140
|
-
buffer.write(payload)
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
self.
|
|
180
|
-
self.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
self._root_tag.
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
return f
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
#
|
|
270
|
-
#
|
|
271
|
-
#
|
|
272
|
-
#
|
|
273
|
-
#
|
|
274
|
-
#
|
|
275
|
-
# :
|
|
276
|
-
#
|
|
277
|
-
#
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
chunk_version = raw_chunk_data
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
.
|
|
311
|
-
.
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
self.
|
|
350
|
-
self.
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
)
|
|
364
|
-
|
|
365
|
-
self.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
"
|
|
401
|
-
"
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
#
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
)
|
|
416
|
-
)
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
value.get_compound(
|
|
420
|
-
"
|
|
421
|
-
)
|
|
422
|
-
.get_short("min", ShortTag())
|
|
423
|
-
.py_int,
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
.
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
value.get_compound(
|
|
433
|
-
"
|
|
434
|
-
)
|
|
435
|
-
.get_short("max", ShortTag())
|
|
436
|
-
.py_int,
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
raise
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
def
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
)
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
root
|
|
497
|
-
root["
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
self.
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
def
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
if
|
|
569
|
-
|
|
570
|
-
return
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
)
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import os
|
|
4
|
+
import struct
|
|
5
|
+
import warnings
|
|
6
|
+
from typing import Tuple, Dict, Union, Optional, List, BinaryIO, Iterable, Any
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
import shutil
|
|
9
|
+
import traceback
|
|
10
|
+
import time
|
|
11
|
+
|
|
12
|
+
from amulet_nbt import (
|
|
13
|
+
AbstractBaseTag,
|
|
14
|
+
load as load_nbt,
|
|
15
|
+
NamedTag,
|
|
16
|
+
CompoundTag,
|
|
17
|
+
StringTag,
|
|
18
|
+
ByteTag,
|
|
19
|
+
ShortTag,
|
|
20
|
+
IntTag,
|
|
21
|
+
ListTag,
|
|
22
|
+
LongTag,
|
|
23
|
+
FloatTag,
|
|
24
|
+
utf8_escape_decoder,
|
|
25
|
+
utf8_escape_encoder,
|
|
26
|
+
ReadContext,
|
|
27
|
+
)
|
|
28
|
+
from amulet.api.player import Player, LOCAL_PLAYER
|
|
29
|
+
from amulet.api.chunk import Chunk
|
|
30
|
+
from amulet.api.selection import SelectionBox, SelectionGroup
|
|
31
|
+
|
|
32
|
+
from leveldb import LevelDB, LevelDBException, LevelDBEncrypted
|
|
33
|
+
from amulet.utils.format_utils import check_all_exist
|
|
34
|
+
from amulet.api.data_types import (
|
|
35
|
+
ChunkCoordinates,
|
|
36
|
+
VersionNumberTuple,
|
|
37
|
+
PlatformType,
|
|
38
|
+
Dimension,
|
|
39
|
+
AnyNDArray,
|
|
40
|
+
)
|
|
41
|
+
from amulet.api.wrapper import WorldFormatWrapper, DefaultSelection
|
|
42
|
+
from amulet.api.errors import (
|
|
43
|
+
ObjectWriteError,
|
|
44
|
+
ObjectReadError,
|
|
45
|
+
PlayerDoesNotExist,
|
|
46
|
+
ChunkDoesNotExist,
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
from .interface.chunk.leveldb_chunk_versions import (
|
|
50
|
+
game_to_chunk_version,
|
|
51
|
+
)
|
|
52
|
+
from .dimension import LevelDBDimensionManager, ChunkData, InternalDimension
|
|
53
|
+
from .interface.chunk import BaseLevelDBInterface, get_interface
|
|
54
|
+
|
|
55
|
+
OVERWORLD = "minecraft:overworld"
|
|
56
|
+
THE_NETHER = "minecraft:the_nether"
|
|
57
|
+
THE_END = "minecraft:the_end"
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class BedrockLevelDAT(NamedTag):
|
|
61
|
+
_path: str
|
|
62
|
+
_level_dat_version: int
|
|
63
|
+
|
|
64
|
+
def __init__(
|
|
65
|
+
self, tag=None, name: str = "", path: str = None, level_dat_version: int = None
|
|
66
|
+
):
|
|
67
|
+
if isinstance(tag, str):
|
|
68
|
+
warnings.warn(
|
|
69
|
+
"You must use BedrockLevelDAT.from_file to load from a file.",
|
|
70
|
+
FutureWarning,
|
|
71
|
+
)
|
|
72
|
+
super().__init__()
|
|
73
|
+
self._path = path = tag
|
|
74
|
+
self._level_dat_version = 8
|
|
75
|
+
if os.path.isfile(path):
|
|
76
|
+
self.load_from(path)
|
|
77
|
+
return
|
|
78
|
+
else:
|
|
79
|
+
if not (isinstance(path, str) and isinstance(level_dat_version, int)):
|
|
80
|
+
raise TypeError(
|
|
81
|
+
"path and level_dat_version must be specified when constructing a BedrockLevelDAT instance."
|
|
82
|
+
)
|
|
83
|
+
super().__init__(tag, name)
|
|
84
|
+
self._path = path
|
|
85
|
+
self._level_dat_version = level_dat_version
|
|
86
|
+
|
|
87
|
+
@classmethod
|
|
88
|
+
def from_file(cls, path: str):
|
|
89
|
+
level_dat_version, name, tag = cls._read_from(path)
|
|
90
|
+
return cls(tag, name, path, level_dat_version)
|
|
91
|
+
|
|
92
|
+
@property
|
|
93
|
+
def path(self) -> Optional[str]:
|
|
94
|
+
return self._path
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def _read_from(path: str) -> Tuple[int, str, AbstractBaseTag]:
|
|
98
|
+
with open(path, "rb") as f:
|
|
99
|
+
level_dat_version = struct.unpack("<i", f.read(4))[0]
|
|
100
|
+
if 4 <= level_dat_version <= 10:
|
|
101
|
+
data_length = struct.unpack("<i", f.read(4))[0]
|
|
102
|
+
root_tag = load_nbt(
|
|
103
|
+
f.read(data_length),
|
|
104
|
+
compressed=False,
|
|
105
|
+
little_endian=True,
|
|
106
|
+
string_decoder=utf8_escape_decoder,
|
|
107
|
+
)
|
|
108
|
+
name = root_tag.name
|
|
109
|
+
value = root_tag.tag
|
|
110
|
+
else:
|
|
111
|
+
# TODO: handle other versions
|
|
112
|
+
raise ObjectReadError(
|
|
113
|
+
f"Unsupported level.dat version {level_dat_version}"
|
|
114
|
+
)
|
|
115
|
+
return level_dat_version, name, value
|
|
116
|
+
|
|
117
|
+
def load_from(self, path: str):
|
|
118
|
+
self._level_dat_version, self.name, self.tag = self._read_from(path)
|
|
119
|
+
|
|
120
|
+
def reload(self):
|
|
121
|
+
self.load_from(self.path)
|
|
122
|
+
|
|
123
|
+
def save(self, path: str = None):
|
|
124
|
+
self.save_to(path or self._path)
|
|
125
|
+
|
|
126
|
+
def save_to(
|
|
127
|
+
self,
|
|
128
|
+
filename_or_buffer: Union[str, BinaryIO] = None,
|
|
129
|
+
*,
|
|
130
|
+
compressed=False,
|
|
131
|
+
little_endian=True,
|
|
132
|
+
string_encoder=utf8_escape_encoder,
|
|
133
|
+
) -> Optional[bytes]:
|
|
134
|
+
payload = super().save_to(
|
|
135
|
+
compressed=compressed,
|
|
136
|
+
little_endian=little_endian,
|
|
137
|
+
string_encoder=string_encoder,
|
|
138
|
+
)
|
|
139
|
+
buffer = BytesIO()
|
|
140
|
+
buffer.write(struct.pack("<ii", self._level_dat_version, len(payload)))
|
|
141
|
+
buffer.write(payload)
|
|
142
|
+
if filename_or_buffer is None:
|
|
143
|
+
return buffer.getvalue()
|
|
144
|
+
elif isinstance(filename_or_buffer, str):
|
|
145
|
+
with open(filename_or_buffer, "wb") as f:
|
|
146
|
+
f.write(buffer.getvalue())
|
|
147
|
+
else:
|
|
148
|
+
filename_or_buffer.write(buffer.getvalue())
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
class LevelDBFormat(WorldFormatWrapper[VersionNumberTuple]):
|
|
152
|
+
"""
|
|
153
|
+
This FormatWrapper class exists to interface with the Bedrock world format.
|
|
154
|
+
"""
|
|
155
|
+
|
|
156
|
+
# The leveldb database. Access it through the public property `level_db`
|
|
157
|
+
_db: Optional[LevelDB]
|
|
158
|
+
# A class to manage dimension data. This is private
|
|
159
|
+
_dimension_manager: Optional[LevelDBDimensionManager]
|
|
160
|
+
|
|
161
|
+
_root_tag: BedrockLevelDAT
|
|
162
|
+
|
|
163
|
+
def __init__(self, path: str):
|
|
164
|
+
"""
|
|
165
|
+
Construct a new instance of :class:`LevelDBFormat`.
|
|
166
|
+
|
|
167
|
+
This should not be used directly. You should instead use :func:`amulet.load_format`.
|
|
168
|
+
|
|
169
|
+
:param path: The file path to the serialised data.
|
|
170
|
+
"""
|
|
171
|
+
super().__init__(path)
|
|
172
|
+
self._platform = "bedrock"
|
|
173
|
+
dat_path = os.path.join(path, "level.dat")
|
|
174
|
+
if os.path.isfile(dat_path):
|
|
175
|
+
self._root_tag = BedrockLevelDAT.from_file(dat_path)
|
|
176
|
+
else:
|
|
177
|
+
# TODO: handle level creation better
|
|
178
|
+
self._root_tag = BedrockLevelDAT(path=dat_path, level_dat_version=9)
|
|
179
|
+
self._db = None
|
|
180
|
+
self._dimension_manager = None
|
|
181
|
+
self._dimension_to_internal: dict[Dimension, InternalDimension] = {}
|
|
182
|
+
self._shallow_load()
|
|
183
|
+
|
|
184
|
+
def _shallow_load(self):
|
|
185
|
+
try:
|
|
186
|
+
self._load_level_dat()
|
|
187
|
+
except:
|
|
188
|
+
pass
|
|
189
|
+
|
|
190
|
+
def _load_level_dat(self):
|
|
191
|
+
"""Load the level.dat file and check the image file"""
|
|
192
|
+
if os.path.isfile(os.path.join(self.path, "world_icon.jpeg")):
|
|
193
|
+
self._world_image_path = os.path.join(self.path, "world_icon.jpeg")
|
|
194
|
+
self.root_tag = BedrockLevelDAT.from_file(os.path.join(self.path, "level.dat"))
|
|
195
|
+
|
|
196
|
+
@staticmethod
|
|
197
|
+
def is_valid(path: str):
|
|
198
|
+
return check_all_exist(path, "db", "level.dat", "levelname.txt")
|
|
199
|
+
|
|
200
|
+
@property
|
|
201
|
+
def valid_formats(self) -> Dict[PlatformType, Tuple[bool, bool]]:
|
|
202
|
+
return {"bedrock": (True, True)}
|
|
203
|
+
|
|
204
|
+
@property
|
|
205
|
+
def version(self) -> VersionNumberTuple:
|
|
206
|
+
if self._version is None:
|
|
207
|
+
self._version = self._get_version()
|
|
208
|
+
return self._version
|
|
209
|
+
|
|
210
|
+
def _get_version(self) -> VersionNumberTuple:
|
|
211
|
+
"""
|
|
212
|
+
The version the world was last opened in.
|
|
213
|
+
|
|
214
|
+
This should be greater than or equal to the chunk versions found within
|
|
215
|
+
|
|
216
|
+
For this format wrapper it returns a tuple of 3/4 ints (the game version number)
|
|
217
|
+
"""
|
|
218
|
+
try:
|
|
219
|
+
return tuple(
|
|
220
|
+
[
|
|
221
|
+
t.py_int
|
|
222
|
+
for t in self.root_tag.compound.get_list("lastOpenedWithVersion")
|
|
223
|
+
]
|
|
224
|
+
)
|
|
225
|
+
except Exception:
|
|
226
|
+
return 1, 2, 0
|
|
227
|
+
|
|
228
|
+
@property
|
|
229
|
+
def root_tag(self) -> BedrockLevelDAT:
|
|
230
|
+
"""The level.dat data for the level."""
|
|
231
|
+
return self._root_tag
|
|
232
|
+
|
|
233
|
+
@root_tag.setter
|
|
234
|
+
def root_tag(self, root_tag: Union[NamedTag, CompoundTag, BedrockLevelDAT]):
|
|
235
|
+
if isinstance(root_tag, CompoundTag):
|
|
236
|
+
self._root_tag.tag = root_tag
|
|
237
|
+
elif isinstance(root_tag, NamedTag):
|
|
238
|
+
self._root_tag.name = root_tag.name
|
|
239
|
+
self._root_tag.tag = root_tag.compound
|
|
240
|
+
else:
|
|
241
|
+
raise ValueError(
|
|
242
|
+
"root_tag must be a CompoundTag, NamedTag or BedrockLevelDAT"
|
|
243
|
+
)
|
|
244
|
+
|
|
245
|
+
@property
|
|
246
|
+
def level_name(self) -> str:
|
|
247
|
+
return self.root_tag.compound.get_string("LevelName", StringTag()).py_str
|
|
248
|
+
|
|
249
|
+
@level_name.setter
|
|
250
|
+
def level_name(self, value: str):
|
|
251
|
+
self.root_tag.compound["LevelName"] = StringTag(value)
|
|
252
|
+
|
|
253
|
+
@property
|
|
254
|
+
def last_played(self) -> int:
|
|
255
|
+
return self.root_tag.compound.get_long("LastPlayed", LongTag()).py_int
|
|
256
|
+
|
|
257
|
+
@property
|
|
258
|
+
def game_version_string(self) -> str:
|
|
259
|
+
try:
|
|
260
|
+
return f'Bedrock {".".join(str(v.py_int) for v in self.root_tag.compound.get_list("lastOpenedWithVersion"))}'
|
|
261
|
+
except Exception:
|
|
262
|
+
return f"Bedrock Unknown Version"
|
|
263
|
+
|
|
264
|
+
@property
|
|
265
|
+
def dimensions(self) -> List[Dimension]:
|
|
266
|
+
self._verify_has_lock()
|
|
267
|
+
return list(self._dimension_to_internal)
|
|
268
|
+
|
|
269
|
+
# def register_dimension(
|
|
270
|
+
# self, dimension_internal: int, dimension_name: Optional[Dimension] = None
|
|
271
|
+
# ):
|
|
272
|
+
# """
|
|
273
|
+
# Register a new dimension.
|
|
274
|
+
#
|
|
275
|
+
# :param dimension_internal: The internal integer representation of the dimension.
|
|
276
|
+
# :param dimension_name: The name of the dimension shown to the user.
|
|
277
|
+
# :return:
|
|
278
|
+
# """
|
|
279
|
+
# self._dimension_manager.register_dimension(dimension_internal, dimension_name)
|
|
280
|
+
|
|
281
|
+
@property
|
|
282
|
+
def level_db(self) -> LevelDB:
|
|
283
|
+
"""The raw leveldb database."""
|
|
284
|
+
if self._db is None:
|
|
285
|
+
raise Exception(
|
|
286
|
+
"The world is not open. The leveldb database cannot be accessed."
|
|
287
|
+
)
|
|
288
|
+
return self._db
|
|
289
|
+
|
|
290
|
+
@property
|
|
291
|
+
def _level_manager(self) -> LevelDBDimensionManager:
|
|
292
|
+
warnings.warn(
|
|
293
|
+
"_level_manager attribute is depreciated. If you want to access the raw leveldb database it can be accessed through the level_db property."
|
|
294
|
+
)
|
|
295
|
+
return self._dimension_manager
|
|
296
|
+
|
|
297
|
+
def _get_interface(
|
|
298
|
+
self, raw_chunk_data: Optional[Any] = None
|
|
299
|
+
) -> BaseLevelDBInterface:
|
|
300
|
+
return get_interface(self._get_interface_key(raw_chunk_data))
|
|
301
|
+
|
|
302
|
+
def _get_interface_key(self, raw_chunk_data: Optional[ChunkData] = None) -> int:
|
|
303
|
+
if raw_chunk_data:
|
|
304
|
+
if b"," in raw_chunk_data:
|
|
305
|
+
chunk_version = raw_chunk_data[b","][0]
|
|
306
|
+
else:
|
|
307
|
+
chunk_version = raw_chunk_data.get(b"v", b"\x00")[0]
|
|
308
|
+
else:
|
|
309
|
+
chunk_version = game_to_chunk_version(
|
|
310
|
+
self.max_world_version[1],
|
|
311
|
+
self.root_tag.compound.get_compound("experiments", CompoundTag())
|
|
312
|
+
.get_byte("caves_and_cliffs", ByteTag())
|
|
313
|
+
.py_int,
|
|
314
|
+
)
|
|
315
|
+
return chunk_version
|
|
316
|
+
|
|
317
|
+
def _decode(
|
|
318
|
+
self,
|
|
319
|
+
interface: BaseLevelDBInterface,
|
|
320
|
+
dimension: Dimension,
|
|
321
|
+
cx: int,
|
|
322
|
+
cz: int,
|
|
323
|
+
raw_chunk_data: Any,
|
|
324
|
+
) -> Tuple[Chunk, AnyNDArray]:
|
|
325
|
+
bounds = self.bounds(dimension).bounds
|
|
326
|
+
return interface.decode(cx, cz, raw_chunk_data, (bounds[0][1], bounds[1][1]))
|
|
327
|
+
|
|
328
|
+
def _encode(
|
|
329
|
+
self,
|
|
330
|
+
interface: BaseLevelDBInterface,
|
|
331
|
+
chunk: Chunk,
|
|
332
|
+
dimension: Dimension,
|
|
333
|
+
chunk_palette: AnyNDArray,
|
|
334
|
+
) -> Any:
|
|
335
|
+
bounds = self.bounds(dimension).bounds
|
|
336
|
+
return interface.encode(
|
|
337
|
+
chunk,
|
|
338
|
+
chunk_palette,
|
|
339
|
+
self.max_world_version,
|
|
340
|
+
(bounds[0][1], bounds[1][1]),
|
|
341
|
+
)
|
|
342
|
+
|
|
343
|
+
def _reload_world(self):
|
|
344
|
+
try:
|
|
345
|
+
self.close()
|
|
346
|
+
except:
|
|
347
|
+
pass
|
|
348
|
+
try:
|
|
349
|
+
self._db = LevelDB(os.path.join(self.path, "db"))
|
|
350
|
+
self._dimension_manager = LevelDBDimensionManager(self)
|
|
351
|
+
self._is_open = True
|
|
352
|
+
self._has_lock = True
|
|
353
|
+
|
|
354
|
+
self._dimension_to_internal.clear()
|
|
355
|
+
self._dimension_to_internal[OVERWORLD] = None
|
|
356
|
+
self._dimension_to_internal[THE_NETHER] = 1
|
|
357
|
+
self._dimension_to_internal[THE_END] = 2
|
|
358
|
+
|
|
359
|
+
experiments = self.root_tag.compound.get_compound(
|
|
360
|
+
"experiments", CompoundTag()
|
|
361
|
+
)
|
|
362
|
+
if (
|
|
363
|
+
experiments.get_byte("caves_and_cliffs", ByteTag()).py_int
|
|
364
|
+
or experiments.get_byte("caves_and_cliffs_internal", ByteTag()).py_int
|
|
365
|
+
or self.version >= (1, 18)
|
|
366
|
+
):
|
|
367
|
+
self._bounds[OVERWORLD] = SelectionGroup(
|
|
368
|
+
SelectionBox(
|
|
369
|
+
(-30_000_000, -64, -30_000_000), (30_000_000, 320, 30_000_000)
|
|
370
|
+
)
|
|
371
|
+
)
|
|
372
|
+
else:
|
|
373
|
+
self._bounds[OVERWORLD] = DefaultSelection
|
|
374
|
+
self._bounds[THE_NETHER] = SelectionGroup(
|
|
375
|
+
SelectionBox(
|
|
376
|
+
(-30_000_000, 0, -30_000_000), (30_000_000, 128, 30_000_000)
|
|
377
|
+
)
|
|
378
|
+
)
|
|
379
|
+
self._bounds[THE_END] = DefaultSelection
|
|
380
|
+
|
|
381
|
+
if b"LevelChunkMetaDataDictionary" in self.level_db:
|
|
382
|
+
data = self.level_db[b"LevelChunkMetaDataDictionary"]
|
|
383
|
+
count, data = struct.unpack("<I", data[:4])[0], data[4:]
|
|
384
|
+
for _ in range(count):
|
|
385
|
+
key, data = data[:8], data[8:]
|
|
386
|
+
context = ReadContext()
|
|
387
|
+
value = load_nbt(
|
|
388
|
+
data,
|
|
389
|
+
little_endian=True,
|
|
390
|
+
compressed=False,
|
|
391
|
+
string_decoder=utf8_escape_decoder,
|
|
392
|
+
read_context=context,
|
|
393
|
+
).compound
|
|
394
|
+
data = data[context.offset :]
|
|
395
|
+
|
|
396
|
+
try:
|
|
397
|
+
dimension_name = value.get_string("DimensionName").py_str
|
|
398
|
+
# The dimension names are stored differently TODO: split local and global names
|
|
399
|
+
dimension_name = {
|
|
400
|
+
"Overworld": OVERWORLD,
|
|
401
|
+
"Nether": THE_NETHER,
|
|
402
|
+
"TheEnd": THE_END,
|
|
403
|
+
}.get(dimension_name, dimension_name)
|
|
404
|
+
|
|
405
|
+
except KeyError:
|
|
406
|
+
# Some entries seem to not have a dimension assigned to them. Is there a default? We will skip over these for now.
|
|
407
|
+
# {'LastSavedBaseGameVersion': StringTag("1.19.81"), 'LastSavedDimensionHeightRange': CompoundTag({'max': ShortTag(320), 'min': ShortTag(-64)})}
|
|
408
|
+
pass
|
|
409
|
+
else:
|
|
410
|
+
previous_bounds = self._bounds.get(
|
|
411
|
+
dimension_name, DefaultSelection
|
|
412
|
+
)
|
|
413
|
+
min_y = min(
|
|
414
|
+
value.get_compound(
|
|
415
|
+
"LastSavedDimensionHeightRange", CompoundTag()
|
|
416
|
+
)
|
|
417
|
+
.get_short("min", ShortTag())
|
|
418
|
+
.py_int,
|
|
419
|
+
value.get_compound(
|
|
420
|
+
"OriginalDimensionHeightRange", CompoundTag()
|
|
421
|
+
)
|
|
422
|
+
.get_short("min", ShortTag())
|
|
423
|
+
.py_int,
|
|
424
|
+
previous_bounds.min_y,
|
|
425
|
+
)
|
|
426
|
+
max_y = max(
|
|
427
|
+
value.get_compound(
|
|
428
|
+
"LastSavedDimensionHeightRange", CompoundTag()
|
|
429
|
+
)
|
|
430
|
+
.get_short("max", ShortTag())
|
|
431
|
+
.py_int,
|
|
432
|
+
value.get_compound(
|
|
433
|
+
"OriginalDimensionHeightRange", CompoundTag()
|
|
434
|
+
)
|
|
435
|
+
.get_short("max", ShortTag())
|
|
436
|
+
.py_int,
|
|
437
|
+
previous_bounds.max_y,
|
|
438
|
+
)
|
|
439
|
+
self._bounds[dimension_name] = SelectionGroup(
|
|
440
|
+
SelectionBox(
|
|
441
|
+
(previous_bounds.min_x, min_y, previous_bounds.min_z),
|
|
442
|
+
(previous_bounds.max_x, max_y, previous_bounds.max_z),
|
|
443
|
+
)
|
|
444
|
+
)
|
|
445
|
+
|
|
446
|
+
# Give all other dimensions found an entry
|
|
447
|
+
known_dimensions = set(self._dimension_to_internal.values())
|
|
448
|
+
for internal_dimension in self._dimension_manager.dimensions:
|
|
449
|
+
if internal_dimension not in known_dimensions:
|
|
450
|
+
dimension_name = f"DIM{internal_dimension}"
|
|
451
|
+
self._dimension_to_internal[dimension_name] = internal_dimension
|
|
452
|
+
self._bounds[dimension_name] = DefaultSelection
|
|
453
|
+
|
|
454
|
+
except LevelDBEncrypted as e:
|
|
455
|
+
self._is_open = self._has_lock = False
|
|
456
|
+
raise LevelDBException(
|
|
457
|
+
"It looks like this world is from the marketplace.\nThese worlds are encrypted and cannot be edited."
|
|
458
|
+
) from e
|
|
459
|
+
except LevelDBException as e:
|
|
460
|
+
msg = str(e)
|
|
461
|
+
self._is_open = self._has_lock = False
|
|
462
|
+
# I don't know if there is a better way of handling this.
|
|
463
|
+
if msg.startswith("IO error:") and msg.endswith(": Permission denied"):
|
|
464
|
+
traceback.print_exc()
|
|
465
|
+
raise LevelDBException(
|
|
466
|
+
f"Failed to load the database. The world may be open somewhere else.\n{msg}"
|
|
467
|
+
) from e
|
|
468
|
+
else:
|
|
469
|
+
raise e
|
|
470
|
+
|
|
471
|
+
def _open(self):
|
|
472
|
+
"""Open the database for reading and writing"""
|
|
473
|
+
self._reload_world()
|
|
474
|
+
|
|
475
|
+
def _create(
|
|
476
|
+
self,
|
|
477
|
+
overwrite: bool,
|
|
478
|
+
bounds: Union[
|
|
479
|
+
SelectionGroup, Dict[Dimension, Optional[SelectionGroup]], None
|
|
480
|
+
] = None,
|
|
481
|
+
**kwargs,
|
|
482
|
+
):
|
|
483
|
+
if os.path.isdir(self.path):
|
|
484
|
+
if overwrite:
|
|
485
|
+
shutil.rmtree(self.path)
|
|
486
|
+
else:
|
|
487
|
+
raise ObjectWriteError(
|
|
488
|
+
f"A world already exists at the path {self.path}"
|
|
489
|
+
)
|
|
490
|
+
|
|
491
|
+
version = self.translation_manager.get_version(
|
|
492
|
+
self.platform, self.version
|
|
493
|
+
).version_number
|
|
494
|
+
self._version = version + (0,) * (5 - len(version))
|
|
495
|
+
|
|
496
|
+
self.root_tag = root = CompoundTag()
|
|
497
|
+
root["StorageVersion"] = IntTag(8)
|
|
498
|
+
root["lastOpenedWithVersion"] = ListTag([IntTag(i) for i in self._version])
|
|
499
|
+
root["Generator"] = IntTag(1)
|
|
500
|
+
root["LastPlayed"] = LongTag(int(time.time()))
|
|
501
|
+
root["LevelName"] = StringTag("World Created By Amulet")
|
|
502
|
+
|
|
503
|
+
os.makedirs(self.path, exist_ok=True)
|
|
504
|
+
self.root_tag.save()
|
|
505
|
+
|
|
506
|
+
db = LevelDB(os.path.join(self.path, "db"), True)
|
|
507
|
+
db.close()
|
|
508
|
+
|
|
509
|
+
self._reload_world()
|
|
510
|
+
|
|
511
|
+
@property
|
|
512
|
+
def has_lock(self) -> bool:
|
|
513
|
+
if self._has_lock:
|
|
514
|
+
return True # TODO: implement a check to ensure access to the database
|
|
515
|
+
return False
|
|
516
|
+
|
|
517
|
+
def _save(self):
|
|
518
|
+
os.makedirs(self.path, exist_ok=True)
|
|
519
|
+
self.root_tag.save()
|
|
520
|
+
with open(os.path.join(self.path, "levelname.txt"), "w", encoding="utf-8") as f:
|
|
521
|
+
f.write(self.level_name)
|
|
522
|
+
|
|
523
|
+
def _close(self):
|
|
524
|
+
self._db.close()
|
|
525
|
+
self._db = None
|
|
526
|
+
self._dimension_manager = None
|
|
527
|
+
self._actor_counter = None
|
|
528
|
+
|
|
529
|
+
def unload(self):
|
|
530
|
+
pass
|
|
531
|
+
|
|
532
|
+
def all_chunk_coords(self, dimension: Dimension) -> Iterable[ChunkCoordinates]:
|
|
533
|
+
self._verify_has_lock()
|
|
534
|
+
if dimension in self._dimension_to_internal:
|
|
535
|
+
yield from self._dimension_manager.all_chunk_coords(
|
|
536
|
+
self._dimension_to_internal[dimension]
|
|
537
|
+
)
|
|
538
|
+
|
|
539
|
+
def has_chunk(self, cx: int, cz: int, dimension: Dimension) -> bool:
|
|
540
|
+
if dimension in self._dimension_to_internal:
|
|
541
|
+
return self._dimension_manager.has_chunk(
|
|
542
|
+
cx, cz, self._dimension_to_internal[dimension]
|
|
543
|
+
)
|
|
544
|
+
return False
|
|
545
|
+
|
|
546
|
+
def _delete_chunk(self, cx: int, cz: int, dimension: Dimension):
|
|
547
|
+
if dimension in self._dimension_to_internal:
|
|
548
|
+
self._dimension_manager.delete_chunk(
|
|
549
|
+
cx, cz, self._dimension_to_internal[dimension]
|
|
550
|
+
)
|
|
551
|
+
|
|
552
|
+
def _put_raw_chunk_data(
|
|
553
|
+
self, cx: int, cz: int, data: ChunkData, dimension: Dimension
|
|
554
|
+
):
|
|
555
|
+
self._dimension_manager.put_chunk_data(
|
|
556
|
+
cx, cz, data, self._dimension_to_internal[dimension]
|
|
557
|
+
)
|
|
558
|
+
|
|
559
|
+
def _get_raw_chunk_data(self, cx: int, cz: int, dimension: Dimension) -> ChunkData:
|
|
560
|
+
"""
|
|
561
|
+
Return the raw data as loaded from disk.
|
|
562
|
+
|
|
563
|
+
:param cx: The x coordinate of the chunk.
|
|
564
|
+
:param cz: The z coordinate of the chunk.
|
|
565
|
+
:param dimension: The dimension to load the data from.
|
|
566
|
+
:return: The raw chunk data.
|
|
567
|
+
"""
|
|
568
|
+
if dimension not in self._dimension_to_internal:
|
|
569
|
+
raise ChunkDoesNotExist
|
|
570
|
+
return self._dimension_manager.get_chunk_data(
|
|
571
|
+
cx, cz, self._dimension_to_internal[dimension]
|
|
572
|
+
)
|
|
573
|
+
|
|
574
|
+
def all_player_ids(self) -> Iterable[str]:
|
|
575
|
+
"""
|
|
576
|
+
Returns a generator of all player ids that are present in the level
|
|
577
|
+
"""
|
|
578
|
+
yield from (
|
|
579
|
+
pid[7:].decode("utf-8")
|
|
580
|
+
for pid, _ in self._db.iterate(b"player_", b"player_\xFF")
|
|
581
|
+
)
|
|
582
|
+
if self.has_player(LOCAL_PLAYER):
|
|
583
|
+
yield LOCAL_PLAYER
|
|
584
|
+
|
|
585
|
+
def has_player(self, player_id: str) -> bool:
|
|
586
|
+
if player_id != LOCAL_PLAYER:
|
|
587
|
+
player_id = f"player_{player_id}"
|
|
588
|
+
return player_id.encode("utf-8") in self._db
|
|
589
|
+
|
|
590
|
+
def _load_player(self, player_id: str) -> Player:
|
|
591
|
+
"""
|
|
592
|
+
Gets the :class:`Player` object that belongs to the specified player id
|
|
593
|
+
|
|
594
|
+
If no parameter is supplied, the data of the local player will be returned
|
|
595
|
+
|
|
596
|
+
:param player_id: The desired player id
|
|
597
|
+
:return: A Player instance
|
|
598
|
+
"""
|
|
599
|
+
player_nbt = self._get_raw_player_data(player_id).compound
|
|
600
|
+
dimension = player_nbt["DimensionId"]
|
|
601
|
+
if isinstance(dimension, IntTag) and IntTag(0) <= dimension <= IntTag(2):
|
|
602
|
+
dimension_str = {
|
|
603
|
+
0: OVERWORLD,
|
|
604
|
+
1: THE_NETHER,
|
|
605
|
+
2: THE_END,
|
|
606
|
+
}[dimension.py_int]
|
|
607
|
+
else:
|
|
608
|
+
dimension_str = OVERWORLD
|
|
609
|
+
|
|
610
|
+
# get the players position
|
|
611
|
+
pos_data = player_nbt.get("Pos")
|
|
612
|
+
if (
|
|
613
|
+
isinstance(pos_data, ListTag)
|
|
614
|
+
and len(pos_data) == 3
|
|
615
|
+
and pos_data.list_data_type == FloatTag.tag_id
|
|
616
|
+
):
|
|
617
|
+
position = tuple(map(float, pos_data))
|
|
618
|
+
position = tuple(
|
|
619
|
+
p if -100_000_000 <= p <= 100_000_000 else 0.0 for p in position
|
|
620
|
+
)
|
|
621
|
+
else:
|
|
622
|
+
position = (0.0, 0.0, 0.0)
|
|
623
|
+
|
|
624
|
+
# get the players rotation
|
|
625
|
+
rot_data = player_nbt.get("Rotation")
|
|
626
|
+
if (
|
|
627
|
+
isinstance(rot_data, ListTag)
|
|
628
|
+
and len(rot_data) == 2
|
|
629
|
+
and rot_data.list_data_type == FloatTag.tag_id
|
|
630
|
+
):
|
|
631
|
+
rotation = tuple(map(float, rot_data))
|
|
632
|
+
rotation = tuple(
|
|
633
|
+
p if -100_000_000 <= p <= 100_000_000 else 0.0 for p in rotation
|
|
634
|
+
)
|
|
635
|
+
else:
|
|
636
|
+
rotation = (0.0, 0.0)
|
|
637
|
+
|
|
638
|
+
return Player(
|
|
639
|
+
player_id,
|
|
640
|
+
dimension_str,
|
|
641
|
+
position,
|
|
642
|
+
rotation,
|
|
643
|
+
)
|
|
644
|
+
|
|
645
|
+
def _get_raw_player_data(self, player_id: str) -> NamedTag:
|
|
646
|
+
if player_id == LOCAL_PLAYER:
|
|
647
|
+
key = player_id.encode("utf-8")
|
|
648
|
+
else:
|
|
649
|
+
key = f"player_{player_id}".encode("utf-8")
|
|
650
|
+
try:
|
|
651
|
+
data = self._db.get(key)
|
|
652
|
+
except KeyError:
|
|
653
|
+
raise PlayerDoesNotExist(f"Player {player_id} doesn't exist")
|
|
654
|
+
return load_nbt(
|
|
655
|
+
data,
|
|
656
|
+
compressed=False,
|
|
657
|
+
little_endian=True,
|
|
658
|
+
string_decoder=utf8_escape_decoder,
|
|
659
|
+
)
|