--- a/net/minecraft/network/chat/ComponentSerialization.java +++ b/net/minecraft/network/chat/ComponentSerialization.java @@ -37,9 +37,31 @@ public class ComponentSerialization { public static final Codec CODEC = Codec.recursive("Component", ComponentSerialization::createCodec); - public static final StreamCodec STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistries(CODEC); + public static final StreamCodec STREAM_CODEC = createTranslationAware(() -> net.minecraft.nbt.NbtAccounter.create(net.minecraft.network.FriendlyByteBuf.DEFAULT_NBT_QUOTA)); // Paper - adventure public static final StreamCodec> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional); - public static final StreamCodec TRUSTED_STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(CODEC); + // Paper start - adventure; use locale from bytebuf for translation + public static final ThreadLocal DONT_RENDER_TRANSLATABLES = ThreadLocal.withInitial(() -> false); + public static final StreamCodec TRUSTED_STREAM_CODEC = createTranslationAware(net.minecraft.nbt.NbtAccounter::unlimitedHeap); + private static StreamCodec createTranslationAware(final Supplier sizeTracker) { + return new StreamCodec<>() { + final StreamCodec streamCodec = ByteBufCodecs.tagCodec(sizeTracker); + @Override + public Component decode(RegistryFriendlyByteBuf registryFriendlyByteBuf) { + net.minecraft.nbt.Tag tag = this.streamCodec.decode(registryFriendlyByteBuf); + RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); + return CODEC.parse(registryOps, tag).getOrThrow(error -> new io.netty.handler.codec.DecoderException("Failed to decode: " + error + " " + tag)); + } + + @Override + public void encode(RegistryFriendlyByteBuf registryFriendlyByteBuf, Component object) { + RegistryOps registryOps = registryFriendlyByteBuf.registryAccess().createSerializationContext(net.minecraft.nbt.NbtOps.INSTANCE); + net.minecraft.nbt.Tag tag = (DONT_RENDER_TRANSLATABLES.get() ? CODEC : ComponentSerialization.localizedCodec(registryFriendlyByteBuf.adventure$locale)) + .encodeStart(registryOps, object).getOrThrow(error -> new io.netty.handler.codec.EncoderException("Failed to encode: " + error + " " + object)); + this.streamCodec.encode(registryFriendlyByteBuf, tag); + } + }; + } + // Paper end - adventure; use locale from bytebuf for translation public static final StreamCodec> TRUSTED_OPTIONAL_STREAM_CODEC = TRUSTED_STREAM_CODEC.apply( ByteBufCodecs::optional ); @@ -100,7 +122,27 @@ return ExtraCodecs.orCompressed(mapCodec3, mapCodec2); } + // Paper start - adventure; create separate codec for each locale + private static final java.util.Map> LOCALIZED_CODECS = new java.util.concurrent.ConcurrentHashMap<>(); + + public static Codec localizedCodec(final java.util.@org.checkerframework.checker.nullness.qual.Nullable Locale locale) { + if (locale == null) { + return CODEC; + } + return LOCALIZED_CODECS.computeIfAbsent(locale, + loc -> Codec.recursive("Component", selfCodec -> createCodec(selfCodec, loc))); + } + + + // Paper end - adventure; create separate codec for each locale + private static Codec createCodec(Codec selfCodec) { + // Paper start - adventure; create separate codec for each locale + return createCodec(selfCodec, null); + } + + private static Codec createCodec(Codec selfCodec, @javax.annotation.Nullable java.util.Locale locale) { + // Paper end - adventure; create separate codec for each locale ComponentContents.Type[] types = new ComponentContents.Type[]{ PlainTextContents.TYPE, TranslatableContents.TYPE, KeybindContents.TYPE, ScoreContents.TYPE, SelectorContents.TYPE, NbtContents.TYPE }; @@ -113,6 +155,34 @@ ) .apply(instance, MutableComponent::new) ); + // Paper start - adventure; create separate codec for each locale + final Codec origCodec = codec; + codec = new Codec<>() { + @Override + public DataResult> decode(final DynamicOps ops, final T input) { + return origCodec.decode(ops, input); + } + + @Override + public DataResult encode(final Component input, final DynamicOps ops, final T prefix) { + final net.kyori.adventure.text.Component adventureComponent; + if (input instanceof io.papermc.paper.adventure.AdventureComponent adv) { + adventureComponent = adv.adventure$component(); + } else if (locale != null && input.getContents() instanceof TranslatableContents && io.papermc.paper.adventure.PaperAdventure.hasAnyTranslations()) { + adventureComponent = io.papermc.paper.adventure.PaperAdventure.asAdventure(input); + } else { + return origCodec.encode(input, ops, prefix); + } + return io.papermc.paper.adventure.PaperAdventure.localizedCodec(locale) + .encode(adventureComponent, ops, prefix); + } + + @Override + public String toString() { + return origCodec.toString() + "[AdventureComponentAware]"; + } + }; + // Paper end - adventure; create separate codec for each locale return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(selfCodec.listOf())), codec) .xmap(either -> either.map(either2 -> either2.map(Component::literal, ComponentSerialization::createFromList), text -> (Component)text), text -> { String string = text.tryCollapseToString();