amulet-core 1.9.19__py3-none-any.whl → 1.9.20__py3-none-any.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 (198) hide show
  1. amulet/__init__.py +27 -27
  2. amulet/__pyinstaller/__init__.py +2 -2
  3. amulet/__pyinstaller/hook-amulet.py +4 -4
  4. amulet/_version.py +21 -21
  5. amulet/api/__init__.py +2 -2
  6. amulet/api/abstract_base_entity.py +128 -128
  7. amulet/api/block.py +630 -630
  8. amulet/api/block_entity.py +71 -71
  9. amulet/api/cache.py +107 -107
  10. amulet/api/chunk/__init__.py +6 -6
  11. amulet/api/chunk/biomes.py +207 -207
  12. amulet/api/chunk/block_entity_dict.py +175 -175
  13. amulet/api/chunk/blocks.py +46 -46
  14. amulet/api/chunk/chunk.py +389 -389
  15. amulet/api/chunk/entity_list.py +75 -75
  16. amulet/api/chunk/status.py +167 -167
  17. amulet/api/data_types/__init__.py +4 -4
  18. amulet/api/data_types/generic_types.py +4 -4
  19. amulet/api/data_types/operation_types.py +16 -16
  20. amulet/api/data_types/world_types.py +49 -49
  21. amulet/api/data_types/wrapper_types.py +71 -71
  22. amulet/api/entity.py +74 -74
  23. amulet/api/errors.py +119 -119
  24. amulet/api/history/__init__.py +36 -36
  25. amulet/api/history/base/__init__.py +3 -3
  26. amulet/api/history/base/base_history.py +26 -26
  27. amulet/api/history/base/history_manager.py +63 -63
  28. amulet/api/history/base/revision_manager.py +73 -73
  29. amulet/api/history/changeable.py +15 -15
  30. amulet/api/history/data_types.py +7 -7
  31. amulet/api/history/history_manager/__init__.py +3 -3
  32. amulet/api/history/history_manager/container.py +102 -102
  33. amulet/api/history/history_manager/database.py +279 -279
  34. amulet/api/history/history_manager/meta.py +93 -93
  35. amulet/api/history/history_manager/object.py +116 -116
  36. amulet/api/history/revision_manager/__init__.py +2 -2
  37. amulet/api/history/revision_manager/disk.py +33 -33
  38. amulet/api/history/revision_manager/ram.py +12 -12
  39. amulet/api/item.py +75 -75
  40. amulet/api/level/__init__.py +4 -4
  41. amulet/api/level/base_level/__init__.py +1 -1
  42. amulet/api/level/base_level/base_level.py +1035 -1026
  43. amulet/api/level/base_level/chunk_manager.py +227 -227
  44. amulet/api/level/base_level/clone.py +389 -389
  45. amulet/api/level/base_level/player_manager.py +101 -101
  46. amulet/api/level/immutable_structure/__init__.py +1 -1
  47. amulet/api/level/immutable_structure/immutable_structure.py +94 -94
  48. amulet/api/level/immutable_structure/void_format_wrapper.py +117 -117
  49. amulet/api/level/structure.py +22 -22
  50. amulet/api/level/world.py +19 -19
  51. amulet/api/partial_3d_array/__init__.py +2 -2
  52. amulet/api/partial_3d_array/base_partial_3d_array.py +263 -263
  53. amulet/api/partial_3d_array/bounded_partial_3d_array.py +528 -528
  54. amulet/api/partial_3d_array/data_types.py +15 -15
  55. amulet/api/partial_3d_array/unbounded_partial_3d_array.py +229 -229
  56. amulet/api/partial_3d_array/util.py +152 -152
  57. amulet/api/player.py +65 -65
  58. amulet/api/registry/__init__.py +2 -2
  59. amulet/api/registry/base_registry.py +34 -34
  60. amulet/api/registry/biome_manager.py +153 -153
  61. amulet/api/registry/block_manager.py +156 -156
  62. amulet/api/selection/__init__.py +2 -2
  63. amulet/api/selection/abstract_selection.py +315 -315
  64. amulet/api/selection/box.py +805 -805
  65. amulet/api/selection/group.py +488 -488
  66. amulet/api/structure.py +37 -37
  67. amulet/api/wrapper/__init__.py +8 -8
  68. amulet/api/wrapper/chunk/interface.py +441 -441
  69. amulet/api/wrapper/chunk/translator.py +567 -567
  70. amulet/api/wrapper/format_wrapper.py +772 -772
  71. amulet/api/wrapper/structure_format_wrapper.py +116 -116
  72. amulet/api/wrapper/world_format_wrapper.py +63 -63
  73. amulet/level/__init__.py +1 -1
  74. amulet/level/formats/anvil_forge_world.py +40 -40
  75. amulet/level/formats/anvil_world/__init__.py +3 -3
  76. amulet/level/formats/anvil_world/_sector_manager.py +291 -384
  77. amulet/level/formats/anvil_world/data_pack/__init__.py +2 -2
  78. amulet/level/formats/anvil_world/data_pack/data_pack.py +224 -224
  79. amulet/level/formats/anvil_world/data_pack/data_pack_manager.py +77 -77
  80. amulet/level/formats/anvil_world/dimension.py +177 -177
  81. amulet/level/formats/anvil_world/format.py +769 -769
  82. amulet/level/formats/anvil_world/region.py +384 -384
  83. amulet/level/formats/construction/__init__.py +3 -3
  84. amulet/level/formats/construction/format_wrapper.py +515 -515
  85. amulet/level/formats/construction/interface.py +134 -134
  86. amulet/level/formats/construction/section.py +60 -60
  87. amulet/level/formats/construction/util.py +165 -165
  88. amulet/level/formats/leveldb_world/__init__.py +3 -3
  89. amulet/level/formats/leveldb_world/chunk.py +33 -33
  90. amulet/level/formats/leveldb_world/dimension.py +385 -419
  91. amulet/level/formats/leveldb_world/format.py +659 -641
  92. amulet/level/formats/leveldb_world/interface/chunk/__init__.py +36 -36
  93. amulet/level/formats/leveldb_world/interface/chunk/base_leveldb_interface.py +836 -836
  94. amulet/level/formats/leveldb_world/interface/chunk/generate_interface.py +31 -31
  95. amulet/level/formats/leveldb_world/interface/chunk/leveldb_0.py +30 -30
  96. amulet/level/formats/leveldb_world/interface/chunk/leveldb_1.py +12 -12
  97. amulet/level/formats/leveldb_world/interface/chunk/leveldb_10.py +12 -12
  98. amulet/level/formats/leveldb_world/interface/chunk/leveldb_11.py +12 -12
  99. amulet/level/formats/leveldb_world/interface/chunk/leveldb_12.py +12 -12
  100. amulet/level/formats/leveldb_world/interface/chunk/leveldb_13.py +12 -12
  101. amulet/level/formats/leveldb_world/interface/chunk/leveldb_14.py +12 -12
  102. amulet/level/formats/leveldb_world/interface/chunk/leveldb_15.py +12 -12
  103. amulet/level/formats/leveldb_world/interface/chunk/leveldb_16.py +12 -12
  104. amulet/level/formats/leveldb_world/interface/chunk/leveldb_17.py +12 -12
  105. amulet/level/formats/leveldb_world/interface/chunk/leveldb_18.py +12 -12
  106. amulet/level/formats/leveldb_world/interface/chunk/leveldb_19.py +12 -12
  107. amulet/level/formats/leveldb_world/interface/chunk/leveldb_2.py +12 -12
  108. amulet/level/formats/leveldb_world/interface/chunk/leveldb_20.py +12 -12
  109. amulet/level/formats/leveldb_world/interface/chunk/leveldb_21.py +12 -12
  110. amulet/level/formats/leveldb_world/interface/chunk/leveldb_22.py +12 -12
  111. amulet/level/formats/leveldb_world/interface/chunk/leveldb_23.py +10 -10
  112. amulet/level/formats/leveldb_world/interface/chunk/leveldb_24.py +10 -10
  113. amulet/level/formats/leveldb_world/interface/chunk/leveldb_25.py +24 -24
  114. amulet/level/formats/leveldb_world/interface/chunk/leveldb_26.py +10 -10
  115. amulet/level/formats/leveldb_world/interface/chunk/leveldb_27.py +10 -10
  116. amulet/level/formats/leveldb_world/interface/chunk/leveldb_28.py +10 -10
  117. amulet/level/formats/leveldb_world/interface/chunk/leveldb_29.py +33 -33
  118. amulet/level/formats/leveldb_world/interface/chunk/leveldb_3.py +57 -57
  119. amulet/level/formats/leveldb_world/interface/chunk/leveldb_30.py +10 -10
  120. amulet/level/formats/leveldb_world/interface/chunk/leveldb_31.py +10 -10
  121. amulet/level/formats/leveldb_world/interface/chunk/leveldb_32.py +10 -10
  122. amulet/level/formats/leveldb_world/interface/chunk/leveldb_33.py +10 -10
  123. amulet/level/formats/leveldb_world/interface/chunk/leveldb_34.py +10 -10
  124. amulet/level/formats/leveldb_world/interface/chunk/leveldb_35.py +10 -10
  125. amulet/level/formats/leveldb_world/interface/chunk/leveldb_36.py +10 -10
  126. amulet/level/formats/leveldb_world/interface/chunk/leveldb_37.py +10 -10
  127. amulet/level/formats/leveldb_world/interface/chunk/leveldb_38.py +10 -10
  128. amulet/level/formats/leveldb_world/interface/chunk/leveldb_39.py +12 -12
  129. amulet/level/formats/leveldb_world/interface/chunk/leveldb_4.py +12 -12
  130. amulet/level/formats/leveldb_world/interface/chunk/leveldb_40.py +16 -16
  131. amulet/level/formats/leveldb_world/interface/chunk/leveldb_5.py +12 -12
  132. amulet/level/formats/leveldb_world/interface/chunk/leveldb_6.py +12 -12
  133. amulet/level/formats/leveldb_world/interface/chunk/leveldb_7.py +12 -12
  134. amulet/level/formats/leveldb_world/interface/chunk/leveldb_8.py +180 -180
  135. amulet/level/formats/leveldb_world/interface/chunk/leveldb_9.py +18 -18
  136. amulet/level/formats/leveldb_world/interface/chunk/leveldb_chunk_versions.py +79 -79
  137. amulet/level/formats/mcstructure/__init__.py +3 -3
  138. amulet/level/formats/mcstructure/chunk.py +50 -50
  139. amulet/level/formats/mcstructure/format_wrapper.py +408 -408
  140. amulet/level/formats/mcstructure/interface.py +175 -175
  141. amulet/level/formats/schematic/__init__.py +3 -3
  142. amulet/level/formats/schematic/chunk.py +55 -55
  143. amulet/level/formats/schematic/data_types.py +4 -4
  144. amulet/level/formats/schematic/format_wrapper.py +373 -373
  145. amulet/level/formats/schematic/interface.py +142 -142
  146. amulet/level/formats/sponge_schem/__init__.py +4 -4
  147. amulet/level/formats/sponge_schem/chunk.py +62 -62
  148. amulet/level/formats/sponge_schem/format_wrapper.py +463 -463
  149. amulet/level/formats/sponge_schem/interface.py +118 -118
  150. amulet/level/formats/sponge_schem/varint/__init__.py +1 -1
  151. amulet/level/formats/sponge_schem/varint/varint.py +87 -87
  152. amulet/level/interfaces/chunk/anvil/anvil_0.py +72 -72
  153. amulet/level/interfaces/chunk/anvil/anvil_1444.py +336 -336
  154. amulet/level/interfaces/chunk/anvil/anvil_1466.py +94 -94
  155. amulet/level/interfaces/chunk/anvil/anvil_1467.py +37 -37
  156. amulet/level/interfaces/chunk/anvil/anvil_1484.py +20 -20
  157. amulet/level/interfaces/chunk/anvil/anvil_1503.py +20 -20
  158. amulet/level/interfaces/chunk/anvil/anvil_1519.py +34 -34
  159. amulet/level/interfaces/chunk/anvil/anvil_1901.py +20 -20
  160. amulet/level/interfaces/chunk/anvil/anvil_1908.py +20 -20
  161. amulet/level/interfaces/chunk/anvil/anvil_1912.py +21 -21
  162. amulet/level/interfaces/chunk/anvil/anvil_1934.py +20 -20
  163. amulet/level/interfaces/chunk/anvil/anvil_2203.py +69 -69
  164. amulet/level/interfaces/chunk/anvil/anvil_2529.py +19 -19
  165. amulet/level/interfaces/chunk/anvil/anvil_2681.py +76 -76
  166. amulet/level/interfaces/chunk/anvil/anvil_2709.py +19 -19
  167. amulet/level/interfaces/chunk/anvil/anvil_2844.py +267 -267
  168. amulet/level/interfaces/chunk/anvil/anvil_3463.py +19 -19
  169. amulet/level/interfaces/chunk/anvil/anvil_na.py +607 -607
  170. amulet/level/interfaces/chunk/anvil/base_anvil_interface.py +326 -326
  171. amulet/level/load.py +59 -59
  172. amulet/level/loader.py +95 -95
  173. amulet/level/translators/chunk/bedrock/__init__.py +267 -267
  174. amulet/level/translators/chunk/bedrock/bedrock_nbt_blockstate_translator.py +46 -46
  175. amulet/level/translators/chunk/bedrock/bedrock_numerical_translator.py +39 -39
  176. amulet/level/translators/chunk/bedrock/bedrock_psudo_numerical_translator.py +37 -37
  177. amulet/level/translators/chunk/java/java_1_18_translator.py +40 -40
  178. amulet/level/translators/chunk/java/java_blockstate_translator.py +94 -94
  179. amulet/level/translators/chunk/java/java_numerical_translator.py +62 -62
  180. amulet/libs/leveldb/__init__.py +7 -7
  181. amulet/operations/__init__.py +5 -5
  182. amulet/operations/clone.py +18 -18
  183. amulet/operations/delete_chunk.py +32 -32
  184. amulet/operations/fill.py +30 -30
  185. amulet/operations/paste.py +65 -65
  186. amulet/operations/replace.py +58 -58
  187. amulet/utils/__init__.py +14 -14
  188. amulet/utils/format_utils.py +41 -41
  189. amulet/utils/generator.py +15 -15
  190. amulet/utils/matrix.py +243 -243
  191. amulet/utils/numpy_helpers.py +46 -46
  192. amulet/utils/world_utils.py +349 -349
  193. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/METADATA +97 -97
  194. amulet_core-1.9.20.dist-info/RECORD +208 -0
  195. amulet_core-1.9.19.dist-info/RECORD +0 -208
  196. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/WHEEL +0 -0
  197. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/entry_points.txt +0 -0
  198. {amulet_core-1.9.19.dist-info → amulet_core-1.9.20.dist-info}/top_level.txt +0 -0
