From 37149a9121f9213ce9d8553a0876fabee4ded3ba Mon Sep 17 00:00:00 2001 From: CallMeEchoCodes Date: Fri, 4 Oct 2024 19:40:59 +1000 Subject: [PATCH] feat: refactor to use `ThreadLocal` instead of class smuggling --- .../text/DynamicTextContent.java | 76 +++++++++-------- .../SpecterSerializationClient.java | 10 +++ .../text/StyledTranslatableVisitor.java | 26 ------ .../text/TranslatableVisitor.java | 25 ------ .../smuggler/TranslationEntryConsumer.java | 24 ------ .../text/smuggler/TranslationEntryMap.java | 21 ----- .../text/smuggler/package-info.java | 4 - .../serialization/client/LanguageMixin.java | 37 ++++----- .../client/TranslatableTextContentMixin.java | 81 ++++++++++--------- .../client/TranslationStorageMixin.java | 35 ++------ 10 files changed, 110 insertions(+), 229 deletions(-) delete mode 100644 specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/StyledTranslatableVisitor.java delete mode 100644 specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/TranslatableVisitor.java delete mode 100644 specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryConsumer.java delete mode 100644 specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryMap.java delete mode 100644 specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/package-info.java diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/api/serialization/text/DynamicTextContent.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/api/serialization/text/DynamicTextContent.java index 3309258..9428111 100644 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/api/serialization/text/DynamicTextContent.java +++ b/specter-serialization/src/client/java/dev/spiritstudios/specter/api/serialization/text/DynamicTextContent.java @@ -3,12 +3,8 @@ import com.mojang.serialization.Codec; import com.mojang.serialization.MapCodec; import com.mojang.serialization.codecs.RecordCodecBuilder; -import dev.spiritstudios.specter.impl.serialization.text.StyledTranslatableVisitor; -import dev.spiritstudios.specter.impl.serialization.text.TranslatableVisitor; -import net.minecraft.text.StringVisitable; -import net.minecraft.text.Style; -import net.minecraft.text.Text; -import net.minecraft.text.TextContent; +import dev.spiritstudios.specter.impl.serialization.SpecterSerializationClient; +import net.minecraft.text.*; import java.util.Optional; @@ -18,37 +14,39 @@ * @param index The index of the argument to resolve this content to. */ public record DynamicTextContent(int index) implements TextContent { - public static MapCodec CODEC = RecordCodecBuilder.mapCodec( - instance -> instance.group(Codec.INT.fieldOf("index").forGetter(DynamicTextContent::index)).apply(instance, DynamicTextContent::new) - ); - - public static final TextContent.Type TYPE = new Type<>( - CODEC, - "dynamic" - ); - - @Override - public Type getType() { - return TYPE; - } - - @Override - public Optional visit(StringVisitable.Visitor visitor) { - if (!(visitor instanceof TranslatableVisitor translatableVisitor) || translatableVisitor.getArgs().length <= index) - return visitor.accept("{" + index + "}"); - - Object arg = translatableVisitor.getArgs()[index]; - if (arg instanceof Text text) return text.visit(visitor); - return visitor.accept(arg.toString()); - } - - @Override - public Optional visit(StringVisitable.StyledVisitor visitor, Style style) { - if (!(visitor instanceof StyledTranslatableVisitor translatableVisitor) || translatableVisitor.getArgs().length <= index) - return visitor.accept(style, "{" + index + "}"); - - Object arg = translatableVisitor.getArgs()[index]; - if (arg instanceof Text text) return text.visit(visitor, style); - return visitor.accept(style, arg.toString()); - } + public static MapCodec CODEC = RecordCodecBuilder.mapCodec( + instance -> instance.group(Codec.INT.fieldOf("index").forGetter(DynamicTextContent::index)).apply(instance, DynamicTextContent::new) + ); + + public static final TextContent.Type TYPE = new Type<>( + CODEC, + "dynamic" + ); + + @Override + public Type getType() { + return TYPE; + } + + @Override + public Optional visit(StringVisitable.Visitor visitor) { + TranslatableTextContent parent = SpecterSerializationClient.CURRENT_TRANSLATABLE.get().peek(); + if (parent == null || parent.getArgs().length <= index) + return visitor.accept("{" + index + "}"); + + Object arg = parent.getArgs()[index]; + if (arg instanceof Text text) return text.visit(visitor); + return visitor.accept(arg.toString()); + } + + @Override + public Optional visit(StringVisitable.StyledVisitor visitor, Style style) { + TranslatableTextContent parent = SpecterSerializationClient.CURRENT_TRANSLATABLE.get().peek(); + if (parent == null || parent.getArgs().length <= index) + return visitor.accept(style, "{" + index + "}"); + + Object arg = parent.getArgs()[index]; + if (arg instanceof Text text) return text.visit(visitor, style); + return visitor.accept(style, arg.toString()); + } } diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/SpecterSerializationClient.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/SpecterSerializationClient.java index bf05800..5e0c483 100644 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/SpecterSerializationClient.java +++ b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/SpecterSerializationClient.java @@ -1,10 +1,20 @@ package dev.spiritstudios.specter.impl.serialization; +import com.google.common.collect.ImmutableMap; import dev.spiritstudios.specter.api.serialization.text.DynamicTextContent; import dev.spiritstudios.specter.api.serialization.text.TextContentRegistry; import net.fabricmc.api.ClientModInitializer; +import net.minecraft.text.Text; +import net.minecraft.text.TranslatableTextContent; + +import java.util.ArrayDeque; +import java.util.Deque; public class SpecterSerializationClient implements ClientModInitializer { + public static final ThreadLocal> TEXT_TRANSLATIONS_BUILDER = ThreadLocal.withInitial(ImmutableMap.Builder::new); + + public static final ThreadLocal> CURRENT_TRANSLATABLE = ThreadLocal.withInitial(ArrayDeque::new); + @Override public void onInitializeClient() { TextContentRegistry.register("index", DynamicTextContent.TYPE); diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/StyledTranslatableVisitor.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/StyledTranslatableVisitor.java deleted file mode 100644 index 077f7ec..0000000 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/StyledTranslatableVisitor.java +++ /dev/null @@ -1,26 +0,0 @@ -package dev.spiritstudios.specter.impl.serialization.text; - -import net.minecraft.text.StringVisitable; -import net.minecraft.text.Style; - -import java.util.Optional; - -public class StyledTranslatableVisitor implements StringVisitable.StyledVisitor { - private final StringVisitable.StyledVisitor visitor; - private final Object[] args; - - public StyledTranslatableVisitor(StringVisitable.StyledVisitor visitor, Object[] args) { - this.visitor = visitor; - this.args = args; - } - - - public Object[] getArgs() { - return args; - } - - @Override - public Optional accept(Style style, String asString) { - return visitor.accept(style, asString); - } -} diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/TranslatableVisitor.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/TranslatableVisitor.java deleted file mode 100644 index 4a234a6..0000000 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/TranslatableVisitor.java +++ /dev/null @@ -1,25 +0,0 @@ -package dev.spiritstudios.specter.impl.serialization.text; - -import net.minecraft.text.StringVisitable; - -import java.util.Optional; - -public class TranslatableVisitor implements StringVisitable.Visitor { - private final StringVisitable.Visitor visitor; - private final Object[] args; - - public TranslatableVisitor(StringVisitable.Visitor visitor, Object[] args) { - this.visitor = visitor; - this.args = args; - } - - - public Object[] getArgs() { - return args; - } - - @Override - public Optional accept(String asString) { - return visitor.accept(asString); - } -} diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryConsumer.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryConsumer.java deleted file mode 100644 index a35c4a0..0000000 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryConsumer.java +++ /dev/null @@ -1,24 +0,0 @@ -package dev.spiritstudios.specter.impl.serialization.text.smuggler; - -import net.minecraft.text.Text; - -import java.util.function.BiConsumer; - -public class TranslationEntryConsumer implements BiConsumer { - private final BiConsumer stringConsumer; - private final BiConsumer textConsumer; - - public TranslationEntryConsumer(BiConsumer stringConsumer, BiConsumer textConsumer) { - this.stringConsumer = stringConsumer; - this.textConsumer = textConsumer; - } - - @Override - public void accept(String s, String s2) { - stringConsumer.accept(s, s2); - } - - public void accept(String s, Text text) { - textConsumer.accept(s, text); - } -} diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryMap.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryMap.java deleted file mode 100644 index 714c9ab..0000000 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/TranslationEntryMap.java +++ /dev/null @@ -1,21 +0,0 @@ -package dev.spiritstudios.specter.impl.serialization.text.smuggler; - -import com.google.common.collect.ImmutableMap; -import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap; -import net.minecraft.text.Text; - -import java.util.HashMap; -import java.util.Map; - -// This is so hacky and I love it -public class TranslationEntryMap extends HashMap { - private final Map translations = new Object2ObjectOpenHashMap<>(); - - public void put(String s, Text text) { - translations.put(s, text); - } - - public Map translations() { - return ImmutableMap.copyOf(translations); - } -} diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/package-info.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/package-info.java deleted file mode 100644 index eadf39c..0000000 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/impl/serialization/text/smuggler/package-info.java +++ /dev/null @@ -1,4 +0,0 @@ -/** - * This is extremely cursed. Reader beware. - */ -package dev.spiritstudios.specter.impl.serialization.text.smuggler; diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/LanguageMixin.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/LanguageMixin.java index 9ce9b7a..02db895 100644 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/LanguageMixin.java +++ b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/LanguageMixin.java @@ -8,7 +8,7 @@ import com.llamalad7.mixinextras.sugar.Share; import com.llamalad7.mixinextras.sugar.ref.LocalBooleanRef; import com.mojang.serialization.JsonOps; -import dev.spiritstudios.specter.impl.serialization.text.smuggler.TranslationEntryConsumer; +import dev.spiritstudios.specter.impl.serialization.SpecterSerializationClient; import net.minecraft.text.Text; import net.minecraft.text.TextCodecs; import net.minecraft.util.Language; @@ -19,26 +19,23 @@ @Mixin(Language.class) public class LanguageMixin { - @WrapOperation(method = "load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/JsonHelper;asString(Lcom/google/gson/JsonElement;Ljava/lang/String;)Ljava/lang/String;")) - private static String load(JsonElement element, String name, Operation original, @Local(argsOnly = true) BiConsumer entryConsumer, @Share("skip") LocalBooleanRef skip) { - if (element.isJsonPrimitive()) { - skip.set(false); - return original.call(element, name); - } + @WrapOperation(method = "load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/JsonHelper;asString(Lcom/google/gson/JsonElement;Ljava/lang/String;)Ljava/lang/String;")) + private static String load(JsonElement element, String name, Operation original, @Local(argsOnly = true) BiConsumer entryConsumer, @Share("skip") LocalBooleanRef skip) { + if (element.isJsonPrimitive()) { + skip.set(false); + return original.call(element, name); + } - if (!(entryConsumer instanceof TranslationEntryConsumer consumer)) - throw new IllegalStateException("Text entry created without a text consumer"); + Text text = TextCodecs.CODEC.parse(JsonOps.INSTANCE, element).getOrThrow(JsonParseException::new); + SpecterSerializationClient.TEXT_TRANSLATIONS_BUILDER.get().put(name, text); - Text text = TextCodecs.CODEC.parse(JsonOps.INSTANCE, element).getOrThrow(JsonParseException::new); - consumer.accept(name, text); + skip.set(true); + return ""; + } - skip.set(true); - return ""; - } - - @WrapOperation(method = "load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V", at = @At(value = "INVOKE", target = "Ljava/util/function/BiConsumer;accept(Ljava/lang/Object;Ljava/lang/Object;)V")) - private static void skip(BiConsumer instance, T t, U u, Operation original, @Share("skip") LocalBooleanRef skip) { - if (skip.get()) return; - instance.accept(t, u); - } + @WrapOperation(method = "load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V", at = @At(value = "INVOKE", target = "Ljava/util/function/BiConsumer;accept(Ljava/lang/Object;Ljava/lang/Object;)V")) + private static void skip(BiConsumer instance, T t, U u, Operation original, @Share("skip") LocalBooleanRef skip) { + if (skip.get()) return; + instance.accept(t, u); + } } diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslatableTextContentMixin.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslatableTextContentMixin.java index 28dffdf..72ebb44 100644 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslatableTextContentMixin.java +++ b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslatableTextContentMixin.java @@ -1,15 +1,9 @@ package dev.spiritstudios.specter.mixin.serialization.client; import com.google.common.collect.ImmutableList; -import com.llamalad7.mixinextras.injector.wrapoperation.Operation; -import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; -import com.llamalad7.mixinextras.sugar.Share; -import com.llamalad7.mixinextras.sugar.ref.LocalRef; -import dev.spiritstudios.specter.impl.serialization.text.StyledTranslatableVisitor; +import dev.spiritstudios.specter.impl.serialization.SpecterSerializationClient; import dev.spiritstudios.specter.impl.serialization.text.TextTranslationSupplier; -import dev.spiritstudios.specter.impl.serialization.text.TranslatableVisitor; import net.minecraft.text.StringVisitable; -import net.minecraft.text.Style; import net.minecraft.text.Text; import net.minecraft.text.TranslatableTextContent; import net.minecraft.util.Language; @@ -26,46 +20,53 @@ @Mixin(TranslatableTextContent.class) public abstract class TranslatableTextContentMixin { - @Shadow - @Final - private String key; + @Shadow + @Final + private String key; - @Shadow - private List translations; + @Shadow + private List translations; - @Shadow - public abstract Object[] getArgs(); + @Inject( + method = { + "visit(Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;", + "visit(Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;" + }, + at = @At("HEAD") + ) + private void push(CallbackInfoReturnable> cir) { + if (SpecterSerializationClient.CURRENT_TRANSLATABLE.get().contains((TranslatableTextContent) (Object) this)) + throw new IllegalStateException("Detected recursive translation: " + key); - @Inject(method = "updateTranslations", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Language;get(Ljava/lang/String;)Ljava/lang/String;"), cancellable = true) - private void updateTranslations(CallbackInfo ci) { - Language language = Language.getInstance(); - if (!(language instanceof TextTranslationSupplier supplier)) - return; + SpecterSerializationClient.CURRENT_TRANSLATABLE.get().push((TranslatableTextContent) (Object) this); + } - Text text = supplier.specter_serialization$getText(key); - if (text == null) return; - translations = ImmutableList.of(text); - ci.cancel(); - } + @Inject( + method = { + "visit(Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;", + "visit(Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;" + }, + at = @At("RETURN") + ) + private void pop(CallbackInfoReturnable> cir) { + SpecterSerializationClient.CURRENT_TRANSLATABLE.get().pop(); - @Inject(method = "visit(Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/TranslatableTextContent;updateTranslations()V", shift = At.Shift.AFTER)) - private void visit(StringVisitable.Visitor visitor, CallbackInfoReturnable> cir, @Share("translatableVisitor") LocalRef> translatableVisitorRef) { - translatableVisitorRef.set(new TranslatableVisitor<>(visitor, getArgs())); - } + if (SpecterSerializationClient.CURRENT_TRANSLATABLE.get().isEmpty()) + SpecterSerializationClient.CURRENT_TRANSLATABLE.remove(); + } - @Inject(method = "visit(Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/TranslatableTextContent;updateTranslations()V", shift = At.Shift.AFTER)) - private void visit(StringVisitable.StyledVisitor visitor, Style style, CallbackInfoReturnable> cir, @Share("translatableVisitor") LocalRef> translatableVisitorRef) { - translatableVisitorRef.set(new StyledTranslatableVisitor<>(visitor, getArgs())); - } - @WrapOperation(method = "visit(Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/StringVisitable;visit(Lnet/minecraft/text/StringVisitable$Visitor;)Ljava/util/Optional;")) - private Optional visitWrapped(StringVisitable instance, StringVisitable.Visitor tVisitor, Operation> original, @Share("translatableVisitor") LocalRef> translatableVisitorRef) { - return original.call(instance, translatableVisitorRef.get()); - } + @Inject(method = "updateTranslations", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Language;get(Ljava/lang/String;)Ljava/lang/String;"), cancellable = true) + private void updateTranslations(CallbackInfo ci) { + Language language = Language.getInstance(); + if (!(language instanceof TextTranslationSupplier supplier)) + return; - @WrapOperation(method = "visit(Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;", at = @At(value = "INVOKE", target = "Lnet/minecraft/text/StringVisitable;visit(Lnet/minecraft/text/StringVisitable$StyledVisitor;Lnet/minecraft/text/Style;)Ljava/util/Optional;")) - private Optional visitWrapped(StringVisitable instance, StringVisitable.StyledVisitor tStyledVisitor, Style style, Operation> original, @Share("translatableVisitor") LocalRef> translatableVisitorRef) { - return original.call(instance, translatableVisitorRef.get(), style); - } + Text text = supplier.specter_serialization$getText(key); + if (text == null) return; + + translations = ImmutableList.of(text); + ci.cancel(); + } } diff --git a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslationStorageMixin.java b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslationStorageMixin.java index 4038727..741e9f2 100644 --- a/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslationStorageMixin.java +++ b/specter-serialization/src/client/java/dev/spiritstudios/specter/mixin/serialization/client/TranslationStorageMixin.java @@ -4,55 +4,30 @@ import com.llamalad7.mixinextras.injector.wrapoperation.Operation; import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation; import com.llamalad7.mixinextras.sugar.Local; +import dev.spiritstudios.specter.impl.serialization.SpecterSerializationClient; import dev.spiritstudios.specter.impl.serialization.text.TextTranslationSupplier; -import dev.spiritstudios.specter.impl.serialization.text.smuggler.TranslationEntryConsumer; -import dev.spiritstudios.specter.impl.serialization.text.smuggler.TranslationEntryMap; import net.minecraft.client.resource.language.TranslationStorage; import net.minecraft.text.Text; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Unique; import org.spongepowered.asm.mixin.injection.At; -import org.spongepowered.asm.mixin.injection.Redirect; -import java.io.InputStream; -import java.util.HashMap; import java.util.Map; -import java.util.function.BiConsumer; @Mixin(TranslationStorage.class) public class TranslationStorageMixin implements TextTranslationSupplier { @Unique - public Map textTranslations; - - /** - * @author CallMeEcho - * @reason No way to do this without overriding the method - */ - @Redirect(method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", at = @At(value = "INVOKE", target = "Lcom/google/common/collect/Maps;newHashMap()Ljava/util/HashMap;", remap = false)) - private static HashMap load() { - return new TranslationEntryMap<>(); - } + private Map textTranslations; @WrapOperation(method = "load(Lnet/minecraft/resource/ResourceManager;Ljava/util/List;Z)Lnet/minecraft/client/resource/language/TranslationStorage;", at = @At(value = "NEW", target = "(Ljava/util/Map;Z)Lnet/minecraft/client/resource/language/TranslationStorage;")) private static TranslationStorage skipImmutable(Map translations, boolean rightToLeft, Operation original, @Local Map map) { - if (!(map instanceof TranslationEntryMap translationEntryMap)) - return original.call(translations, rightToLeft); + TranslationStorage storage = original.call(map, rightToLeft); + ((TranslationStorageMixin) (Object) storage).textTranslations = SpecterSerializationClient.TEXT_TRANSLATIONS_BUILDER.get().build(); - TranslationStorage storage = original.call(translationEntryMap, rightToLeft); - ((TranslationStorageMixin) (Object) storage).textTranslations = translationEntryMap.translations(); + SpecterSerializationClient.TEXT_TRANSLATIONS_BUILDER.remove(); return storage; } - @WrapOperation(method = "load(Ljava/lang/String;Ljava/util/List;Ljava/util/Map;)V", at = @At(value = "INVOKE", target = "Lnet/minecraft/util/Language;load(Ljava/io/InputStream;Ljava/util/function/BiConsumer;)V")) - private static void load(InputStream inputStream, BiConsumer entryConsumer, Operation original, @Local(argsOnly = true) Map translations) { - if (!(translations instanceof TranslationEntryMap translationEntryMap)) { - original.call(inputStream, entryConsumer); - return; - } - - original.call(inputStream, new TranslationEntryConsumer(entryConsumer, translationEntryMap::put)); - } - @ModifyReturnValue(method = "hasTranslation", at = @At("RETURN")) private boolean hasTranslation(boolean original, @Local(argsOnly = true) String key) { if (textTranslations == null) return original;