Edit on GitHub

hexdoc.minecraft

 1__all__ = [
 2    "I18n",
 3    "LocalizedItem",
 4    "LocalizedStr",
 5    "Tag",
 6    "TagValue",
 7    "assets",
 8    "recipe",
 9]
10
11from . import assets, recipe
12from .i18n import I18n, LocalizedItem, LocalizedStr
13from .tags import Tag, TagValue
class I18n(hexdoc.model.base.ValidationContextModel):
100class I18n(ValidationContextModel):
101    """Handles localization of strings."""
102
103    lookup: dict[str, LocalizedStr]
104    lang: str
105    default_i18n: I18n | None
106    enabled: bool
107    lang_props: LangProps
108
109    @classmethod
110    def list_all(cls, loader: ModResourceLoader):
111        # don't list languages which this particular mod doesn't support
112        # eg. if Hex has translations for ru_ru but an addon doesn't
113        return set(
114            id.path
115            for resource_dir, id, _ in cls._load_lang_resources(loader)
116            if not resource_dir.external
117        )
118
119    @classmethod
120    def load_all(cls, loader: ModResourceLoader, enabled: bool):
121        # lang -> (key -> value)
122        lookups = defaultdict[str, dict[str, LocalizedStr]](dict)
123        internal_langs = set[str]()
124
125        for resource_dir, lang_id, data in cls._load_lang_resources(loader):
126            lang = lang_id.path
127            lookups[lang] |= cls.parse_lookup(data)
128            if not resource_dir.external:
129                internal_langs.add(lang)
130
131        default_lang = loader.props.default_lang
132        default_lookup = lookups[default_lang]
133        default_i18n = cls(
134            lookup=default_lookup,
135            lang=default_lang,
136            default_i18n=None,
137            enabled=enabled,
138            lang_props=loader.props.lang[default_lang],
139        )
140
141        return {default_lang: default_i18n} | {
142            lang: cls(
143                lookup=lookup,
144                lang=lang,
145                default_i18n=default_i18n,
146                enabled=enabled,
147                lang_props=loader.props.lang[lang],
148            )
149            for lang, lookup in lookups.items()
150            if lang in internal_langs and lang != default_lang
151        }
152
153    @classmethod
154    def load(
155        cls,
156        loader: ModResourceLoader,
157        enabled: bool,
158        lang: str,
159    ) -> Self:
160        lookup = dict[str, LocalizedStr]()
161        is_internal = False
162
163        for resource_dir, _, data in cls._load_lang_resources(loader, lang):
164            lookup |= cls.parse_lookup(data)
165            if not resource_dir.external:
166                is_internal = True
167
168        if enabled and not is_internal:
169            raise FileNotFoundError(
170                f"Lang {lang} exists, but {loader.props.modid} does not support it"
171            )
172
173        default_lang = loader.props.default_lang
174        default_i18n = None
175        if lang != default_lang:
176            default_i18n = cls.load(loader, enabled, default_lang)
177
178        return cls(
179            lookup=lookup,
180            lang=lang,
181            default_i18n=default_i18n,
182            enabled=enabled,
183            lang_props=loader.props.lang[lang],
184        )
185
186    @classmethod
187    def parse_lookup(cls, raw_lookup: dict[str, str]) -> dict[str, LocalizedStr]:
188        return {
189            key: LocalizedStr(key=key, value=value.replace("%%", "%"))
190            for key, value in raw_lookup.items()
191        }
192
193    @classmethod
194    def _load_lang_resources(cls, loader: ModResourceLoader, lang: str = "*"):
195        return loader.load_resources(
196            "assets",
197            namespace="*",
198            folder="lang",
199            glob=[
200                f"{lang}.json",
201                f"{lang}.json5",
202                f"{lang}.flatten.json",
203                f"{lang}.flatten.json5",
204            ],
205            decode=decode_and_flatten_json_dict,
206            export=cls._export,
207        )
208
209    @classmethod
210    def _export(cls, new: dict[str, str], current: dict[str, str] | None):
211        return json.dumps((current or {}) | new)
212
213    @model_validator(mode="after")
214    def _warn_if_disabled(self):
215        if not self.enabled:
216            logger.info(
217                f"I18n is disabled for {self.lang}. Warnings about missing translations"
218                + " will only be logged in verbose mode."
219            )
220        elif self.lang_props.quiet:
221            logger.info(
222                f"Quiet mode is enabled for {self.lang}. Warnings about missing"
223                + " translations will only be logged in verbose mode."
224            )
225        return self
226
227    @property
228    def is_default(self):
229        return self.default_i18n is None
230
231    def localize(
232        self,
233        *keys: str,
234        default: str | None = None,
235        silent: bool = False,
236    ) -> LocalizedStr:
237        """Looks up the given string in the lang table if i18n is enabled. Otherwise,
238        returns the original key.
239
240        If multiple keys are provided, returns the value of the first key which exists.
241        That is, subsequent keys are treated as fallbacks for the first.
242
243        Raises ValueError if i18n is enabled and default is None but the key has no
244        corresponding localized value.
245        """
246
247        for key in keys:
248            if key in self.lookup:
249                return self.lookup[key]
250
251        if silent or not self.enabled or self.lang_props.quiet:
252            log_level = logging.DEBUG
253        else:
254            log_level = logging.WARNING
255
256        log_keys = set(keys) - self._logged_missing_keys
257        if log_keys:
258            self._logged_missing_keys.update(log_keys)
259            match list(log_keys):
260                case [key]:
261                    message = f"key {key}"
262                case _:
263                    message = "keys " + ", ".join(log_keys)
264            logger.log(log_level, f"No translation in {self.lang} for {message}")
265
266        if default is not None:
267            return LocalizedStr.skip_i18n(default)
268
269        if self.default_i18n:
270            return self.default_i18n.localize(*keys, default=default, silent=silent)
271
272        return LocalizedStr.skip_i18n(keys[0])
273
274    def localize_pattern(
275        self,
276        op_id: ResourceLocation,
277        silent: bool = False,
278    ) -> LocalizedStr:
279        """Localizes the given pattern id (internal name, eg. brainsweep).
280
281        Raises ValueError if i18n is enabled but the key has no localization.
282        """
283        key_group = ValueIfVersion(">=1.20", "action", "spell")()
284
285        # prefer the book-specific translation if it exists
286        return self.localize(
287            f"hexcasting.{key_group}.book.{op_id}",
288            f"hexcasting.{key_group}.{op_id}",
289            silent=silent,
290        )
291
292    def localize_item(
293        self,
294        item: str | ResourceLocation | ItemStack,
295        silent: bool = False,
296    ) -> LocalizedItem:
297        """Localizes the given item resource name.
298
299        Raises ValueError if i18n is enabled but the key has no localization.
300        """
301        match item:
302            case str():
303                item = ItemStack.from_str(item)
304            case ResourceLocation(namespace=namespace, path=path):
305                item = ItemStack(namespace=namespace, path=path)
306            case _:
307                pass
308
309        localized = self.localize(
310            item.i18n_key(),
311            item.i18n_key("block"),
312            silent=silent,
313        )
314        return LocalizedItem(key=localized.key, value=localized.value)
315
316    def localize_entity(self, entity: ResourceLocation, type: str | None = None):
317        if type:
318            entity = type / entity
319        return self.localize(entity.i18n_key("entity"))
320
321    def localize_key(self, key: str, silent: bool = False) -> LocalizedStr:
322        if not key.startswith("key."):
323            key = "key." + key
324        return self.localize(key, silent=silent)
325
326    def localize_item_tag(self, tag: ResourceLocation, silent: bool = False):
327        localized = self.localize(
328            f"tag.{tag.namespace}.{tag.path}",
329            f"tag.item.{tag.namespace}.{tag.path}",
330            f"tag.block.{tag.namespace}.{tag.path}",
331            default=self.fallback_tag_name(tag),
332            silent=silent,
333        )
334        return LocalizedStr(key=localized.key, value=f"Tag: {localized.value}")
335
336    def fallback_tag_name(self, tag: ResourceLocation):
337        """Generates a more-or-less reasonable fallback name for a tag.
338
339        For example:
340        * `forge:ores` -> Ores
341        * `c:saplings/almond` -> Almond Saplings
342        * `c:tea_ingredients/gloopy/weak` -> Tea Ingredients, Gloopy, Weak
343        """
344
345        if tag.path.count("/") == 1:
346            before, after = tag.path.split("/")
347            return f"{after} {before}".title()
348
349        return tag.path.replace("_", " ").replace("/", ", ").title()
350
351    def localize_texture(self, texture_id: ResourceLocation, silent: bool = False):
352        path = texture_id.path.removeprefix("textures/").removesuffix(".png")
353        root, rest = path.split("/", 1)
354
355        # TODO: refactor / extensibilify
356        if root == "mob_effect":
357            root = "effect"
358
359        return self.localize(f"{root}.{texture_id.namespace}.{rest}", silent=silent)
360
361    def localize_lang(self, silent: bool = False):
362        name = self.localize("language.name", silent=silent)
363        region = self.localize("language.region", silent=silent)
364        return f"{name} ({region})"
365
366    @model_validator(mode="after")
367    def _init_logger_cache(self):
368        self._logged_missing_keys = set[str]()
369        return self

