amulet-core 2.0a3__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.

Files changed (210) hide show
  1. amulet/__init__.cp312-win_amd64.pyd +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.0a3.dist-info/METADATA +103 -0
  207. amulet_core-2.0a3.dist-info/RECORD +210 -0
  208. amulet_core-2.0a3.dist-info/WHEEL +5 -0
  209. amulet_core-2.0a3.dist-info/entry_points.txt +2 -0
  210. amulet_core-2.0a3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,72 @@
1
+ from __future__ import annotations
2
+
3
+ import types
4
+ import typing
5
+ from builtins import str as DimensionId
6
+ from builtins import str as InternalDimensionId
7
+
8
+ import amulet.level.abc._raw_level
9
+ from amulet.biome import Biome
10
+ from amulet.block import BlockStack
11
+ from amulet.level.abc._raw_level import RawDimension, RawLevelFriend
12
+ from amulet.level.java._raw._chunk import decode_chunk, encode_chunk
13
+ from amulet.level.java.anvil._dimension import AnvilDimension
14
+ from amulet.level.java.chunk import JavaChunk
15
+ from amulet.selection.group import SelectionGroup
16
+
17
+ __all__ = [
18
+ "AnvilDimension",
19
+ "Biome",
20
+ "BlockStack",
21
+ "ChunkCoordinates",
22
+ "DimensionId",
23
+ "InternalDimensionId",
24
+ "JavaChunk",
25
+ "JavaRawDimension",
26
+ "RawChunkType",
27
+ "RawDimension",
28
+ "RawLevelFriend",
29
+ "SelectionGroup",
30
+ "decode_chunk",
31
+ "encode_chunk",
32
+ ]
33
+
34
+ class JavaRawDimension(
35
+ amulet.level.abc._raw_level.RawLevelFriend, amulet.level.abc._raw_level.RawDimension
36
+ ):
37
+ def __init__(
38
+ self,
39
+ raw_level_ref: typing.Callable[[], JavaRawLevel | None],
40
+ anvil_dimension: AnvilDimension,
41
+ relative_path: InternalDimensionId,
42
+ dimension_id: DimensionId,
43
+ bounds: SelectionGroup,
44
+ default_block: BlockStack,
45
+ default_biome: Biome,
46
+ ) -> None: ...
47
+ def all_chunk_coords(self) -> typing.Iterable[ChunkCoordinates]: ...
48
+ def bounds(self) -> SelectionGroup: ...
49
+ def compact(self) -> None:
50
+ """
51
+ Compact all region files
52
+ """
53
+
54
+ def default_biome(self) -> Biome: ...
55
+ def default_block(self) -> BlockStack: ...
56
+ def delete_chunk(self, cx: int, cz: int) -> None: ...
57
+ def get_raw_chunk(self, cx: int, cz: int) -> RawChunkType: ...
58
+ def has_chunk(self, cx: int, cz: int) -> bool: ...
59
+ def native_chunk_to_raw_chunk(
60
+ self, chunk: JavaChunk, cx: int, cz: int
61
+ ) -> RawChunkType: ...
62
+ def raw_chunk_to_native_chunk(
63
+ self, raw_chunk: RawChunkType, cx: int, cz: int
64
+ ) -> JavaChunk: ...
65
+ def set_raw_chunk(self, cx: int, cz: int, chunk: RawChunkType) -> None: ...
66
+ @property
67
+ def dimension_id(self) -> DimensionId: ...
68
+ @property
69
+ def relative_path(self) -> InternalDimensionId: ...
70
+
71
+ ChunkCoordinates: types.GenericAlias # value = tuple[int, int]
72
+ RawChunkType: types.GenericAlias # value = dict[str, amulet_nbt.NamedTag]
@@ -0,0 +1,507 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import BinaryIO, Callable
4
+ import copy
5
+ from threading import RLock
6
+ import logging
7
+ from dataclasses import dataclass
8
+ import shutil
9
+ import time
10
+ import glob
11
+ import json
12
+ import struct
13
+ import os
14
+
15
+ import portalocker
16
+
17
+ from amulet_nbt import (
18
+ IntTag,
19
+ LongTag,
20
+ StringTag,
21
+ ListTag,
22
+ CompoundTag,
23
+ read_nbt,
24
+ NamedTag,
25
+ )
26
+
27
+ from amulet.data_types import DimensionId
28
+ from amulet.block import BlockStack, Block
29
+ from amulet.biome import Biome
30
+ from amulet.game import get_game_version
31
+ from amulet.selection import SelectionGroup, SelectionBox
32
+ from amulet.errors import LevelWriteError
33
+ from amulet.level.abc import (
34
+ RawLevel,
35
+ IdRegistry,
36
+ )
37
+ from amulet.version import VersionNumber
38
+ from amulet.utils.signal import Signal, SignalInstanceCacheName
39
+ from amulet.utils.weakref import DetachableWeakRef
40
+
41
+ from amulet.level.java.anvil import AnvilDimension
42
+ from ._data_pack import DataPackManager, DataPack
43
+ from ._dimension import JavaRawDimension
44
+ from ._typing import InternalDimensionId
45
+ from ._constant import OVERWORLD, THE_NETHER, THE_END, DefaultSelection
46
+
47
+
48
+ log = logging.getLogger(__name__)
49
+
50
+
51
+ @dataclass
52
+ class JavaCreateArgsV1:
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 JavaRawLevelOpenData:
66
+ back_reference: Callable[[], JavaRawLevel | None]
67
+ detach_back_reference: Callable[[], None]
68
+ lock_file: BinaryIO
69
+ lock_time: float
70
+ data_pack: DataPackManager
71
+ dimensions: dict[InternalDimensionId, JavaRawDimension]
72
+ dimension_ids: dict[DimensionId, InternalDimensionId]
73
+ dimensions_lock: RLock
74
+ block_id_override: IdRegistry
75
+ biome_id_override: IdRegistry
76
+
77
+ def __init__(
78
+ self,
79
+ raw_level: JavaRawLevel,
80
+ lock_file: BinaryIO,
81
+ lock_time: float,
82
+ data_pack: DataPackManager,
83
+ ) -> None:
84
+ self.back_reference, self.detach_back_reference = DetachableWeakRef.new(
85
+ raw_level
86
+ )
87
+ self.lock_file = lock_file
88
+ self.lock_time = lock_time
89
+ self.data_pack = data_pack
90
+ self.dimensions = {}
91
+ self.dimension_ids = {}
92
+ self.dimensions_lock = RLock()
93
+ self.block_id_override = IdRegistry()
94
+ self.biome_id_override = IdRegistry()
95
+
96
+
97
+ class JavaRawLevel(RawLevel[JavaRawDimension]):
98
+ _path: str
99
+ _level_dat: NamedTag
100
+ _data_version: VersionNumber
101
+ _raw_open_data: JavaRawLevelOpenData | None
102
+
103
+ __slots__ = (
104
+ "_path",
105
+ "_level_dat",
106
+ "_data_version",
107
+ "_raw_open_data",
108
+ SignalInstanceCacheName,
109
+ )
110
+
111
+ def __init__(self, _ikwiad: bool = False) -> None:
112
+ if not _ikwiad:
113
+ raise RuntimeError(
114
+ "JavaRawLevel must be constructed using the create or load classmethod."
115
+ )
116
+
117
+ @classmethod
118
+ def load(cls, path: str) -> JavaRawLevel:
119
+ self = cls(True)
120
+ self._path = path
121
+ self._raw_open_data = None
122
+ self.reload()
123
+ return self
124
+
125
+ @classmethod
126
+ def create(cls, args: JavaCreateArgsV1) -> JavaRawLevel:
127
+ overwrite = args.overwrite
128
+ path = args.path
129
+ version = args.version
130
+ level_name = args.level_name
131
+
132
+ if os.path.isdir(path):
133
+ if overwrite:
134
+ shutil.rmtree(path)
135
+ else:
136
+ raise LevelWriteError(f"A world already exists at the path {path}")
137
+ os.makedirs(path, exist_ok=True)
138
+
139
+ data_version = get_game_version("java", version).max_version
140
+
141
+ root = CompoundTag()
142
+ root["Data"] = data = CompoundTag()
143
+ data["version"] = IntTag(19133)
144
+ data["DataVersion"] = IntTag(data_version[0])
145
+ data["LastPlayed"] = LongTag(int(time.time() * 1000))
146
+ data["LevelName"] = StringTag(level_name)
147
+
148
+ root.save_to(os.path.join(path, "level.dat"))
149
+
150
+ return cls.load(path)
151
+
152
+ def is_open(self) -> bool:
153
+ return self._raw_open_data is not None
154
+
155
+ @property
156
+ def _o(self) -> JavaRawLevelOpenData:
157
+ o = self._raw_open_data
158
+ if o is None:
159
+ raise RuntimeError("The level is not open.")
160
+ return o
161
+
162
+ def _update_data_version(self) -> None:
163
+ """Pull the data version from the level.dat file"""
164
+ self._data_version = VersionNumber(
165
+ self._level_dat.compound.get_compound("Data", CompoundTag())
166
+ .get_int("DataVersion", IntTag(-1))
167
+ .py_int
168
+ )
169
+
170
+ def reload(self) -> None:
171
+ """Reload the raw level."""
172
+ if self.is_open():
173
+ raise RuntimeError("Cannot reload a level when it is open.")
174
+ self._level_dat = read_nbt(os.path.join(self.path, "level.dat"))
175
+ self._update_data_version()
176
+
177
+ opened = Signal[()]()
178
+
179
+ def open(self) -> None:
180
+ """Open the raw level."""
181
+ if self.is_open():
182
+ return
183
+
184
+ # create the session.lock file
185
+ try:
186
+ # Try to open the file
187
+ lock = portalocker.Lock(
188
+ os.path.join(self.path, "session.lock"),
189
+ "wb+",
190
+ fail_when_locked=True,
191
+ )
192
+ lock_file: BinaryIO = lock.acquire() # type: ignore
193
+ except portalocker.LockException:
194
+ raise Exception(
195
+ f"Could not acquire session.lock. The world may be open somewhere else."
196
+ )
197
+ else:
198
+ # write the current time to the file
199
+ lock_time = time.time()
200
+ lock_file.write(struct.pack(">Q", int(lock_time * 1000)))
201
+
202
+ # flush the changes to disk
203
+ lock_file.flush()
204
+ os.fsync(lock_file.fileno())
205
+
206
+ packs = []
207
+ enabled_packs = (
208
+ self.level_dat.compound.get_compound("Data", CompoundTag())
209
+ .get_compound("DataPacks", CompoundTag())
210
+ .get_list("Enabled", ListTag())
211
+ )
212
+ for pack in enabled_packs:
213
+ if isinstance(pack, StringTag):
214
+ pack_name: str = pack.py_str
215
+ if pack_name == "vanilla":
216
+ pass
217
+ elif pack_name.startswith("file/"):
218
+ path = os.path.join(self.path, "datapacks", pack_name[5:])
219
+ if DataPack.is_path_valid(path):
220
+ packs.append(DataPack(path))
221
+ data_pack = DataPackManager(packs)
222
+
223
+ self._raw_open_data = JavaRawLevelOpenData(
224
+ self, lock_file, lock_time, data_pack
225
+ )
226
+ self.opened.emit()
227
+
228
+ closed = Signal[()]()
229
+
230
+ def close(self) -> None:
231
+ """Close the raw level."""
232
+ if not self.is_open():
233
+ return
234
+ open_data = self._o
235
+ portalocker.unlock(open_data.lock_file)
236
+ open_data.lock_file.close()
237
+ open_data.detach_back_reference()
238
+ self._raw_open_data = None
239
+ self.closed.emit()
240
+
241
+ @property
242
+ def path(self) -> str:
243
+ return self._path
244
+
245
+ @property
246
+ def level_dat(self) -> NamedTag:
247
+ """Get the level.dat file for the world"""
248
+ return copy.deepcopy(self._level_dat)
249
+
250
+ @level_dat.setter
251
+ def level_dat(self, level_dat: NamedTag) -> None:
252
+ if not isinstance(level_dat, NamedTag):
253
+ raise TypeError
254
+ if not self.is_open():
255
+ raise RuntimeError("Level is not open.")
256
+ self._level_dat = level_dat = copy.deepcopy(level_dat)
257
+ self._update_data_version()
258
+ level_dat.save_to(os.path.join(self.path, "level.dat"))
259
+
260
+ @property
261
+ def platform(self) -> str:
262
+ return "java"
263
+
264
+ @property
265
+ def data_version(self) -> VersionNumber:
266
+ """
267
+ The game data version that the level was last opened in.
268
+ This is used to determine the data format to save in.
269
+ """
270
+ return self._data_version
271
+
272
+ @property
273
+ def modified_time(self) -> float:
274
+ """Unix timestamp of when the level was last modified."""
275
+ return (
276
+ self.level_dat.compound.get_compound("Data", CompoundTag())
277
+ .get_long("LastPlayed", LongTag())
278
+ .py_int
279
+ / 1000
280
+ )
281
+
282
+ @property
283
+ def level_name(self) -> str:
284
+ return (
285
+ self._level_dat.compound.get_compound("Data", CompoundTag())
286
+ .get_string("LevelName", StringTag("Undefined"))
287
+ .py_str
288
+ )
289
+
290
+ @level_name.setter
291
+ def level_name(self, value: str) -> None:
292
+ level_dat = self.level_dat
293
+ level_dat.compound.setdefault_compound("Data")["LevelName"] = StringTag(value)
294
+ self.level_dat = level_dat
295
+
296
+ def _get_dimension_bounds(self, dimension_type_str: DimensionId) -> SelectionGroup:
297
+ if self._data_version >= VersionNumber(2709): # This number might be smaller
298
+ # If in a version that supports custom height data packs
299
+ dimension_settings = (
300
+ self.level_dat.compound.get_compound("Data", CompoundTag())
301
+ .get_compound("WorldGenSettings", CompoundTag())
302
+ .get_compound("dimensions", CompoundTag())
303
+ .get_compound(dimension_type_str, CompoundTag())
304
+ )
305
+
306
+ # "type" can be a reference (string) or inline (compound) dimension-type data.
307
+ dimension_type = dimension_settings.get("type")
308
+
309
+ if isinstance(dimension_type, StringTag):
310
+ # Reference type. Load the dimension data
311
+ dimension_type_str = dimension_type.py_str
312
+ if ":" in dimension_type_str:
313
+ namespace, base_name = dimension_type_str.split(":", 1)
314
+ else:
315
+ namespace = "minecraft"
316
+ base_name = dimension_type_str
317
+ name_tuple = namespace, base_name
318
+
319
+ # First try and load the reference from the data pack and then from defaults
320
+ dimension_path = f"data/{namespace}/dimension_type/{base_name}.json"
321
+ if self._o.data_pack.has_file(dimension_path):
322
+ with self._o.data_pack.open(dimension_path) as d:
323
+ try:
324
+ dimension_settings_json = json.load(d)
325
+ except json.JSONDecodeError:
326
+ pass
327
+ else:
328
+ if "min_y" in dimension_settings_json and isinstance(
329
+ dimension_settings_json["min_y"], int
330
+ ):
331
+ min_y = dimension_settings_json["min_y"]
332
+ if min_y % 16:
333
+ min_y = 16 * (min_y // 16)
334
+ else:
335
+ min_y = 0
336
+ if "height" in dimension_settings_json and isinstance(
337
+ dimension_settings_json["height"], int
338
+ ):
339
+ height = dimension_settings_json["height"]
340
+ if height % 16:
341
+ height = -16 * (-height // 16)
342
+ else:
343
+ height = 256
344
+
345
+ return SelectionGroup(
346
+ SelectionBox(
347
+ (-30_000_000, min_y, -30_000_000),
348
+ (30_000_000, min_y + height, 30_000_000),
349
+ )
350
+ )
351
+
352
+ elif name_tuple in {
353
+ ("minecraft", "overworld"),
354
+ ("minecraft", "overworld_caves"),
355
+ }:
356
+ if self.data_version >= VersionNumber(2825):
357
+ # If newer than the height change version
358
+ return SelectionGroup(
359
+ SelectionBox(
360
+ (-30_000_000, -64, -30_000_000),
361
+ (30_000_000, 320, 30_000_000),
362
+ )
363
+ )
364
+ else:
365
+ return DefaultSelection
366
+ elif name_tuple in {
367
+ ("minecraft", "the_nether"),
368
+ ("minecraft", "the_end"),
369
+ }:
370
+ return DefaultSelection
371
+ else:
372
+ log.error(f"Could not find dimension_type {':'.join(name_tuple)}")
373
+
374
+ elif isinstance(dimension_type, CompoundTag):
375
+ # Inline type
376
+ dimension_type_compound = dimension_type
377
+ min_y = (
378
+ dimension_type_compound.get_int("min_y", IntTag()).py_int // 16
379
+ ) * 16
380
+ height = (
381
+ -dimension_type_compound.get_int("height", IntTag(256)).py_int // 16
382
+ ) * -16
383
+ return SelectionGroup(
384
+ SelectionBox(
385
+ (-30_000_000, min_y, -30_000_000),
386
+ (30_000_000, min_y + height, 30_000_000),
387
+ )
388
+ )
389
+ else:
390
+ log.error(
391
+ f'level_dat["Data"]["WorldGenSettings"]["dimensions"]["{dimension_type_str}"]["type"] was not a StringTag or CompoundTag.'
392
+ )
393
+
394
+ # Return the default if nothing else returned
395
+ return DefaultSelection
396
+
397
+ def _register_dimension(
398
+ self,
399
+ relative_dimension_path: InternalDimensionId,
400
+ dimension_name: DimensionId,
401
+ ) -> None:
402
+ """
403
+ Register a new dimension.
404
+
405
+ :param relative_dimension_path: The relative path to the dimension directory from the world root.
406
+ "" for the world root.
407
+ :param dimension_name: The name of the dimension shown to the user
408
+ """
409
+ if relative_dimension_path:
410
+ path = os.path.join(self.path, relative_dimension_path)
411
+ else:
412
+ path = self.path
413
+
414
+ if (
415
+ relative_dimension_path not in self._o.dimensions
416
+ and dimension_name not in self._o.dimension_ids
417
+ ):
418
+ self._o.dimension_ids[dimension_name] = relative_dimension_path
419
+ self._o.dimensions[relative_dimension_path] = JavaRawDimension(
420
+ self._o.back_reference,
421
+ # TODO: The data version can change while the level is open.
422
+ # That may cause problems with this.
423
+ AnvilDimension(
424
+ path,
425
+ mcc=self.data_version > VersionNumber(2203),
426
+ layers=("region",)
427
+ + ("entities",) * (self.data_version >= VersionNumber(2681)),
428
+ ),
429
+ relative_dimension_path,
430
+ dimension_name,
431
+ self._get_dimension_bounds(dimension_name),
432
+ # TODO: Is this data stored somewhere?
433
+ BlockStack(Block("java", VersionNumber(3700), "minecraft", "air")),
434
+ (
435
+ Biome("java", VersionNumber(3700), "minecraft", "nether_wastes")
436
+ if dimension_name == THE_NETHER
437
+ else (
438
+ Biome("java", VersionNumber(3700), "minecraft", "the_end")
439
+ if dimension_name == THE_END
440
+ else Biome("java", VersionNumber(3700), "minecraft", "plains")
441
+ )
442
+ ),
443
+ )
444
+
445
+ def _find_dimensions(self) -> None:
446
+ with self._o.dimensions_lock:
447
+ if self._o.dimensions:
448
+ return
449
+
450
+ # load all the levels
451
+ self._register_dimension("", OVERWORLD)
452
+ self._register_dimension("DIM-1", THE_NETHER)
453
+ self._register_dimension("DIM1", THE_END)
454
+
455
+ for level_path in glob.glob(os.path.join(glob.escape(self.path), "DIM*")):
456
+ if os.path.isdir(level_path):
457
+ dir_name = os.path.basename(level_path)
458
+ if AnvilDimension.level_regex.fullmatch(dir_name) is None:
459
+ continue
460
+ self._register_dimension(dir_name, dir_name)
461
+
462
+ for region_path in glob.glob(
463
+ os.path.join(
464
+ glob.escape(self.path), "dimensions", "*", "*", "**", "region"
465
+ ),
466
+ recursive=True,
467
+ ):
468
+ if not os.path.isdir(region_path):
469
+ continue
470
+ dimension_path = os.path.dirname(region_path)
471
+ rel_dim_path = os.path.relpath(dimension_path, self.path)
472
+ _, dimension, *base_name = rel_dim_path.split(os.sep)
473
+
474
+ dimension_name = f"{dimension}:{'/'.join(base_name)}"
475
+ self._register_dimension(rel_dim_path, dimension_name)
476
+
477
+ def dimension_ids(self) -> frozenset[DimensionId]:
478
+ self._find_dimensions()
479
+ return frozenset(self._o.dimension_ids)
480
+
481
+ def get_dimension(self, dimension_id: DimensionId) -> JavaRawDimension:
482
+ self._find_dimensions()
483
+ internal_dimension_id = self._o.dimension_ids.get(dimension_id, dimension_id)
484
+ if internal_dimension_id not in self._o.dimensions:
485
+ raise RuntimeError(f"Dimension {dimension_id} does not exist")
486
+ return self._o.dimensions[internal_dimension_id]
487
+
488
+ def compact(self) -> None:
489
+ """Compact all region files"""
490
+ for dimension_id in self.dimension_ids():
491
+ self.get_dimension(dimension_id).compact()
492
+
493
+ @property
494
+ def block_id_override(self) -> IdRegistry:
495
+ """
496
+ A two-way map from hard coded numerical block id <-> block string.
497
+ This only stores overridden values. If the value is not present here you should check the translator.
498
+ """
499
+ return self._o.block_id_override
500
+
501
+ @property
502
+ def biome_id_override(self) -> IdRegistry:
503
+ """
504
+ A two-way map from hard coded numerical biome id <-> biome string.
505
+ This only stores overridden values. If the value is not present here you should check the translator.
506
+ """
507
+ return self._o.biome_id_override