mcdex 0.1.0.dev0__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.
Files changed (161) hide show
  1. mcdex/__init__.py +281 -0
  2. mcdex/advancements/__init__.py +16 -0
  3. mcdex/advancements/_mc_advancements.py +3558 -0
  4. mcdex/advancements/advancements.json +12101 -0
  5. mcdex/attributes/__init__.py +16 -0
  6. mcdex/attributes/_mc_attributes.py +83 -0
  7. mcdex/attributes/attributes.json +289 -0
  8. mcdex/banner_patterns/__init__.py +16 -0
  9. mcdex/banner_patterns/_mc_banner_patterns.py +86 -0
  10. mcdex/banner_patterns/banner_patterns.json +310 -0
  11. mcdex/base/__init__.py +146 -0
  12. mcdex/base/_advancement_id.py +22 -0
  13. mcdex/base/_attribute_id.py +22 -0
  14. mcdex/base/_banner_pattern_id.py +26 -0
  15. mcdex/base/_biome_id.py +22 -0
  16. mcdex/base/_block_entity_id.py +22 -0
  17. mcdex/base/_block_id.py +35 -0
  18. mcdex/base/_cat_sound_variant_id.py +23 -0
  19. mcdex/base/_cat_variant_id.py +22 -0
  20. mcdex/base/_catalogue_meta.py +38 -0
  21. mcdex/base/_chicken_sound_variant_id.py +23 -0
  22. mcdex/base/_chicken_variant_id.py +23 -0
  23. mcdex/base/_cow_sound_variant_id.py +23 -0
  24. mcdex/base/_cow_variant_id.py +22 -0
  25. mcdex/base/_damage_type_id.py +21 -0
  26. mcdex/base/_dimension_id.py +21 -0
  27. mcdex/base/_effect_id.py +21 -0
  28. mcdex/base/_enchantment_id.py +22 -0
  29. mcdex/base/_entity_id.py +35 -0
  30. mcdex/base/_equipment_slot.py +34 -0
  31. mcdex/base/_frog_variant_id.py +22 -0
  32. mcdex/base/_game_id.py +88 -0
  33. mcdex/base/_game_rule_id.py +21 -0
  34. mcdex/base/_item_id.py +98 -0
  35. mcdex/base/_mc_thing.py +289 -0
  36. mcdex/base/_mc_version.py +140 -0
  37. mcdex/base/_music_id.py +25 -0
  38. mcdex/base/_not_in_version.py +68 -0
  39. mcdex/base/_painting_variant_id.py +23 -0
  40. mcdex/base/_particle_id.py +21 -0
  41. mcdex/base/_pig_sound_variant_id.py +23 -0
  42. mcdex/base/_pig_variant_id.py +22 -0
  43. mcdex/base/_potion_id.py +25 -0
  44. mcdex/base/_potion_modifier.py +30 -0
  45. mcdex/base/_sound_id.py +21 -0
  46. mcdex/base/_statistic_id.py +22 -0
  47. mcdex/base/_structure_id.py +21 -0
  48. mcdex/base/_trim_material_id.py +26 -0
  49. mcdex/base/_trim_pattern_id.py +25 -0
  50. mcdex/base/_version.py +188 -0
  51. mcdex/base/_villager_profession_id.py +24 -0
  52. mcdex/base/_villager_type_id.py +25 -0
  53. mcdex/base/_wolf_sound_variant_id.py +23 -0
  54. mcdex/base/_wolf_variant_id.py +22 -0
  55. mcdex/base/_zombie_nautilus_variant_id.py +23 -0
  56. mcdex/biomes/__init__.py +16 -0
  57. mcdex/biomes/_mc_biomes.py +137 -0
  58. mcdex/biomes/biomes.json +652 -0
  59. mcdex/block_entities/__init__.py +16 -0
  60. mcdex/block_entities/_mc_block_entities.py +95 -0
  61. mcdex/block_entities/block_entities.json +331 -0
  62. mcdex/blocks/__init__.py +16 -0
  63. mcdex/blocks/_mc_blocks.py +1338 -0
  64. mcdex/blocks/blocks.json +7770 -0
  65. mcdex/cat_sound_variants/__init__.py +16 -0
  66. mcdex/cat_sound_variants/_mc_cat_sound_variants.py +47 -0
  67. mcdex/cat_sound_variants/cat_sound_variants.json +25 -0
  68. mcdex/cat_variants/__init__.py +16 -0
  69. mcdex/cat_variants/_mc_cat_variants.py +54 -0
  70. mcdex/cat_variants/cat_variants.json +91 -0
  71. mcdex/chicken_sound_variants/__init__.py +17 -0
  72. mcdex/chicken_sound_variants/_mc_chicken_sound_variants.py +48 -0
  73. mcdex/chicken_sound_variants/chicken_sound_variants.json +24 -0
  74. mcdex/chicken_variants/__init__.py +16 -0
  75. mcdex/chicken_variants/_mc_chicken_variants.py +47 -0
  76. mcdex/chicken_variants/chicken_variants.json +30 -0
  77. mcdex/cow_sound_variants/__init__.py +16 -0
  78. mcdex/cow_sound_variants/_mc_cow_sound_variants.py +47 -0
  79. mcdex/cow_sound_variants/cow_sound_variants.json +24 -0
  80. mcdex/cow_variants/__init__.py +16 -0
  81. mcdex/cow_variants/_mc_cow_variants.py +47 -0
  82. mcdex/cow_variants/cow_variants.json +30 -0
  83. mcdex/damage_types/__init__.py +16 -0
  84. mcdex/damage_types/_mc_damage_types.py +94 -0
  85. mcdex/damage_types/damage_types.json +317 -0
  86. mcdex/dimensions/__init__.py +16 -0
  87. mcdex/dimensions/_mc_dimensions.py +46 -0
  88. mcdex/dimensions/dimensions.json +29 -0
  89. mcdex/effects/__init__.py +16 -0
  90. mcdex/effects/_mc_effects.py +83 -0
  91. mcdex/effects/effects.json +251 -0
  92. mcdex/enchantments/__init__.py +16 -0
  93. mcdex/enchantments/_mc_enchantments.py +86 -0
  94. mcdex/enchantments/enchantments.json +270 -0
  95. mcdex/entities/__init__.py +16 -0
  96. mcdex/entities/_mc_entities.py +206 -0
  97. mcdex/entities/entities.json +1042 -0
  98. mcdex/frog_variants/__init__.py +16 -0
  99. mcdex/frog_variants/_mc_frog_variants.py +47 -0
  100. mcdex/frog_variants/frog_variants.json +29 -0
  101. mcdex/game_rules/__init__.py +16 -0
  102. mcdex/game_rules/_mc_game_rules.py +110 -0
  103. mcdex/game_rules/game_rules.json +436 -0
  104. mcdex/items/__init__.py +16 -0
  105. mcdex/items/_mc_items.py +1842 -0
  106. mcdex/items/items.json +20060 -0
  107. mcdex/music/__init__.py +16 -0
  108. mcdex/music/_music.py +65 -0
  109. mcdex/music/music.json +143 -0
  110. mcdex/painting_variants/__init__.py +16 -0
  111. mcdex/painting_variants/_mc_painting_variants.py +96 -0
  112. mcdex/painting_variants/painting_variants.json +369 -0
  113. mcdex/particles/__init__.py +16 -0
  114. mcdex/particles/_mc_particles.py +184 -0
  115. mcdex/particles/particles.json +917 -0
  116. mcdex/pig_sound_variants/__init__.py +16 -0
  117. mcdex/pig_sound_variants/_mc_pig_sound_variants.py +48 -0
  118. mcdex/pig_sound_variants/pig_sound_variants.json +30 -0
  119. mcdex/pig_variants/__init__.py +16 -0
  120. mcdex/pig_variants/_mc_pig_variants.py +46 -0
  121. mcdex/pig_variants/pig_variants.json +30 -0
  122. mcdex/potions/__init__.py +20 -0
  123. mcdex/potions/_mc_potions.py +101 -0
  124. mcdex/potions/_modified_potion.py +129 -0
  125. mcdex/potions/potions.json +297 -0
  126. mcdex/py.typed +0 -0
  127. mcdex/sounds/__init__.py +16 -0
  128. mcdex/sounds/_mc_sounds.py +2395 -0
  129. mcdex/sounds/sounds.json +13077 -0
  130. mcdex/statistics/__init__.py +16 -0
  131. mcdex/statistics/_mc_statistics.py +123 -0
  132. mcdex/statistics/statistics.json +521 -0
  133. mcdex/structures/__init__.py +16 -0
  134. mcdex/structures/_mc_structures.py +99 -0
  135. mcdex/structures/structures.json +367 -0
  136. mcdex/trim_materials/__init__.py +16 -0
  137. mcdex/trim_materials/_mc_trim_materials.py +54 -0
  138. mcdex/trim_materials/trim_materials.json +77 -0
  139. mcdex/trim_patterns/__init__.py +16 -0
  140. mcdex/trim_patterns/_mc_trim_patterns.py +61 -0
  141. mcdex/trim_patterns/trim_patterns.json +119 -0
  142. mcdex/villager_professions/__init__.py +17 -0
  143. mcdex/villager_professions/_mc_villager_professions.py +61 -0
  144. mcdex/villager_professions/villager_professions.json +125 -0
  145. mcdex/villager_types/__init__.py +16 -0
  146. mcdex/villager_types/_mc_villager_types.py +52 -0
  147. mcdex/villager_types/villager_types.json +54 -0
  148. mcdex/wolf_sound_variants/__init__.py +17 -0
  149. mcdex/wolf_sound_variants/_mc_wolf_sound_variants.py +53 -0
  150. mcdex/wolf_sound_variants/wolf_sound_variants.json +54 -0
  151. mcdex/wolf_variants/__init__.py +16 -0
  152. mcdex/wolf_variants/_mc_wolf_variants.py +53 -0
  153. mcdex/wolf_variants/wolf_variants.json +66 -0
  154. mcdex/zombie_nautilus_variants/__init__.py +17 -0
  155. mcdex/zombie_nautilus_variants/_mc_zombie_nautilus_variants.py +48 -0
  156. mcdex/zombie_nautilus_variants/zombie_nautilus_variants.json +23 -0
  157. mcdex-0.1.0.dev0.dist-info/METADATA +1285 -0
  158. mcdex-0.1.0.dev0.dist-info/RECORD +161 -0
  159. mcdex-0.1.0.dev0.dist-info/WHEEL +5 -0
  160. mcdex-0.1.0.dev0.dist-info/licenses/LICENSE +201 -0
  161. mcdex-0.1.0.dev0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,289 @@
