amulet-core 2.0a6__cp312-cp312-win_amd64.whl → 2.0a8__cp312-cp312-win_amd64.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__.cp312-win_amd64.pyd +0 -0
- amulet/__init__.py.cpp +6 -0
- amulet/__init__.pyi +2 -2
- amulet/_init.py +0 -2
- amulet/_version.py +3 -3
- amulet/biome.pyi +0 -2
- amulet/block.pyi +0 -2
- amulet/block_entity.pyi +0 -2
- amulet/chunk.hpp +2 -1
- amulet/chunk.pyi +0 -2
- amulet/chunk_components.pyi +20 -18
- amulet/collections/eq.py.hpp +1 -1
- amulet/collections/mapping.py.hpp +18 -11
- amulet/collections/mutable_mapping.py.hpp +17 -6
- amulet/collections/sequence.py.hpp +5 -6
- amulet/collections.pyi +8 -5
- amulet/entity.py +22 -20
- amulet/game/translate/_functions/_code_functions/_text.py +2 -2
- amulet/game/translate/_functions/abc.py +10 -3
- amulet/img/__init__.py +10 -0
- amulet/img/missing_no.png +0 -0
- amulet/img/missing_pack.png +0 -0
- amulet/level/__init__.pyi +2 -6
- amulet/level/abc/_chunk_handle.py +45 -22
- amulet/level/abc/_level/_creatable_level.py +1 -2
- amulet/level/abc/_level/_level.py +1 -5
- amulet/level/java/__init__.pyi +0 -5
- amulet/level/java/_raw/__init__.pyi +0 -4
- amulet/level/java/_raw/java_chunk_decode.cpp +2 -4
- amulet/level/java/long_array.pyi +2 -1
- amulet/mesh/block/__init__.pyi +301 -0
- amulet/mesh/block/_cube.py +198 -0
- amulet/mesh/block/_missing_block.py +20 -0
- amulet/mesh/block/block_mesh.cpp +107 -0
- amulet/mesh/block/block_mesh.hpp +207 -0
- amulet/mesh/util.py +17 -0
- amulet/player.py +4 -6
- amulet/pybind11/collections.hpp +80 -38
- amulet/pybind11/numpy.hpp +26 -0
- amulet/pybind11/py_module.hpp +16 -51
- amulet/pybind11/type_hints.hpp +51 -0
- amulet/pybind11/types.hpp +14 -6
- amulet/pybind11/typing.hpp +7 -0
- amulet/resource_pack/__init__.py +63 -0
- amulet/resource_pack/abc/__init__.py +2 -0
- amulet/resource_pack/abc/resource_pack.py +38 -0
- amulet/resource_pack/abc/resource_pack_manager.py +85 -0
- amulet/resource_pack/java/__init__.py +2 -0
- amulet/resource_pack/java/download_resources.py +212 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_black.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_brown.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_cyan.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_green.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_blue.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_light_gray.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_lime.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_magenta.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_orange.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_pink.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_purple.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_red.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_white.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/banner/banner_yellow.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/barrier.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/end_portal.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/grass.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/lava.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/structure_void.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/assets/minecraft/textures/block/water.png +0 -0
- amulet/resource_pack/java/java_vanilla_fix/pack.png +0 -0
- amulet/resource_pack/java/resource_pack.py +44 -0
- amulet/resource_pack/java/resource_pack_manager.py +563 -0
- amulet/resource_pack/unknown_resource_pack.py +10 -0
- amulet/utils/__init__.pyi +0 -5
- amulet/utils/call_spec/_call_spec.py +2 -7
- amulet/utils/cast.py +10 -0
- amulet/utils/comment_json.py +188 -0
- amulet/utils/matrix.py +3 -3
- amulet/utils/numpy_helpers.py +2 -2
- amulet/utils/shareable_lock.py +2 -2
- amulet/utils/world_utils.py +2 -2
- amulet/version.pyi +0 -8
- {amulet_core-2.0a6.dist-info → amulet_core-2.0a8.dist-info}/METADATA +2 -2
- {amulet_core-2.0a6.dist-info → amulet_core-2.0a8.dist-info}/RECORD +91 -103
- {amulet_core-2.0a6.dist-info → amulet_core-2.0a8.dist-info}/WHEEL +1 -1
- amulet/chunk_/components/biome.py +0 -155
- amulet/chunk_/components/block_entity.py +0 -117
- amulet/chunk_/components/entity.py +0 -64
- amulet/chunk_/components/height_2d.py +0 -16
- amulet/level/bedrock/__init__.py +0 -2
- amulet/level/bedrock/_chunk_handle.py +0 -19
- amulet/level/bedrock/_dimension.py +0 -22
- amulet/level/bedrock/_level.py +0 -187
- amulet/level/bedrock/_raw/__init__.py +0 -5
- amulet/level/bedrock/_raw/_actor_counter.py +0 -53
- amulet/level/bedrock/_raw/_chunk.py +0 -54
- amulet/level/bedrock/_raw/_chunk_decode.py +0 -668
- amulet/level/bedrock/_raw/_chunk_encode.py +0 -602
- amulet/level/bedrock/_raw/_constant.py +0 -9
- amulet/level/bedrock/_raw/_dimension.py +0 -343
- amulet/level/bedrock/_raw/_level.py +0 -463
- amulet/level/bedrock/_raw/_level_dat.py +0 -90
- amulet/level/bedrock/_raw/_typing.py +0 -6
- amulet/level/bedrock/_raw/leveldb_chunk_versions.py +0 -83
- amulet/level/bedrock/chunk/__init__.py +0 -1
- amulet/level/bedrock/chunk/_chunk.py +0 -126
- amulet/level/bedrock/chunk/components/chunk_version.py +0 -12
- amulet/level/bedrock/chunk/components/finalised_state.py +0 -13
- amulet/level/bedrock/chunk/components/raw_chunk.py +0 -15
- amulet/level/construction/__init__.py +0 -0
- amulet/level/java/_chunk_handle.pyi +0 -15
- amulet/level/java/_dimension.pyi +0 -13
- amulet/level/java/_level.pyi +0 -120
- amulet/level/java/_raw/_chunk_decode.py +0 -561
- amulet/level/java/_raw/_chunk_encode.py +0 -463
- amulet/level/java/_raw/_constant.pyi +0 -20
- amulet/level/java/_raw/_data_pack/__init__.pyi +0 -8
- amulet/level/java/_raw/_data_pack/data_pack.pyi +0 -197
- amulet/level/java/_raw/_data_pack/data_pack_manager.pyi +0 -75
- amulet/level/java/_raw/_dimension.pyi +0 -72
- amulet/level/java/_raw/_level.pyi +0 -238
- amulet/level/java/_raw/_typing.pyi +0 -5
- amulet/level/java/anvil/__init__.pyi +0 -11
- amulet/level/java/anvil/_dimension.pyi +0 -109
- amulet/level/java/anvil/_region.pyi +0 -197
- amulet/level/java/anvil/_sector_manager.pyi +0 -142
- amulet/level/java_forge/__init__.py +0 -0
- amulet/level/mcstructure/__init__.py +0 -0
- amulet/level/nbt/__init__.py +0 -0
- amulet/level/schematic/__init__.py +0 -0
- amulet/level/sponge_schematic/__init__.py +0 -0
- amulet/pybind11/python.hpp +0 -14
- amulet/utils/call_spec/__init__.pyi +0 -53
- amulet/utils/call_spec/_call_spec.pyi +0 -272
- amulet/utils/matrix.pyi +0 -177
- amulet/utils/shareable_lock.pyi +0 -190
- amulet/utils/signal/__init__.pyi +0 -25
- amulet/utils/signal/_signal.pyi +0 -84
- amulet/utils/task_manager.pyi +0 -168
- amulet/utils/typing.py +0 -4
- amulet/utils/typing.pyi +0 -6
- amulet/utils/weakref.pyi +0 -50
- amulet/utils/world_utils.pyi +0 -109
- /amulet/img/{missing_world_icon.png → missing_world.png} +0 -0
- /amulet/{level/bedrock/chunk/components → mesh}/__init__.py +0 -0
- {amulet_core-2.0a6.dist-info → amulet_core-2.0a8.dist-info}/entry_points.txt +0 -0
- {amulet_core-2.0a6.dist-info → amulet_core-2.0a8.dist-info}/top_level.txt +0 -0
|
@@ -1,463 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import copy
|
|
4
|
-
from typing import Optional, Callable
|
|
5
|
-
from collections.abc import Iterable
|
|
6
|
-
from threading import RLock
|
|
7
|
-
import os
|
|
8
|
-
import struct
|
|
9
|
-
import logging
|
|
10
|
-
from dataclasses import dataclass
|
|
11
|
-
import shutil
|
|
12
|
-
import time
|
|
13
|
-
|
|
14
|
-
from leveldb import LevelDB
|
|
15
|
-
|
|
16
|
-
from amulet_nbt import (
|
|
17
|
-
IntTag,
|
|
18
|
-
LongTag,
|
|
19
|
-
StringTag,
|
|
20
|
-
ListTag,
|
|
21
|
-
CompoundTag,
|
|
22
|
-
ByteTag,
|
|
23
|
-
ShortTag,
|
|
24
|
-
ReadOffset,
|
|
25
|
-
read_nbt,
|
|
26
|
-
utf8_escape_encoding,
|
|
27
|
-
)
|
|
28
|
-
|
|
29
|
-
from amulet.data_types import DimensionId
|
|
30
|
-
from amulet.selection import SelectionGroup, SelectionBox
|
|
31
|
-
from amulet.errors import PlayerDoesNotExist, LevelWriteError
|
|
32
|
-
from amulet.level.abc import (
|
|
33
|
-
RawLevel,
|
|
34
|
-
RawLevelPlayerComponent,
|
|
35
|
-
IdRegistry,
|
|
36
|
-
RawLevelBufferedComponent,
|
|
37
|
-
)
|
|
38
|
-
from amulet.version import VersionNumber
|
|
39
|
-
from amulet.utils.signal import Signal, SignalInstanceCacheName
|
|
40
|
-
from amulet.utils.weakref import DetachableWeakRef
|
|
41
|
-
|
|
42
|
-
from ._level_dat import BedrockLevelDAT
|
|
43
|
-
from ._actor_counter import ActorCounter
|
|
44
|
-
from ._dimension import BedrockRawDimension
|
|
45
|
-
from ._constant import OVERWORLD, THE_NETHER, THE_END, LOCAL_PLAYER, DefaultSelection
|
|
46
|
-
from ._typing import InternalDimension, PlayerID, RawPlayer
|
|
47
|
-
|
|
48
|
-
log = logging.getLogger(__name__)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
@dataclass
|
|
52
|
-
class BedrockCreateArgsV1:
|
|
53
|
-
"""A class to house call arguments to create.
|
|
54
|
-
|
|
55
|
-
If the call arguments to create need to be modified in the future a new arguments class can be created.
|
|
56
|
-
The create method can inspect which class it was given and access arguments accordingly.
|
|
57
|
-
"""
|
|
58
|
-
|
|
59
|
-
overwrite: bool
|
|
60
|
-
path: str
|
|
61
|
-
version: VersionNumber
|
|
62
|
-
level_name: str
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
class BedrockRawLevelOpenData:
|
|
66
|
-
"""Data that only exists when the level is open"""
|
|
67
|
-
|
|
68
|
-
back_reference: Callable[[], BedrockRawLevel | None]
|
|
69
|
-
detach_back_reference: Callable[[], None]
|
|
70
|
-
dimensions: dict[DimensionId | InternalDimension, BedrockRawDimension]
|
|
71
|
-
dimensions_lock: RLock
|
|
72
|
-
db: LevelDB
|
|
73
|
-
dimension_aliases: frozenset[DimensionId]
|
|
74
|
-
actor_counter: ActorCounter
|
|
75
|
-
block_id_override: IdRegistry
|
|
76
|
-
biome_id_override: IdRegistry
|
|
77
|
-
|
|
78
|
-
def __init__(
|
|
79
|
-
self, raw_level: BedrockRawLevel, db: LevelDB, actor_counter: ActorCounter
|
|
80
|
-
):
|
|
81
|
-
self.back_reference, self.detach_back_reference = DetachableWeakRef.new(
|
|
82
|
-
raw_level
|
|
83
|
-
)
|
|
84
|
-
self.db = db
|
|
85
|
-
self.dimensions = {}
|
|
86
|
-
self.dimensions_lock = RLock()
|
|
87
|
-
self.dimension_aliases = frozenset()
|
|
88
|
-
self.actor_counter = actor_counter
|
|
89
|
-
self.block_id_override = IdRegistry()
|
|
90
|
-
self.biome_id_override = IdRegistry()
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
class BedrockRawLevel(
|
|
94
|
-
RawLevel[BedrockRawDimension],
|
|
95
|
-
RawLevelPlayerComponent[PlayerID, RawPlayer],
|
|
96
|
-
RawLevelBufferedComponent,
|
|
97
|
-
):
|
|
98
|
-
_path: str
|
|
99
|
-
_level_dat: BedrockLevelDAT
|
|
100
|
-
_raw_open_data: BedrockRawLevelOpenData | None
|
|
101
|
-
|
|
102
|
-
__slots__ = (
|
|
103
|
-
"_path",
|
|
104
|
-
"_level_dat",
|
|
105
|
-
"_raw_open_data",
|
|
106
|
-
SignalInstanceCacheName,
|
|
107
|
-
)
|
|
108
|
-
|
|
109
|
-
def __init__(self, _ikwiad: bool = False) -> None:
|
|
110
|
-
if not _ikwiad:
|
|
111
|
-
raise RuntimeError(
|
|
112
|
-
"BedrockRawLevel must be constructed using the create or load classmethod."
|
|
113
|
-
)
|
|
114
|
-
|
|
115
|
-
@classmethod
|
|
116
|
-
def load(cls, path: str) -> BedrockRawLevel:
|
|
117
|
-
self = cls(True)
|
|
118
|
-
self._path = path
|
|
119
|
-
self._raw_open_data = None
|
|
120
|
-
self.reload()
|
|
121
|
-
return self
|
|
122
|
-
|
|
123
|
-
@classmethod
|
|
124
|
-
def create(cls, args: BedrockCreateArgsV1) -> BedrockRawLevel:
|
|
125
|
-
overwrite = args.overwrite
|
|
126
|
-
path = args.path
|
|
127
|
-
version = args.version
|
|
128
|
-
level_name = args.level_name
|
|
129
|
-
|
|
130
|
-
if os.path.isdir(path):
|
|
131
|
-
if overwrite:
|
|
132
|
-
shutil.rmtree(path)
|
|
133
|
-
else:
|
|
134
|
-
raise LevelWriteError(f"A directory already exists at the path {path}")
|
|
135
|
-
os.makedirs(path, exist_ok=True)
|
|
136
|
-
|
|
137
|
-
root = CompoundTag()
|
|
138
|
-
root["StorageVersion"] = IntTag(8)
|
|
139
|
-
root["lastOpenedWithVersion"] = ListTag(
|
|
140
|
-
[IntTag(i) for i in version.padded_version(5)]
|
|
141
|
-
)
|
|
142
|
-
root["Generator"] = IntTag(1)
|
|
143
|
-
root["LastPlayed"] = LongTag(int(time.time()))
|
|
144
|
-
root["LevelName"] = StringTag(level_name)
|
|
145
|
-
BedrockLevelDAT(root, level_dat_version=9).save_to(
|
|
146
|
-
os.path.join(path, "level.dat")
|
|
147
|
-
)
|
|
148
|
-
|
|
149
|
-
with open(os.path.join(path, "levelname.txt"), "w", encoding="utf-8") as f:
|
|
150
|
-
f.write(level_name)
|
|
151
|
-
|
|
152
|
-
db = LevelDB(os.path.join(path, "db"), True)
|
|
153
|
-
db.close()
|
|
154
|
-
|
|
155
|
-
return cls.load(path)
|
|
156
|
-
|
|
157
|
-
def is_open(self) -> bool:
|
|
158
|
-
return self._raw_open_data is not None
|
|
159
|
-
|
|
160
|
-
@property
|
|
161
|
-
def _o(self) -> BedrockRawLevelOpenData:
|
|
162
|
-
o = self._raw_open_data
|
|
163
|
-
if o is None:
|
|
164
|
-
raise RuntimeError("The level is not open.")
|
|
165
|
-
return o
|
|
166
|
-
|
|
167
|
-
def reload(self) -> None:
|
|
168
|
-
"""Reload the raw level."""
|
|
169
|
-
if self.is_open():
|
|
170
|
-
raise RuntimeError("Cannot reload a level when it is open.")
|
|
171
|
-
self._level_dat = BedrockLevelDAT.from_file(
|
|
172
|
-
os.path.join(self.path, "level.dat")
|
|
173
|
-
)
|
|
174
|
-
|
|
175
|
-
opened = Signal[()]()
|
|
176
|
-
|
|
177
|
-
def open(self) -> None:
|
|
178
|
-
"""Open the raw level."""
|
|
179
|
-
if self.is_open():
|
|
180
|
-
return
|
|
181
|
-
db = LevelDB(os.path.join(self.path, "db"))
|
|
182
|
-
actor_counter = ActorCounter()
|
|
183
|
-
self._raw_open_data = BedrockRawLevelOpenData(self, db, actor_counter)
|
|
184
|
-
actor_counter.init(self)
|
|
185
|
-
self.opened.emit()
|
|
186
|
-
|
|
187
|
-
# TODO: implement error handling and level closing if the db errors
|
|
188
|
-
# except LevelDBEncrypted as e:
|
|
189
|
-
# self._is_open = self._has_lock = False
|
|
190
|
-
# raise LevelDBException(
|
|
191
|
-
# "It looks like this world is from the marketplace.\nThese worlds are encrypted and cannot be edited."
|
|
192
|
-
# ) from e
|
|
193
|
-
# except LevelDBException as e:
|
|
194
|
-
# msg = str(e)
|
|
195
|
-
# self._is_open = self._has_lock = False
|
|
196
|
-
# # I don't know if there is a better way of handling this.
|
|
197
|
-
# if msg.startswith("IO error:") and msg.endswith(": Permission denied"):
|
|
198
|
-
# traceback.print_exc()
|
|
199
|
-
# raise LevelDBException(
|
|
200
|
-
# f"Failed to load the database. The world may be open somewhere else.\n{msg}"
|
|
201
|
-
# ) from e
|
|
202
|
-
# else:
|
|
203
|
-
# raise e
|
|
204
|
-
|
|
205
|
-
closed = Signal[()]()
|
|
206
|
-
|
|
207
|
-
def close(self) -> None:
|
|
208
|
-
"""Close the raw level."""
|
|
209
|
-
if not self.is_open():
|
|
210
|
-
return
|
|
211
|
-
open_data = self._o
|
|
212
|
-
self._raw_open_data = None
|
|
213
|
-
open_data.db.close()
|
|
214
|
-
open_data.detach_back_reference()
|
|
215
|
-
self.closed.emit()
|
|
216
|
-
|
|
217
|
-
def pre_save(self) -> None:
|
|
218
|
-
self._level_dat.save_to(os.path.join(self.path, "level.dat"))
|
|
219
|
-
|
|
220
|
-
def save(self) -> None:
|
|
221
|
-
pass
|
|
222
|
-
|
|
223
|
-
@property
|
|
224
|
-
def path(self) -> str:
|
|
225
|
-
return self._path
|
|
226
|
-
|
|
227
|
-
@property
|
|
228
|
-
def level_db(self) -> LevelDB:
|
|
229
|
-
"""
|
|
230
|
-
The leveldb database.
|
|
231
|
-
Changes made to this are made directly to the level.
|
|
232
|
-
"""
|
|
233
|
-
return self._o.db
|
|
234
|
-
|
|
235
|
-
@property
|
|
236
|
-
def level_dat(self) -> BedrockLevelDAT:
|
|
237
|
-
"""Get the level.dat file for the world"""
|
|
238
|
-
return copy.deepcopy(self._level_dat)
|
|
239
|
-
|
|
240
|
-
@level_dat.setter
|
|
241
|
-
def level_dat(self, level_dat: BedrockLevelDAT) -> None:
|
|
242
|
-
"""Set the level.dat. :meth:`pre_save` need to be run to push this to disk."""
|
|
243
|
-
if not isinstance(level_dat, BedrockLevelDAT):
|
|
244
|
-
raise TypeError
|
|
245
|
-
if not self.is_open():
|
|
246
|
-
raise RuntimeError("Level is not open.")
|
|
247
|
-
self._level_dat = copy.deepcopy(level_dat)
|
|
248
|
-
|
|
249
|
-
@property
|
|
250
|
-
def platform(self) -> str:
|
|
251
|
-
return "bedrock"
|
|
252
|
-
|
|
253
|
-
@property
|
|
254
|
-
def version(self) -> VersionNumber:
|
|
255
|
-
"""
|
|
256
|
-
The game version that the level was last opened in.
|
|
257
|
-
This is used to determine the data format to save in.
|
|
258
|
-
"""
|
|
259
|
-
try:
|
|
260
|
-
last_opened_tag = self.level_dat.compound.get_list("lastOpenedWithVersion")
|
|
261
|
-
assert last_opened_tag is not None
|
|
262
|
-
return VersionNumber(*(t.py_int for t in last_opened_tag))
|
|
263
|
-
except Exception:
|
|
264
|
-
return VersionNumber(1, 2, 0)
|
|
265
|
-
|
|
266
|
-
@property
|
|
267
|
-
def modified_time(self) -> float:
|
|
268
|
-
try:
|
|
269
|
-
return self.level_dat.compound.get_long("LastPlayed", LongTag()).py_int
|
|
270
|
-
except Exception:
|
|
271
|
-
return 0
|
|
272
|
-
|
|
273
|
-
def _find_dimensions(self) -> None:
|
|
274
|
-
with self._o.dimensions_lock:
|
|
275
|
-
if self._o.dimensions:
|
|
276
|
-
return
|
|
277
|
-
|
|
278
|
-
dimenion_bounds = {}
|
|
279
|
-
|
|
280
|
-
# find dimension bounds
|
|
281
|
-
experiments = self.level_dat.compound.get_compound(
|
|
282
|
-
"experiments", CompoundTag()
|
|
283
|
-
)
|
|
284
|
-
if (
|
|
285
|
-
experiments.get_byte("caves_and_cliffs", ByteTag()).py_int
|
|
286
|
-
or experiments.get_byte("caves_and_cliffs_internal", ByteTag()).py_int
|
|
287
|
-
or self.version >= VersionNumber(1, 18)
|
|
288
|
-
):
|
|
289
|
-
dimenion_bounds[OVERWORLD] = SelectionGroup(
|
|
290
|
-
SelectionBox(
|
|
291
|
-
(-30_000_000, -64, -30_000_000), (30_000_000, 320, 30_000_000)
|
|
292
|
-
)
|
|
293
|
-
)
|
|
294
|
-
else:
|
|
295
|
-
dimenion_bounds[OVERWORLD] = DefaultSelection
|
|
296
|
-
dimenion_bounds[THE_NETHER] = SelectionGroup(
|
|
297
|
-
SelectionBox(
|
|
298
|
-
(-30_000_000, 0, -30_000_000), (30_000_000, 128, 30_000_000)
|
|
299
|
-
)
|
|
300
|
-
)
|
|
301
|
-
dimenion_bounds[THE_END] = DefaultSelection
|
|
302
|
-
|
|
303
|
-
if b"LevelChunkMetaDataDictionary" in self.level_db:
|
|
304
|
-
data = self.level_db[b"LevelChunkMetaDataDictionary"]
|
|
305
|
-
count, data = struct.unpack("<I", data[:4])[0], data[4:]
|
|
306
|
-
for _ in range(count):
|
|
307
|
-
key, data = data[:8], data[8:]
|
|
308
|
-
offset = ReadOffset()
|
|
309
|
-
value = read_nbt(
|
|
310
|
-
data,
|
|
311
|
-
little_endian=True,
|
|
312
|
-
compressed=False,
|
|
313
|
-
string_encoding=utf8_escape_encoding,
|
|
314
|
-
read_offset=offset,
|
|
315
|
-
).compound
|
|
316
|
-
data = data[offset.offset :]
|
|
317
|
-
|
|
318
|
-
try:
|
|
319
|
-
dimension_name_tag = value.get_string("DimensionName")
|
|
320
|
-
assert dimension_name_tag is not None
|
|
321
|
-
dimension_name = dimension_name_tag.py_str
|
|
322
|
-
# The dimension names are stored differently TODO: split local and global names
|
|
323
|
-
dimension_name = {
|
|
324
|
-
"Overworld": OVERWORLD,
|
|
325
|
-
"Nether": THE_NETHER,
|
|
326
|
-
"TheEnd": THE_END,
|
|
327
|
-
}.get(dimension_name, dimension_name)
|
|
328
|
-
|
|
329
|
-
except KeyError:
|
|
330
|
-
# Some entries seem to not have a dimension assigned to them. Is there a default? We will skip over these for now.
|
|
331
|
-
# {'LastSavedBaseGameVersion': StringTag("1.19.81"), 'LastSavedDimensionHeightRange': CompoundTag({'max': ShortTag(320), 'min': ShortTag(-64)})}
|
|
332
|
-
pass
|
|
333
|
-
else:
|
|
334
|
-
previous_bounds = dimenion_bounds.get(
|
|
335
|
-
dimension_name, DefaultSelection
|
|
336
|
-
)
|
|
337
|
-
min_y = min(
|
|
338
|
-
value.get_compound(
|
|
339
|
-
"LastSavedDimensionHeightRange", CompoundTag()
|
|
340
|
-
)
|
|
341
|
-
.get_short("min", ShortTag())
|
|
342
|
-
.py_int,
|
|
343
|
-
value.get_compound(
|
|
344
|
-
"OriginalDimensionHeightRange", CompoundTag()
|
|
345
|
-
)
|
|
346
|
-
.get_short("min", ShortTag())
|
|
347
|
-
.py_int,
|
|
348
|
-
previous_bounds.min_y,
|
|
349
|
-
)
|
|
350
|
-
max_y = max(
|
|
351
|
-
value.get_compound(
|
|
352
|
-
"LastSavedDimensionHeightRange", CompoundTag()
|
|
353
|
-
)
|
|
354
|
-
.get_short("max", ShortTag())
|
|
355
|
-
.py_int,
|
|
356
|
-
value.get_compound(
|
|
357
|
-
"OriginalDimensionHeightRange", CompoundTag()
|
|
358
|
-
)
|
|
359
|
-
.get_short("max", ShortTag())
|
|
360
|
-
.py_int,
|
|
361
|
-
previous_bounds.max_y,
|
|
362
|
-
)
|
|
363
|
-
dimenion_bounds[dimension_name] = SelectionGroup(
|
|
364
|
-
SelectionBox(
|
|
365
|
-
(previous_bounds.min_x, min_y, previous_bounds.min_z),
|
|
366
|
-
(previous_bounds.max_x, max_y, previous_bounds.max_z),
|
|
367
|
-
)
|
|
368
|
-
)
|
|
369
|
-
|
|
370
|
-
dimensions = set()
|
|
371
|
-
|
|
372
|
-
def register_dimension(
|
|
373
|
-
dimension: InternalDimension, alias: Optional[str] = None
|
|
374
|
-
) -> None:
|
|
375
|
-
"""
|
|
376
|
-
Register a new dimension.
|
|
377
|
-
|
|
378
|
-
:param dimension: The internal representation of the dimension
|
|
379
|
-
:param alias: The name of the level visible to the user. Defaults to f"DIM{dimension}"
|
|
380
|
-
:return:
|
|
381
|
-
"""
|
|
382
|
-
if dimension not in self._o.dimensions:
|
|
383
|
-
if alias is None:
|
|
384
|
-
alias = f"DIM{dimension}"
|
|
385
|
-
self._o.dimensions[dimension] = self._o.dimensions[alias] = (
|
|
386
|
-
BedrockRawDimension(
|
|
387
|
-
self._o.back_reference,
|
|
388
|
-
dimension,
|
|
389
|
-
alias,
|
|
390
|
-
dimenion_bounds.get(alias, DefaultSelection),
|
|
391
|
-
)
|
|
392
|
-
)
|
|
393
|
-
dimensions.add(alias)
|
|
394
|
-
|
|
395
|
-
register_dimension(None, OVERWORLD)
|
|
396
|
-
register_dimension(1, THE_NETHER)
|
|
397
|
-
register_dimension(2, THE_END)
|
|
398
|
-
|
|
399
|
-
for key in self._o.db.keys():
|
|
400
|
-
if len(key) == 13 and key[12] in [44, 118]: # "," "v"
|
|
401
|
-
register_dimension(struct.unpack("<i", key[8:12])[0])
|
|
402
|
-
|
|
403
|
-
self._o.dimension_aliases = frozenset(dimensions)
|
|
404
|
-
|
|
405
|
-
def dimension_ids(self) -> frozenset[DimensionId]:
|
|
406
|
-
self._find_dimensions()
|
|
407
|
-
return self._o.dimension_aliases
|
|
408
|
-
|
|
409
|
-
def get_dimension(
|
|
410
|
-
self, dimension_id: DimensionId | InternalDimension
|
|
411
|
-
) -> BedrockRawDimension:
|
|
412
|
-
self._find_dimensions()
|
|
413
|
-
if dimension_id not in self._o.dimensions:
|
|
414
|
-
raise RuntimeError(f"Dimension {dimension_id} does not exist")
|
|
415
|
-
return self._o.dimensions[dimension_id]
|
|
416
|
-
|
|
417
|
-
def players(self) -> Iterable[PlayerID]:
|
|
418
|
-
yield from (
|
|
419
|
-
pid[7:].decode("utf-8")
|
|
420
|
-
for pid, _ in self.level_db.iterate(b"player_", b"player_\xFF")
|
|
421
|
-
)
|
|
422
|
-
if self.has_player(LOCAL_PLAYER):
|
|
423
|
-
yield LOCAL_PLAYER
|
|
424
|
-
|
|
425
|
-
def has_player(self, player_id: PlayerID) -> bool:
|
|
426
|
-
if player_id != LOCAL_PLAYER:
|
|
427
|
-
player_id = f"player_{player_id}"
|
|
428
|
-
return player_id.encode("utf-8") in self.level_db
|
|
429
|
-
|
|
430
|
-
def get_raw_player(self, player_id: PlayerID) -> RawPlayer:
|
|
431
|
-
if player_id == LOCAL_PLAYER:
|
|
432
|
-
key = player_id.encode("utf-8")
|
|
433
|
-
else:
|
|
434
|
-
key = f"player_{player_id}".encode("utf-8")
|
|
435
|
-
try:
|
|
436
|
-
data = self.level_db.get(key)
|
|
437
|
-
except KeyError:
|
|
438
|
-
raise PlayerDoesNotExist(f"Player {player_id} doesn't exist")
|
|
439
|
-
return read_nbt(
|
|
440
|
-
data,
|
|
441
|
-
compressed=False,
|
|
442
|
-
little_endian=True,
|
|
443
|
-
string_encoding=utf8_escape_encoding,
|
|
444
|
-
)
|
|
445
|
-
|
|
446
|
-
def set_raw_player(self, player_id: PlayerID, player: RawPlayer) -> None:
|
|
447
|
-
raise NotImplementedError
|
|
448
|
-
|
|
449
|
-
@property
|
|
450
|
-
def block_id_override(self) -> IdRegistry:
|
|
451
|
-
"""
|
|
452
|
-
A two-way map from hard coded numerical block id <-> block string.
|
|
453
|
-
This only stores overridden values. If the value is not present here you should check the translator.
|
|
454
|
-
"""
|
|
455
|
-
return self._o.block_id_override
|
|
456
|
-
|
|
457
|
-
@property
|
|
458
|
-
def biome_id_override(self) -> IdRegistry:
|
|
459
|
-
"""
|
|
460
|
-
A two-way map from hard coded numerical biome id <-> biome string.
|
|
461
|
-
This only stores overridden values. If the value is not present here you should check the translator.
|
|
462
|
-
"""
|
|
463
|
-
return self._o.biome_id_override
|
|
@@ -1,90 +0,0 @@
|
|
|
1
|
-
from __future__ import annotations
|
|
2
|
-
|
|
3
|
-
import struct
|
|
4
|
-
from typing import BinaryIO, Any
|
|
5
|
-
from copy import deepcopy
|
|
6
|
-
|
|
7
|
-
from amulet_nbt import (
|
|
8
|
-
read_nbt,
|
|
9
|
-
NamedTag,
|
|
10
|
-
utf8_escape_encoding,
|
|
11
|
-
AnyNBT,
|
|
12
|
-
EncodingPreset,
|
|
13
|
-
StringEncoding,
|
|
14
|
-
)
|
|
15
|
-
from amulet.errors import LevelReadError
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
class BedrockLevelDAT(NamedTag):
|
|
19
|
-
_level_dat_version: int
|
|
20
|
-
|
|
21
|
-
def __init__(
|
|
22
|
-
self,
|
|
23
|
-
tag: AnyNBT | None = None,
|
|
24
|
-
name: str | bytes = "",
|
|
25
|
-
level_dat_version: int | None = None,
|
|
26
|
-
) -> None:
|
|
27
|
-
if not isinstance(level_dat_version, int):
|
|
28
|
-
raise TypeError(
|
|
29
|
-
"level_dat_version must be specified when constructing a BedrockLevelDAT instance."
|
|
30
|
-
)
|
|
31
|
-
super().__init__(tag, name)
|
|
32
|
-
self._level_dat_version = level_dat_version
|
|
33
|
-
|
|
34
|
-
def __reduce__(self) -> Any:
|
|
35
|
-
return BedrockLevelDAT, (self.tag, self.name, self.level_dat_version)
|
|
36
|
-
|
|
37
|
-
def __copy__(self) -> BedrockLevelDAT:
|
|
38
|
-
return BedrockLevelDAT(self.tag, self.name, self.level_dat_version)
|
|
39
|
-
|
|
40
|
-
def __deepcopy__(self, memo: dict) -> BedrockLevelDAT:
|
|
41
|
-
return BedrockLevelDAT(
|
|
42
|
-
deepcopy(self.tag, memo=memo), self.name, self.level_dat_version
|
|
43
|
-
)
|
|
44
|
-
|
|
45
|
-
@property
|
|
46
|
-
def level_dat_version(self) -> int:
|
|
47
|
-
return self._level_dat_version
|
|
48
|
-
|
|
49
|
-
@classmethod
|
|
50
|
-
def from_file(cls, path: str) -> BedrockLevelDAT:
|
|
51
|
-
with open(path, "rb") as f:
|
|
52
|
-
level_dat_version = struct.unpack("<i", f.read(4))[0]
|
|
53
|
-
if 4 <= level_dat_version <= 10:
|
|
54
|
-
data_length = struct.unpack("<i", f.read(4))[0]
|
|
55
|
-
root_tag = read_nbt(
|
|
56
|
-
f.read(data_length),
|
|
57
|
-
compressed=False,
|
|
58
|
-
little_endian=True,
|
|
59
|
-
string_encoding=utf8_escape_encoding,
|
|
60
|
-
)
|
|
61
|
-
name = root_tag.name
|
|
62
|
-
tag = root_tag.tag
|
|
63
|
-
else:
|
|
64
|
-
# TODO: handle other versions
|
|
65
|
-
raise LevelReadError(
|
|
66
|
-
f"Unsupported level.dat version {level_dat_version}"
|
|
67
|
-
)
|
|
68
|
-
return cls(tag, name, level_dat_version)
|
|
69
|
-
|
|
70
|
-
def save_to(
|
|
71
|
-
self,
|
|
72
|
-
filename_or_buffer: str | BinaryIO | None = None,
|
|
73
|
-
*,
|
|
74
|
-
preset: EncodingPreset | None = None,
|
|
75
|
-
compressed: bool = False,
|
|
76
|
-
little_endian: bool = True,
|
|
77
|
-
string_encoding: StringEncoding = utf8_escape_encoding,
|
|
78
|
-
) -> bytes:
|
|
79
|
-
payload = super().save_to(
|
|
80
|
-
compressed=compressed,
|
|
81
|
-
little_endian=little_endian,
|
|
82
|
-
string_encoding=string_encoding,
|
|
83
|
-
)
|
|
84
|
-
dat = struct.pack("<ii", self._level_dat_version, len(payload)) + payload
|
|
85
|
-
if isinstance(filename_or_buffer, str):
|
|
86
|
-
with open(filename_or_buffer, "wb") as f:
|
|
87
|
-
f.write(dat)
|
|
88
|
-
elif filename_or_buffer is not None:
|
|
89
|
-
filename_or_buffer.write(dat)
|
|
90
|
-
return dat
|
|
@@ -1,83 +0,0 @@
|
|
|
1
|
-
from typing import Dict, Tuple, TypeAlias
|
|
2
|
-
|
|
3
|
-
# from amulet.api.data_types import VersionNumberTuple
|
|
4
|
-
VersionNumberTuple: TypeAlias = tuple[int, ...]
|
|
5
|
-
|
|
6
|
-
# This is a dictionary of the first and last times each chunk version was written by a game version
|
|
7
|
-
# It is used to convert the chunk version to the game version that could have saved that chunk.
|
|
8
|
-
# It is also used to convert back to the chunk version when saving based on the game version.
|
|
9
|
-
chunk_version_to_max_version: Dict[
|
|
10
|
-
int, Tuple[VersionNumberTuple, VersionNumberTuple]
|
|
11
|
-
] = {
|
|
12
|
-
0: ((0, 9, 0, 0), (0, 9, 1, 9999)),
|
|
13
|
-
1: ((0, 9, 2, 0), (0, 9, 4, 9999)),
|
|
14
|
-
2: ((0, 9, 5, 0), (0, 16, 999, 9999)),
|
|
15
|
-
3: ((0, 17, 0, 0), (0, 17, 999, 9999)),
|
|
16
|
-
4: ((0, 18, 0, 0), (0, 18, 0, 0)),
|
|
17
|
-
5: ((0, 18, 0, 0), (1, 1, 999, 9999)),
|
|
18
|
-
6: ((1, 2, 0, 0), (1, 2, 0, 0)),
|
|
19
|
-
7: ((1, 2, 0, 0), (1, 2, 999, 9999)),
|
|
20
|
-
8: ((1, 3, 0, 0), (1, 7, 999, 9999)),
|
|
21
|
-
9: ((1, 8, 0, 0), (1, 8, 999, 9999)),
|
|
22
|
-
10: ((1, 9, 0, 0), (1, 9, 999, 9999)),
|
|
23
|
-
11: ((1, 10, 0, 0), (1, 10, 999, 9999)),
|
|
24
|
-
12: ((1, 11, 0, 0), (1, 11, 0, 9999)),
|
|
25
|
-
13: ((1, 11, 1, 0), (1, 11, 1, 9999)),
|
|
26
|
-
14: ((1, 11, 2, 0), (1, 11, 999, 999)),
|
|
27
|
-
15: ((1, 12, 0, 0), (1, 15, 999, 9999)),
|
|
28
|
-
16: ((1, 15, 999, 9999), (1, 15, 999, 9999)),
|
|
29
|
-
17: ((1, 15, 999, 9999), (1, 15, 999, 9999)),
|
|
30
|
-
18: ((1, 16, 0, 0), (1, 16, 0, 0)),
|
|
31
|
-
19: ((1, 16, 0, 0), (1, 16, 100, 55)),
|
|
32
|
-
20: ((1, 16, 100, 56), (1, 16, 100, 57)),
|
|
33
|
-
21: ((1, 16, 100, 58), (1, 16, 210, 0)),
|
|
34
|
-
22: ((1, 16, 210, 0), (1, 17, 999, 999)), # caves and cliffs disabled
|
|
35
|
-
# used with experimental features
|
|
36
|
-
23: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
37
|
-
24: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
38
|
-
25: ((1, 17, 0, 0), (1, 17, 20, 999)), # 1.17.0-20 caves and cliffs enabled
|
|
39
|
-
26: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
40
|
-
27: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
41
|
-
28: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
42
|
-
29: ((1, 17, 30, 0), (1, 17, 30, 999)), # 1.17.30 caves and cliffs enabled
|
|
43
|
-
30: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
44
|
-
31: ((1, 17, 40, 0), (1, 17, 999, 999)), # 1.17.40 caves and cliffs enabled
|
|
45
|
-
32: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
46
|
-
33: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
47
|
-
34: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
48
|
-
35: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
49
|
-
36: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
50
|
-
37: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
51
|
-
38: ((1, 17, 0, 0), (1, 17, 0, 0)), # ?
|
|
52
|
-
# continue without experimental gameplay
|
|
53
|
-
39: ((1, 18, 0, 0), (1, 18, 29, 999)),
|
|
54
|
-
40: ((1, 18, 30, 0), (999, 999, 999, 999)),
|
|
55
|
-
} # TODO: fill this list with the actual last game version number each chunk version was last used in
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
def chunk_to_game_version(
|
|
59
|
-
max_game_version: VersionNumberTuple, chunk_version: int
|
|
60
|
-
) -> VersionNumberTuple:
|
|
61
|
-
"""Find the game version to use based on the chunk version."""
|
|
62
|
-
return min(chunk_version_to_max_version[chunk_version][1], max_game_version)
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
def game_to_chunk_version(
|
|
66
|
-
max_game_version: VersionNumberTuple, cnc: bool = False
|
|
67
|
-
) -> int:
|
|
68
|
-
"""Get the chunk version that should be used for the given game version number."""
|
|
69
|
-
# The comparison can fail if they are no the same length
|
|
70
|
-
max_game_version = (max_game_version + (0,) * (4 - len(max_game_version)))[:4]
|
|
71
|
-
cnc = cnc or max_game_version >= (1, 18, 0)
|
|
72
|
-
for chunk_version, (first, last) in chunk_version_to_max_version.items():
|
|
73
|
-
if (
|
|
74
|
-
first <= max_game_version <= last # if the version is in the range
|
|
75
|
-
and cnc
|
|
76
|
-
== (
|
|
77
|
-
chunk_version > 22
|
|
78
|
-
) # and it is in the correct range (caves and cliffs or not)
|
|
79
|
-
):
|
|
80
|
-
return chunk_version
|
|
81
|
-
|
|
82
|
-
# If all else fails return the minimum we support
|
|
83
|
-
return 6
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
from ._chunk import BedrockChunk0, BedrockChunk29, BedrockChunk, BedrockChunk
|