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
amulet/errors.py ADDED
@@ -0,0 +1,63 @@
1
+ class DimensionDoesNotExist(Exception):
2
+ """An error thrown if trying to load data from a dimension that does not exist."""
3
+
4
+
5
+ class ChunkLoadError(Exception):
6
+ """
7
+ An error thrown if a chunk failed to load for some reason.
8
+
9
+ This may be due to a corrupt chunk, an unsupported chunk format or just because the chunk does not exist to be loaded.
10
+
11
+ Catching this error will also catch :class:`ChunkDoesNotExist`
12
+
13
+ >>> try:
14
+ >>> # get chunk
15
+ >>> chunk = world.get_chunk(cx, cz, dimension)
16
+ >>> except ChunkLoadError:
17
+ >>> # will catch all chunks that have failed to load
18
+ >>> # either because they do not exist or errored during loading.
19
+ """
20
+
21
+
22
+ class ChunkDoesNotExist(ChunkLoadError):
23
+ """
24
+ An error thrown if a chunk does not exist and therefor cannot be loaded.
25
+
26
+ >>> try:
27
+ >>> # get chunk
28
+ >>> chunk = world.get_chunk(cx, cz, dimension)
29
+ >>> except ChunkDoesNotExist:
30
+ >>> # will catch all chunks that do not exist
31
+ >>> # will not catch corrupt chunks
32
+ >>> except ChunkLoadError:
33
+ >>> # will only catch chunks that errored during loading
34
+ >>> # chunks that do not exist were caught by the previous except section.
35
+ """
36
+
37
+
38
+ class PlayerLoadError(Exception):
39
+ """
40
+ An error thrown if a player failed to load for some reason.
41
+ """
42
+
43
+
44
+ class PlayerDoesNotExist(PlayerLoadError):
45
+ """
46
+ An error thrown if a player does not exist.
47
+ """
48
+
49
+
50
+ class LevelReadError(Exception):
51
+ """
52
+ An error thrown when the raw level data cannot be read from.
53
+
54
+ This is usually because the data has been opened somewhere else.
55
+ """
56
+
57
+
58
+ class LevelWriteError(Exception):
59
+ """
60
+ An error thrown when the raw level data cannot be written to.
61
+
62
+ This is usually because the data has been opened somewhere else.
63
+ """
@@ -0,0 +1,7 @@
1
+ """
2
+ A module to store data about the game including state enumerations and translations between different game versions.
3
+ """
4
+
5
+ from ._game import game_platforms, game_versions, get_game_version
6
+ from .java import JavaGameVersion
7
+ from .bedrock import BedrockGameVersion
amulet/game/_game.py ADDED
@@ -0,0 +1,152 @@
1
+ from __future__ import annotations
2
+
3
+ import traceback
4
+ from functools import cache
5
+ from typing import overload, Literal, TYPE_CHECKING
6
+ from collections.abc import Sequence
7
+ from threading import RLock
8
+ import pickle
9
+ import os
10
+ import glob
11
+ import json
12
+ import gzip
13
+
14
+ from amulet.version import VersionNumber
15
+
16
+
17
+ if TYPE_CHECKING:
18
+ from .abc import GameVersion
19
+ from ._universal import UniversalVersion
20
+ from .java import JavaGameVersion
21
+ from .bedrock import BedrockGameVersion
22
+
23
+
24
+ _versions: dict[str, list[GameVersion]] | None = None
25
+ _lock = RLock()
26
+
27
+
28
+ def _compile_raw_versions() -> None:
29
+ global _versions
30
+ with _lock:
31
+ json_path = os.environ.get("AMULET_GAME_VERSION_JSON_PATH")
32
+ if json_path is None:
33
+ raise RuntimeError("Could not find game version data.")
34
+ from .java import JavaGameVersion
35
+ from .bedrock import BedrockGameVersion
36
+ from ._universal import UniversalVersion
37
+
38
+ _versions = {}
39
+ _versions.setdefault("universal", []).append(
40
+ UniversalVersion.from_json(os.path.join(json_path, "versions", "universal"))
41
+ )
42
+ for init_path in glob.glob(
43
+ os.path.join(glob.escape(json_path), "versions", "*", "__init__.json")
44
+ ):
45
+ version_path = os.path.dirname(init_path)
46
+
47
+ with open(os.path.join(version_path, "__init__.json")) as f:
48
+ init = json.load(f)
49
+
50
+ platform = init["platform"]
51
+ if platform == "bedrock":
52
+ _versions.setdefault("bedrock", []).append(
53
+ BedrockGameVersion.from_json(version_path)
54
+ )
55
+ elif platform == "java":
56
+ _versions.setdefault("java", []).append(
57
+ JavaGameVersion.from_json(version_path)
58
+ )
59
+ elif platform == "universal":
60
+ pass
61
+ else:
62
+ raise RuntimeError
63
+ with open(
64
+ os.path.join(os.path.dirname(__file__), "versions.pkl.gz"), "wb"
65
+ ) as pkl:
66
+ pkl.write(gzip.compress(pickle.dumps(_versions)))
67
+
68
+
69
+ def _get_versions() -> dict[str, list[GameVersion]]:
70
+ global _versions
71
+ with _lock:
72
+ if _versions is None:
73
+ pkl_path = os.path.join(os.path.dirname(__file__), "versions.pkl.gz")
74
+ if os.path.isfile(pkl_path):
75
+ try:
76
+ with open(pkl_path, "rb") as pkl:
77
+ _versions = pickle.loads(gzip.decompress(pkl.read()))
78
+ except:
79
+ traceback.print_exc()
80
+
81
+ if _versions is None:
82
+ _compile_raw_versions()
83
+
84
+ assert _versions is not None
85
+ return _versions
86
+
87
+
88
+ def game_platforms() -> list[str]:
89
+ """
90
+ Get a list of all the platforms there are Version classes for.
91
+ These are currently 'java' and 'bedrock'
92
+ """
93
+ return list(_get_versions().keys())
94
+
95
+
96
+ @overload
97
+ def game_versions(platform: Literal["java"]) -> Sequence[JavaGameVersion]: ...
98
+
99
+
100
+ @overload
101
+ def game_versions(platform: Literal["bedrock"]) -> Sequence[BedrockGameVersion]: ...
102
+
103
+
104
+ @overload
105
+ def game_versions(platform: str) -> Sequence[GameVersion]: ...
106
+
107
+
108
+ def game_versions(platform: str) -> Sequence[GameVersion]:
109
+ """
110
+ Get all known version classes for the platform.
111
+
112
+ :param platform: The platform name (use :attr:`platforms` to get the valid platforms)
113
+ :return: The version classes for the platform
114
+ :raises KeyError: If the platform is not present.
115
+ """
116
+ if platform not in _get_versions():
117
+ raise KeyError(f'The requested platform "{platform}" is not present')
118
+ return tuple(_get_versions()[platform])
119
+
120
+
121
+ @overload
122
+ def get_game_version(
123
+ platform: Literal["java"], version_number: VersionNumber
124
+ ) -> JavaGameVersion: ...
125
+
126
+
127
+ @overload
128
+ def get_game_version(
129
+ platform: Literal["bedrock"], version_number: VersionNumber
130
+ ) -> BedrockGameVersion: ...
131
+
132
+
133
+ @overload
134
+ def get_game_version(platform: str, version_number: VersionNumber) -> GameVersion: ...
135
+
136
+
137
+ @cache # type: ignore
138
+ def get_game_version(platform: str, version_number: VersionNumber) -> GameVersion:
139
+ """
140
+ Get a Version class for the requested platform and version number
141
+
142
+ :param platform: The platform name (use ``TranslationManager.platforms`` to get the valid platforms)
143
+ :param version_number: The version number or DataVersion (use ``TranslationManager.version_numbers`` to get version numbers for a given platforms)
144
+ :return: The Version class for the given inputs.
145
+ :raises KeyError: If it does not exist.
146
+ """
147
+ if platform not in _get_versions():
148
+ raise KeyError(f'The requested platform "{platform}" is not present')
149
+ for version in _get_versions()[platform]:
150
+ if version.supports_version(platform, version_number):
151
+ return version
152
+ raise KeyError(f"Version {platform}, {version_number} is not supported")
@@ -0,0 +1 @@
1
+ from ._version import UniversalVersion
@@ -0,0 +1,17 @@
1
+ from amulet.biome import Biome
2
+ from amulet.game.abc import BiomeData
3
+ from amulet.version import VersionNumber
4
+
5
+
6
+ class UniversalBiomeData(BiomeData):
7
+ def to_universal(self, biome: Biome) -> Biome:
8
+ if not self._game_version.supports_version(biome.platform, biome.version):
9
+ raise ValueError("The block is not compatible with this version")
10
+ return biome
11
+
12
+ def from_universal(
13
+ self, target_platform: str, target_version: VersionNumber, biome: Biome
14
+ ) -> Biome:
15
+ if not self._game_version.supports_version(biome.platform, biome.version):
16
+ raise ValueError("The block is not compatible with this version")
17
+ return biome
@@ -0,0 +1,47 @@
1
+ from typing import Callable
2
+
3
+ from amulet.data_types import BlockCoordinates
4
+ from amulet.block import Block
5
+ from amulet.block_entity import BlockEntity
6
+ from amulet.entity import Entity
7
+
8
+ from amulet.game.abc import BlockData
9
+ from amulet.version import VersionNumber
10
+
11
+
12
+ class UniversalBlockData(BlockData):
13
+ def to_universal(
14
+ self,
15
+ block: Block,
16
+ block_entity: BlockEntity | None,
17
+ extra: (
18
+ tuple[
19
+ BlockCoordinates,
20
+ Callable[[BlockCoordinates], tuple[Block, BlockEntity | None]],
21
+ ]
22
+ | None
23
+ ),
24
+ ) -> tuple[Block, BlockEntity | None, bool]:
25
+ # Converting universal to universal so just return as is
26
+ if not self._game_version.supports_version(block.platform, block.version):
27
+ raise ValueError("The block is not compatible with this version")
28
+ return block, block_entity, False
29
+
30
+ def from_universal(
31
+ self,
32
+ target_platform: str,
33
+ target_version: VersionNumber,
34
+ block: Block,
35
+ block_entity: BlockEntity | None,
36
+ extra: (
37
+ tuple[
38
+ BlockCoordinates,
39
+ Callable[[BlockCoordinates], tuple[Block, BlockEntity | None]],
40
+ ]
41
+ | None
42
+ ),
43
+ ) -> tuple[Block, BlockEntity | None, bool] | tuple[Entity, None, bool]:
44
+ # Converting universal to universal so just return as is
45
+ if not self._game_version.supports_version(block.platform, block.version):
46
+ raise ValueError("The block is not compatible with this version")
47
+ return block, block_entity, False
@@ -0,0 +1,68 @@
1
+ from __future__ import annotations
2
+ from typing import Self
3
+ import os
4
+ import json
5
+
6
+ from amulet.game.abc import GameVersion, load_json_block_spec, load_json_biome_data
7
+ from amulet.version import VersionNumber
8
+
9
+ from ._block import UniversalBlockData
10
+ from ._biome import UniversalBiomeData
11
+
12
+
13
+ class UniversalVersion(GameVersion):
14
+ _block: UniversalBlockData
15
+ _biome: UniversalBiomeData
16
+
17
+ @classmethod
18
+ def from_json(cls, version_path: str) -> Self:
19
+ with open(os.path.join(version_path, "__init__.json")) as f:
20
+ init = json.load(f)
21
+ assert init["platform"] == "universal"
22
+
23
+ block_format = init["block_format"]
24
+
25
+ self = cls()
26
+
27
+ # Load the block specification and translations
28
+ block_spec = load_json_block_spec(version_path, block_format)
29
+
30
+ self._block = UniversalBlockData(
31
+ self,
32
+ block_spec,
33
+ )
34
+
35
+ biomes, _, _ = load_json_biome_data(version_path)
36
+ biomes_namespace = dict[str, list[str]]()
37
+ for namespace, base_name in biomes:
38
+ biomes_namespace.setdefault(namespace, []).append(base_name)
39
+
40
+ self._biome = UniversalBiomeData(self, biomes_namespace)
41
+
42
+ return self
43
+
44
+ def __repr__(self) -> str:
45
+ return f"UniversalVersion()"
46
+
47
+ def supports_version(self, platform: str, version: VersionNumber) -> bool:
48
+ return platform == "universal"
49
+
50
+ @property
51
+ def platform(self) -> str:
52
+ return "universal"
53
+
54
+ @property
55
+ def min_version(self) -> VersionNumber:
56
+ return VersionNumber(1)
57
+
58
+ @property
59
+ def max_version(self) -> VersionNumber:
60
+ return VersionNumber(1)
61
+
62
+ @property
63
+ def block(self) -> UniversalBlockData:
64
+ return self._block
65
+
66
+ @property
67
+ def biome(self) -> UniversalBiomeData:
68
+ return self._biome
@@ -0,0 +1,22 @@
1
+ from .json_interface import JSONInterface, JSONCompatible, JSONDict, JSONList
2
+ from .version import GameVersion
3
+ from .block import (
4
+ BlockData,
5
+ DatabaseBlockData,
6
+ BlockDataNumericalComponent,
7
+ BlockTranslationError,
8
+ )
9
+ from .biome import (
10
+ BiomeData,
11
+ DatabaseBiomeData,
12
+ BiomeDataNumericalComponent,
13
+ BiomeTranslationError,
14
+ load_json_biome_data,
15
+ )
16
+ from ._block_specification import (
17
+ BlockSpec,
18
+ NBTSpec,
19
+ PropertySpec,
20
+ PropertyValueSpec,
21
+ load_json_block_spec,
22
+ )
@@ -0,0 +1,150 @@
1
+ from typing import Self
2
+ from types import MappingProxyType
3
+ from collections.abc import Mapping, Iterator, Hashable
4
+ from dataclasses import dataclass, field
5
+ import os
6
+ import json
7
+ import glob
8
+ from concurrent.futures import ThreadPoolExecutor
9
+
10
+ from amulet_nbt import read_snbt
11
+ from amulet.block import PropertyValueType
12
+
13
+ from .json_interface import JSONInterface, JSONDict, JSONCompatible
14
+
15
+
16
+ def immutable_from_snbt(snbt: str) -> PropertyValueType:
17
+ val = read_snbt(snbt)
18
+ assert isinstance(val, PropertyValueType)
19
+ return val
20
+
21
+
22
+ @dataclass(frozen=True)
23
+ class PropertyValueSpec:
24
+ default: PropertyValueType
25
+ states: tuple[PropertyValueType, ...]
26
+
27
+
28
+ class PropertySpec(Mapping[str, PropertyValueSpec], Hashable):
29
+ _properties: Mapping[str, PropertyValueSpec]
30
+ _hash: int | None
31
+
32
+ def __init__(
33
+ self, properties: Mapping[str, PropertyValueSpec] = MappingProxyType({})
34
+ ):
35
+ self._properties = dict(properties)
36
+ self._hash = None
37
+
38
+ def __getitem__(self, name: str) -> PropertyValueSpec:
39
+ return self._properties[name]
40
+
41
+ def __len__(self) -> int:
42
+ return len(self._properties)
43
+
44
+ def __iter__(self) -> Iterator[str]:
45
+ yield from self._properties
46
+
47
+ def __hash__(self) -> int:
48
+ if self._hash is None:
49
+ self._hash = hash(frozenset(self._properties.items()))
50
+ return self._hash
51
+
52
+
53
+ @dataclass(frozen=True)
54
+ class NBTSpec:
55
+ namespace: str
56
+ base_name: str
57
+ snbt: str
58
+
59
+
60
+ @dataclass(frozen=True)
61
+ class BlockSpec(JSONInterface):
62
+ properties: PropertySpec = field(default_factory=PropertySpec)
63
+ nbt: NBTSpec | None = None
64
+
65
+ @classmethod
66
+ def from_json(cls, obj: JSONCompatible) -> Self:
67
+ assert isinstance(obj, dict)
68
+ properties = obj.get("properties", {})
69
+ default_properties = obj.get("defaults", {})
70
+ assert isinstance(properties, dict) and isinstance(default_properties, dict)
71
+ assert properties.keys() == default_properties.keys()
72
+
73
+ properties_data = {}
74
+ for name, values in properties.items():
75
+ assert isinstance(values, list)
76
+ default_str = default_properties[name]
77
+ assert isinstance(default_str, str)
78
+ default_nbt = immutable_from_snbt(default_str)
79
+
80
+ states: list[PropertyValueType] = []
81
+ for val_str in values:
82
+ assert isinstance(val_str, str)
83
+ val_nbt = immutable_from_snbt(val_str)
84
+ states.append(val_nbt)
85
+
86
+ properties_data[name] = PropertyValueSpec(default_nbt, tuple(states))
87
+
88
+ if "nbt_identifier" in obj:
89
+ nbt_id_raw = obj["nbt_identifier"]
90
+ assert isinstance(nbt_id_raw, list)
91
+ namespace, base_name = nbt_id_raw
92
+ assert isinstance(namespace, str) and isinstance(base_name, str)
93
+
94
+ snbt = obj["snbt"]
95
+ assert isinstance(snbt, str)
96
+ nbt = NBTSpec(namespace, base_name, snbt)
97
+ else:
98
+ nbt = None
99
+ assert "snbt" not in obj
100
+
101
+ assert not set(obj.keys()).difference(
102
+ ("properties", "defaults", "nbt_identifier", "snbt")
103
+ ), obj.keys()
104
+
105
+ return cls(PropertySpec(properties_data), nbt)
106
+
107
+ def to_json(self) -> JSONDict:
108
+ spec: JSONDict = {}
109
+ if self.properties:
110
+ spec["properties"] = properties = {}
111
+ spec["defaults"] = defaults = {}
112
+ for name, state in self.properties.items():
113
+ properties[name] = [val.to_snbt() for val in state.states]
114
+ defaults[name] = state.default.to_snbt()
115
+ if self.nbt is not None:
116
+ spec["nbt_identifier"] = [self.nbt.namespace, self.nbt.base_name]
117
+ spec["snbt"] = self.nbt.snbt
118
+ return spec
119
+
120
+
121
+ def _read_glob(path: str) -> str:
122
+ with open(path) as f:
123
+ return f.read()
124
+
125
+
126
+ def load_json_block_spec(
127
+ version_path: str, block_format: str
128
+ ) -> dict[str, dict[str, BlockSpec]]:
129
+ """Load all block specification files for the given version."""
130
+ block_spec = dict[str, dict[str, BlockSpec]]()
131
+ paths = glob.glob(
132
+ os.path.join(
133
+ glob.escape(version_path),
134
+ "block",
135
+ block_format,
136
+ "specification",
137
+ "*",
138
+ "*",
139
+ "*.json",
140
+ )
141
+ )
142
+ with ThreadPoolExecutor() as e:
143
+ for file_path, data in zip(paths, e.map(_read_glob, paths)):
144
+ *_, namespace, _, base_name = os.path.splitext(os.path.normpath(file_path))[
145
+ 0
146
+ ].split(os.sep)
147
+ block_spec.setdefault(namespace, {})[base_name] = BlockSpec.from_json(
148
+ json.loads(data)
149
+ )
150
+ return block_spec