package io.papermc.paper.util; import com.mojang.datafixers.util.Pair; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.Decoder; import com.mojang.serialization.DynamicOps; import com.mojang.serialization.MapCodec; import com.mojang.serialization.RecordBuilder; import com.mojang.serialization.codecs.OptionalFieldCodec; import io.papermc.paper.adventure.AdventureCodecs; import io.papermc.paper.registry.RegistryAccess; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import java.util.Optional; import java.util.function.Function; import net.kyori.adventure.key.Key; import net.kyori.adventure.util.TriState; import net.minecraft.core.Holder; import net.minecraft.resources.RegistryOps; import org.bukkit.Keyed; import org.bukkit.Registry; import org.jspecify.annotations.NullMarked; @NullMarked public final class PaperCodecs { public static final Codec TRI_STATE_CODEC = Codec.STRING.comapFlatMap(s -> { try { return DataResult.success(TriState.valueOf(s)); } catch (Exception e) { return DataResult.error(() -> "Unknown TriState value: " + s); } }, TriState::name); /** * This codec is lenient on decoding and encoding compared to native OptionalFieldCodec * which only has options to be lenient on decoding. */ public static MapCodec> lenientCodec(final String name, final Codec codec) { return new OptionalFieldCodec<>(name, codec, true) { @Override public RecordBuilder encode(final Optional input, final DynamicOps ops, final RecordBuilder prefix) { if (input.isPresent()) { final DataResult result = codec.encodeStart(ops, input.get()); if (!result.isError()) { return prefix.add(name, result); } } return prefix; } }; } public static Decoder registryFileDecoderFor(final Decoder directNmsDecoder, final Function, A> directHolderConverter, final RegistryKey registryKey, final boolean allowInline) { // TODO remove Keyed final Decoder.Terminal terminalDecoder = directNmsDecoder.map(nms -> directHolderConverter.apply(Holder.direct(nms))).terminal(); return Decoder.ofTerminal(new Decoder.Terminal<>() { @Override public DataResult decode(final DynamicOps ops, final T input) { // logic based on RegistryFileCodec if (ops instanceof RegistryOps) { // Pretty sure we can just use our RegistryAccess here. These codecs aren't ever // used for deserialization, so we don't need to rely on different implementations // of HolderGetter like vanilla's RegistryFileCodec does. We are always just // getting existing dialog elements, never creating empty reference holders. final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); final DataResult> keyDataResult = AdventureCodecs.KEY_CODEC.decode(ops, input); if (keyDataResult.result().isEmpty()) { return !allowInline ? DataResult.error(() -> "Inline definitions not allowed here") : terminalDecoder.decode(ops, input); } final Pair pair = keyDataResult.result().get(); final TypedKey elementKey = TypedKey.create(registryKey, pair.getFirst()); final A value = registry.get(elementKey); if (value == null) { return DataResult.error(() -> "Failed to get element " + elementKey); } return DataResult.success(value); } else { return terminalDecoder.decode(ops, input); } } }); } private PaperCodecs() { } }