Handles localization of strings.

lookup: dict[str, LocalizedStr]
lang: str
default_i18n: I18n | None
enabled: bool
@classmethod
def list_all(cls, loader: hexdoc.core.ModResourceLoader):
109    @classmethod
110    def list_all(cls, loader: ModResourceLoader):
111        # don't list languages which this particular mod doesn't support
112        # eg. if Hex has translations for ru_ru but an addon doesn't
113        return set(
114            id.path
115            for resource_dir, id, _ in cls._load_lang_resources(loader)
116            if not resource_dir.external
117        )
@classmethod
def load_all(cls, loader: hexdoc.core.ModResourceLoader, enabled: bool):
119    @classmethod
120    def load_all(cls, loader: ModResourceLoader, enabled: bool):
121        # lang -> (key -> value)
122        lookups = defaultdict[str, dict[str, LocalizedStr]](dict)
123        internal_langs = set[str]()
124
125        for resource_dir, lang_id, data in cls._load_lang_resources(loader):
126            lang = lang_id.path
127            lookups[lang] |= cls.parse_lookup(data)
128            if not resource_dir.external:
129                internal_langs.add(lang)
130
131        default_lang = loader.props.default_lang
132        default_lookup = lookups[default_lang]
133        default_i18n = cls(
134            lookup=default_lookup,
135            lang=default_lang,
136            default_i18n=None,
137            enabled=enabled,
138            lang_props=loader.props.lang[default_lang],
139        )
140
141        return {default_lang: default_i18n} | {
142            lang: cls(
143                lookup=lookup,
144                lang=lang,
145                default_i18n=default_i18n,
146                enabled=enabled,
147                lang_props=loader.props.lang[lang],
148            )
149            for lang, lookup in lookups.items()
150            if lang in internal_langs and lang != default_lang
151        }
@classmethod
def load( cls, loader: hexdoc.core.ModResourceLoader, enabled: bool, lang: str) -> Self:
153    @classmethod
154    def load(
155        cls,
156        loader: ModResourceLoader,
157        enabled: bool,
158        lang: str,
159    ) -> Self:
160        lookup = dict[str, LocalizedStr]()
161        is_internal = False
162
163        for resource_dir, _, data in cls._load_lang_resources(loader, lang):
164            lookup |= cls.parse_lookup(data)
165            if not resource_dir.external:
166                is_internal = True
167
168        if enabled and not is_internal:
169            raise FileNotFoundError(
170                f"Lang {lang} exists, but {loader.props.modid} does not support it"
171            )
172
173        default_lang = loader.props.default_lang
174        default_i18n = None
175        if lang != default_lang:
176            default_i18n = cls.load(loader, enabled, default_lang)
177
178        return cls(
179            lookup=lookup,
180            lang=lang,
181            default_i18n=default_i18n,
182            enabled=enabled,
183            lang_props=loader.props.lang[lang],
184        )
@classmethod
def parse_lookup( cls, raw_lookup: dict[str, str]) -> dict[str, LocalizedStr]:
186    @classmethod
187    def parse_lookup(cls, raw_lookup: dict[str, str]) -> dict[str, LocalizedStr]:
188        return {
189            key: LocalizedStr(key=key, value=value.replace("%%", "%"))
190            for key, value in raw_lookup.items()
191        }
is_default
227    @property
228    def is_default(self):
229        return self.default_i18n is None
def localize( self, *keys: str, default: str | None = None, silent: bool = False) -> LocalizedStr:
231    def localize(
232        self,
233        *keys: str,
234        default: str | None = None,
235        silent: bool = False,
236    ) -> LocalizedStr:
237        """Looks up the given string in the lang table if i18n is enabled. Otherwise,
238        returns the original key.
239
240        If multiple keys are provided, returns the value of the first key which exists.
241        That is, subsequent keys are treated as fallbacks for the first.
242
243        Raises ValueError if i18n is enabled and default is None but the key has no
244        corresponding localized value.
245        """
246
247        for key in keys:
248            if key in self.lookup:
249                return self.lookup[key]
250
251        if silent or not self.enabled or self.lang_props.quiet:
252            log_level = logging.DEBUG
253        else:
254            log_level = logging.WARNING
255
256        log_keys = set(keys) - self._logged_missing_keys
257        if log_keys:
258            self._logged_missing_keys.update(log_keys)
259            match list(log_keys):
260                case [key]:
261                    message = f"key {key}"
262                case _:
263                    message = "keys " + ", ".join(log_keys)
264            logger.log(log_level, f"No translation in {self.lang} for {message}")
265
266        if default is not None:
267            return LocalizedStr.skip_i18n(default)
268
269        if self.default_i18n:
270            return self.default_i18n.localize(*keys, default=default, silent=silent)
271
272        return LocalizedStr.skip_i18n(keys[0])

