amulet-core 2.0a5__cp312-cp312-macosx_10_13_universal2.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of amulet-core might be problematic. Click here for more details.

Files changed (210) hide show
  1. amulet/__init__.cpython-312-darwin.so +0 -0
  2. amulet/__init__.pyi +30 -0
  3. amulet/__pyinstaller/__init__.py +2 -0
  4. amulet/__pyinstaller/hook-amulet.py +4 -0
  5. amulet/_init.py +28 -0
  6. amulet/_version.py +21 -0
  7. amulet/biome.cpp +36 -0
  8. amulet/biome.hpp +43 -0
  9. amulet/biome.pyi +77 -0
  10. amulet/block.cpp +435 -0
  11. amulet/block.hpp +119 -0
  12. amulet/block.pyi +273 -0
  13. amulet/block_entity.cpp +12 -0
  14. amulet/block_entity.hpp +56 -0
  15. amulet/block_entity.pyi +80 -0
  16. amulet/chunk.cpp +16 -0
  17. amulet/chunk.hpp +99 -0
  18. amulet/chunk.pyi +30 -0
  19. amulet/chunk_/components/biome.py +155 -0
  20. amulet/chunk_/components/block_entity.py +117 -0
  21. amulet/chunk_/components/entity.py +64 -0
  22. amulet/chunk_/components/height_2d.py +16 -0
  23. amulet/chunk_components.pyi +95 -0
  24. amulet/collections.pyi +37 -0
  25. amulet/data_types.py +29 -0
  26. amulet/entity.py +180 -0
  27. amulet/errors.py +63 -0
  28. amulet/game/__init__.py +7 -0
  29. amulet/game/_game.py +152 -0
  30. amulet/game/_universal/__init__.py +1 -0
  31. amulet/game/_universal/_biome.py +17 -0
  32. amulet/game/_universal/_block.py +47 -0
  33. amulet/game/_universal/_version.py +68 -0
  34. amulet/game/abc/__init__.py +22 -0
  35. amulet/game/abc/_block_specification.py +150 -0
  36. amulet/game/abc/biome.py +213 -0
  37. amulet/game/abc/block.py +331 -0
  38. amulet/game/abc/game_version_container.py +25 -0
  39. amulet/game/abc/json_interface.py +27 -0
  40. amulet/game/abc/version.py +44 -0
  41. amulet/game/bedrock/__init__.py +1 -0
  42. amulet/game/bedrock/_biome.py +35 -0
  43. amulet/game/bedrock/_block.py +42 -0
  44. amulet/game/bedrock/_version.py +165 -0
  45. amulet/game/java/__init__.py +2 -0
  46. amulet/game/java/_biome.py +35 -0
  47. amulet/game/java/_block.py +60 -0
  48. amulet/game/java/_version.py +176 -0
  49. amulet/game/translate/__init__.py +12 -0
  50. amulet/game/translate/_functions/__init__.py +15 -0
  51. amulet/game/translate/_functions/_code_functions/__init__.py +0 -0
  52. amulet/game/translate/_functions/_code_functions/_text.py +553 -0
  53. amulet/game/translate/_functions/_code_functions/banner_pattern.py +67 -0
  54. amulet/game/translate/_functions/_code_functions/bedrock_chest_connection.py +152 -0
  55. amulet/game/translate/_functions/_code_functions/bedrock_moving_block_pos.py +88 -0
  56. amulet/game/translate/_functions/_code_functions/bedrock_sign.py +152 -0
  57. amulet/game/translate/_functions/_code_functions/bedrock_skull_rotation.py +16 -0
  58. amulet/game/translate/_functions/_code_functions/custom_name.py +146 -0
  59. amulet/game/translate/_functions/_frozen.py +66 -0
  60. amulet/game/translate/_functions/_state.py +54 -0
  61. amulet/game/translate/_functions/_typing.py +98 -0
  62. amulet/game/translate/_functions/abc.py +116 -0
  63. amulet/game/translate/_functions/carry_nbt.py +160 -0
  64. amulet/game/translate/_functions/carry_properties.py +80 -0
  65. amulet/game/translate/_functions/code.py +143 -0
  66. amulet/game/translate/_functions/map_block_name.py +66 -0
  67. amulet/game/translate/_functions/map_nbt.py +111 -0
  68. amulet/game/translate/_functions/map_properties.py +93 -0
  69. amulet/game/translate/_functions/multiblock.py +112 -0
  70. amulet/game/translate/_functions/new_block.py +42 -0
  71. amulet/game/translate/_functions/new_entity.py +43 -0
  72. amulet/game/translate/_functions/new_nbt.py +206 -0
  73. amulet/game/translate/_functions/new_properties.py +64 -0
  74. amulet/game/translate/_functions/sequence.py +51 -0
  75. amulet/game/translate/_functions/walk_input_nbt.py +331 -0
  76. amulet/game/translate/_translator.py +433 -0
  77. amulet/item.py +75 -0
  78. amulet/level/__init__.pyi +27 -0
  79. amulet/level/_load.py +100 -0
  80. amulet/level/abc/__init__.py +12 -0
  81. amulet/level/abc/_chunk_handle.py +335 -0
  82. amulet/level/abc/_dimension.py +86 -0
  83. amulet/level/abc/_history/__init__.py +1 -0
  84. amulet/level/abc/_history/_cache.py +224 -0
  85. amulet/level/abc/_history/_history_manager.py +291 -0
  86. amulet/level/abc/_level/__init__.py +5 -0
  87. amulet/level/abc/_level/_compactable_level.py +10 -0
  88. amulet/level/abc/_level/_creatable_level.py +29 -0
  89. amulet/level/abc/_level/_disk_level.py +17 -0
  90. amulet/level/abc/_level/_level.py +453 -0
  91. amulet/level/abc/_level/_loadable_level.py +42 -0
  92. amulet/level/abc/_player_storage.py +7 -0
  93. amulet/level/abc/_raw_level.py +187 -0
  94. amulet/level/abc/_registry.py +40 -0
  95. amulet/level/bedrock/__init__.py +2 -0
  96. amulet/level/bedrock/_chunk_handle.py +19 -0
  97. amulet/level/bedrock/_dimension.py +22 -0
  98. amulet/level/bedrock/_level.py +187 -0
  99. amulet/level/bedrock/_raw/__init__.py +5 -0
  100. amulet/level/bedrock/_raw/_actor_counter.py +53 -0
  101. amulet/level/bedrock/_raw/_chunk.py +54 -0
  102. amulet/level/bedrock/_raw/_chunk_decode.py +668 -0
  103. amulet/level/bedrock/_raw/_chunk_encode.py +602 -0
  104. amulet/level/bedrock/_raw/_constant.py +9 -0
  105. amulet/level/bedrock/_raw/_dimension.py +343 -0
  106. amulet/level/bedrock/_raw/_level.py +463 -0
  107. amulet/level/bedrock/_raw/_level_dat.py +90 -0
  108. amulet/level/bedrock/_raw/_typing.py +6 -0
  109. amulet/level/bedrock/_raw/leveldb_chunk_versions.py +83 -0
  110. amulet/level/bedrock/chunk/__init__.py +1 -0
  111. amulet/level/bedrock/chunk/_chunk.py +126 -0
  112. amulet/level/bedrock/chunk/components/__init__.py +0 -0
  113. amulet/level/bedrock/chunk/components/chunk_version.py +12 -0
  114. amulet/level/bedrock/chunk/components/finalised_state.py +13 -0
  115. amulet/level/bedrock/chunk/components/raw_chunk.py +15 -0
  116. amulet/level/construction/__init__.py +0 -0
  117. amulet/level/java/__init__.pyi +21 -0
  118. amulet/level/java/_chunk_handle.py +17 -0
  119. amulet/level/java/_chunk_handle.pyi +15 -0
  120. amulet/level/java/_dimension.py +20 -0
  121. amulet/level/java/_dimension.pyi +13 -0
  122. amulet/level/java/_level.py +184 -0
  123. amulet/level/java/_level.pyi +120 -0
  124. amulet/level/java/_raw/__init__.pyi +19 -0
  125. amulet/level/java/_raw/_chunk.pyi +23 -0
  126. amulet/level/java/_raw/_chunk_decode.py +561 -0
  127. amulet/level/java/_raw/_chunk_encode.py +463 -0
  128. amulet/level/java/_raw/_constant.py +9 -0
  129. amulet/level/java/_raw/_constant.pyi +20 -0
  130. amulet/level/java/_raw/_data_pack/__init__.py +2 -0
  131. amulet/level/java/_raw/_data_pack/__init__.pyi +8 -0
  132. amulet/level/java/_raw/_data_pack/data_pack.py +241 -0
  133. amulet/level/java/_raw/_data_pack/data_pack.pyi +197 -0
  134. amulet/level/java/_raw/_data_pack/data_pack_manager.py +77 -0
  135. amulet/level/java/_raw/_data_pack/data_pack_manager.pyi +75 -0
  136. amulet/level/java/_raw/_dimension.py +86 -0
  137. amulet/level/java/_raw/_dimension.pyi +72 -0
  138. amulet/level/java/_raw/_level.py +507 -0
  139. amulet/level/java/_raw/_level.pyi +238 -0
  140. amulet/level/java/_raw/_typing.py +3 -0
  141. amulet/level/java/_raw/_typing.pyi +5 -0
  142. amulet/level/java/anvil/__init__.py +2 -0
  143. amulet/level/java/anvil/__init__.pyi +11 -0
  144. amulet/level/java/anvil/_dimension.py +170 -0
  145. amulet/level/java/anvil/_dimension.pyi +109 -0
  146. amulet/level/java/anvil/_region.py +421 -0
  147. amulet/level/java/anvil/_region.pyi +197 -0
  148. amulet/level/java/anvil/_sector_manager.py +223 -0
  149. amulet/level/java/anvil/_sector_manager.pyi +142 -0
  150. amulet/level/java/chunk.pyi +81 -0
  151. amulet/level/java/chunk_/_chunk.py +260 -0
  152. amulet/level/java/chunk_/components/inhabited_time.py +12 -0
  153. amulet/level/java/chunk_/components/last_update.py +12 -0
  154. amulet/level/java/chunk_/components/legacy_version.py +12 -0
  155. amulet/level/java/chunk_/components/light_populated.py +12 -0
  156. amulet/level/java/chunk_/components/named_height_2d.py +37 -0
  157. amulet/level/java/chunk_/components/status.py +11 -0
  158. amulet/level/java/chunk_/components/terrain_populated.py +12 -0
  159. amulet/level/java/chunk_components.pyi +22 -0
  160. amulet/level/java/long_array.pyi +38 -0
  161. amulet/level/java_forge/__init__.py +0 -0
  162. amulet/level/mcstructure/__init__.py +0 -0
  163. amulet/level/nbt/__init__.py +0 -0
  164. amulet/level/schematic/__init__.py +0 -0
  165. amulet/level/sponge_schematic/__init__.py +0 -0
  166. amulet/level/temporary_level/__init__.py +1 -0
  167. amulet/level/temporary_level/_level.py +16 -0
  168. amulet/palette/__init__.pyi +8 -0
  169. amulet/palette/biome_palette.pyi +45 -0
  170. amulet/palette/block_palette.pyi +45 -0
  171. amulet/player.py +64 -0
  172. amulet/py.typed +0 -0
  173. amulet/selection/__init__.py +2 -0
  174. amulet/selection/abstract_selection.py +342 -0
  175. amulet/selection/box.py +852 -0
  176. amulet/selection/group.py +481 -0
  177. amulet/utils/__init__.pyi +28 -0
  178. amulet/utils/call_spec/__init__.py +24 -0
  179. amulet/utils/call_spec/__init__.pyi +53 -0
  180. amulet/utils/call_spec/_call_spec.py +262 -0
  181. amulet/utils/call_spec/_call_spec.pyi +272 -0
  182. amulet/utils/format_utils.py +41 -0
  183. amulet/utils/generator.py +18 -0
  184. amulet/utils/matrix.py +243 -0
  185. amulet/utils/matrix.pyi +177 -0
  186. amulet/utils/numpy.pyi +11 -0
  187. amulet/utils/numpy_helpers.py +19 -0
  188. amulet/utils/shareable_lock.py +335 -0
  189. amulet/utils/shareable_lock.pyi +190 -0
  190. amulet/utils/signal/__init__.py +10 -0
  191. amulet/utils/signal/__init__.pyi +25 -0
  192. amulet/utils/signal/_signal.py +228 -0
  193. amulet/utils/signal/_signal.pyi +84 -0
  194. amulet/utils/task_manager.py +235 -0
  195. amulet/utils/task_manager.pyi +168 -0
  196. amulet/utils/typed_property.py +111 -0
  197. amulet/utils/typing.py +4 -0
  198. amulet/utils/typing.pyi +6 -0
  199. amulet/utils/weakref.py +70 -0
  200. amulet/utils/weakref.pyi +50 -0
  201. amulet/utils/world_utils.py +102 -0
  202. amulet/utils/world_utils.pyi +109 -0
  203. amulet/version.cpp +136 -0
  204. amulet/version.hpp +142 -0
  205. amulet/version.pyi +94 -0
  206. amulet_core-2.0a5.dist-info/METADATA +103 -0
  207. amulet_core-2.0a5.dist-info/RECORD +210 -0
  208. amulet_core-2.0a5.dist-info/WHEEL +5 -0
  209. amulet_core-2.0a5.dist-info/entry_points.txt +2 -0
  210. amulet_core-2.0a5.dist-info/top_level.txt +1 -0
@@ -0,0 +1,463 @@
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
@@ -0,0 +1,90 @@
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
@@ -0,0 +1,6 @@
1
+ from typing import TypeAlias
2
+ from amulet_nbt import NamedTag
3
+
4
+ InternalDimension: TypeAlias = int | None
5
+ PlayerID: TypeAlias = str
6
+ RawPlayer: TypeAlias = NamedTag
@@ -0,0 +1,83 @@
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
@@ -0,0 +1 @@
1
+ from ._chunk import BedrockChunk0, BedrockChunk29, BedrockChunk, BedrockChunk