From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001 From: Riley Park Date: Fri, 29 Jan 2021 17:54:03 +0100 Subject: [PATCH] Adventure == AT == public net.minecraft.network.chat.HoverEvent$ItemStackInfo item public net.minecraft.network.chat.HoverEvent$ItemStackInfo count public net.minecraft.network.chat.HoverEvent$ItemStackInfo tag public net.minecraft.network.chat.contents.TranslatableContents filterAllowedArguments(Ljava/lang/Object;)Lcom/mojang/serialization/DataResult; Co-authored-by: zml Co-authored-by: Jake Potrebic diff --git a/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java new file mode 100644 index 0000000000000000000000000000000000000000..b33e394d88517c7afc2f549bae5a2063316ada73 --- /dev/null +++ b/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java @@ -0,0 +1,458 @@ +package io.papermc.paper.adventure; + +import com.google.gson.JsonElement; +import com.google.gson.JsonParser; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +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.Encoder; +import com.mojang.serialization.JsonOps; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.netty.buffer.ByteBuf; +import io.netty.handler.codec.DecoderException; +import io.netty.handler.codec.EncoderException; +import java.io.IOException; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.UUID; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.BlockNBTComponent; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ComponentLike; +import net.kyori.adventure.text.EntityNBTComponent; +import net.kyori.adventure.text.KeybindComponent; +import net.kyori.adventure.text.NBTComponent; +import net.kyori.adventure.text.NBTComponentBuilder; +import net.kyori.adventure.text.ScoreComponent; +import net.kyori.adventure.text.SelectorComponent; +import net.kyori.adventure.text.StorageNBTComponent; +import net.kyori.adventure.text.TextComponent; +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.TranslationArgument; +import net.kyori.adventure.text.event.ClickEvent; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.format.NamedTextColor; +import net.kyori.adventure.text.format.Style; +import net.kyori.adventure.text.format.TextColor; +import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.minecraft.core.UUIDUtil; +import net.minecraft.core.registries.BuiltInRegistries; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.NbtAccounter; +import net.minecraft.nbt.NbtOps; +import net.minecraft.nbt.Tag; +import net.minecraft.nbt.TagParser; +import net.minecraft.network.RegistryFriendlyByteBuf; +import net.minecraft.network.chat.ComponentSerialization; +import net.minecraft.network.chat.contents.KeybindContents; +import net.minecraft.network.chat.contents.ScoreContents; +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.StringRepresentable; +import net.minecraft.world.item.Item; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.checkerframework.checker.nullness.qual.NonNull; +import org.checkerframework.checker.nullness.qual.Nullable; +import org.checkerframework.framework.qual.DefaultQualifier; +import org.intellij.lang.annotations.Subst; + +import static com.mojang.serialization.codecs.RecordCodecBuilder.mapCodec; +import static java.util.function.Function.identity; +import static net.kyori.adventure.text.Component.text; +import static net.kyori.adventure.text.TranslationArgument.bool; +import static net.kyori.adventure.text.TranslationArgument.component; +import static net.kyori.adventure.text.TranslationArgument.numeric; +import static com.mojang.serialization.Codec.recursive; + +@DefaultQualifier(NonNull.class) +public final class AdventureCodecs { + + public static final Codec COMPONENT_CODEC = recursive("adventure Component", AdventureCodecs::createCodec); + public static final StreamCodec STREAM_COMPONENT_CODEC = ByteBufCodecs.fromCodecWithRegistriesTrusted(COMPONENT_CODEC); + + static final Codec TEXT_COLOR_CODEC = Codec.STRING.comapFlatMap(s -> { + if (s.startsWith("#")) { + @Nullable TextColor value = TextColor.fromHexString(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure TextColor"); + } else { + final @Nullable NamedTextColor value = NamedTextColor.NAMES.value(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure NamedTextColor"); + } + }, textColor -> { + if (textColor instanceof NamedTextColor named) { + return NamedTextColor.NAMES.keyOrThrow(named); + } else { + return textColor.asHexString(); + } + }); + + static final Codec KEY_CODEC = Codec.STRING.comapFlatMap(s -> { + return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key"); + }, Key::asString); + + static final Codec CLICK_EVENT_ACTION_CODEC = Codec.STRING.comapFlatMap(s -> { + final ClickEvent.@Nullable Action value = ClickEvent.Action.NAMES.value(s); + return value != null ? DataResult.success(value) : DataResult.error(() -> "Cannot convert " + s + " to adventure ClickEvent$Action"); + }, ClickEvent.Action.NAMES::keyOrThrow); + static final Codec CLICK_EVENT_CODEC = RecordCodecBuilder.create((instance) -> { + return instance.group( + CLICK_EVENT_ACTION_CODEC.fieldOf("action").forGetter(ClickEvent::action), + Codec.STRING.fieldOf("value").forGetter(ClickEvent::value) + ).apply(instance, ClickEvent::clickEvent); + }); + + static Codec showEntityCodec(final Codec componentCodec) { + return RecordCodecBuilder.create((instance) -> { + return instance.group( + KEY_CODEC.fieldOf("type").forGetter(HoverEvent.ShowEntity::type), + UUIDUtil.LENIENT_CODEC.fieldOf("id").forGetter(HoverEvent.ShowEntity::id), + componentCodec.lenientOptionalFieldOf("name").forGetter(he -> Optional.ofNullable(he.name())) + ).apply(instance, (key, uuid, component) -> { + return HoverEvent.ShowEntity.showEntity(key, uuid, component.orElse(null)); + }); + }); + } + + static Codec showItemCodec(final Codec componentCodec) { + return net.minecraft.network.chat.HoverEvent.ItemStackInfo.CODEC.xmap(isi -> { + @Subst("key") final String typeKey = isi.item.unwrapKey().orElseThrow().toString(); + return HoverEvent.ShowItem.showItem(Key.key(typeKey), isi.count, PaperAdventure.asBinaryTagHolder(isi.tag.orElse(null))); + }, si -> { + final Item itemType = BuiltInRegistries.ITEM.get(PaperAdventure.asVanilla(si.item())); + final ItemStack stack; + try { + final @Nullable CompoundTag tag = si.nbt() != null ? si.nbt().get(PaperAdventure.NBT_CODEC) : null; + stack = new ItemStack(BuiltInRegistries.ITEM.wrapAsHolder(itemType), si.count(), Optional.ofNullable(tag)); + } catch (final IOException e) { + throw new RuntimeException(e); + } + return new net.minecraft.network.chat.HoverEvent.ItemStackInfo(stack); + }); + } + + static final HoverEventType SHOW_ENTITY_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showEntityCodec, HoverEvent.Action.SHOW_ENTITY, "show_entity", AdventureCodecs::legacyDeserializeEntity); + static final HoverEventType SHOW_ITEM_HOVER_EVENT_TYPE = new HoverEventType<>(AdventureCodecs::showItemCodec, HoverEvent.Action.SHOW_ITEM, "show_item", AdventureCodecs::legacyDeserializeItem); + static final HoverEventType SHOW_TEXT_HOVER_EVENT_TYPE = new HoverEventType<>(identity(), HoverEvent.Action.SHOW_TEXT, "show_text", DataResult::success); + static final Codec> HOVER_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new HoverEventType[]{ SHOW_ENTITY_HOVER_EVENT_TYPE, SHOW_ITEM_HOVER_EVENT_TYPE, SHOW_TEXT_HOVER_EVENT_TYPE }); + + static DataResult legacyDeserializeEntity(final Component component, final @Nullable RegistryOps ops, final Codec componentCodec) { + try { + final CompoundTag tag = TagParser.parseTag(PlainTextComponentSerializer.plainText().serialize(component)); + final DynamicOps dynamicOps = ops != null ? ops.withParent(JsonOps.INSTANCE) : JsonOps.INSTANCE; + final DataResult entityNameResult = componentCodec.parse(dynamicOps, JsonParser.parseString(tag.getString("name"))); + @Subst("key") final String keyString = tag.getString("type"); + final UUID entityUUID = UUID.fromString(tag.getString("id")); + return entityNameResult.map(name -> HoverEvent.ShowEntity.showEntity(Key.key(keyString), entityUUID, name)); + } catch (final Exception ex) { + return DataResult.error(() -> "Failed to parse tooltip: " + ex.getMessage()); + } + } + + static DataResult legacyDeserializeItem(final Component component, final @Nullable RegistryOps ops, final Codec componentCodec) { + try { + final CompoundTag tag = TagParser.parseTag(PlainTextComponentSerializer.plainText().serialize(component)); + final DynamicOps dynamicOps = ops != null ? ops.withParent(NbtOps.INSTANCE) : NbtOps.INSTANCE; + final DataResult stackResult = ItemStack.CODEC.parse(dynamicOps, tag).map(CraftItemStack::asCraftMirror); + return stackResult.map(stack -> { + return HoverEvent.ShowItem.showItem(stack.getType().key(), stack.getAmount(), /* TODO */); + }); + } catch (final CommandSyntaxException | IOException ex) { + return DataResult.error(() -> "Failed to parse item tag: " + ex.getMessage()); + } + } + + @FunctionalInterface + interface LegacyDeserializer { + DataResult apply(Component component, @Nullable RegistryOps ops, Codec componentCodec); + } + + record HoverEventType(Function, MapCodec>> codec, String id, Function, MapCodec>> legacyCodec) implements StringRepresentable { + HoverEventType(final Function, Codec> contentCodec, final HoverEvent.Action action, final String id, final LegacyDeserializer legacyDeserializer) { + this(cc -> contentCodec.apply(cc).xmap(v -> HoverEvent.hoverEvent(action, v), HoverEvent::value).fieldOf("contents"), + id, + codec -> (new Codec>() { + public DataResult, D>> decode(final DynamicOps dynamicOps, final D object) { + return codec.decode(dynamicOps, object).flatMap(pair -> { + final DataResult dataResult; + if (dynamicOps instanceof final RegistryOps registryOps) { + dataResult = legacyDeserializer.apply(pair.getFirst(), registryOps, codec); + } else { + dataResult = legacyDeserializer.apply(pair.getFirst(), null, codec); + } + + return dataResult.map(value -> Pair.of(HoverEvent.hoverEvent(action, value), pair.getSecond())); + }); + } + + public DataResult encode(final HoverEvent hoverEvent, final DynamicOps dynamicOps, final D object) { + return DataResult.error(() -> "Can't encode in legacy format"); + } + }).fieldOf("value") + ); + } + @Override + public String getSerializedName() { + return this.id; + } + } + + private static final Function, HoverEventType> GET_HOVER_EVENT_TYPE = he -> { + if (he.action() == HoverEvent.Action.SHOW_ENTITY) { + return SHOW_ENTITY_HOVER_EVENT_TYPE; + } else if (he.action() == HoverEvent.Action.SHOW_ITEM) { + return SHOW_ITEM_HOVER_EVENT_TYPE; + } else if (he.action() == HoverEvent.Action.SHOW_TEXT) { + return SHOW_TEXT_HOVER_EVENT_TYPE; + } else { + throw new IllegalStateException(); + } + }; + static final Codec> HOVER_EVENT_CODEC = Codec.withAlternative( + HOVER_EVENT_TYPE_CODEC.>dispatchMap("action", GET_HOVER_EVENT_TYPE, het -> het.codec.apply(COMPONENT_CODEC)).codec(), + HOVER_EVENT_TYPE_CODEC.>dispatchMap("action", GET_HOVER_EVENT_TYPE, het -> het.legacyCodec.apply(COMPONENT_CODEC)).codec() + ); + + public static final MapCodec