Looks up the given string in the lang table if i18n is enabled. Otherwise, returns the original key.

If multiple keys are provided, returns the value of the first key which exists. That is, subsequent keys are treated as fallbacks for the first.

Raises ValueError if i18n is enabled and default is None but the key has no corresponding localized value.

def localize_pattern( self, op_id: hexdoc.core.ResourceLocation, silent: bool = False) -> LocalizedStr:
274    def localize_pattern(
275        self,
276        op_id: ResourceLocation,
277        silent: bool = False,
278    ) -> LocalizedStr:
279        """Localizes the given pattern id (internal name, eg. brainsweep).
280
281        Raises ValueError if i18n is enabled but the key has no localization.
282        """
283        key_group = ValueIfVersion(">=1.20", "action", "spell")()
284
285        # prefer the book-specific translation if it exists
286        return self.localize(
287            f"hexcasting.{key_group}.book.{op_id}",
288            f"hexcasting.{key_group}.{op_id}",
289            silent=silent,
290        )

Localizes the given pattern id (internal name, eg. brainsweep).

Raises ValueError if i18n is enabled but the key has no localization.

def localize_item( self, item: str | hexdoc.core.ResourceLocation | hexdoc.core.ItemStack, silent: bool = False) -> LocalizedItem:
292    def localize_item(
293        self,
294        item: str | ResourceLocation | ItemStack,
295        silent: bool = False,
296    ) -> LocalizedItem:
297        """Localizes the given item resource name.
298
299        Raises ValueError if i18n is enabled but the key has no localization.
300        """
301        match item:
302            case str():
303                item = ItemStack.from_str(item)
304            case ResourceLocation(namespace=namespace, path=path):
305                item = ItemStack(namespace=namespace, path=path)
306            case _:
307                pass
308
309        localized = self.localize(
310            item.i18n_key(),
311            item.i18n_key("block"),
312            silent=silent,
313        )
314        return LocalizedItem(key=localized.key, value=localized.value)

