diff --git a/src/main/java/net/minestom/server/MinecraftServer.java b/src/main/java/net/minestom/server/MinecraftServer.java index b3545e4ca..502987259 100644 --- a/src/main/java/net/minestom/server/MinecraftServer.java +++ b/src/main/java/net/minestom/server/MinecraftServer.java @@ -4,6 +4,7 @@ import net.kyori.adventure.audience.Audience; import net.kyori.adventure.audience.ForwardingAudience; import net.minestom.server.advancements.AdvancementManager; import net.minestom.server.adventure.BossBarManager; +import net.minestom.server.adventure.SerializationManager; import net.minestom.server.benchmark.BenchmarkManager; import net.minestom.server.command.CommandManager; import net.minestom.server.data.DataManager; @@ -119,6 +120,7 @@ public final class MinecraftServer implements ForwardingAudience.Single { private static DimensionTypeManager dimensionTypeManager; private static BiomeManager biomeManager; private static AdvancementManager advancementManager; + private static SerializationManager serializationManager; private static BossBarManager bossBarManager; private static ExtensionManager extensionManager; @@ -185,6 +187,7 @@ public final class MinecraftServer implements ForwardingAudience.Single { dimensionTypeManager = new DimensionTypeManager(); biomeManager = new BiomeManager(); advancementManager = new AdvancementManager(); + serializationManager = new SerializationManager(); bossBarManager = new BossBarManager(); updateManager = new UpdateManager(); @@ -433,6 +436,16 @@ public final class MinecraftServer implements ForwardingAudience.Single { return connectionManager; } + /** + * Gets the manager handing component serialization. + * + * @return the manager + */ + public static SerializationManager getSerializationManager() { + checkInitStatus(serializationManager); + return serializationManager; + } + /** * Gets the boss bar manager. * diff --git a/src/main/java/net/minestom/server/adventure/BossBarManager.java b/src/main/java/net/minestom/server/adventure/BossBarManager.java index b50e32139..0e7c0d3a3 100644 --- a/src/main/java/net/minestom/server/adventure/BossBarManager.java +++ b/src/main/java/net/minestom/server/adventure/BossBarManager.java @@ -168,7 +168,7 @@ public class BossBarManager implements BossBar.Listener { BossBarPacket createAddPacket() { return this.createGenericPacket(ADD, packet -> { - packet.title = GsonComponentSerializer.gson().serialize(bar.name()); + packet.title = MinecraftServer.getSerializationManager().serialize(bar.name()); packet.color = bar.color().ordinal(); packet.division = bar.overlay().ordinal(); packet.health = bar.progress(); @@ -188,7 +188,7 @@ public class BossBarManager implements BossBar.Listener { } BossBarPacket createTitleUpdate(@NotNull Component title) { - return this.createGenericPacket(UPDATE_TITLE, packet -> packet.title = GsonComponentSerializer.gson().serialize(title)); + return this.createGenericPacket(UPDATE_TITLE, packet -> packet.title = MinecraftServer.getSerializationManager().serialize(title)); } BossBarPacket createFlagsUpdate() { diff --git a/src/main/java/net/minestom/server/adventure/SerializationManager.java b/src/main/java/net/minestom/server/adventure/SerializationManager.java new file mode 100644 index 000000000..fb8051990 --- /dev/null +++ b/src/main/java/net/minestom/server/adventure/SerializationManager.java @@ -0,0 +1,87 @@ +package net.minestom.server.adventure; + +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; +import org.jetbrains.annotations.NotNull; + +import java.util.Set; +import java.util.concurrent.CopyOnWriteArraySet; +import java.util.function.Function; + +/** + * Manager class for handling Adventure serialization. By default this will simply + * serialize components to Strings using {@link GsonComponentSerializer}. However, this + * class can be used to change the way text is serialized. For example, a pre-JSON + * implementation of Minestom could change this to the plain component serializer. + *

+ * This manager also provides the ability to wrap the serializer in a renderer that + * performs operations on each component before the final serialization. + * + * @see #setSerializer(Function) (Function) + * @see #addRenderer(Function) + */ +public class SerializationManager { + private final Set> renderers = new CopyOnWriteArraySet<>(); + private Function serializer = component -> GsonComponentSerializer.gson().serialize(component); + + /** + * Gets the root serializer that is used to convert Components into Strings. + * + * @return the serializer + */ + public @NotNull Function getSerializer() { + return this.serializer; + } + + /** + * Sets the root serializer that is used to convert Components into Strings. This + * method does not replace any existing renderers set with {@link #addRenderer(Function)}. + * + * @param serializer the serializer + */ + public void setSerializer(@NotNull Function serializer) { + this.serializer = serializer; + } + + /** + * Adds a renderer that will be applied to each serializer. The order in which + * each renderer will be applied is arbitrary. If you want control over the order + * of renderers, create a multi-function using {@link Function#andThen(Function)}. + * + * @param renderer the renderer + */ + public void addRenderer(@NotNull Function renderer) { + this.renderers.add(renderer); + } + + /** + * Removes a renderer. + * + * @param renderer the renderer + */ + public void removeRenderer(@NotNull Function renderer) { + this.renderers.remove(renderer); + } + + /** + * Removes all current renderers. + */ + public void clearRenderers() { + this.renderers.clear(); + } + + /** + * Serializes a component into a String using the current serializer. + * + * @param component the component + * + * @return the serialized string + */ + public String serialize(Component component) { + for (Function renderer : this.renderers) { + component = renderer.apply(component); + } + + return this.serializer.apply(component); + } +} diff --git a/src/main/java/net/minestom/server/command/ConsoleSender.java b/src/main/java/net/minestom/server/command/ConsoleSender.java index 36b3ad6cc..782f2a699 100644 --- a/src/main/java/net/minestom/server/command/ConsoleSender.java +++ b/src/main/java/net/minestom/server/command/ConsoleSender.java @@ -24,6 +24,7 @@ public class ConsoleSender implements CommandSender { @Override public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { + // we don't use the serializer here as we just need the plain text of the message LOGGER.info(PlainComponentSerializer.plain().serialize(message)); } diff --git a/src/main/java/net/minestom/server/entity/Player.java b/src/main/java/net/minestom/server/entity/Player.java index 5e9ecd4d6..2b33dd512 100644 --- a/src/main/java/net/minestom/server/entity/Player.java +++ b/src/main/java/net/minestom/server/entity/Player.java @@ -820,7 +820,7 @@ public class Player extends LivingEntity implements CommandSender { @Override public void sendMessage(@NonNull Identity source, @NonNull Component message, @NonNull MessageType type) { - ChatMessagePacket chatMessagePacket = new ChatMessagePacket(GsonComponentSerializer.gson().serialize(message), type, source.uuid()); + ChatMessagePacket chatMessagePacket = new ChatMessagePacket(MinecraftServer.getSerializationManager().serialize(message), type, source.uuid()); playerConnection.sendPacket(chatMessagePacket); } @@ -964,10 +964,10 @@ public class Player extends LivingEntity implements CommandSender { @Override public void sendPlayerListHeaderAndFooter(@NonNull Component header, @NonNull Component footer) { - playerConnection.sendPacket(new PlayerListHeaderAndFooterPacket( - GsonComponentSerializer.gson().serialize(header), - GsonComponentSerializer.gson().serialize(footer) - )); + PlayerListHeaderAndFooterPacket packet = new PlayerListHeaderAndFooterPacket(); + packet.header = MinecraftServer.getSerializationManager().serialize(header); + packet.footer = MinecraftServer.getSerializationManager().serialize(footer); + playerConnection.sendPacket(packet); } /** @@ -1047,7 +1047,7 @@ public class Player extends LivingEntity implements CommandSender { @Override public void sendActionBar(@NonNull Component message) { - TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, GsonComponentSerializer.gson().serialize(message)); + TitlePacket titlePacket = new TitlePacket(TitlePacket.Action.SET_ACTION_BAR, MinecraftServer.getSerializationManager().serialize(message)); playerConnection.sendPacket(titlePacket); } @@ -1830,9 +1830,9 @@ public class Player extends LivingEntity implements CommandSender { // Packet type depends on the current player connection state final ServerPacket disconnectPacket; if (connectionState == ConnectionState.LOGIN) { - disconnectPacket = new LoginDisconnectPacket(GsonComponentSerializer.gson().serialize(component)); + disconnectPacket = new LoginDisconnectPacket(MinecraftServer.getSerializationManager().serialize(component)); } else { - disconnectPacket = new DisconnectPacket(GsonComponentSerializer.gson().serialize(component)); + disconnectPacket = new DisconnectPacket(MinecraftServer.getSerializationManager().serialize(component)); } if (playerConnection instanceof NettyPlayerConnection) { diff --git a/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java b/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java index 003540418..667491d06 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/PlayerListHeaderAndFooterPacket.java @@ -1,6 +1,7 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.text.serializer.plain.PlainComponentSerializer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; @@ -10,7 +11,7 @@ import org.jetbrains.annotations.NotNull; import java.util.Objects; public class PlayerListHeaderAndFooterPacket implements ServerPacket { - private static final String EMPTY_COMPONENT = PlainComponentSerializer.plain().serialize(Component.empty()); + private static final String EMPTY_COMPONENT = GsonComponentSerializer.gson().serialize(Component.empty()); public String header; public String footer; diff --git a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java index f99e0e53b..51b8e7047 100644 --- a/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java +++ b/src/main/java/net/minestom/server/network/packet/server/play/TitlePacket.java @@ -2,6 +2,7 @@ package net.minestom.server.network.packet.server.play; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; import net.kyori.adventure.title.Title; +import net.minestom.server.MinecraftServer; import net.minestom.server.network.packet.server.ServerPacket; import net.minestom.server.network.packet.server.ServerPacketIdentifier; import net.minestom.server.utils.TickUtils; @@ -109,8 +110,8 @@ public class TitlePacket implements ServerPacket { List packets = new ArrayList<>(4); // base packets - packets.add(new TitlePacket(SET_TITLE, GsonComponentSerializer.gson().serialize(title.title()))); - packets.add(new TitlePacket(SET_SUBTITLE, GsonComponentSerializer.gson().serialize(title.subtitle()))); + packets.add(new TitlePacket(SET_TITLE, MinecraftServer.getSerializationManager().serialize(title.title()))); + packets.add(new TitlePacket(SET_SUBTITLE, MinecraftServer.getSerializationManager().serialize(title.subtitle()))); // times packet Title.Times times = title.times();