amulet/api/block.py CHANGED
@@ -1,630 +1,630 @@
1
- from __future__ import annotations
2
-
3
- from sys import getsizeof
4
- import re
5
- from typing import Dict, Iterable, Tuple, Union, Mapping
6
- from amulet_nbt import ByteTag, ShortTag, IntTag, LongTag, StringTag, from_snbt
7
-
8
- from .errors import BlockException
9
-
10
- PropertyValueType = Union[
11
- ByteTag,
12
- ShortTag,
13
- IntTag,
14
- LongTag,
15
- StringTag,
16
- ]
17
- PropertyType = Mapping[str, PropertyValueType]
18
- PropertyTypeMultiple = Dict[str, Tuple[PropertyValueType, ...]]
19
-
20
- PropertyDataTypes = (
21
- ByteTag,
22
- ShortTag,
23
- IntTag,
24
- LongTag,
25
- StringTag,
26
- )
27
-
28
-
29
- class Block:
30
- """
31
- A class to manage the state of a block.
32
-
33
- It is an immutable object that contains a namespaced name, properties and extra blocks.
34
-
35
- Here's a few examples on how create a Block object:
36
-
37
- >>> # Create a block with the namespace `minecraft` and base name `stone`
38
- >>> stone = Block("minecraft", "stone")
39
-
40
- >>> # Create a water block with the level property
41
- >>> water = Block(
42
- >>> "minecraft", # the namespace
43
- >>> "water", # the base name
44
- >>> { # A dictionary of properties.
45
- >>> # Keys must be strings and values must be a numerical or string NBT type.
46
- >>> "level": StringTag("0") # define a property `level` with a string value `1`
47
- >>> }
48
- >>> )
49
-
50
- >>> # The above two examples can also be achieved by creating the block from the Java blockstate.
51
- >>> stone = Block.from_string_blockstate("minecraft:stone")
52
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
53
-
54
- Java 1.13 added the concept of waterlogging blocks whereby some blocks have a `waterlogged` property.
55
- Bedrock achieved the same behaviour by added a layering system. The layering system allows any block to be the second block.
56
- In order to support both formats and future proof the editor Amulet adds the concept of extra blocks.
57
- Extra blocks are a sequence of zero or more blocks stored in the `extra_blocks` property of the Block instance.
58
- Amulet places no restrictions on which blocks can be extra blocks but some validation will be done when saving based on the format being saved to.
59
-
60
- >>> # Create a waterlogged stone block.
61
- >>> waterlogged_stone = Block("minecraft", "stone",
62
- >>> # extra_blocks can be a Block instance or iterable of Block instances.
63
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
64
- >>> )
65
-
66
- >>> # The above can also be achieved by adding together a stone and water block.
67
- >>> waterlogged_stone = stone + water
68
- >>> repr(waterlogged_stone)
69
- 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
70
- """
71
-
72
- __slots__ = (
73
- "_namespaced_name",
74
- "_namespace",
75
- "_base_name",
76
- "_properties",
77
- "_extra_blocks",
78
- "_blockstate",
79
- "_snbt_blockstate",
80
- "_full_blockstate",
81
- ) # Reduces memory footprint
82
-
83
- snbt_blockstate_regex = re.compile(
84
- r"(?:(?P<namespace>[a-z0-9_.-]+):)?(?P<base_name>[a-z0-9/._-]+)(?:\[(?P<property_name>[a-z0-9_]+)=(?P<property_value>[a-z0-9_\"']+)(?P<properties>.*)\])?"
85
- )
86
- blockstate_regex = re.compile(
87
- r"(?:(?P<namespace>[a-z0-9_.-]+):)?(?P<base_name>[a-z0-9/._-]+)(?:\[(?P<property_name>[a-z0-9_]+)=(?P<property_value>[a-z0-9_]+)(?P<properties>.*)\])?"
88
- )
89
-
90
- snbt_properties_regex = re.compile(
91
- r"(?:,(?P<name>[a-z0-9_]+)=(?P<value>[a-z0-9_\"']+))"
92
- )
93
- properties_regex = re.compile(r"(?:,(?P<name>[a-z0-9_]+)=(?P<value>[a-z0-9_]+))")
94
-
95
- _extra_blocks: Tuple[Block, ...]
96
-
97
- def __init__(
98
- self,
99
- namespace: str,
100
- base_name: str,
101
- properties: PropertyType = None,
102
- extra_blocks: Union[Block, Iterable[Block]] = None,
103
- ):
104
- """
105
- Constructs a :class:`Block` instance.
106
-
107
- >>> stone = Block("minecraft", "stone")
108
- >>>
109
- >>> # Create a water block with the level property
110
- >>> water = Block(
111
- >>> "minecraft", # the namespace
112
- >>> "water", # the base name
113
- >>> { # A dictionary of properties.
114
- >>> # Keys must be strings and values must be a numerical or string NBT type.
115
- >>> "level": StringTag("0") # define a property `level` with a string value `1`
116
- >>> }
117
- >>> )
118
-
119
- :param namespace: The string namespace of the block. eg `minecraft`
120
- :param base_name: The string base name of the block. eg `stone`
121
- :param properties: A dictionary of properties. Keys must be strings and values must be a numerical or string NBT type.
122
- :param extra_blocks: A :class:`Block` instance or iterable of :class:`Block` instances
123
- """
124
- assert (isinstance(namespace, str) or namespace is None) and isinstance(
125
- base_name, str
126
- ), f"namespace and base_name must be strings {namespace} {base_name}"
127
- self._namespace = namespace
128
- self._base_name = base_name
129
- self._namespaced_name = f"{namespace}:{base_name}"
130
-
131
- self._blockstate = None
132
- self._snbt_blockstate = None
133
- self._full_blockstate = None
134
-
135
- if properties is None:
136
- properties = {}
137
- assert isinstance(properties, dict) and all(
138
- isinstance(val, PropertyDataTypes) for val in properties.values()
139
- ), properties
140
-
141
- self._properties = properties
142
- self._extra_blocks = ()
143
- if extra_blocks:
144
- eb = []
145
-
146
- def unpack_block(block_: Iterable[Block]):
147
- for b in block_:
148
- if b.extra_blocks:
149
- unpack_block(b)
150
- else:
151
- eb.append(b)
152
-
153
- unpack_block(extra_blocks)
154
- self._extra_blocks = tuple(eb)
155
-
156
- @classmethod
157
- def join(cls, blocks: Iterable[Block]):
158
- """
159
- Join an interable of Block objects back into one block class.
160
- This is the reverse of the __iter__ method.
161
-
162
- >>> stone = Block.from_string_blockstate("minecraft:stone")
163
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
164
- >>> stone_water = Block.join([stone, water])
165
- >>> stone_water2 = Block.join(stone_water)
166
- >>> assert stone_water == stone_water2
167
-
168
- :param blocks: An iterable of one or more Block objects.
169
- :return: A new Block instance
170
- """
171
- blocks = list(blocks)
172
- return Block(
173
- blocks[0].namespace, blocks[0].base_name, blocks[0].properties, blocks[1:]
174
- )
175
-
176
- @classmethod
177
- def from_string_blockstate(cls, blockstate: str):
178
- """
179
- Parse a Java format blockstate where values are all strings and populate a :class:`Block` class with the data.
180
-
181
- >>> stone = Block.from_string_blockstate("minecraft:stone")
182
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
183
-
184
- :param blockstate: The Java blockstate string to parse.
185
- :return: A Block instance containing the state.
186
- """
187
- namespace, block_name, properties = cls.parse_blockstate_string(blockstate)
188
- return cls(namespace, block_name, properties)
189
-
190
- @classmethod
191
- def from_snbt_blockstate(cls, blockstate: str):
192
- """
193
- Parse a blockstate where values are SNBT of any type and populate a :class:`Block` class with the data.
194
- """
195
- namespace, block_name, properties = cls.parse_blockstate_string(
196
- blockstate, True
197
- )
198
- return cls(namespace, block_name, properties)
199
-
200
- @property
201
- def namespaced_name(self) -> str:
202
- """
203
- The namespace:base_name of the blockstate represented by the :class:`Block` object.
204
-
205
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
206
- >>> water.namespaced_name
207
- 'minecraft:water'
208
-
209
- :return: The namespace:base_name of the blockstate
210
- """
211
- return self._namespaced_name
212
-
213
- @property
214
- def namespace(self) -> str:
215
- """
216
- The namespace of the blockstate represented by the :class:`Block` object.
217
-
218
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
219
- >>> water.namespace
220
- 'minecraft'
221
-
222
- :return: The namespace of the blockstate
223
- """
224
- return self._namespace
225
-
226
- @property
227
- def base_name(self) -> str:
228
- """
229
- The base name of the blockstate represented by the :class:`Block` object.
230
-
231
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
232
- >>> water.base_name
233
- 'water'
234
-
235
- :return: The base name of the blockstate
236
- """
237
- return self._base_name
238
-
239
- @property
240
- def properties(self) -> PropertyType:
241
- """
242
- The mapping of properties of the blockstate represented by the :class:`Block` object.
243
-
244
- >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
245
- >>> water.properties
246
- {"level": StringTag("0")}
247
-
248
- :return: A dictionary of the properties of the blockstate
249
- """
250
- return dict(self._properties)
251
-
252
- @property
253
- def blockstate(self) -> str:
254
- """
255
- The Java blockstate string of this :class:`Block` object
256
- Note if there are extra blocks this will only show the base block.
257
- Note this will only contain properties with StringTag values.
258
-
259
- >>> stone = Block("minecraft", "stone")
260
- >>> stone.blockstate
261
- 'minecraft:stone'
262
- >>> water = Block("minecraft", "water", {"level": StringTag("0")})
263
- >>> water.blockstate
264
- `minecraft:water[level=0]`
265
-
266
- :return: The blockstate string
267
- """
268
- if self._blockstate is None:
269
- self._blockstate = self.namespaced_name
270
- if self.properties:
271
- props = [
272
- f"{key}={value.py_str}"
273
- for key, value in sorted(self.properties.items())
274
- if isinstance(value, StringTag)
275
- ]
276
- self._blockstate += f"[{','.join(props)}]"
277
- return self._blockstate
278
-
279
- @property
280
- def snbt_blockstate(self) -> str:
281
- """
282
- A modified version of the Java blockstate format that supports all NBT types.
283
- Converts the property values to the SNBT format to preserve type.
284
- Note if there are extra blocks this will only show the base block.
285
-
286
- >>> bell = Block("minecraft", "bell", {
287
- >>> "attachment":StringTag("standing"),
288
- >>> "direction":IntTag(0),
289
- >>> "toggle_bit":ByteTag(0)
290
- >>> })
291
- >>> bell.snbt_blockstate
292
- 'minecraft:bell[attachment="standing",direction=0,toggle_bit=0b]'
293
-
294
- :return: The SNBT blockstate string
295
- """
296
- if self._snbt_blockstate is None:
297
- self._snbt_blockstate = self.namespaced_name
298
- if self.properties:
299
- props = [
300
- f"{key}={value.to_snbt()}"
301
- for key, value in sorted(self.properties.items())
302
- ]
303
- self._snbt_blockstate += f"[{','.join(props)}]"
304
- return self._snbt_blockstate
305
-
306
- @property
307
- def full_blockstate(self) -> str:
308
- """
309
- The SNBT blockstate string of the base block and extra blocks.
310
-
311
- >>> bell = Block("minecraft", "bell", {
312
- >>> "attachment":StringTag("standing"),
313
- >>> "direction":IntTag(0),
314
- >>> "toggle_bit":ByteTag(0)
315
- >>> })
316
- >>> water = Block("minecraft", "water", {"liquid_depth": IntTag(0)})
317
- >>> waterlogged_bell = bell + water
318
- >>> waterlogged_bell.full_blockstate
319
- 'minecraft:bell[attachment="standing",direction=0,toggle_bit=0b]{minecraft:water[liquid_depth=0]}'
320
-
321
- :return: The blockstate string
322
- """
323
- if self._full_blockstate is None:
324
- if self.extra_blocks:
325
- self._full_blockstate = f"{self.snbt_blockstate}{{{' , '.join(block.snbt_blockstate for block in self.extra_blocks)}}}"
326
- else:
327
- self._full_blockstate = self.snbt_blockstate
328
- return self._full_blockstate
329
-
330
- @property
331
- def base_block(self) -> Block:
332
- """
333
- Returns an instance of :class:`Block` containing only the base block without any extra blocks
334
-
335
- >>> waterlogged_stone = Block("minecraft", "stone",
336
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
337
- >>> )
338
- >>> waterlogged_stone.base_block
339
- Block(minecraft:stone)
340
-
341
- :return: A Block object
342
- """
343
- if self.extra_blocks:
344
- return Block(
345
- namespace=self.namespace,
346
- base_name=self.base_name,
347
- properties=self.properties,
348
- )
349
- else:
350
- return self
351
-
352
- @property
353
- def extra_blocks(self) -> Tuple[Block, ...]:
354
- """
355
- Returns a tuple of the extra blocks contained in the :class:`Block` instance
356
-
357
- >>> waterlogged_stone = Block("minecraft", "stone",
358
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
359
- >>> )
360
- >>> waterlogged_stone.extra_blocks
361
- (Block(minecraft:water[level="0"]),)
362
-
363
- :return: A tuple of :class:`Block` objects
364
- """
365
- return self._extra_blocks
366
-
367
- @property
368
- def block_tuple(self) -> Tuple[Block, ...]:
369
- """
370
- Returns the stack of blocks represented by this object as a tuple.
371
- This is a tuple of base_block and extra_blocks
372
-
373
- >>> waterlogged_stone = Block("minecraft", "stone",
374
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
375
- >>> )
376
- >>> waterlogged_stone.block_tuple
377
- (Block(minecraft:stone), Block(minecraft:water[level="0"]))
378
-
379
- :return: A tuple of :class:`Block` objects
380
- """
381
- return (self.base_block,) + self.extra_blocks
382
-
383
- @staticmethod
384
- def parse_blockstate_string(
385
- blockstate: str, snbt: bool = False
386
- ) -> Tuple[str, str, PropertyType]:
387
- """
388
- Parse a Java or SNBT blockstate string and return the raw data.
389
-
390
- To parse the blockstate and return a :class:`Block` instance use :func:`from_string_blockstate` or :func:`from_snbt_blockstate`
391
-
392
- :param blockstate: The blockstate to parse
393
- :param snbt: Are the property values in SNBT format. If false all values must be an instance of :class:`~StringTag`
394
- :return: namespace, block_name, properties
395
-
396
- """
397
- if snbt:
398
- match = Block.snbt_blockstate_regex.match(blockstate)
399
- else:
400
- match = Block.blockstate_regex.match(blockstate)
401
- namespace = match.group("namespace") or "minecraft"
402
- base_name = match.group("base_name")
403
-
404
- if match.group("property_name") is not None:
405
- properties = {match.group("property_name"): match.group("property_value")}
406
- properties_string = match.group("properties")
407
- if properties_string is not None:
408
- if snbt:
409
- for match in Block.snbt_properties_regex.finditer(
410
- properties_string
411
- ):
412
- properties[match.group("name")] = match.group("value")
413
- else:
414
- for match in Block.properties_regex.finditer(properties_string):
415
- properties[match.group("name")] = match.group("value")
416
- else:
417
- properties = {}
418
-
419
- if snbt:
420
- properties_dict = {k: from_snbt(v) for k, v in sorted(properties.items())}
421
- else:
422
- properties_dict = {k: StringTag(v) for k, v in sorted(properties.items())}
423
-
424
- return (
425
- namespace,
426
- base_name,
427
- properties_dict,
428
- )
429
-
430
- def __str__(self) -> str:
431
- """
432
-
433
- >>> waterlogged_stone = Block("minecraft", "stone",
434
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
435
- >>> )
436
- >>> str(waterlogged_stone)
437
- 'minecraft:stone{minecraft:water[level="0"]}'
438
-
439
- :return: A string showing the information of the :class:`Block` class.
440
- """
441
- return self.full_blockstate
442
-
443
- def __repr__(self) -> str:
444
- """
445
-
446
- >>> waterlogged_stone = Block("minecraft", "stone",
447
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
448
- >>> )
449
- >>> repr(waterlogged_stone)
450
- 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
451
-
452
- :return: The base blockstate string of the Block object along with the blockstate strings of included extra blocks
453
- """
454
- if self.extra_blocks:
455
- return f"Block({self.base_block}, extra_blocks=({', '.join([str(b) for b in self.extra_blocks])}))"
456
- else:
457
- return f"Block({self.base_block})"
458
-
459
- def __iter__(self) -> Iterable[Block]:
460
- """
461
- Iterate through all the blocks in this :class:`Block` instance.
462
-
463
- >>> waterlogged_stone = Block("minecraft", "stone",
464
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
465
- >>> )
466
- >>> for block in waterlogged_stone:
467
- >>> print(block)
468
- minecraft:stone
469
- minecraft:water[level="0"]
470
- """
471
- yield self.base_block
472
- yield from self.extra_blocks
473
-
474
- def __len__(self) -> int:
475
- """
476
- The number of blocks contained within the :class:`Block` instance.
477
-
478
- >>> waterlogged_stone = Block("minecraft", "stone",
479
- >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
480
- >>> )
481
- >>> len(waterlogged_stone)
482
- 2
483
- """
484
- return len(self._extra_blocks) + 1
485
-
486
- def __eq__(self, other: Block) -> bool:
487
- """
488
- Checks the equality of this Block object to another Block object
489
-
490
- >>> stone = Block("minecraft", "stone")
491
- >>> stone == stone
492
- True
493
-
494
- :param other: The Block object to check against
495
- :return: True if the Blocks objects are equal, False otherwise
496
- """
497
- if not isinstance(other, Block):
498
- return NotImplemented
499
-
500
- return (
501
- self.namespaced_name == other.namespaced_name
502
- and self.properties == other.properties
503
- and self.extra_blocks == other.extra_blocks
504
- )
505
-
506
- def __gt__(self, other: Block) -> bool:
507
- """
508
- Allows blocks to be sorted so numpy.unique can be used on them
509
- """
510
- if not isinstance(other, Block):
511
- return NotImplemented
512
- return hash(self).__gt__(hash(other))
513
-
514
- def __hash__(self) -> int:
515
- """
516
- Hashes the Block object
517
-
518
- :return: A hash of the Block object
519
- """
520
- return hash(self.full_blockstate)
521
-
522
- def __add__(self, other: Block) -> Block:
523
- """
524
- Add the blocks from `other` to this block.
525
-
526
- >>> stone = Block("minecraft", "stone")
527
- >>> water = Block("minecraft", "water", {"level": StringTag("0")})
528
- >>> waterlogged_stone = stone + water
529
- >>> repr(waterlogged_stone)
530
- 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
531
-
532
- :param other: The :class:`Block` object to add to this :class:`Block`
533
- :return: A new instance of :class:`Block` with the blocks from other appended to the end of :attr:`extra_blocks`
534
- """
535
- if not isinstance(other, Block):
536
- return NotImplemented
537
-
538
- return Block(
539
- namespace=self.namespace,
540
- base_name=self.base_name,
541
- properties=self.properties,
542
- extra_blocks=[*self.extra_blocks, other],
543
- )
544
-
545
- def __sub__(self, other: Block) -> Block:
546
- """
547
- Remove all blocks in `other` from the :attr:`extra_blocks` of this instance of :class:`Block`
548
-
549
- >>> stone = Block("minecraft", "stone")
550
- >>> water = Block("minecraft", "water", {"level": StringTag("0")})
551
- >>> waterlogged_stone = stone + water
552
- >>> stone = waterlogged_stone - water
553
-
554
- :param other: The Block object to subtract from this :class:`Block`.
555
- :return: A new :class:`Block` instance with the blocks in `other` removed from the extra blocks.
556
- """
557
- if not isinstance(other, Block):
558
- return NotImplemented
559
-
560
- extra_blocks_to_remove = set(other)
561
- new_extras = tuple(
562
- eb for eb in self.extra_blocks if eb not in extra_blocks_to_remove
563
- )
564
-
565
- return Block(
566
- namespace=self.namespace,
567
- base_name=self.base_name,
568
- properties=self.properties,
569
- extra_blocks=new_extras,
570
- )
571
-
572
- def remove_layer(self, layer: int) -> Block:
573
- """
574
- Removes the block at the given index and returns the resulting new Block object.
575
-
576
- >>> stone = Block("minecraft", "stone")
577
- >>> water = Block("minecraft", "water", {"level": StringTag("0")})
578
- >>> waterlogged_stone = stone + water
579
- >>> stone = waterlogged_stone.remove_layer(1)
580
-
581
- :param layer: The layer of extra block to remove.
582
- :return: A new instance of Block with the same data but with the block at the specified layer removed.
583
- :raises `BlockException`: Raised when you remove the base block from a Block with no other extra blocks.
584
- """
585
- if layer == 0:
586
- if self.extra_blocks:
587
- new_base = self._extra_blocks[0]
588
- return Block(
589
- namespace=new_base.namespace,
590
- base_name=new_base.base_name,
591
- properties=new_base.properties,
592
- extra_blocks=[*self._extra_blocks[1:]],
593
- )
594
- else:
595
- raise BlockException(
596
- "Removing the base block with no extra blocks is not supported"
597
- )
598
- elif layer > len(self.extra_blocks):
599
- raise BlockException("You cannot remove a non-existent layer")
600
- else:
601
- return Block(
602
- namespace=self.namespace,
603
- base_name=self.base_name,
604
- properties=self.properties,
605
- extra_blocks=[
606
- *self.extra_blocks[: layer - 1],
607
- *self.extra_blocks[layer:],
608
- ],
609
- )
610
-
611
- def __sizeof__(self):
612
- size = (
613
- getsizeof(self._namespace)
614
- + getsizeof(self._base_name)
615
- + getsizeof(self._namespaced_name)
616
- + getsizeof(self._properties)
617
- + getsizeof(self._blockstate)
618
- + getsizeof(self._extra_blocks)
619
- + getsizeof(self._snbt_blockstate)
620
- + getsizeof(self._full_blockstate)
621
- )
622
- for eb in self.extra_blocks:
623
- size += getsizeof(eb)
624
- return size
625
-
626
-
627
- # some blocks that probably will not change. Keeping these in one place will make them easier to change if they do.
628
- UniversalAirBlock = Block("universal_minecraft", "air")
629
- # do not rely on this staying the same.
630
- UniversalAirLikeBlocks = (UniversalAirBlock, Block("universal_minecraft", "cave_air"))
1
+ from __future__ import annotations
2
+
3
+ from sys import getsizeof
4
+ import re
5
+ from typing import Dict, Iterable, Tuple, Union, Mapping
6
+ from amulet_nbt import ByteTag, ShortTag, IntTag, LongTag, StringTag, from_snbt
7
+
8
+ from .errors import BlockException
9
+
10
+ PropertyValueType = Union[
11
+ ByteTag,
12
+ ShortTag,
13
+ IntTag,
14
+ LongTag,
15
+ StringTag,
16
+ ]
17
+ PropertyType = Mapping[str, PropertyValueType]
18
+ PropertyTypeMultiple = Dict[str, Tuple[PropertyValueType, ...]]
19
+
20
+ PropertyDataTypes = (
21
+ ByteTag,
22
+ ShortTag,
23
+ IntTag,
24
+ LongTag,
25
+ StringTag,
26
+ )
27
+
28
+
29
+ class Block:
30
+ """
31
+ A class to manage the state of a block.
32
+
33
+ It is an immutable object that contains a namespaced name, properties and extra blocks.
34
+
35
+ Here's a few examples on how create a Block object:
36
+
37
+ >>> # Create a block with the namespace `minecraft` and base name `stone`
38
+ >>> stone = Block("minecraft", "stone")
39
+
40
+ >>> # Create a water block with the level property
41
+ >>> water = Block(
42
+ >>> "minecraft", # the namespace
43
+ >>> "water", # the base name
44
+ >>> { # A dictionary of properties.
45
+ >>> # Keys must be strings and values must be a numerical or string NBT type.
46
+ >>> "level": StringTag("0") # define a property `level` with a string value `1`
47
+ >>> }
48
+ >>> )
49
+
50
+ >>> # The above two examples can also be achieved by creating the block from the Java blockstate.
51
+ >>> stone = Block.from_string_blockstate("minecraft:stone")
52
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
53
+
54
+ Java 1.13 added the concept of waterlogging blocks whereby some blocks have a `waterlogged` property.
55
+ Bedrock achieved the same behaviour by added a layering system. The layering system allows any block to be the second block.
56
+ In order to support both formats and future proof the editor Amulet adds the concept of extra blocks.
57
+ Extra blocks are a sequence of zero or more blocks stored in the `extra_blocks` property of the Block instance.
58
+ Amulet places no restrictions on which blocks can be extra blocks but some validation will be done when saving based on the format being saved to.
59
+
60
+ >>> # Create a waterlogged stone block.
61
+ >>> waterlogged_stone = Block("minecraft", "stone",
62
+ >>> # extra_blocks can be a Block instance or iterable of Block instances.
63
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
64
+ >>> )
65
+
66
+ >>> # The above can also be achieved by adding together a stone and water block.
67
+ >>> waterlogged_stone = stone + water
68
+ >>> repr(waterlogged_stone)
69
+ 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
70
+ """
71
+
72
+ __slots__ = (
73
+ "_namespaced_name",
74
+ "_namespace",
75
+ "_base_name",
76
+ "_properties",
77
+ "_extra_blocks",
78
+ "_blockstate",
79
+ "_snbt_blockstate",
80
+ "_full_blockstate",
81
+ ) # Reduces memory footprint
82
+
83
+ snbt_blockstate_regex = re.compile(
84
+ r"(?:(?P<namespace>[a-z0-9_.-]+):)?(?P<base_name>[a-z0-9/._-]+)(?:\[(?P<property_name>[a-z0-9_]+)=(?P<property_value>[a-z0-9_\"']+)(?P<properties>.*)\])?"
85
+ )
86
+ blockstate_regex = re.compile(
87
+ r"(?:(?P<namespace>[a-z0-9_.-]+):)?(?P<base_name>[a-z0-9/._-]+)(?:\[(?P<property_name>[a-z0-9_]+)=(?P<property_value>[a-z0-9_]+)(?P<properties>.*)\])?"
88
+ )
89
+
90
+ snbt_properties_regex = re.compile(
91
+ r"(?:,(?P<name>[a-z0-9_]+)=(?P<value>[a-z0-9_\"']+))"
92
+ )
93
+ properties_regex = re.compile(r"(?:,(?P<name>[a-z0-9_]+)=(?P<value>[a-z0-9_]+))")
94
+
95
+ _extra_blocks: Tuple[Block, ...]
96
+
97
+ def __init__(
98
+ self,
99
+ namespace: str,
100
+ base_name: str,
101
+ properties: PropertyType = None,
102
+ extra_blocks: Union[Block, Iterable[Block]] = None,
103
+ ):
104
+ """
105
+ Constructs a :class:`Block` instance.
106
+
107
+ >>> stone = Block("minecraft", "stone")
108
+ >>>
109
+ >>> # Create a water block with the level property
110
+ >>> water = Block(
111
+ >>> "minecraft", # the namespace
112
+ >>> "water", # the base name
113
+ >>> { # A dictionary of properties.
114
+ >>> # Keys must be strings and values must be a numerical or string NBT type.
115
+ >>> "level": StringTag("0") # define a property `level` with a string value `1`
116
+ >>> }
117
+ >>> )
118
+
119
+ :param namespace: The string namespace of the block. eg `minecraft`
120
+ :param base_name: The string base name of the block. eg `stone`
121
+ :param properties: A dictionary of properties. Keys must be strings and values must be a numerical or string NBT type.
122
+ :param extra_blocks: A :class:`Block` instance or iterable of :class:`Block` instances
123
+ """
124
+ assert (isinstance(namespace, str) or namespace is None) and isinstance(
125
+ base_name, str
126
+ ), f"namespace and base_name must be strings {namespace} {base_name}"
127
+ self._namespace = namespace
128
+ self._base_name = base_name
129
+ self._namespaced_name = f"{namespace}:{base_name}"
130
+
131
+ self._blockstate = None
132
+ self._snbt_blockstate = None
133
+ self._full_blockstate = None
134
+
135
+ if properties is None:
136
+ properties = {}
137
+ assert isinstance(properties, dict) and all(
138
+ isinstance(val, PropertyDataTypes) for val in properties.values()
139
+ ), properties
140
+
141
+ self._properties = properties
142
+ self._extra_blocks = ()
143
+ if extra_blocks:
144
+ eb = []
145
+
146
+ def unpack_block(block_: Iterable[Block]):
147
+ for b in block_:
148
+ if b.extra_blocks:
149
+ unpack_block(b)
150
+ else:
151
+ eb.append(b)
152
+
153
+ unpack_block(extra_blocks)
154
+ self._extra_blocks = tuple(eb)
155
+
156
+ @classmethod
157
+ def join(cls, blocks: Iterable[Block]):
158
+ """
159
+ Join an interable of Block objects back into one block class.
160
+ This is the reverse of the __iter__ method.
161
+
162
+ >>> stone = Block.from_string_blockstate("minecraft:stone")
163
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
164
+ >>> stone_water = Block.join([stone, water])
165
+ >>> stone_water2 = Block.join(stone_water)
166
+ >>> assert stone_water == stone_water2
167
+
168
+ :param blocks: An iterable of one or more Block objects.
169
+ :return: A new Block instance
170
+ """
171
+ blocks = list(blocks)
172
+ return Block(
173
+ blocks[0].namespace, blocks[0].base_name, blocks[0].properties, blocks[1:]
174
+ )
175
+
176
+ @classmethod
177
+ def from_string_blockstate(cls, blockstate: str):
178
+ """
179
+ Parse a Java format blockstate where values are all strings and populate a :class:`Block` class with the data.
180
+
181
+ >>> stone = Block.from_string_blockstate("minecraft:stone")
182
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
183
+
184
+ :param blockstate: The Java blockstate string to parse.
185
+ :return: A Block instance containing the state.
186
+ """
187
+ namespace, block_name, properties = cls.parse_blockstate_string(blockstate)
188
+ return cls(namespace, block_name, properties)
189
+
190
+ @classmethod
191
+ def from_snbt_blockstate(cls, blockstate: str):
192
+ """
193
+ Parse a blockstate where values are SNBT of any type and populate a :class:`Block` class with the data.
194
+ """
195
+ namespace, block_name, properties = cls.parse_blockstate_string(
196
+ blockstate, True
197
+ )
198
+ return cls(namespace, block_name, properties)
199
+
200
+ @property
201
+ def namespaced_name(self) -> str:
202
+ """
203
+ The namespace:base_name of the blockstate represented by the :class:`Block` object.
204
+
205
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
206
+ >>> water.namespaced_name
207
+ 'minecraft:water'
208
+
209
+ :return: The namespace:base_name of the blockstate
210
+ """
211
+ return self._namespaced_name
212
+
213
+ @property
214
+ def namespace(self) -> str:
215
+ """
216
+ The namespace of the blockstate represented by the :class:`Block` object.
217
+
218
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
219
+ >>> water.namespace
220
+ 'minecraft'
221
+
222
+ :return: The namespace of the blockstate
223
+ """
224
+ return self._namespace
225
+
226
+ @property
227
+ def base_name(self) -> str:
228
+ """
229
+ The base name of the blockstate represented by the :class:`Block` object.
230
+
231
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
232
+ >>> water.base_name
233
+ 'water'
234
+
235
+ :return: The base name of the blockstate
236
+ """
237
+ return self._base_name
238
+
239
+ @property
240
+ def properties(self) -> PropertyType:
241
+ """
242
+ The mapping of properties of the blockstate represented by the :class:`Block` object.
243
+
244
+ >>> water = Block.from_string_blockstate("minecraft:water[level=0]")
245
+ >>> water.properties
246
+ {"level": StringTag("0")}
247
+
248
+ :return: A dictionary of the properties of the blockstate
249
+ """
250
+ return dict(self._properties)
251
+
252
+ @property
253
+ def blockstate(self) -> str:
254
+ """
255
+ The Java blockstate string of this :class:`Block` object
256
+ Note if there are extra blocks this will only show the base block.
257
+ Note this will only contain properties with StringTag values.
258
+
259
+ >>> stone = Block("minecraft", "stone")
260
+ >>> stone.blockstate
261
+ 'minecraft:stone'
262
+ >>> water = Block("minecraft", "water", {"level": StringTag("0")})
263
+ >>> water.blockstate
264
+ `minecraft:water[level=0]`
265
+
266
+ :return: The blockstate string
267
+ """
268
+ if self._blockstate is None:
269
+ self._blockstate = self.namespaced_name
270
+ if self.properties:
271
+ props = [
272
+ f"{key}={value.py_str}"
273
+ for key, value in sorted(self.properties.items())
274
+ if isinstance(value, StringTag)
275
+ ]
276
+ self._blockstate += f"[{','.join(props)}]"
277
+ return self._blockstate
278
+
279
+ @property
280
+ def snbt_blockstate(self) -> str:
281
+ """
282
+ A modified version of the Java blockstate format that supports all NBT types.
283
+ Converts the property values to the SNBT format to preserve type.
284
+ Note if there are extra blocks this will only show the base block.
285
+
286
+ >>> bell = Block("minecraft", "bell", {
287
+ >>> "attachment":StringTag("standing"),
288
+ >>> "direction":IntTag(0),
289
+ >>> "toggle_bit":ByteTag(0)
290
+ >>> })
291
+ >>> bell.snbt_blockstate
292
+ 'minecraft:bell[attachment="standing",direction=0,toggle_bit=0b]'
293
+
294
+ :return: The SNBT blockstate string
295
+ """
296
+ if self._snbt_blockstate is None:
297
+ self._snbt_blockstate = self.namespaced_name
298
+ if self.properties:
299
+ props = [
300
+ f"{key}={value.to_snbt()}"
301
+ for key, value in sorted(self.properties.items())
302
+ ]
303
+ self._snbt_blockstate += f"[{','.join(props)}]"
304
+ return self._snbt_blockstate
305
+
306
+ @property
307
+ def full_blockstate(self) -> str:
308
+ """
309
+ The SNBT blockstate string of the base block and extra blocks.
310
+
311
+ >>> bell = Block("minecraft", "bell", {
312
+ >>> "attachment":StringTag("standing"),
313
+ >>> "direction":IntTag(0),
314
+ >>> "toggle_bit":ByteTag(0)
315
+ >>> })
316
+ >>> water = Block("minecraft", "water", {"liquid_depth": IntTag(0)})
317
+ >>> waterlogged_bell = bell + water
318
+ >>> waterlogged_bell.full_blockstate
319
+ 'minecraft:bell[attachment="standing",direction=0,toggle_bit=0b]{minecraft:water[liquid_depth=0]}'
320
+
321
+ :return: The blockstate string
322
+ """
323
+ if self._full_blockstate is None:
324
+ if self.extra_blocks:
325
+ self._full_blockstate = f"{self.snbt_blockstate}{{{' , '.join(block.snbt_blockstate for block in self.extra_blocks)}}}"
326
+ else:
327
+ self._full_blockstate = self.snbt_blockstate
328
+ return self._full_blockstate
329
+
330
+ @property
331
+ def base_block(self) -> Block:
332
+ """
333
+ Returns an instance of :class:`Block` containing only the base block without any extra blocks
334
+
335
+ >>> waterlogged_stone = Block("minecraft", "stone",
336
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
337
+ >>> )
338
+ >>> waterlogged_stone.base_block
339
+ Block(minecraft:stone)
340
+
341
+ :return: A Block object
342
+ """
343
+ if self.extra_blocks:
344
+ return Block(
345
+ namespace=self.namespace,
346
+ base_name=self.base_name,
347
+ properties=self.properties,
348
+ )
349
+ else:
350
+ return self
351
+
352
+ @property
353
+ def extra_blocks(self) -> Tuple[Block, ...]:
354
+ """
355
+ Returns a tuple of the extra blocks contained in the :class:`Block` instance
356
+
357
+ >>> waterlogged_stone = Block("minecraft", "stone",
358
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
359
+ >>> )
360
+ >>> waterlogged_stone.extra_blocks
361
+ (Block(minecraft:water[level="0"]),)
362
+
363
+ :return: A tuple of :class:`Block` objects
364
+ """
365
+ return self._extra_blocks
366
+
367
+ @property
368
+ def block_tuple(self) -> Tuple[Block, ...]:
369
+ """
370
+ Returns the stack of blocks represented by this object as a tuple.
371
+ This is a tuple of base_block and extra_blocks
372
+
373
+ >>> waterlogged_stone = Block("minecraft", "stone",
374
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
375
+ >>> )
376
+ >>> waterlogged_stone.block_tuple
377
+ (Block(minecraft:stone), Block(minecraft:water[level="0"]))
378
+
379
+ :return: A tuple of :class:`Block` objects
380
+ """
381
+ return (self.base_block,) + self.extra_blocks
382
+
383
+ @staticmethod
384
+ def parse_blockstate_string(
385
+ blockstate: str, snbt: bool = False
386
+ ) -> Tuple[str, str, PropertyType]:
387
+ """
388
+ Parse a Java or SNBT blockstate string and return the raw data.
389
+
390
+ To parse the blockstate and return a :class:`Block` instance use :func:`from_string_blockstate` or :func:`from_snbt_blockstate`
391
+
392
+ :param blockstate: The blockstate to parse
393
+ :param snbt: Are the property values in SNBT format. If false all values must be an instance of :class:`~StringTag`
394
+ :return: namespace, block_name, properties
395
+
396
+ """
397
+ if snbt:
398
+ match = Block.snbt_blockstate_regex.match(blockstate)
399
+ else:
400
+ match = Block.blockstate_regex.match(blockstate)
401
+ namespace = match.group("namespace") or "minecraft"
402
+ base_name = match.group("base_name")
403
+
404
+ if match.group("property_name") is not None:
405
+ properties = {match.group("property_name"): match.group("property_value")}
406
+ properties_string = match.group("properties")
407
+ if properties_string is not None:
408
+ if snbt:
409
+ for match in Block.snbt_properties_regex.finditer(
410
+ properties_string
411
+ ):
412
+ properties[match.group("name")] = match.group("value")
413
+ else:
414
+ for match in Block.properties_regex.finditer(properties_string):
415
+ properties[match.group("name")] = match.group("value")
416
+ else:
417
+ properties = {}
418
+
419
+ if snbt:
420
+ properties_dict = {k: from_snbt(v) for k, v in sorted(properties.items())}
421
+ else:
422
+ properties_dict = {k: StringTag(v) for k, v in sorted(properties.items())}
423
+
424
+ return (
425
+ namespace,
426
+ base_name,
427
+ properties_dict,
428
+ )
429
+
430
+ def __str__(self) -> str:
431
+ """
432
+
433
+ >>> waterlogged_stone = Block("minecraft", "stone",
434
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
435
+ >>> )
436
+ >>> str(waterlogged_stone)
437
+ 'minecraft:stone{minecraft:water[level="0"]}'
438
+
439
+ :return: A string showing the information of the :class:`Block` class.
440
+ """
441
+ return self.full_blockstate
442
+
443
+ def __repr__(self) -> str:
444
+ """
445
+
446
+ >>> waterlogged_stone = Block("minecraft", "stone",
447
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
448
+ >>> )
449
+ >>> repr(waterlogged_stone)
450
+ 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
451
+
452
+ :return: The base blockstate string of the Block object along with the blockstate strings of included extra blocks
453
+ """
454
+ if self.extra_blocks:
455
+ return f"Block({self.base_block}, extra_blocks=({', '.join([str(b) for b in self.extra_blocks])}))"
456
+ else:
457
+ return f"Block({self.base_block})"
458
+
459
+ def __iter__(self) -> Iterable[Block]:
460
+ """
461
+ Iterate through all the blocks in this :class:`Block` instance.
462
+
463
+ >>> waterlogged_stone = Block("minecraft", "stone",
464
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
465
+ >>> )
466
+ >>> for block in waterlogged_stone:
467
+ >>> print(block)
468
+ minecraft:stone
469
+ minecraft:water[level="0"]
470
+ """
471
+ yield self.base_block
472
+ yield from self.extra_blocks
473
+
474
+ def __len__(self) -> int:
475
+ """
476
+ The number of blocks contained within the :class:`Block` instance.
477
+
478
+ >>> waterlogged_stone = Block("minecraft", "stone",
479
+ >>> extra_blocks=Block("minecraft", "water", {"level": StringTag("0")})
480
+ >>> )
481
+ >>> len(waterlogged_stone)
482
+ 2
483
+ """
484
+ return len(self._extra_blocks) + 1
485
+
486
+ def __eq__(self, other: Block) -> bool:
487
+ """
488
+ Checks the equality of this Block object to another Block object
489
+
490
+ >>> stone = Block("minecraft", "stone")
491
+ >>> stone == stone
492
+ True
493
+
494
+ :param other: The Block object to check against
495
+ :return: True if the Blocks objects are equal, False otherwise
496
+ """
497
+ if not isinstance(other, Block):
498
+ return NotImplemented
499
+
500
+ return (
501
+ self.namespaced_name == other.namespaced_name
502
+ and self.properties == other.properties
503
+ and self.extra_blocks == other.extra_blocks
504
+ )
505
+
506
+ def __gt__(self, other: Block) -> bool:
507
+ """
508
+ Allows blocks to be sorted so numpy.unique can be used on them
509
+ """
510
+ if not isinstance(other, Block):
511
+ return NotImplemented
512
+ return hash(self).__gt__(hash(other))
513
+
514
+ def __hash__(self) -> int:
515
+ """
516
+ Hashes the Block object
517
+
518
+ :return: A hash of the Block object
519
+ """
520
+ return hash(self.full_blockstate)
521
+
522
+ def __add__(self, other: Block) -> Block:
523
+ """
524
+ Add the blocks from `other` to this block.
525
+
526
+ >>> stone = Block("minecraft", "stone")
527
+ >>> water = Block("minecraft", "water", {"level": StringTag("0")})
528
+ >>> waterlogged_stone = stone + water
529
+ >>> repr(waterlogged_stone)
530
+ 'Block(minecraft:stone, extra_blocks=(minecraft:water[level="0"]))'
531
+
532
+ :param other: The :class:`Block` object to add to this :class:`Block`
533
+ :return: A new instance of :class:`Block` with the blocks from other appended to the end of :attr:`extra_blocks`
534
+ """
535
+ if not isinstance(other, Block):
536
+ return NotImplemented
537
+
538
+ return Block(
539
+ namespace=self.namespace,
540
+ base_name=self.base_name,
541
+ properties=self.properties,
542
+ extra_blocks=[*self.extra_blocks, other],
543
+ )
544
+
545
+ def __sub__(self, other: Block) -> Block:
546
+ """
547
+ Remove all blocks in `other` from the :attr:`extra_blocks` of this instance of :class:`Block`
548
+
549
+ >>> stone = Block("minecraft", "stone")
550
+ >>> water = Block("minecraft", "water", {"level": StringTag("0")})
551
+ >>> waterlogged_stone = stone + water
552
+ >>> stone = waterlogged_stone - water
553
+
554
+ :param other: The Block object to subtract from this :class:`Block`.
555
+ :return: A new :class:`Block` instance with the blocks in `other` removed from the extra blocks.
556
+ """
557
+ if not isinstance(other, Block):
558
+ return NotImplemented
559
+
560
+ extra_blocks_to_remove = set(other)
561
+ new_extras = tuple(
562
+ eb for eb in self.extra_blocks if eb not in extra_blocks_to_remove
563
+ )
564
+
565
+ return Block(
566
+ namespace=self.namespace,
567
+ base_name=self.base_name,
568
+ properties=self.properties,
569
+ extra_blocks=new_extras,
570
+ )
571
+
572
+ def remove_layer(self, layer: int) -> Block:
573
+ """
574
+ Removes the block at the given index and returns the resulting new Block object.
575
+
576
+ >>> stone = Block("minecraft", "stone")
577
+ >>> water = Block("minecraft", "water", {"level": StringTag("0")})
578
+ >>> waterlogged_stone = stone + water
579
+ >>> stone = waterlogged_stone.remove_layer(1)
580
+
581
+ :param layer: The layer of extra block to remove.
582
+ :return: A new instance of Block with the same data but with the block at the specified layer removed.
583
+ :raises `BlockException`: Raised when you remove the base block from a Block with no other extra blocks.
584
+ """
585
+ if layer == 0:
586
+ if self.extra_blocks:
587
+ new_base = self._extra_blocks[0]
588
+ return Block(
589
+ namespace=new_base.namespace,
590
+ base_name=new_base.base_name,
591
+ properties=new_base.properties,
592
+ extra_blocks=[*self._extra_blocks[1:]],
593
+ )
594
+ else:
595
+ raise BlockException(
596
+ "Removing the base block with no extra blocks is not supported"
597
+ )
598
+ elif layer > len(self.extra_blocks):
599
+ raise BlockException("You cannot remove a non-existent layer")
600
+ else:
601
+ return Block(
602
+ namespace=self.namespace,
603
+ base_name=self.base_name,
604
+ properties=self.properties,
605
+ extra_blocks=[
606
+ *self.extra_blocks[: layer - 1],
607
+ *self.extra_blocks[layer:],
608
+ ],
609
+ )
610
+
611
+ def __sizeof__(self):
612
+ size = (
613
+ getsizeof(self._namespace)
614
+ + getsizeof(self._base_name)
615
+ + getsizeof(self._namespaced_name)
616
+ + getsizeof(self._properties)
617
+ + getsizeof(self._blockstate)
618
+ + getsizeof(self._extra_blocks)
619
+ + getsizeof(self._snbt_blockstate)
620
+ + getsizeof(self._full_blockstate)
621
+ )
622
+ for eb in self.extra_blocks:
623
+ size += getsizeof(eb)
624
+ return size
625
+
626
+
627
+ # some blocks that probably will not change. Keeping these in one place will make them easier to change if they do.
628
+ UniversalAirBlock = Block("universal_minecraft", "air")
629
+ # do not rely on this staying the same.
630
+ UniversalAirLikeBlocks = (UniversalAirBlock, Block("universal_minecraft", "cave_air"))