Localizes the given item resource name.

Raises ValueError if i18n is enabled but the key has no localization.

def localize_entity( self, entity: hexdoc.core.ResourceLocation, type: str | None = None):
316    def localize_entity(self, entity: ResourceLocation, type: str | None = None):
317        if type:
318            entity = type / entity
319        return self.localize(entity.i18n_key("entity"))
def localize_key( self, key: str, silent: bool = False) -> LocalizedStr:
321    def localize_key(self, key: str, silent: bool = False) -> LocalizedStr:
322        if not key.startswith("key."):
323            key = "key." + key
324        return self.localize(key, silent=silent)
def localize_item_tag( self, tag: hexdoc.core.ResourceLocation, silent: bool = False):
326    def localize_item_tag(self, tag: ResourceLocation, silent: bool = False):
327        localized = self.localize(
328            f"tag.{tag.namespace}.{tag.path}",
329            f"tag.item.{tag.namespace}.{tag.path}",
330            f"tag.block.{tag.namespace}.{tag.path}",
331            default=self.fallback_tag_name(tag),
332            silent=silent,
333        )
334        return LocalizedStr(key=localized.key, value=f"Tag: {localized.value}")
def fallback_tag_name(self, tag: hexdoc.core.ResourceLocation):
336    def fallback_tag_name(self, tag: ResourceLocation):
337        """Generates a more-or-less reasonable fallback name for a tag.
338
339        For example:
340        * `forge:ores` -> Ores
341        * `c:saplings/almond` -> Almond Saplings
342        * `c:tea_ingredients/gloopy/weak` -> Tea Ingredients, Gloopy, Weak
343        """
344
345        if tag.path.count("/") == 1:
346            before, after = tag.path.split("/")
347            return f"{after} {before}".title()
348
349        return tag.path.replace("_", " ").replace("/", ", ").title()

