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.
- mcdex/__init__.py +281 -0
- mcdex/advancements/__init__.py +16 -0
- mcdex/advancements/_mc_advancements.py +3558 -0
- mcdex/advancements/advancements.json +12101 -0
- mcdex/attributes/__init__.py +16 -0
- mcdex/attributes/_mc_attributes.py +83 -0
- mcdex/attributes/attributes.json +289 -0
- mcdex/banner_patterns/__init__.py +16 -0
- mcdex/banner_patterns/_mc_banner_patterns.py +86 -0
- mcdex/banner_patterns/banner_patterns.json +310 -0
- mcdex/base/__init__.py +146 -0
- mcdex/base/_advancement_id.py +22 -0
- mcdex/base/_attribute_id.py +22 -0
- mcdex/base/_banner_pattern_id.py +26 -0
- mcdex/base/_biome_id.py +22 -0
- mcdex/base/_block_entity_id.py +22 -0
- mcdex/base/_block_id.py +35 -0
- mcdex/base/_cat_sound_variant_id.py +23 -0
- mcdex/base/_cat_variant_id.py +22 -0
- mcdex/base/_catalogue_meta.py +38 -0
- mcdex/base/_chicken_sound_variant_id.py +23 -0
- mcdex/base/_chicken_variant_id.py +23 -0
- mcdex/base/_cow_sound_variant_id.py +23 -0
- mcdex/base/_cow_variant_id.py +22 -0
- mcdex/base/_damage_type_id.py +21 -0
- mcdex/base/_dimension_id.py +21 -0
- mcdex/base/_effect_id.py +21 -0
- mcdex/base/_enchantment_id.py +22 -0
- mcdex/base/_entity_id.py +35 -0
- mcdex/base/_equipment_slot.py +34 -0
- mcdex/base/_frog_variant_id.py +22 -0
- mcdex/base/_game_id.py +88 -0
- mcdex/base/_game_rule_id.py +21 -0
- mcdex/base/_item_id.py +98 -0
- mcdex/base/_mc_thing.py +289 -0
- mcdex/base/_mc_version.py +140 -0
- mcdex/base/_music_id.py +25 -0
- mcdex/base/_not_in_version.py +68 -0
- mcdex/base/_painting_variant_id.py +23 -0
- mcdex/base/_particle_id.py +21 -0
- mcdex/base/_pig_sound_variant_id.py +23 -0
- mcdex/base/_pig_variant_id.py +22 -0
- mcdex/base/_potion_id.py +25 -0
- mcdex/base/_potion_modifier.py +30 -0
- mcdex/base/_sound_id.py +21 -0
- mcdex/base/_statistic_id.py +22 -0
- mcdex/base/_structure_id.py +21 -0
- mcdex/base/_trim_material_id.py +26 -0
- mcdex/base/_trim_pattern_id.py +25 -0
- mcdex/base/_version.py +188 -0
- mcdex/base/_villager_profession_id.py +24 -0
- mcdex/base/_villager_type_id.py +25 -0
- mcdex/base/_wolf_sound_variant_id.py +23 -0
- mcdex/base/_wolf_variant_id.py +22 -0
- mcdex/base/_zombie_nautilus_variant_id.py +23 -0
- mcdex/biomes/__init__.py +16 -0
- mcdex/biomes/_mc_biomes.py +137 -0
- mcdex/biomes/biomes.json +652 -0
- mcdex/block_entities/__init__.py +16 -0
- mcdex/block_entities/_mc_block_entities.py +95 -0
- mcdex/block_entities/block_entities.json +331 -0
- mcdex/blocks/__init__.py +16 -0
- mcdex/blocks/_mc_blocks.py +1338 -0
- mcdex/blocks/blocks.json +7770 -0
- mcdex/cat_sound_variants/__init__.py +16 -0
- mcdex/cat_sound_variants/_mc_cat_sound_variants.py +47 -0
- mcdex/cat_sound_variants/cat_sound_variants.json +25 -0
- mcdex/cat_variants/__init__.py +16 -0
- mcdex/cat_variants/_mc_cat_variants.py +54 -0
- mcdex/cat_variants/cat_variants.json +91 -0
- mcdex/chicken_sound_variants/__init__.py +17 -0
- mcdex/chicken_sound_variants/_mc_chicken_sound_variants.py +48 -0
- mcdex/chicken_sound_variants/chicken_sound_variants.json +24 -0
- mcdex/chicken_variants/__init__.py +16 -0
- mcdex/chicken_variants/_mc_chicken_variants.py +47 -0
- mcdex/chicken_variants/chicken_variants.json +30 -0
- mcdex/cow_sound_variants/__init__.py +16 -0
- mcdex/cow_sound_variants/_mc_cow_sound_variants.py +47 -0
- mcdex/cow_sound_variants/cow_sound_variants.json +24 -0
- mcdex/cow_variants/__init__.py +16 -0
- mcdex/cow_variants/_mc_cow_variants.py +47 -0
- mcdex/cow_variants/cow_variants.json +30 -0
- mcdex/damage_types/__init__.py +16 -0
- mcdex/damage_types/_mc_damage_types.py +94 -0
- mcdex/damage_types/damage_types.json +317 -0
- mcdex/dimensions/__init__.py +16 -0
- mcdex/dimensions/_mc_dimensions.py +46 -0
- mcdex/dimensions/dimensions.json +29 -0
- mcdex/effects/__init__.py +16 -0
- mcdex/effects/_mc_effects.py +83 -0
- mcdex/effects/effects.json +251 -0
- mcdex/enchantments/__init__.py +16 -0
- mcdex/enchantments/_mc_enchantments.py +86 -0
- mcdex/enchantments/enchantments.json +270 -0
- mcdex/entities/__init__.py +16 -0
- mcdex/entities/_mc_entities.py +206 -0
- mcdex/entities/entities.json +1042 -0
- mcdex/frog_variants/__init__.py +16 -0
- mcdex/frog_variants/_mc_frog_variants.py +47 -0
- mcdex/frog_variants/frog_variants.json +29 -0
- mcdex/game_rules/__init__.py +16 -0
- mcdex/game_rules/_mc_game_rules.py +110 -0
- mcdex/game_rules/game_rules.json +436 -0
- mcdex/items/__init__.py +16 -0
- mcdex/items/_mc_items.py +1842 -0
- mcdex/items/items.json +20060 -0
- mcdex/music/__init__.py +16 -0
- mcdex/music/_music.py +65 -0
- mcdex/music/music.json +143 -0
- mcdex/painting_variants/__init__.py +16 -0
- mcdex/painting_variants/_mc_painting_variants.py +96 -0
- mcdex/painting_variants/painting_variants.json +369 -0
- mcdex/particles/__init__.py +16 -0
- mcdex/particles/_mc_particles.py +184 -0
- mcdex/particles/particles.json +917 -0
- mcdex/pig_sound_variants/__init__.py +16 -0
- mcdex/pig_sound_variants/_mc_pig_sound_variants.py +48 -0
- mcdex/pig_sound_variants/pig_sound_variants.json +30 -0
- mcdex/pig_variants/__init__.py +16 -0
- mcdex/pig_variants/_mc_pig_variants.py +46 -0
- mcdex/pig_variants/pig_variants.json +30 -0
- mcdex/potions/__init__.py +20 -0
- mcdex/potions/_mc_potions.py +101 -0
- mcdex/potions/_modified_potion.py +129 -0
- mcdex/potions/potions.json +297 -0
- mcdex/py.typed +0 -0
- mcdex/sounds/__init__.py +16 -0
- mcdex/sounds/_mc_sounds.py +2395 -0
- mcdex/sounds/sounds.json +13077 -0
- mcdex/statistics/__init__.py +16 -0
- mcdex/statistics/_mc_statistics.py +123 -0
- mcdex/statistics/statistics.json +521 -0
- mcdex/structures/__init__.py +16 -0
- mcdex/structures/_mc_structures.py +99 -0
- mcdex/structures/structures.json +367 -0
- mcdex/trim_materials/__init__.py +16 -0
- mcdex/trim_materials/_mc_trim_materials.py +54 -0
- mcdex/trim_materials/trim_materials.json +77 -0
- mcdex/trim_patterns/__init__.py +16 -0
- mcdex/trim_patterns/_mc_trim_patterns.py +61 -0
- mcdex/trim_patterns/trim_patterns.json +119 -0
- mcdex/villager_professions/__init__.py +17 -0
- mcdex/villager_professions/_mc_villager_professions.py +61 -0
- mcdex/villager_professions/villager_professions.json +125 -0
- mcdex/villager_types/__init__.py +16 -0
- mcdex/villager_types/_mc_villager_types.py +52 -0
- mcdex/villager_types/villager_types.json +54 -0
- mcdex/wolf_sound_variants/__init__.py +17 -0
- mcdex/wolf_sound_variants/_mc_wolf_sound_variants.py +53 -0
- mcdex/wolf_sound_variants/wolf_sound_variants.json +54 -0
- mcdex/wolf_variants/__init__.py +16 -0
- mcdex/wolf_variants/_mc_wolf_variants.py +53 -0
- mcdex/wolf_variants/wolf_variants.json +66 -0
- mcdex/zombie_nautilus_variants/__init__.py +17 -0
- mcdex/zombie_nautilus_variants/_mc_zombie_nautilus_variants.py +48 -0
- mcdex/zombie_nautilus_variants/zombie_nautilus_variants.json +23 -0
- mcdex-0.1.0.dev0.dist-info/METADATA +1285 -0
- mcdex-0.1.0.dev0.dist-info/RECORD +161 -0
- mcdex-0.1.0.dev0.dist-info/WHEEL +5 -0
- mcdex-0.1.0.dev0.dist-info/licenses/LICENSE +201 -0
- mcdex-0.1.0.dev0.dist-info/top_level.txt +1 -0
mcdex/base/_mc_thing.py
ADDED
|
@@ -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)
|
mcdex/base/_music_id.py
ADDED
|
@@ -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
|
+
"""
|
mcdex/base/_potion_id.py
ADDED
|
@@ -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')
|