package net.minecraft.network.chat; import com.google.gson.JsonElement; import com.mojang.datafixers.util.Either; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.JsonOps; import com.mojang.serialization.MapCodec; import com.mojang.serialization.MapDecoder; import com.mojang.serialization.MapEncoder; import com.mojang.serialization.MapLike; import com.mojang.serialization.RecordBuilder; import com.mojang.serialization.codecs.RecordCodecBuilder; import io.netty.buffer.ByteBuf; import java.util.List; import java.util.Optional; import java.util.function.Function; import java.util.stream.Stream; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.contents.KeybindContents; import net.minecraft.network.chat.contents.NbtContents; import net.minecraft.network.chat.contents.PlainTextContents; import net.minecraft.network.chat.contents.ScoreContents; import net.minecraft.network.chat.contents.SelectorContents; import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; import net.minecraft.resources.RegistryOps; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.GsonHelper; import net.minecraft.util.StringRepresentable; 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> OPTIONAL_STREAM_CODEC = STREAM_CODEC.apply(ByteBufCodecs::optional); public static final StreamCodec TRUSTED_STREAM_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(CODEC); public static final StreamCodec> TRUSTED_OPTIONAL_STREAM_CODEC = TRUSTED_STREAM_CODEC.apply( ByteBufCodecs::optional ); public static final StreamCodec TRUSTED_CONTEXT_FREE_STREAM_CODEC = ByteBufCodecs.fromCodecTrusted(CODEC); public static Codec flatRestrictedCodec(int maxSize) { return new Codec() { @Override public DataResult> decode(DynamicOps dynamicOps, T object) { return ComponentSerialization.CODEC .decode(dynamicOps, object) .flatMap( pair -> this.isTooLarge(dynamicOps, (Component)pair.getFirst()) ? DataResult.error(() -> "Component was too large: greater than max size " + maxSize) : DataResult.success(pair) ); } public DataResult encode(Component input, DynamicOps ops, T value) { return ComponentSerialization.CODEC.encodeStart(ops, input); } private boolean isTooLarge(DynamicOps ops, Component component) { DataResult dataResult = ComponentSerialization.CODEC.encodeStart(asJsonOps(ops), component); return dataResult.isSuccess() && GsonHelper.encodesLongerThan(dataResult.getOrThrow(), maxSize); } private static DynamicOps asJsonOps(DynamicOps ops) { return (DynamicOps)(ops instanceof RegistryOps registryOps ? registryOps.withParent(JsonOps.INSTANCE) : JsonOps.INSTANCE); } }; } private static MutableComponent createFromList(List components) { MutableComponent mutableComponent = ((Component)components.get(0)).copy(); for (int i = 1; i < components.size(); i++) { mutableComponent.append((Component)components.get(i)); } return mutableComponent; } public static MapCodec createLegacyComponentMatcher( T[] types, Function> codecGetter, Function typeGetter, String typeFieldName ) { MapCodec mapCodec = new ComponentSerialization.FuzzyCodec( Stream.of(types).map(codecGetter).toList(), object -> (MapEncoder)codecGetter.apply((StringRepresentable)typeGetter.apply(object)) ); Codec codec = StringRepresentable.fromValues(() -> types); MapCodec mapCodec2 = codec.dispatchMap(typeFieldName, typeGetter, codecGetter); MapCodec mapCodec3 = new ComponentSerialization.StrictEither<>(typeFieldName, mapCodec2, mapCodec); return ExtraCodecs.orCompressed(mapCodec3, mapCodec2); } private static Codec createCodec(Codec codec) { ComponentContents.Type[] types = new ComponentContents.Type[]{ PlainTextContents.TYPE, TranslatableContents.TYPE, KeybindContents.TYPE, ScoreContents.TYPE, SelectorContents.TYPE, NbtContents.TYPE }; MapCodec mapCodec = createLegacyComponentMatcher(types, ComponentContents.Type::codec, ComponentContents::type, "type"); Codec codec2 = RecordCodecBuilder.create( instance -> instance.group( mapCodec.forGetter(Component::getContents), ExtraCodecs.nonEmptyList(codec.listOf()).optionalFieldOf("extra", List.of()).forGetter(Component::getSiblings), Style.Serializer.MAP_CODEC.forGetter(Component::getStyle) ) .apply(instance, MutableComponent::new) ); return Codec.either(Codec.either(Codec.STRING, ExtraCodecs.nonEmptyList(codec.listOf())), codec2) .xmap(either -> either.map(eitherx -> eitherx.map(Component::literal, ComponentSerialization::createFromList), component -> component), component -> { String string = component.tryCollapseToString(); return string != null ? Either.left(Either.left(string)) : Either.right(component); }); } static class FuzzyCodec extends MapCodec { private final List> codecs; private final Function> encoderGetter; public FuzzyCodec(List> codecs, Function> encoderGetter) { this.codecs = codecs; this.encoderGetter = encoderGetter; } @Override public DataResult decode(DynamicOps dynamicOps, MapLike mapLike) { for (MapDecoder mapDecoder : this.codecs) { DataResult dataResult = mapDecoder.decode(dynamicOps, mapLike); if (dataResult.result().isPresent()) { return (DataResult)dataResult; } } return DataResult.error(() -> "No matching codec found"); } @Override public RecordBuilder encode(T object, DynamicOps dynamicOps, RecordBuilder recordBuilder) { MapEncoder mapEncoder = (MapEncoder)this.encoderGetter.apply(object); return mapEncoder.encode(object, dynamicOps, recordBuilder); } @Override public Stream keys(DynamicOps dynamicOps) { return this.codecs.stream().flatMap(mapCodec -> mapCodec.keys(dynamicOps)).distinct(); } public String toString() { return "FuzzyCodec[" + this.codecs + "]"; } } static class StrictEither extends MapCodec { private final String typeFieldName; private final MapCodec typed; private final MapCodec fuzzy; public StrictEither(String typeFieldName, MapCodec typed, MapCodec fuzzy) { this.typeFieldName = typeFieldName; this.typed = typed; this.fuzzy = fuzzy; } @Override public DataResult decode(DynamicOps dynamicOps, MapLike mapLike) { return mapLike.get(this.typeFieldName) != null ? this.typed.decode(dynamicOps, mapLike) : this.fuzzy.decode(dynamicOps, mapLike); } @Override public RecordBuilder encode(T object, DynamicOps dynamicOps, RecordBuilder recordBuilder) { return this.fuzzy.encode(object, dynamicOps, recordBuilder); } @Override public Stream keys(DynamicOps dynamicOps) { return Stream.concat(this.typed.keys(dynamicOps), this.fuzzy.keys(dynamicOps)).distinct(); } } }