Generates a more-or-less reasonable fallback name for a tag.

For example:

  • forge:ores -> Ores
  • c:saplings/almond -> Almond Saplings
  • c:tea_ingredients/gloopy/weak -> Tea Ingredients, Gloopy, Weak
def localize_texture( self, texture_id: hexdoc.core.ResourceLocation, silent: bool = False):
351    def localize_texture(self, texture_id: ResourceLocation, silent: bool = False):
352        path = texture_id.path.removeprefix("textures/").removesuffix(".png")
353        root, rest = path.split("/", 1)
354
355        # TODO: refactor / extensibilify
356        if root == "mob_effect":
357            root = "effect"
358
359        return self.localize(f"{root}.{texture_id.namespace}.{rest}", silent=silent)
def localize_lang(self, silent: bool = False):
361    def localize_lang(self, silent: bool = False):
362        name = self.localize("language.name", silent=silent)
363        region = self.localize("language.region", silent=silent)
364        return f"{name} ({region})"
class LocalizedItem(hexdoc.minecraft.LocalizedStr):
94class LocalizedItem(LocalizedStr, frozen=True):
95    @classmethod
96    def _localize(cls, i18n: I18n, key: str) -> Self:
97        return cls.model_validate(i18n.localize_item(key))

Represents a string which has been localized.