1
+ """
2
+ MCThing is the shared base of every catalogue of mcdex, as 'MCEntities'
3
+ and 'MCBlocks': an enumeration whose members are the things of one kind
4
+ of the game, each the same thing in every version, with the id each
5
+ version knows it by.
6
+ """
7
+ # Apache-2.0 license
8
+ # Copyright (c) 2026 Asger Jon Vistisen
9
+ from __future__ import annotations
10
+
11
+ import json
12
+ from importlib.resources import files
13
+ from typing import TYPE_CHECKING
14
+
15
+ from worktoy.desc import Field
16
+ from worktoy.utilities import textFmt
17
+ from worktoy.waitaminute import TypeException
18
+
19
+ from . import CatalogueMeta, GameId, NotInVersion, Version
20
+
21
+ if TYPE_CHECKING: # pragma: no cover, no mutate
22
+ from typing import Any, TypeAlias
23
+
24
+ VersionIds: TypeAlias = dict[Version, str]
25
+ Changes: TypeAlias = tuple[tuple[int, Any], ...]
26
+
27
+
28
+ class MCThing(CatalogueMeta.keeNum):
29
+ """
30
+ A thing of the game is one thing in every version, whatever each
31
+ version calls it. A catalogue is an enumeration of the things of one
32
+ kind, and each thing is a member of it, made once as the catalogue is:
33
+
34
+ pinkWool = MCItems.PINK_WOOL
35
+ pinkWool is MCItems['pink_wool'] # True
36
+ pinkWool['1.12.2']
37
+ # ItemId('pink_wool', 'minecraft:wool:6')
38
+ for item in MCItems: ... # every item, in order
39
+ len(MCItems)
40
+ 'pink_wool' in MCItems # True
41
+
42
+ A member's value is its 'uniqueName', mcdex's name for it, the same in
43
+ every version, and indexing the member by a version gives the id that
44
+ version knows it by, an object of the kind's id class
45
+ ('__id_class__').
46
+
47
+ Implementation
48
+ --------------
49
+
50
+ 'MCThing' is the root of 'CatalogueMeta', worktoy's enumeration
51
+ metaclass with membership by unique name too, and has no members of its
52
+ own: each catalogue is a subclass
53
+ declaring one per name of its data file, 'PINK_WOOL = Kee[str](
54
+ "pink_wool")', the member named by the unique name in capitals. A
55
+ subclass names its data file, beside its module ('__data_file__'), the
56
+ key the file keeps the records under ('__data_key__'), its id class
57
+ and, for messages, the word for one ('__kind__'). A test may give the
58
+ records itself instead ('__data_records__').
59
+
60
+ The data file is read at the first use of the catalogue, not at
61
+ import, and a member's record at the first use of the member, each
62
+ kept by the catalogue: the members themselves are frozen. A record
63
+ holds one table, 'ids', keyed by the first version each id applies
64
+ from, as version texts or 'Version' members. The ids begin at the
65
+ first version the data covers; an id is the 'minecraftId' of that
66
+ version, or '' where vanilla has no such thing. 'versionIds' is the
67
+ full table of ids, with a key for every 'Version' from the first
68
+ version of the data, built at its first read and returned as a copy.
69
+
70
+ A member is found by its name in any case, 'MCItems.Pink_Wool', by its
71
+ unique name, as 'MCSounds["entity.creeper.primed"]', or by its
72
+ position. A unique name the catalogue lacks raises 'KeyError'. An
73
+ empty unique name, a name the data lacks, a missing or empty table, or
74
+ ids that leave the thing in no version raise 'ValueError' at the first
75
+ use of the member; a version text that names no release raises
76
+ 'KeeResolveError'; an id other than a 'str' raises 'TypeException'.
77
+ Versions are compared by their position in 'Version', as comparing the
78
+ members themselves is slow.
79
+ """
80
+
81
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
82
+ # NAMESPACE # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
83
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
84
+
85
+ # Class Variables
86
+ __absent__ = ''
87
+ __id_class__: type = GameId
88
+ __kind__ = 'thing'
89
+ __data_file__ = ''
90
+ __data_key__ = ''
91
+ __data_records__: dict = {}
92
+
93
+ # Private Variables
94
+ __all_versions__: dict = {}
95
+ __catalogue_records__: dict = {}
96
+ __unique_names__: dict = {}
97
+ __thing_tables__: dict = {}
98
+
99
+ # Public Variables
100
+ uniqueName: Field[str] = Field()
101
+ versionIds: Field[VersionIds] = Field()
102
+
103
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
104
+ # GETTERS # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
105
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
106
+
107
+ @uniqueName.GET
108
+ def _getUniqueName(self, ) -> str:
109
+ """mcdex's name for the thing, the member's value: 'pink_wool'."""
110
+ return self.value
111
+
112
+ @versionIds.GET
113
+ def _getVersionIds(self, ) -> VersionIds:
114
+ """
115
+ The id of the thing in every version from the first version of the
116
+ data, as a new dict, '' where vanilla has no such thing.
117
+ """
118
+ return dict(self._table('ids'))
119
+
120
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
121
+ # Python API # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
122
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
123
+
124
+ def __getitem__(self, version: Any) -> GameId:
125
+ """
126
+ The id of the thing in the given version, of the kind's id class: its
127
+ 'uniqueName' and the 'minecraftId' that version knows it by. The
128
+ version is a 'Version' member, its name, its text as the game reports
129
+ it, or its 'MCVersion'; one 'Version' cannot resolve raises
130
+ 'KeeResolveError'.
131
+
132
+ Raises
133
+ ------
134
+ NotInVersion
135
+ Vanilla has no such thing in that version.
136
+ ValueError
137
+ The version is earlier than the data.
138
+ """
139
+ version_ = Version(version)
140
+ position = self._position(version_)
141
+ changes = self._changes('ids')
142
+ first, _ = changes[0]
143
+ if position < first:
144
+ infoSpec = """mcdex has no data on '%s' before %s"""
145
+ info = infoSpec % (self.uniqueName, self._versions()[first].value)
146
+ raise ValueError(info)
147
+ id_ = self._valueIn(changes, position)
148
+ if id_ == self.__absent__:
149
+ raise NotInVersion(self, version_)
150
+ return self.__id_class__(self.uniqueName, id_)
151
+
152
+ @classmethod
153
+ def __class_resolve__(cls, identifier: Any) -> Any:
154
+ """
155
+ A member by its unique name, where its name in capitals does not
156
+ find it, as 'entity.creeper.primed' for 'ENTITY_CREEPER_PRIMED'. A
157
+ unique name the catalogue lacks raises 'KeyError'; anything else
158
+ goes on to the enumeration's other steps.
159
+ """
160
+ if not isinstance(identifier, str):
161
+ return NotImplemented
162
+ found = cls._byUniqueName().get(identifier)
163
+ if found is None:
164
+ infoSpec = """mcdex has no %s named '%s'"""
165
+ raise KeyError(textFmt(infoSpec % (cls.__kind__, identifier)))
166
+ return found
167
+
168
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
169
+ # DOMAIN SPECIFIC # # # # # # # # # # # # # # # # # # # # # # # # # # # #
170
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
171
+
172
+ @classmethod
173
+ def _versions(cls, ) -> tuple[Version, ...]:
174
+ """Every 'Version', by position, listed at the first call."""
175
+ if not MCThing.__all_versions__:
176
+ MCThing.__all_versions__['all'] = (*Version,)
177
+ return MCThing.__all_versions__['all']
178
+
179
+ @classmethod
180
+ def _position(cls, version: Version) -> int:
181
+ """
182
+ The position of the 'Version' among them all, as '_versions' lists
183
+ them; the positions are listed at the first call. The position is the
184
+ enumeration's to give, not the member's: worktoy no longer promises
185
+ the 'index' of a member.
186
+ """
187
+ if 'positions' not in MCThing.__all_versions__:
188
+ versions = cls._versions()
189
+ positions = {version_: i for i, version_ in enumerate(versions)}
190
+ MCThing.__all_versions__['positions'] = positions
191
+ return MCThing.__all_versions__['positions'][version]
192
+
193
+ @classmethod
194
+ def _records(cls, ) -> dict:
195
+ """The records of the data file, read at the first call."""
196
+ if cls.__data_records__:
197
+ return cls.__data_records__
198
+ if cls.__name__ not in MCThing.__catalogue_records__:
199
+ package = cls.__module__.rsplit('.', 1)[0]
200
+ path = files(package).joinpath(cls.__data_file__)
201
+ text = path.read_text('utf-8')
202
+ records = json.loads(text)[cls.__data_key__]
203
+ MCThing.__catalogue_records__[cls.__name__] = records
204
+ return MCThing.__catalogue_records__[cls.__name__]
205
+
206
+ @classmethod
207
+ def _byUniqueName(cls, ) -> dict:
208
+ """The members by their unique names, listed at the first call."""
209
+ if cls.__name__ not in MCThing.__unique_names__:
210
+ named = {member.value: member for member in cls}
211
+ MCThing.__unique_names__[cls.__name__] = named
212
+ return MCThing.__unique_names__[cls.__name__]
213
+
214
+ def _record(self, ) -> dict:
215
+ """The member's record in the data, checked for its 'ids'."""
216
+ clsName = type(self).__name__
217
+ if not self.value:
218
+ infoSpec = """%s.%s has an empty 'uniqueName'"""
219
+ raise ValueError(infoSpec % (clsName, self.name))
220
+ records = self._records()
221
+ if self.value not in records:
222
+ infoSpec = """%s('%s') has no record in the data"""
223
+ raise ValueError(infoSpec % (clsName, self.value))
224
+ record = records[self.value]
225
+ if not record.get('ids'):
226
+ infoSpec = """%s('%s') received no 'ids'"""
227
+ raise ValueError(infoSpec % (clsName, self.value))
228
+ return record
229
+
230
+ def _changes(self, table: str) -> Changes:
231
+ """
232
+ The changes of one of the member's tables, as (position of the first
233
+ version, value), in order, checked and kept at the first call. A
234
+ subclass with tables besides 'ids' reads them in '_read'.
235
+ """
236
+ key = (type(self).__name__, self.name, table, 'changes')
237
+ if key not in MCThing.__thing_tables__:
238
+ MCThing.__thing_tables__[key] = self._read(table, self._record())
239
+ return MCThing.__thing_tables__[key]
240
+
241
+ def _read(self, table: str, record: dict) -> Changes:
242
+ """
243
+ The changes of the 'ids' table of a record: each id must be a 'str',
244
+ and some version must have the thing.
245
+ """
246
+ return self._readIds(type(self).__name__, self.value, record[table])
247
+
248
+ @classmethod
249
+ def _readIds(cls, owner: str, uniqueName: str, ids: dict) -> Changes:
250
+ """
251
+ The changes of a table of ids, each a 'str', some version having the
252
+ thing; 'owner' and 'uniqueName' name the thing in the error.
253
+ """
254
+ out = []
255
+ for key, id_ in ids.items():
256
+ if not isinstance(id_, str):
257
+ raise TypeException('minecraftId', id_, str)
258
+ out.append((cls._position(Version(key)), id_))
259
+ if all(id_ == cls.__absent__ for _, id_ in out):
260
+ infoSpec = """%s('%s') is in no version"""
261
+ raise ValueError(infoSpec % (owner, uniqueName))
262
+ return cls._sorted(out)
263
+
264
+ def _table(self, table: str) -> dict:
265
+ """
266
+ A table of the member, a value for each version, built at the first
267
+ call and kept: the ids from the first version of the data.
268
+ """
269
+ key = (type(self).__name__, self.name, table, 'table')
270
+ if key not in MCThing.__thing_tables__:
271
+ MCThing.__thing_tables__[key] = self._tableOf(self._changes(table))
272
+ return MCThing.__thing_tables__[key]
273
+
274
+ @classmethod
275
+ def _tableOf(cls, changes: Changes) -> dict:
276
+ """A value for each version from the first of the changes."""
277
+ versions = cls._versions()
278
+ first, _ = changes[0]
279
+ return {versions[i]: cls._valueIn(changes, i)
280
+ for i in range(first, len(versions))}
281
+
282
+ @staticmethod
283
+ def _sorted(pairs: list) -> Changes:
284
+ return (*sorted(pairs, key=lambda pair: pair[0]),)
285
+
286
+ @staticmethod
287
+ def _valueIn(changes: Changes, index: int) -> Any:
288
+ """The value of the last change at or before the position."""
289
+ return [value for first, value in changes if first <= index][-1]
@@ -0,0 +1,140 @@
1
+ """
2
+ MCVersion encapsulates Minecraft versions. The releases are enumerated by
3
+ the 'Version' class, whose member values are instances of 'MCVersion'.
4
+ This allows for versions missing from the enumeration, such as
5
+ particular snapshots.
6
+ """
7
+ # Apache-2.0 license
8
+ # Copyright (c) 2026 Asger Jon Vistisen
9
+ from __future__ import annotations
10
+
11
+ import re
12
+ from typing import TYPE_CHECKING
13
+
14
+ from worktoy.dispatch import overload
15
+ from worktoy.desc import FixBox
16
+ from worktoy.mcls import BaseObject
17
+
18
+ if TYPE_CHECKING: # pragma: no cover, no mutate
19
+ from typing import Any, TypeAlias
20
+
21
+ Key: TypeAlias = tuple[int, int, int]
22
+
23
+
24
+ class MCVersion(BaseObject):
25
+ """
26
+ A Minecraft version is three numbers, as 1.7.10, written with the last
27
+ left out when it is zero, as 26.3 or 1.8. Versions must compare as
28
+ numbers, so that 1.9 comes before 1.10, be equal by their numbers, key
29
+ a dictionary, and be read from the text the game reports:
30
+
31
+ MCVersion(1, 7, 10)
32
+ MCVersion(26, 3) # 26.3.0
33
+ MCVersion('1.7.10')
34
+ str(MCVersion(26, 3, 0)) # '26.3'
35
+
36
+ Implementation
37
+ --------------
38
+
39
+ 'major', 'minor' and 'micro' are 'FixBox[int]', written once by the
40
+ constructor, so a version never changes and hashes the same for life.
41
+ The constructor takes three components, two with 'micro' zero, or
42
+ their text, and refuses a negative component or text other than two
43
+ or three dot separated decimals without leading zeros with
44
+ 'ValueError'. Equality, hash and ordering compare the tuple of the
45
+ three components, so 26.3 and 26.3.0 are one version. A comparison
46
+ with anything but an 'MCVersion' returns 'NotImplemented'.
47
+ """
48
+
49
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
50
+ # NAMESPACE # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
51
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
52
+
53
+ # Class Variables
54
+ __text_pattern__ = re.compile(
55
+ r'(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(?:\.(0|[1-9][0-9]*))?'
56
+ )
57
+
58
+ # Public Variables
59
+ major = FixBox[int]()
60
+ minor = FixBox[int]()
61
+ micro = FixBox[int]()
62
+
63
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
64
+ # Python API # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
65
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
66
+
67
+ def __str__(self, ) -> str:
68
+ """'1.7.10', or '26.3' when 'micro' is zero."""
69
+ if self.micro:
70
+ return '%d.%d.%d' % self._key()
71
+ return '%d.%d' % (self.major, self.minor)
72
+
73
+ def __repr__(self, ) -> str:
74
+ """'MCVersion(1, 7, 10)', which evaluates to an equal version."""
75
+ return '%s(%d, %d, %d)' % (type(self).__name__, *self._key())
76
+
77
+ def __eq__(self, other: Any) -> bool:
78
+ if isinstance(other, MCVersion):
79
+ return self._key() == other._key()
80
+ return NotImplemented
81
+
82
+ def __hash__(self, ) -> int:
83
+ return hash(self._key())
84
+
85
+ def __lt__(self, other: Any) -> bool:
86
+ if isinstance(other, MCVersion):
87
+ return self._key() < other._key()
88
+ return NotImplemented
89
+
90
+ def __le__(self, other: Any) -> bool:
91
+ if isinstance(other, MCVersion):
92
+ return self._key() <= other._key()
93
+ return NotImplemented
94
+
95
+ def __gt__(self, other: Any) -> bool:
96
+ if isinstance(other, MCVersion):
97
+ return self._key() > other._key()
98
+ return NotImplemented
99
+
100
+ def __ge__(self, other: Any) -> bool:
101
+ if isinstance(other, MCVersion):
102
+ return self._key() >= other._key()
103
+ return NotImplemented
104
+
105
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
106
+ # CONSTRUCTORS # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
107
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
108
+
109
+ @overload(int, int, int)
110
+ def __init__(self, major: int, minor: int, micro: int) -> None:
111
+ for name_, value_ in (('major', major), ('minor', minor),
112
+ ('micro', micro)):
113
+ if value_ < 0:
114
+ infoSpec = """%s() received a negative '%s': %d"""
115
+ info = infoSpec % (type(self).__name__, name_, value_)
116
+ raise ValueError(info)
117
+ self.major = major
118
+ self.minor = minor
119
+ self.micro = micro
120
+
121
+ @overload(int, int)
122
+ def __init__(self, major: int, minor: int) -> None:
123
+ self.__init__(major, minor, 0)
124
+
125
+ @overload(str)
126
+ def __init__(self, text: str) -> None:
127
+ if (match_ := self.__text_pattern__.fullmatch(text)) is None:
128
+ infoSpec = """%s() cannot read a version from: '%s'"""
129
+ info = infoSpec % (type(self).__name__, text)
130
+ raise ValueError(info)
131
+ major, minor, micro = match_.groups(default='0')
132
+ self.__init__(int(major), int(minor), int(micro))
133
+
134
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
135
+ # DOMAIN SPECIFIC # # # # # # # # # # # # # # # # # # # # # # # # # # # #
136
+ # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # # #
137
+
138
+ def _key(self, ) -> Key:
139
+ """The three components as one tuple, for comparisons and hashing."""
140
+ return (self.major, self.minor, self.micro)
@@ -0,0 +1,25 @@
1
+ """
2
+ MusicId pairs a song's unique name with the id a version of the game needs to
3
+ identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class MusicId(GameId):
13
+ """
14
+ The id of a song, as a music disc plays it, in one version: its 'uniqueName',
15
+ the same in every version, and its 'minecraftId', the song a jukebox_playable
16
+ component plays in that version.
17
+
18
+ A command takes it only inside an item's data, whose shape changed over
19
+ the versions: see the README's 'Ids that go inside an item'.
20
+
21
+ Implementation
22
+ --------------
23
+
24
+ All of it is 'GameId's: a 'MusicId' is equal only to a 'MusicId'.
25
+ """
@@ -0,0 +1,68 @@
1
+ """
2
+ NotInVersion is raised when an item or an entity is asked for in a
3
+ version of the game that vanilla does not have it in.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from typing import TYPE_CHECKING
10
+
11
+ from worktoy.utilities import textFmt
12
+ from worktoy.waitaminute import WorkToyException
13
+
14
+ if TYPE_CHECKING: # pragma: no cover, no mutate
15
+ from . import MCThing, Version
16
+
17
+
18
+ class NotInVersion(WorkToyException, LookupError):
19
+ """
20
+ NotInVersion subclasses 'LookupError' and is raised when an item or an
21
+ entity is asked for in a version of the game that vanilla does not
22
+ have it in: vanilla 1.7.10 has no elytra yet, vanilla 1.10 no lit
23
+ furnace any more, and vanilla 1.9.4 no stray. A mod may bring the item
24
+ to that version under an id of its own, so this says nothing of
25
+ whether a given game has the item; it is what a test cites when the
26
+ game lacks it:
27
+
28
+ Vanilla 1.7.10 has no 'elytra'; vanilla added it in 1.9!
29
+ Vanilla 1.10 has no 'lit_furnace'; vanilla removed it in 1.9!
30
+ Vanilla 1.9.4 has no 'stray'; vanilla added it in 1.10!
31
+
32
+ Attributes
33
+ ----------
34
+ item : MCThing
35
+ The thing asked for, a member of its catalogue.
36
+ version : Version
37
+ The version that vanilla does not have it in.
38
+ """
39
+
40
+ __slots__ = ('item', 'version')
41
+
42
+ def __init__(self, item: MCThing, version: Version) -> None:
43
+ self.item = item
44
+ self.version = version
45
+ LookupError.__init__(self, )
46
+
47
+ def __str__(self, ) -> str:
48
+ """
49
+ Names the first later version vanilla has it in; when there is none,
50
+ the version that removed it, after the last one that had it.
51
+ """
52
+ ids = self.item.versionIds
53
+ name_ = self.item.uniqueName
54
+ modifier = getattr(self.item, 'modifier', '')
55
+ if modifier:
56
+ name_ = '%s %s' % (modifier.value, name_)
57
+ later = [v for v, id_ in ids.items() if id_ and v > self.version]
58
+ if later:
59
+ infoSpec = """Vanilla %s has no '%s'; vanilla added it in %s!"""
60
+ info = infoSpec % (self.version.value, name_, later[0].value)
61
+ return textFmt(info)
62
+ last = [v for v, id_ in ids.items() if id_ and v < self.version][-1]
63
+ removed = [v for v, id_ in ids.items() if not id_ and v > last][0]
64
+ infoSpec = """Vanilla %s has no '%s'; vanilla removed it in %s!"""
65
+ info = infoSpec % (self.version.value, name_, removed.value)
66
+ return textFmt(info)
67
+
68
+ __repr__ = __str__
@@ -0,0 +1,23 @@
1
+ """
2
+ PaintingVariantId pairs a painting variant's unique name with the id a version
3
+ of the game needs to identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class PaintingVariantId(GameId):
13
+ """
14
+ The id of a painting variant in one version: its 'uniqueName', the same in
15
+ every version, and its 'minecraftId', what /summon takes for a painting of
16
+ that variant in that version.
17
+
18
+ Implementation
19
+ --------------
20
+
21
+ All of it is 'GameId's: a 'PaintingVariantId' is equal only to a
22
+ 'PaintingVariantId'.
23
+ """
@@ -0,0 +1,21 @@
1
+ """
2
+ ParticleId pairs a particle's unique name with the id a version of the game
3
+ needs to identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class ParticleId(GameId):
13
+ """
14
+ The id of a particle in one version: its 'uniqueName', the same in
15
+ every version, and its 'minecraftId', what '/particle' takes in that version.
16
+
17
+ Implementation
18
+ --------------
19
+
20
+ All of it is 'GameId's: a 'ParticleId' is equal only to a 'ParticleId'.
21
+ """
@@ -0,0 +1,23 @@
1
+ """
2
+ PigSoundVariantId pairs a pig sound variant's unique name with the id a version
3
+ of the game needs to identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class PigSoundVariantId(GameId):
13
+ """
14
+ The id of a pig sound variant in one version: its 'uniqueName', the same in
15
+ every version, and its 'minecraftId', what /summon takes for a pig of that
16
+ sound variant in that version.
17
+
18
+ Implementation
19
+ --------------
20
+
21
+ All of it is 'GameId's: a 'PigSoundVariantId' is equal only to a
22
+ 'PigSoundVariantId'.
23
+ """
@@ -0,0 +1,22 @@
1
+ """
2
+ PigVariantId pairs a pig variant's unique name with the id a version of the
3
+ game needs to identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class PigVariantId(GameId):
13
+ """
14
+ The id of a pig variant in one version: its 'uniqueName', the same in every
15
+ version, and its 'minecraftId', what /summon takes for a pig of that variant
16
+ in that version.
17
+
18
+ Implementation
19
+ --------------
20
+
21
+ All of it is 'GameId's: a 'PigVariantId' is equal only to a 'PigVariantId'.
22
+ """
@@ -0,0 +1,25 @@
1
+ """
2
+ PotionId pairs a potion's unique name with the id a version of the game
3
+ needs to identify it.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from . import GameId
10
+
11
+
12
+ class PotionId(GameId):
13
+ """
14
+ The id of a potion in one version: its 'uniqueName', the same in every
15
+ version, and its 'minecraftId', the potion an item is made with: from 1.9 its
16
+ name, before it the potion item's damage value in that version.
17
+
18
+ A command takes it only inside a potion item's data, whose shape changed over
19
+ the versions: see the README's 'Ids that go inside an item'.
20
+
21
+ Implementation
22
+ --------------
23
+
24
+ All of it is 'GameId's: a 'PotionId' is equal only to a 'PotionId'.
25
+ """
@@ -0,0 +1,30 @@
1
+ """
2
+ PotionModifier is the enumeration of what makes a potion more than its
3
+ base potion: the game's long and strong potions.
4
+ """
5
+ # Apache-2.0 license
6
+ # Copyright (c) 2026 Asger Jon Vistisen
7
+ from __future__ import annotations
8
+
9
+ from worktoy.keenum import Kee, KeeNum
10
+
11
+
12
+ class PotionModifier(KeeNum):
13
+ """
14
+ A long potion lasts longer than its base potion, a strong one is
15
+ stronger, as the game makes each of them: the long night vision is the
16
+ night vision potion for 9600 ticks, not 3600. With a base potion, a
17
+ modifier is the potion:
18
+
19
+ MCPotions[MCPotions.NIGHT_VISION, PotionModifier.LONG]
20
+ MCPotions['swiftness', 'strong']
21
+
22
+ Implementation
23
+ --------------
24
+
25
+ A member's value is the word the game names such a potion with, as in
26
+ 'minecraft:long_night_vision'.
27
+ """
28
+
29
+ LONG = Kee[str]('long')
30
+ STRONG = Kee[str]('strong')