amulet-core 2.0a5__cp311-cp311-macosx_10_9_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-311-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,453 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Optional, TypeVar, Type, Generic, Iterator, Callable
5
+ from contextlib import contextmanager, AbstractContextManager as ContextManager
6
+ import os
7
+ import logging
8
+ from weakref import finalize
9
+
10
+ from runtime_final import final
11
+ from PIL import Image
12
+
13
+ from amulet import IMG_DIRECTORY
14
+ from amulet.version import PlatformType, VersionNumber
15
+ from amulet.data_types import DimensionId
16
+
17
+ from amulet.chunk import Chunk
18
+
19
+ from amulet.utils.shareable_lock import ShareableRLock
20
+ from amulet.utils.signal import Signal, SignalInstanceCacheName
21
+ from amulet.utils.task_manager import AbstractCancelManager, VoidCancelManager
22
+
23
+ from amulet.level.abc._history import HistoryManager
24
+ from amulet.utils.weakref import CallableWeakMethod
25
+
26
+
27
+ if TYPE_CHECKING:
28
+ from amulet.level.abc import Dimension # noqa
29
+ from amulet.level.abc import PlayerStorage
30
+ from amulet.level.abc import RawLevel # noqa
31
+
32
+ log = logging.getLogger(__name__)
33
+
34
+ missing_world_icon_path = os.path.abspath(
35
+ os.path.join(IMG_DIRECTORY, "missing_world_icon.png")
36
+ )
37
+ missing_world_icon: Optional[Image.Image] = None
38
+
39
+
40
+ RawLevelT = TypeVar("RawLevelT", bound="RawLevel")
41
+ DimensionT = TypeVar("DimensionT", bound="Dimension")
42
+
43
+
44
+ class LevelOpenData:
45
+ """Private level attributes that only exist when the level is open."""
46
+
47
+ history_manager: HistoryManager
48
+
49
+ def __init__(self) -> None:
50
+ self.history_manager = HistoryManager()
51
+
52
+
53
+ OpenLevelDataT = TypeVar("OpenLevelDataT", bound=LevelOpenData)
54
+
55
+
56
+ class Level(ABC, Generic[OpenLevelDataT, DimensionT, RawLevelT]):
57
+ """Base class for all levels."""
58
+
59
+ __finalise: finalize
60
+ _open_data: OpenLevelDataT | None
61
+ _level_lock: ShareableRLock
62
+ _history_enabled: bool
63
+
64
+ __slots__ = (
65
+ "__weakref__",
66
+ "__finalise",
67
+ SignalInstanceCacheName,
68
+ "_open_data",
69
+ "_level_lock",
70
+ "_history_enabled",
71
+ )
72
+
73
+ def __init__(self) -> None:
74
+ """
75
+ This cannot be called directly.
76
+ You must use one of the constructor classmethods
77
+ """
78
+ # Private attributes
79
+ self._open_data = None
80
+ self._level_lock = ShareableRLock()
81
+ self._history_enabled = True
82
+ self.__finalise = finalize(self, CallableWeakMethod(self.close))
83
+
84
+ def __del__(self) -> None:
85
+ self.__finalise()
86
+
87
+ opened = Signal[()]()
88
+
89
+ @final
90
+ def open(self, task_manager: AbstractCancelManager = VoidCancelManager()) -> None:
91
+ """Open the level.
92
+
93
+ If the level is already open, this does nothing.
94
+
95
+ :param task_manager: The cancel manager through which cancel can be requested.
96
+ :raises amulet.utils.task_manager.TaskCancelled: If the task is cancelled.
97
+ """
98
+ with self.lock_unique(task_manager=task_manager):
99
+ if self.is_open(task_manager=task_manager):
100
+ # Do nothing if already open
101
+ return
102
+ self._open()
103
+ if self._open_data is None:
104
+ raise RuntimeError("_open_data has not been set")
105
+ self._open_data.history_manager.history_changed.connect(
106
+ self.history_changed
107
+ )
108
+ self.opened.emit()
109
+
110
+ @abstractmethod
111
+ def _open(self) -> None:
112
+ raise NotImplementedError
113
+
114
+ @final
115
+ def is_open(
116
+ self, task_manager: AbstractCancelManager = VoidCancelManager()
117
+ ) -> bool:
118
+ """Has the level been opened.
119
+
120
+ :param task_manager: The cancel manager through which cancel can be requested.
121
+ :return: True if the level is open otherwise False.
122
+ :raises amulet.utils.task_manager.TaskCancelled: If the task is cancelled.
123
+ """
124
+ with self.lock_shared(task_manager=task_manager):
125
+ return self._open_data is not None
126
+
127
+ @final
128
+ @property
129
+ def _o(self) -> OpenLevelDataT:
130
+ o = self._open_data
131
+ if o is None:
132
+ raise RuntimeError("Level is not open")
133
+ return o
134
+
135
+ # Has the internal state changed
136
+ changed = Signal[()]()
137
+ # Has the external state been changed without our knowledge
138
+ external_changed = Signal[()]()
139
+
140
+ @abstractmethod
141
+ def save(self) -> None:
142
+ raise NotImplementedError
143
+
144
+ # Signal to notify all listeners that the data they hold is no longer valid
145
+ purged = Signal[()]()
146
+
147
+ def purge(self) -> None:
148
+ """
149
+ Unload all loaded data.
150
+ This is a nuclear function and must be used with :meth:`lock_unique`
151
+
152
+ This is functionally the same as closing and reopening the level.
153
+ """
154
+ self._o.history_manager.reset()
155
+ self.purged.emit()
156
+ self.history_changed.emit()
157
+
158
+ # Signal to notify all listeners that the level has been closed.
159
+ closing = Signal[()]()
160
+ closed = Signal[()]()
161
+
162
+ @final
163
+ def close(self, task_manager: AbstractCancelManager = VoidCancelManager()) -> None:
164
+ """Close the level.
165
+
166
+ If the level is not open, this does nothing.
167
+
168
+ :param task_manager: The cancel manager through which cancel can be requested.
169
+ :raises amulet.utils.task_manager.TaskCancelled: If the task is cancelled.
170
+ """
171
+ with self.lock_unique(task_manager=task_manager):
172
+ if not self.is_open(task_manager=task_manager):
173
+ # Do nothing if already closed
174
+ return
175
+ self.closing.emit()
176
+ self._close()
177
+ if self._open_data is not None:
178
+ raise RuntimeError("_open_data is still set")
179
+ self.closed.emit()
180
+
181
+ @abstractmethod
182
+ def _close(self) -> None:
183
+ raise NotImplementedError
184
+
185
+ @property
186
+ @abstractmethod
187
+ def platform(self) -> PlatformType:
188
+ raise NotImplementedError
189
+
190
+ @property
191
+ @abstractmethod
192
+ def max_game_version(self) -> VersionNumber:
193
+ raise NotImplementedError
194
+
195
+ # Emitted when the undo or redo count has changed
196
+ history_changed = Signal[()]()
197
+
198
+ @property
199
+ def undo_count(self) -> int:
200
+ return self._o.history_manager.undo_count
201
+
202
+ def undo(self) -> None:
203
+ with self.lock_unique(blocking=False):
204
+ self._o.history_manager.undo()
205
+
206
+ @property
207
+ def redo_count(self) -> int:
208
+ return self._o.history_manager.redo_count
209
+
210
+ def redo(self) -> None:
211
+ with self.lock_unique(blocking=False):
212
+ self._o.history_manager.redo()
213
+
214
+ @contextmanager
215
+ def _lock(
216
+ self,
217
+ *,
218
+ edit: bool,
219
+ parallel: bool,
220
+ blocking: bool,
221
+ timeout: float,
222
+ task_manager: AbstractCancelManager = VoidCancelManager(),
223
+ ) -> Iterator[None]:
224
+ if parallel:
225
+ lock = self._level_lock.shared(blocking, timeout, task_manager)
226
+ else:
227
+ lock = self._level_lock.unique(blocking, timeout, task_manager)
228
+ with lock:
229
+ if edit and self.history_enabled:
230
+ self._o.history_manager.create_undo_bin()
231
+ yield
232
+
233
+ def lock_unique(
234
+ self,
235
+ *,
236
+ blocking: bool = True,
237
+ timeout: float = -1,
238
+ task_manager: AbstractCancelManager = VoidCancelManager(),
239
+ ) -> ContextManager[None]:
240
+ """
241
+ Get exclusive access to the level without editing it.
242
+ If you want to edit the level with exclusive access, use :meth:`edit_serial` instead.
243
+ If the level is being used by other threads, this will block until they are done.
244
+ Once acquired it will block other threads from accessing the level until this is released.
245
+
246
+ >>> level: Level
247
+ >>> with level.lock_unique():
248
+ >>> # Lock is acquired before entering this block
249
+ >>> ...
250
+ >>> # Lock is released before exiting this block
251
+
252
+ :param blocking: Should this block until the lock can be acquired. Default is True.
253
+ :param timeout: Maximum amount of time to block for. Has no effect is blocking is False. Default is forever.
254
+ :param task_manager: The cancel manager through which cancel can be requested.
255
+ :return: A context manager to acquire the lock.
256
+ :raises amulet.utils.shareable_lock.LockNotAcquired: If timeout was reached or cancel was called.
257
+ """
258
+ return self._lock(
259
+ edit=False,
260
+ parallel=False,
261
+ blocking=blocking,
262
+ timeout=timeout,
263
+ task_manager=task_manager,
264
+ )
265
+
266
+ def lock_shared(
267
+ self,
268
+ *,
269
+ blocking: bool = True,
270
+ timeout: float = -1,
271
+ task_manager: AbstractCancelManager = VoidCancelManager(),
272
+ ) -> ContextManager[None]:
273
+ """
274
+ Share the level without editing it.
275
+ If you want to edit the level in parallel with other threads, use :meth:`edit_parallel` instead.
276
+
277
+ >>> level: Level
278
+ >>> with level.lock_shared():
279
+ >>> # Lock is acquired before entering this block
280
+ >>> ...
281
+ >>> # Lock is released before exiting this block
282
+
283
+ :param blocking: Should this block until the lock can be acquired. Default is True.
284
+ :param timeout: Maximum amount of time to block for. Has no effect is blocking is False. Default is forever.
285
+ :param task_manager: The cancel manager through which cancel can be requested.
286
+ :return: A context manager to acquire the lock.
287
+ :raises amulet.utils.shareable_lock.LockNotAcquired: If timeout was reached or cancel was called.
288
+ """
289
+ return self._lock(
290
+ edit=False,
291
+ parallel=True,
292
+ blocking=blocking,
293
+ timeout=timeout,
294
+ task_manager=task_manager,
295
+ )
296
+
297
+ def edit_serial(
298
+ self,
299
+ *,
300
+ blocking: bool = True,
301
+ timeout: float = -1,
302
+ task_manager: AbstractCancelManager = VoidCancelManager(),
303
+ ) -> ContextManager[None]:
304
+ """
305
+ Get exclusive editing permissions for this level.
306
+ This is useful if you are doing something nuclear to the level and you don't want other code editing it at
307
+ the same time. Usually :meth:`edit_parallel` is sufficient when used with other locks.
308
+
309
+ >>> level: Level
310
+ >>> with level.edit_serial(): # This will block the thread until all other threads have finished with the level
311
+ >>> # Lock is acquired before entering this block
312
+ >>> # any code here will be run without any other threads touching the level
313
+ >>> ...
314
+ >>> # Lock is released before exiting this block
315
+ >>> # Other threads can modify the level here
316
+
317
+ :param blocking: Should this block until the lock can be acquired. Default is True.
318
+ :param timeout: Maximum amount of time to block for. Has no effect is blocking is False. Default is forever.
319
+ :param task_manager: The cancel manager through which cancel can be requested.
320
+ :return: A context manager to acquire the lock.
321
+ :raises amulet.utils.shareable_lock.LockNotAcquired: If timeout was reached or cancel was called.
322
+ """
323
+ return self._lock(
324
+ edit=True,
325
+ parallel=False,
326
+ blocking=blocking,
327
+ timeout=timeout,
328
+ task_manager=task_manager,
329
+ )
330
+
331
+ def edit_parallel(
332
+ self,
333
+ *,
334
+ blocking: bool = True,
335
+ timeout: float = -1,
336
+ task_manager: AbstractCancelManager = VoidCancelManager(),
337
+ ) -> ContextManager[None]:
338
+ """
339
+ Edit the level in parallal with other threads.
340
+ This allows multiple threads that don't make nuclear changes to work in parallel.
341
+ Before modifying the level data, you must use this or :meth:`edit_serial`.
342
+ Using this ensures that no nuclear changes are made by other threads while your code is running.
343
+ If another thread has exclusive access to the level, this will block until it has finished and vice versa.
344
+ If you are going to make any nuclear changes to the level you must use :meth:`edit_serial` instead.
345
+
346
+ >>> level: Level
347
+ >>> with level.edit_serial(): # This will block the thread until all other unique calls have finished with the level
348
+ >>> # Lock is acquired before entering this block
349
+ >>> # any code here will be run in parallel with other parallel calls.
350
+ >>> ...
351
+ >>> # Lock is released before exiting this block
352
+ >>> # Other threads can modify the level here
353
+
354
+ :param blocking: Should this block until the lock can be acquired. Default is True.
355
+ :param timeout: Maximum amount of time to block for. Has no effect is blocking is False. Default is forever.
356
+ :param task_manager: The cancel manager through which cancel can be requested.
357
+ :return: A context manager to acquire the lock.
358
+ :raises amulet.utils.shareable_lock.LockNotAcquired: If timeout was reached or cancel was called.
359
+ """
360
+ return self._lock(
361
+ edit=True,
362
+ parallel=True,
363
+ blocking=blocking,
364
+ timeout=timeout,
365
+ task_manager=task_manager,
366
+ )
367
+
368
+ history_enabled_changed = Signal[()]()
369
+
370
+ @property
371
+ def history_enabled(self) -> bool:
372
+ """Should edit_serial and edit_parallel create undo points and should setting data load the original state."""
373
+ return self._history_enabled
374
+
375
+ @history_enabled.setter
376
+ def history_enabled(self, history_enabled: bool) -> None:
377
+ self._history_enabled = history_enabled
378
+ self.history_enabled_changed.emit()
379
+
380
+ @property
381
+ def thumbnail(self) -> Image.Image:
382
+ global missing_world_icon
383
+ if missing_world_icon is None:
384
+ missing_world_icon = Image.open(missing_world_icon_path)
385
+ return missing_world_icon
386
+
387
+ @property
388
+ @abstractmethod
389
+ def level_name(self) -> str:
390
+ """The human-readable name of the level"""
391
+ raise NotImplementedError
392
+
393
+ @property
394
+ @abstractmethod
395
+ def modified_time(self) -> float:
396
+ """The unix float timestamp of when the level was last modified."""
397
+ raise NotImplementedError
398
+
399
+ @property
400
+ def sub_chunk_size(self) -> int:
401
+ """
402
+ The dimensions of a sub-chunk.
403
+ """
404
+ return 16
405
+
406
+ @abstractmethod
407
+ def dimension_ids(self) -> frozenset[DimensionId]:
408
+ raise NotImplementedError
409
+
410
+ @abstractmethod
411
+ def get_dimension(self, dimension_id: DimensionId) -> DimensionT:
412
+ raise NotImplementedError
413
+
414
+ @property
415
+ @abstractmethod
416
+ def raw(self) -> RawLevelT:
417
+ """
418
+ Direct access to the level data.
419
+ Only use this if you know what you are doing.
420
+ """
421
+ raise NotImplementedError
422
+
423
+ @property
424
+ @abstractmethod
425
+ def player(self) -> PlayerStorage:
426
+ """Methods to interact with the player data for the level."""
427
+ raise NotImplementedError
428
+
429
+ @property
430
+ @abstractmethod
431
+ def native_chunk_class(self) -> Type[Chunk]:
432
+ raise NotImplementedError
433
+
434
+
435
+ LevelT = TypeVar("LevelT", bound=Level)
436
+
437
+
438
+ class LevelFriend(Generic[LevelT]):
439
+ """A base class for friends of the level that need to store a pointer to the level"""
440
+
441
+ _l_ref: Callable[[], LevelT | None]
442
+
443
+ __slots__ = ("_level_ref",)
444
+
445
+ def __init__(self, level_ref: Callable[[], LevelT | None]):
446
+ self._l_ref = level_ref
447
+
448
+ @property
449
+ def _l(self) -> LevelT:
450
+ level = self._l_ref()
451
+ if level is None:
452
+ raise RuntimeError("The level is no longer accessible")
453
+ return level
@@ -0,0 +1,42 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+ from abc import ABC, abstractmethod
5
+
6
+ from ._level import Level
7
+
8
+
9
+ class LoadableLevel(Level, ABC):
10
+ """Level extension class for levels that can be loaded from existing data."""
11
+
12
+ __slots__ = ()
13
+
14
+ @staticmethod
15
+ @abstractmethod
16
+ def can_load(token: Any) -> bool:
17
+ """
18
+ Returns whether this level class is able to load the given data.
19
+
20
+ :param token: The token to check. Usually a file or directory path.
21
+ :return: True if the level can be loaded by this format wrapper, False otherwise.
22
+ """
23
+ raise NotImplementedError
24
+
25
+ @classmethod
26
+ @abstractmethod
27
+ def load(cls, token: Any) -> LoadableLevel:
28
+ """
29
+ Create a new instance from existing data.
30
+ You must call :meth:`~amulet.level.abc.Level.open` to open the level for editing.
31
+ :param token: The token to use to load the data.
32
+ :return: A new Level instance
33
+ """
34
+ raise NotImplementedError
35
+
36
+ @abstractmethod
37
+ def reload(self) -> None:
38
+ """
39
+ Reload the metadata in the existing instance.
40
+ This can only be done when the level is not open.
41
+ """
42
+ raise NotImplementedError
@@ -0,0 +1,7 @@
1
+ from __future__ import annotations
2
+
3
+ from ._level import LevelFriend
4
+
5
+
6
+ class PlayerStorage(LevelFriend):
7
+ __slots__ = ()
@@ -0,0 +1,187 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Iterable, TypeVar, Generic, Callable
4
+ from abc import ABC, abstractmethod
5
+ from weakref import ref
6
+
7
+ from amulet.data_types import ChunkCoordinates, DimensionId
8
+ from amulet.chunk import Chunk
9
+ from amulet.block import BlockStack
10
+ from amulet.biome import Biome
11
+ from amulet.selection import SelectionGroup
12
+
13
+ PlayerIDT = TypeVar("PlayerIDT")
14
+ RawPlayerT = TypeVar("RawPlayerT")
15
+ ChunkT = TypeVar("ChunkT", bound=Chunk)
16
+ RawChunkT = TypeVar("RawChunkT")
17
+
18
+
19
+ class RawDimension(ABC, Generic[RawChunkT, ChunkT]):
20
+ __slots__ = ()
21
+
22
+ @property
23
+ @abstractmethod
24
+ def dimension_id(self) -> DimensionId:
25
+ raise NotImplementedError
26
+
27
+ @abstractmethod
28
+ def bounds(self) -> SelectionGroup:
29
+ """The editable region of the dimension."""
30
+ raise NotImplementedError
31
+
32
+ @abstractmethod
33
+ def default_block(self) -> BlockStack:
34
+ """The default block for this dimension"""
35
+ raise NotImplementedError
36
+
37
+ @abstractmethod
38
+ def default_biome(self) -> Biome:
39
+ """The default biome for this dimension"""
40
+ raise NotImplementedError
41
+
42
+ @abstractmethod
43
+ def all_chunk_coords(self) -> Iterable[ChunkCoordinates]:
44
+ """Get an iterable of all the chunk coordinates that exist in the raw level data."""
45
+ raise NotImplementedError
46
+
47
+ @abstractmethod
48
+ def has_chunk(self, cx: int, cz: int) -> bool:
49
+ """Check if the chunk exists in the raw level data."""
50
+ raise NotImplementedError
51
+
52
+ @abstractmethod
53
+ def delete_chunk(self, cx: int, cz: int) -> None:
54
+ raise NotImplementedError
55
+
56
+ @abstractmethod
57
+ def get_raw_chunk(self, cx: int, cz: int) -> RawChunkT:
58
+ """Get the chunk data in its raw format.
59
+
60
+ This is usually the exact data that exists on disk.
61
+ The raw chunk format varies between each level class.
62
+ Each call to this method will return a unique object.
63
+
64
+ :param cx: The chunk x coordinate
65
+ :param cz: The chunk z coordinate
66
+ :return: The raw chunk.
67
+ """
68
+ raise NotImplementedError
69
+
70
+ @abstractmethod
71
+ def set_raw_chunk(self, cx: int, cz: int, chunk: RawChunkT) -> None:
72
+ """Set the chunk in its raw format."""
73
+ raise NotImplementedError
74
+
75
+ @abstractmethod
76
+ def raw_chunk_to_native_chunk(
77
+ self, raw_chunk: RawChunkT, cx: int, cz: int
78
+ ) -> ChunkT:
79
+ """Unpack data from the raw chunk format (as stored on disk) into editable classes.
80
+
81
+ This takes ownership of the raw_chunk object.
82
+
83
+ :param cx: The chunk x coordinate
84
+ :param cz: The chunk z coordinate
85
+ :param raw_chunk: The raw chunk to unpack.
86
+ :return: The unpacked chunk.
87
+ """
88
+ raise NotImplementedError
89
+
90
+ @abstractmethod
91
+ def native_chunk_to_raw_chunk(self, chunk: ChunkT, cx: int, cz: int) -> RawChunkT:
92
+ """Pack the data from the editable classes into the raw format.
93
+
94
+ This takes ownership of the chunk object.
95
+
96
+ :param cx: The chunk x coordinate
97
+ :param cz: The chunk z coordinate
98
+ :param chunk: The native chunk to pack
99
+ :return: The packed chunk.
100
+ """
101
+ raise NotImplementedError
102
+
103
+
104
+ RawDimensionT = TypeVar("RawDimensionT", bound=RawDimension)
105
+
106
+
107
+ class RawLevel(ABC, Generic[RawDimensionT]):
108
+ """
109
+ A class with raw access to the level.
110
+ All of these methods directly read from or write to the level.
111
+ There is no way to undo changes made with these methods.
112
+ """
113
+
114
+ __slots__ = ("__weakref__",)
115
+
116
+ @abstractmethod
117
+ def dimension_ids(self) -> frozenset[DimensionId]:
118
+ raise NotImplementedError
119
+
120
+ @abstractmethod
121
+ def get_dimension(self, dimension_id: DimensionId) -> RawDimensionT:
122
+ raise NotImplementedError
123
+
124
+
125
+ RawLevelT = TypeVar("RawLevelT", bound=RawLevel)
126
+
127
+
128
+ class RawLevelFriend(Generic[RawLevelT]):
129
+ """A base class for friends of the raw level that need to store a pointer to the raw level"""
130
+
131
+ _r_ref: Callable[[], RawLevelT | None]
132
+
133
+ __slots__ = ("_r_ref",)
134
+
135
+ def __init__(self, raw_level_ref: Callable[[], RawLevelT | None]) -> None:
136
+ self._r_ref = raw_level_ref
137
+
138
+ @property
139
+ def _r(self) -> RawLevelT:
140
+ r = self._r_ref()
141
+ if r is None:
142
+ raise RuntimeError("Cannot access raw level")
143
+ return r
144
+
145
+
146
+ class RawLevelPlayerComponent(ABC, Generic[PlayerIDT, RawPlayerT]):
147
+ """An extension for the RawLevel class for implementations that have player data."""
148
+
149
+ __slots__ = ()
150
+
151
+ @abstractmethod
152
+ def players(self) -> Iterable[PlayerIDT]:
153
+ raise NotImplementedError
154
+
155
+ @abstractmethod
156
+ def has_player(self, player_id: PlayerIDT) -> bool:
157
+ raise NotImplementedError
158
+
159
+ @abstractmethod
160
+ def get_raw_player(self, player_id: PlayerIDT) -> RawPlayerT:
161
+ raise NotImplementedError
162
+
163
+ @abstractmethod
164
+ def set_raw_player(self, player_id: PlayerIDT, player: RawPlayerT) -> None:
165
+ raise NotImplementedError
166
+
167
+
168
+ class RawLevelBufferedComponent(ABC):
169
+ """
170
+ An extension for the RawLevel class for implementations that need a data buffer.
171
+
172
+ This should be used for formats that cannot stream data and must read and write the data in one go.
173
+ """
174
+
175
+ __slots__ = ()
176
+
177
+ @abstractmethod
178
+ def pre_save(self) -> None:
179
+ """A method to run before data is pushed to the raw level.
180
+ This is useful to save the level.dat before pushing chunk data."""
181
+ raise NotImplementedError
182
+
183
+ @abstractmethod
184
+ def save(self) -> None:
185
+ """A method to run after data is pushed to the raw level.
186
+ This is useful for structure files to write the actual data to disk."""
187
+ raise NotImplementedError