@total_ordering
class LocalizedStr(hexdoc.model.base.HexdocModel):
28@total_ordering
29class LocalizedStr(HexdocModel, frozen=True):
30    """Represents a string which has been localized."""
31
32    model_config = DEFAULT_CONFIG | json_schema_extra_config(type_str, inherited)
33
34    key: str
35    value: str
36
37    @classmethod
38    def skip_i18n(cls, key: str) -> Self:
39        """Returns an instance of this class with `value = key`."""
40        return cls(key=key, value=key)
41
42    @classmethod
43    def with_value(cls, value: str) -> Self:
44        """Returns an instance of this class with an empty key."""
45        return cls(key="", value=value)
46
47    @model_validator(mode="wrap")
48    @classmethod
49    def _check_localize(
50        cls,
51        value: str | Any,
52        handler: ModelWrapValidatorHandler[Self],
53        info: ValidationInfo,
54    ) -> Self:
55        # NOTE: if we need LocalizedStr to work as a dict key, add another check which
56        # returns cls.skip_i18n(value) if info.context is falsy
57        if not isinstance(value, str):
58            return handler(value)
59
60        i18n = I18n.of(info)
61        return cls._localize(i18n, value)
62
63    @classmethod
64    def _localize(cls, i18n: I18n, key: str) -> Self:
65        return cls.model_validate(i18n.localize(key))
66
67    def map(self, fn: Callable[[str], str]) -> Self:
68        """Returns a copy of this object with `new.value = fn(old.value)`."""
69        return self.model_copy(update={"value": fn(self.value)})
70
71    def __repr__(self) -> str:
72        return self.value
73
74    def __str__(self) -> str:
75        return self.value
76
77    def __eq__(self, other: Self | str | Any):
78        match other:
79            case LocalizedStr():
80                return self.value == other.value
81            case str():
82                return self.value == other
83            case _:
84                return super().__eq__(other)
85
86    def __lt__(self, other: Self | str):
87        match other:
88            case LocalizedStr():
89                return self.value < other.value
90            case str():
91                return self.value < other

Represents a string which has been localized.

key: str
value: str
@classmethod
def skip_i18n(cls, key: str) -> Self:
37    @classmethod
38    def skip_i18n(cls, key: str) -> Self:
39        """Returns an instance of this class with `value = key`."""
40        return cls(key=key, value=key)

Returns an instance of this class with value = key.

@classmethod
def with_value(cls, value: str) -> Self:
42    @classmethod
43    def with_value(cls, value: str) -> Self:
44        """Returns an instance of this class with an empty key."""
45        return cls(key="", value=value)

Returns an instance of this class with an empty key.

def map(self, fn: Callable[[str], str]) -> Self:
67    def map(self, fn: Callable[[str], str]) -> Self:
68        """Returns a copy of this object with `new.value = fn(old.value)`."""
69        return self.model_copy(update={"value": fn(self.value)})

Returns a copy of this object with new.value = fn(old.value).

class Tag(hexdoc.model.base.HexdocModel):
 41class Tag(HexdocModel):
 42    GASLIGHTING_ITEMS: ClassVar = TagLoader("hexdoc", "items", "gaslighting")
 43    """Item/block ids that gaslight you. This tag isn't real, it's all in your head.
 44
 45    File: `hexdoc/tags/items/gaslighting.json`
 46    """
 47    SPOILERED_ADVANCEMENTS: ClassVar = TagLoader("hexdoc", "advancements", "spoilered")
 48    """Advancements for entries that should be blurred in the web book.
 49
 50    File: `hexdoc/tags/advancements/spoilered.json`
 51    """
 52
 53    registry: str = Field(exclude=True)
 54    values: PydanticOrderedSet[TagValue]
 55    replace: bool = False
 56
 57    @classmethod
 58    def load(
 59        cls,
 60        registry: str,
 61        id: ResourceLocation,
 62        loader: ModResourceLoader,
 63    ) -> Self:
 64        values = PydanticOrderedSet[TagValue]()
 65        replace = False
 66
 67        for _, _, tag in loader.load_resources(
 68            "data",
 69            folder=f"tags/{registry}",
 70            id=id,
 71            decode=lambda raw_data: Tag._convert(
 72                registry=registry,
 73                raw_data=raw_data,
 74            ),
 75            export=cls._export,
 76        ):
 77            if tag.replace:
 78                values.clear()
 79            for value in tag._load_values(loader):
 80                values.add(value)
 81
 82        return cls(registry=registry, values=values, replace=replace)
 83
 84    @classmethod
 85    def _convert(cls, *, registry: str, raw_data: str) -> Self:
 86        data = decode_json_dict(raw_data)
 87        return cls.model_validate(data | {"registry": registry})
 88
 89    @property
 90    def value_ids(self) -> Iterator[ResourceLocation]:
 91        for value in self.values:
 92            match value:
 93                case ResourceLocation():
 94                    yield value
 95                case OptionalTagValue(id=id):
 96                    yield id
 97
 98    @property
 99    def value_ids_set(self):
