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();