diff --git a/src/main/java/net/minestom/server/adventure/provider/MinestomFlattenerProvider.java b/src/main/java/net/minestom/server/adventure/provider/MinestomFlattenerProvider.java new file mode 100644 index 000000000..d5c47d09f --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/provider/MinestomFlattenerProvider.java @@ -0,0 +1,22 @@ +package net.minestom.server.adventure.provider; + +import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.flattener.ComponentFlattener; +import net.kyori.adventure.translation.GlobalTranslator; +import net.minestom.server.adventure.MinestomAdventure; + +final class MinestomFlattenerProvider { + static final ComponentFlattener INSTANCE; + static { + final ComponentFlattener.Builder builder = ComponentFlattener.basic().toBuilder(); + + // handle server-side translations if needed + builder.complexMapper(TranslatableComponent.class, ((component, consumer) -> { + if (MinestomAdventure.AUTOMATIC_COMPONENT_TRANSLATION) { + consumer.accept(GlobalTranslator.render(component, MinestomAdventure.getDefaultLocale())); + } + })); + + INSTANCE = builder.build(); + } +} diff --git a/src/main/java/net/minestom/server/adventure/provider/MinestomGsonComponentSerializerProvider.java b/src/main/java/net/minestom/server/adventure/provider/MinestomGsonComponentSerializerProvider.java new file mode 100644 index 000000000..e5edc3d63 --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/provider/MinestomGsonComponentSerializerProvider.java @@ -0,0 +1,29 @@ +package net.minestom.server.adventure.provider; + +import java.util.function.Consumer; + +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("UnstableApiUsage") // we are permitted to provide this +public final class MinestomGsonComponentSerializerProvider implements GsonComponentSerializer.Provider { + @Override + public @NotNull GsonComponentSerializer gson() { + return GsonComponentSerializer.builder() + .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) + .build(); + } + + @Override + public @NotNull GsonComponentSerializer gsonLegacy() { + return GsonComponentSerializer.builder() + .legacyHoverEventSerializer(NBTLegacyHoverEventSerializer.INSTANCE) + .downsampleColors() + .build(); + } + + @Override + public @NotNull Consumer builder() { + return builder -> {}; // we don't need to touch the builder here + } +} diff --git a/src/main/java/net/minestom/server/adventure/provider/MinestomLegacyComponentSerializerProvider.java b/src/main/java/net/minestom/server/adventure/provider/MinestomLegacyComponentSerializerProvider.java new file mode 100644 index 000000000..89d514b9e --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/provider/MinestomLegacyComponentSerializerProvider.java @@ -0,0 +1,31 @@ +package net.minestom.server.adventure.provider; + +import java.util.function.Consumer; + +import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("UnstableApiUsage") // we are permitted to provide this +public final class MinestomLegacyComponentSerializerProvider implements LegacyComponentSerializer.Provider { + @Override + public @NotNull LegacyComponentSerializer legacyAmpersand() { + return LegacyComponentSerializer.builder() + .character(LegacyComponentSerializer.AMPERSAND_CHAR) + .flattener(MinestomFlattenerProvider.INSTANCE) + .build(); + } + + @Override + public @NotNull LegacyComponentSerializer legacySection() { + return LegacyComponentSerializer.builder() + .character(LegacyComponentSerializer.SECTION_CHAR) + .flattener(MinestomFlattenerProvider.INSTANCE) + .build(); + } + + @Override + public @NotNull Consumer legacy() { + // we will provide our flattener to allow for custom translations/etc + return builder -> builder.flattener(MinestomFlattenerProvider.INSTANCE); + } +} diff --git a/src/main/java/net/minestom/server/adventure/provider/MinestomPlainTextComponentSerializerProvider.java b/src/main/java/net/minestom/server/adventure/provider/MinestomPlainTextComponentSerializerProvider.java new file mode 100644 index 000000000..85ec04fa6 --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/provider/MinestomPlainTextComponentSerializerProvider.java @@ -0,0 +1,22 @@ +package net.minestom.server.adventure.provider; + +import java.util.function.Consumer; + +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import org.jetbrains.annotations.NotNull; + +@SuppressWarnings("UnstableApiUsage") // we are permitted to provide this +public final class MinestomPlainTextComponentSerializerProvider implements PlainTextComponentSerializer.Provider { + @Override + public @NotNull PlainTextComponentSerializer plainTextSimple() { + return PlainTextComponentSerializer.builder() + .flattener(MinestomFlattenerProvider.INSTANCE) + .build(); + } + + @Override + public @NotNull Consumer plainText() { + // we will provide our flattener to allow for custom translations/etc + return builder -> builder.flattener(MinestomFlattenerProvider.INSTANCE); + } +} diff --git a/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java b/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java new file mode 100644 index 000000000..a69f59669 --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/provider/NBTLegacyHoverEventSerializer.java @@ -0,0 +1,101 @@ +package net.minestom.server.adventure.provider; + +import java.util.UUID; + +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.HoverEvent; +import net.kyori.adventure.text.serializer.gson.LegacyHoverEventSerializer; +import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer; +import net.kyori.adventure.util.Codec; +import net.minestom.server.adventure.MinestomAdventure; +import org.jetbrains.annotations.NotNull; +import org.jglrxavpok.hephaistos.nbt.NBT; +import org.jglrxavpok.hephaistos.nbt.NBTCompound; +import org.jglrxavpok.hephaistos.nbt.NBTException; + +import java.io.IOException; +import java.util.Objects; + +final class NBTLegacyHoverEventSerializer implements LegacyHoverEventSerializer { + static final NBTLegacyHoverEventSerializer INSTANCE = new NBTLegacyHoverEventSerializer(); + + private static final String ITEM_TYPE = "id", ITEM_COUNT = "Count", ITEM_TAG = "tag"; + private static final String ENTITY_TYPE = "type", ENTITY_NAME = "name", ENTITY_ID = "id"; + + private NBTLegacyHoverEventSerializer() { + } + + @Override + public HoverEvent.@NotNull ShowItem deserializeShowItem(@NotNull Component input) throws IOException { + final String raw = PlainTextComponentSerializer.plainText().serialize(input); + try { + // attempt the parse + final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw); + if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound"); + final NBTCompound contents = (NBTCompound) nbt, tag = contents.getCompound(ITEM_TAG); + + // create the event + return HoverEvent.ShowItem.of( + Key.key(Objects.requireNonNullElse(contents.getString(ITEM_TYPE), "")), + Objects.requireNonNullElse(contents.getByte(ITEM_COUNT), (byte) 1), + tag == null ? null : BinaryTagHolder.encode(tag, MinestomAdventure.NBT_CODEC) + ); + } catch (final NBTException e) { + throw new IOException(e); + } + } + + @Override + public HoverEvent.@NotNull ShowEntity deserializeShowEntity(@NotNull Component input, Codec.Decoder componentDecoder) throws IOException { + final String raw = PlainTextComponentSerializer.plainText().serialize(input); + + try { + final NBT nbt = MinestomAdventure.NBT_CODEC.decode(raw); + if (!(nbt instanceof NBTCompound)) throw new IOException("contents were not a compound"); + + final NBTCompound contents = (NBTCompound) nbt; + + return HoverEvent.ShowEntity.of( + Key.key(Objects.requireNonNullElse(contents.getString(ENTITY_TYPE), "")), + UUID.fromString(Objects.requireNonNullElse(contents.getString(ENTITY_ID), "")), + componentDecoder.decode(Objects.requireNonNullElse(contents.getString(ENTITY_NAME), "")) + ); + } catch (NBTException e) { + throw new IOException(e); + } + } + + @Override + public @NotNull Component serializeShowItem(HoverEvent.@NotNull ShowItem input) throws IOException { + final NBTCompound tag = new NBTCompound(); + tag.setString(ITEM_TYPE, input.item().asString()); + tag.setByte(ITEM_COUNT, (byte) input.count()); + + final BinaryTagHolder nbt = input.nbt(); + if (nbt != null) { + try { + tag.set(ITEM_TAG, nbt.get(MinestomAdventure.NBT_CODEC)); + } catch (NBTException e) { + throw new IOException(e); + } + } + + return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); + } + + @Override + public @NotNull Component serializeShowEntity(HoverEvent.@NotNull ShowEntity input, Codec.Encoder componentEncoder) throws IOException { + final NBTCompound tag = new NBTCompound(); + tag.setString(ENTITY_ID, input.id().toString()); + tag.setString(ENTITY_TYPE, input.type().asString()); + + final Component name = input.name(); + if (name != null) { + tag.setString(ENTITY_NAME, componentEncoder.encode(name)); + } + + return Component.text(MinestomAdventure.NBT_CODEC.encode(tag)); + } +} diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.gson.GsonComponentSerializer$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.gson.GsonComponentSerializer$Provider new file mode 100644 index 000000000..fca9d2e88 --- /dev/null +++ b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.gson.GsonComponentSerializer$Provider @@ -0,0 +1 @@ +net.minestom.server.adventure.provider.MinestomGsonComponentSerializerProvider \ No newline at end of file diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer$Provider new file mode 100644 index 000000000..b819ffe3a --- /dev/null +++ b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer$Provider @@ -0,0 +1 @@ +net.minestom.server.adventure.provider.MinestomLegacyComponentSerializerProvider \ No newline at end of file diff --git a/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer$Provider b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer$Provider new file mode 100644 index 000000000..c2235e783 --- /dev/null +++ b/src/main/resources/META-INF/services/net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer$Provider @@ -0,0 +1 @@ +net.minestom.server.adventure.provider.MinestomPlainTextComponentSerializerProvider \ No newline at end of file