100        return set(self.value_ids)
101
102    def __ror__(self, other: set[ResourceLocation]):
103        new = set(other)
104        new |= self.value_ids_set
105        return new
106
107    def __contains__(self, x: Any) -> bool:
108        if isinstance(x, BaseResourceLocation):
109            return x.id in self.value_ids_set
110        return NotImplemented
111
112    def _export(self: Tag, current: Tag | None):
113        if self.replace or current is None:
114            tag = self
115        else:
116            tag = self.model_copy(
117                update={"values": current.values | self.values},
118            )
119        return tag.model_dump_json(by_alias=True)
120
121    def _load_values(self, loader: ModResourceLoader) -> Iterator[TagValue]:
122        for value in self.values:
123            match value:
124                case (
125                    (ResourceLocation() as child_id)
126                    | OptionalTagValue(id=child_id)
127                ) if child_id.is_tag:
128                    try:
129                        child = Tag.load(self.registry, child_id, loader)
130                        yield from child._load_values(loader)
131                    except FileNotFoundError:
132                        yield value
133                case _:
134                    yield value

Base class for all Pydantic models in hexdoc.

Sets the default model config, and overrides __init__ to allow using the init_context context manager to set validation context for constructors.

GASLIGHTING_ITEMS: ClassVar = TagLoader(namespace='hexdoc', registry='items', path='gaslighting')

Item/block ids that gaslight you. This tag isn't real, it's all in your head.

File: hexdoc/tags/items/gaslighting.json

SPOILERED_ADVANCEMENTS: ClassVar = TagLoader(namespace='hexdoc', registry='advancements', path='spoilered')

Advancements for entries that should be blurred in the web book.

File: hexdoc/tags/advancements/spoilered.json

registry: str
values: hexdoc.utils.PydanticOrderedSet[hexdoc.core.ResourceLocation | hexdoc.minecraft.tags.OptionalTagValue]
replace: bool
@classmethod
def load( cls, registry: str, id: hexdoc.core.ResourceLocation, loader: hexdoc.core.ModResourceLoader) -> Self:
57    @classmethod
58    def load(
59        cls,
60        registry: str,
61        id: ResourceLocation,
62        loader: ModResourceLoader,
63    ) -> Self:
64        values = PydanticOrderedSet[TagValue]()
65        replace = False
66
67        for _, _, tag in loader.load_resources(
68            "data",
69            folder=f"tags/{registry}",
70            id=id,
71            decode=lambda raw_data: Tag._convert(
72                registry=registry,
73                raw_data=raw_data,
74            ),
75            export=cls._export,
76        ):
77            if tag.replace:
78                values.clear()
79            for value in tag._load_values(loader):
80                values.add(value)
81
82        return cls(registry=registry, values=values, replace=replace)
value_ids: Iterator[hexdoc.core.ResourceLocation]
89    @property
90    def value_ids(self) -> Iterator[ResourceLocation]:
91        for value in self.values:
92            match value:
93                case ResourceLocation():
94                    yield value
95                case OptionalTagValue(id=id):
96                    yield id
value_ids_set
 98    @property
 99    def value_ids_set(self):
100        return set(self.value_ids)
TagValue = hexdoc.core.ResourceLocation | hexdoc.minecraft.tags.OptionalTagValue