From 661a9b4741a2a8a274313dd63526cc7f86e8afe1 Mon Sep 17 00:00:00 2001 From: Konicai <71294714+Konicai@users.noreply.github.com> Date: Thu, 17 Aug 2023 23:07:55 -0400 Subject: [PATCH] Improvements to MessageTranslator (#3803) * Renames for clarity and refactor convertToJavaMessage * Bump adventure, velociy. Require CharacterAndFormat in MessageTranslator * Fix deprecations related to DummyLegacyHoverEventSerializer * Patch serialization of ScoreComponent until Adventure 1.15.0 --- .../geyser/inventory/AnvilContainer.java | 2 +- .../updater/AnvilInventoryUpdater.java | 2 +- .../text/DummyLegacyHoverEventSerializer.java | 6 +- .../CommandBlockBlockEntityTranslator.java | 2 +- .../translator/text/MessageTranslator.java | 94 +++++++++---------- .../chat/MessageTranslatorTest.java | 16 ++-- gradle/libs.versions.toml | 6 +- 7 files changed, 63 insertions(+), 65 deletions(-) diff --git a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java index 471aff8b2..5b0800e44 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/AnvilContainer.java @@ -75,7 +75,7 @@ public class AnvilContainer extends Container { String originalName = ItemUtils.getCustomName(getInput().getNbt()); - String plainOriginalName = MessageTranslator.convertToPlainText(originalName, session.locale()); + String plainOriginalName = MessageTranslator.convertToPlainTextLenient(originalName, session.locale()); String plainNewName = MessageTranslator.convertToPlainText(rename); if (!plainOriginalName.equals(plainNewName)) { // Strip out formatting since Java Edition does not allow it diff --git a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java index c92724100..ea4ef674b 100644 --- a/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java +++ b/core/src/main/java/org/geysermc/geyser/inventory/updater/AnvilInventoryUpdater.java @@ -118,7 +118,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater { // Changing the item in the input slot resets the name field on Bedrock, but // does not result in a FilterTextPacket - String originalName = MessageTranslator.convertToPlainText(ItemUtils.getCustomName(input.getNbt()), session.locale()); + String originalName = MessageTranslator.convertToPlainTextLenient(ItemUtils.getCustomName(input.getNbt()), session.locale()); ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName); session.sendDownstreamPacket(renameItemPacket); diff --git a/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java index fdce1f879..e934fc124 100644 --- a/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java +++ b/core/src/main/java/org/geysermc/geyser/text/DummyLegacyHoverEventSerializer.java @@ -28,7 +28,7 @@ package org.geysermc.geyser.text; import net.kyori.adventure.key.Key; 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.json.LegacyHoverEventSerializer; import net.kyori.adventure.util.Codec; import org.jetbrains.annotations.NotNull; @@ -40,9 +40,9 @@ public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSe private final HoverEvent.ShowItem dummyShowItem; public DummyLegacyHoverEventSerializer() { - dummyShowEntity = HoverEvent.ShowEntity.of(Key.key("geysermc", "dummyshowitem"), + dummyShowEntity = HoverEvent.ShowEntity.showEntity(Key.key("geysermc", "dummyshowitem"), UUID.nameUUIDFromBytes("entitiesareprettyneat".getBytes(StandardCharsets.UTF_8))); - dummyShowItem = HoverEvent.ShowItem.of(Key.key("geysermc", "dummyshowentity"), 0); + dummyShowItem = HoverEvent.ShowItem.showItem(Key.key("geysermc", "dummyshowentity"), 0); } @Override diff --git a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java index 9e743667f..36345394b 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/level/block/entity/CommandBlockBlockEntityTranslator.java @@ -43,7 +43,7 @@ public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator imp // Java and Bedrock values builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); builder.put("auto", ((ByteTag) tag.get("auto")).getValue()); - builder.put("CustomName", MessageTranslator.convertMessage(((StringTag) tag.get("CustomName")).getValue())); + builder.put("CustomName", MessageTranslator.convertJsonMessage(((StringTag) tag.get("CustomName")).getValue())); builder.put("powered", ((ByteTag) tag.get("powered")).getValue()); builder.put("Command", ((StringTag) tag.get("Command")).getValue()); builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); diff --git a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java index dd069c5f5..5f811ab49 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java @@ -28,7 +28,9 @@ package org.geysermc.geyser.translator.text; import com.github.steveice10.mc.protocol.data.DefaultComponentSerializer; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.ScoreComponent; import net.kyori.adventure.text.TranslatableComponent; +import net.kyori.adventure.text.flattener.ComponentFlattener; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; @@ -50,8 +52,8 @@ public class MessageTranslator { // Possible TODO: replace the legacy hover event serializer with an empty one since we have no use for hover events private static final GsonComponentSerializer GSON_SERIALIZER; - private static final LegacyComponentSerializer LEGACY_SERIALIZER; - private static final String ALL_COLORS; + private static final LegacyComponentSerializer BEDROCK_SERIALIZER; + private static final String BEDROCK_COLORS; // Store team colors for player names private static final Map TEAM_COLORS = new EnumMap<>(TeamColor.class); @@ -99,47 +101,43 @@ public class MessageTranslator { // Tell MCProtocolLib to use this serializer, too. DefaultComponentSerializer.set(GSON_SERIALIZER); - LegacyComponentSerializer legacySerializer; - String allColors; - try { - Class.forName("net.kyori.adventure.text.serializer.legacy.CharacterAndFormat"); + // Customize the formatting characters of our legacy serializer for bedrock edition + List formats = new ArrayList<>(CharacterAndFormat.defaults()); + // The following two do not yet exist on Bedrock - https://bugs.mojang.com/browse/MCPE-41729 + formats.remove(CharacterAndFormat.STRIKETHROUGH); + formats.remove(CharacterAndFormat.UNDERLINED); - List formats = new ArrayList<>(CharacterAndFormat.defaults()); - // The following two do not yet exist on Bedrock - https://bugs.mojang.com/browse/MCPE-41729 - formats.remove(CharacterAndFormat.STRIKETHROUGH); - formats.remove(CharacterAndFormat.UNDERLINED); + formats.add(CharacterAndFormat.characterAndFormat('g', TextColor.color(221, 214, 5))); // Minecoin Gold + // Add the new characters implemented in 1.19.80 + formats.add(CharacterAndFormat.characterAndFormat('h', TextColor.color(227, 212, 209))); // Quartz + formats.add(CharacterAndFormat.characterAndFormat('i', TextColor.color(206, 202, 202))); // Iron + formats.add(CharacterAndFormat.characterAndFormat('j', TextColor.color(68, 58, 59))); // Netherite + formats.add(CharacterAndFormat.characterAndFormat('m', TextColor.color(151, 22, 7))); // Redstone + formats.add(CharacterAndFormat.characterAndFormat('n', TextColor.color(180, 104, 77))); // Copper + formats.add(CharacterAndFormat.characterAndFormat('p', TextColor.color(222, 177, 45))); // Gold + formats.add(CharacterAndFormat.characterAndFormat('q', TextColor.color(17, 160, 54))); // Emerald + formats.add(CharacterAndFormat.characterAndFormat('s', TextColor.color(44, 186, 168))); // Diamond + formats.add(CharacterAndFormat.characterAndFormat('t', TextColor.color(33, 73, 123))); // Lapis + formats.add(CharacterAndFormat.characterAndFormat('u', TextColor.color(154, 92, 198))); // Amethyst - formats.add(CharacterAndFormat.characterAndFormat('g', TextColor.color(221, 214, 5))); // Minecoin Gold - // Add the new characters implemented in 1.19.80 - formats.add(CharacterAndFormat.characterAndFormat('h', TextColor.color(227, 212, 209))); // Quartz - formats.add(CharacterAndFormat.characterAndFormat('i', TextColor.color(206, 202, 202))); // Iron - formats.add(CharacterAndFormat.characterAndFormat('j', TextColor.color(68, 58, 59))); // Netherite - formats.add(CharacterAndFormat.characterAndFormat('m', TextColor.color(151, 22, 7))); // Redstone - formats.add(CharacterAndFormat.characterAndFormat('n', TextColor.color(180, 104, 77))); // Copper - formats.add(CharacterAndFormat.characterAndFormat('p', TextColor.color(222, 177, 45))); // Gold - formats.add(CharacterAndFormat.characterAndFormat('q', TextColor.color(17, 160, 54))); // Emerald - formats.add(CharacterAndFormat.characterAndFormat('s', TextColor.color(44, 186, 168))); // Diamond - formats.add(CharacterAndFormat.characterAndFormat('t', TextColor.color(33, 73, 123))); // Lapis - formats.add(CharacterAndFormat.characterAndFormat('u', TextColor.color(154, 92, 198))); // Amethyst + // Can be removed once Adventure 1.15.0 is released (see https://github.com/KyoriPowered/adventure/pull/954) + ComponentFlattener flattener = ComponentFlattener.basic().toBuilder() + .mapper(ScoreComponent.class, component -> "") + .build(); - legacySerializer = LegacyComponentSerializer.legacySection().toBuilder() - .formats(formats) - .build(); + BEDROCK_SERIALIZER = LegacyComponentSerializer.legacySection().toBuilder() + .formats(formats) + .flattener(flattener) + .build(); - StringBuilder colorBuilder = new StringBuilder(); - for (CharacterAndFormat format : formats) { - if (format.format() instanceof TextColor) { - colorBuilder.append(format.character()); - } + // cache all the legacy character codes + StringBuilder colorBuilder = new StringBuilder(); + for (CharacterAndFormat format : formats) { + if (format.format() instanceof TextColor) { + colorBuilder.append(format.character()); } - allColors = colorBuilder.toString(); - } catch (ClassNotFoundException ignored) { - // Velocity doesn't have this yet. - legacySerializer = LegacyComponentSerializer.legacySection(); - allColors = "0123456789abcdef"; } - LEGACY_SERIALIZER = legacySerializer; - ALL_COLORS = allColors; + BEDROCK_COLORS = colorBuilder.toString(); } /** @@ -154,7 +152,7 @@ public class MessageTranslator { // Translate any components that require it message = RENDERER.render(message, locale); - String legacy = LEGACY_SERIALIZER.serialize(message); + String legacy = BEDROCK_SERIALIZER.serialize(message); StringBuilder finalLegacy = new StringBuilder(); char[] legacyChars = legacy.toCharArray(); @@ -170,7 +168,7 @@ public class MessageTranslator { } char next = legacyChars[++i]; - if (ALL_COLORS.indexOf(next) != -1) { + if (BEDROCK_COLORS.indexOf(next) != -1) { // Append this color code, as well as a necessary reset code if (!lastFormatReset) { finalLegacy.append(RESET); @@ -189,12 +187,12 @@ public class MessageTranslator { } } - public static String convertMessage(String message, String locale) { + public static String convertJsonMessage(String message, String locale) { return convertMessage(GSON_SERIALIZER.deserialize(message), locale); } - public static String convertMessage(String message) { - return convertMessage(message, GeyserLocale.getDefaultLocale()); + public static String convertJsonMessage(String message) { + return convertJsonMessage(message, GeyserLocale.getDefaultLocale()); } public static String convertMessage(Component message) { @@ -202,7 +200,7 @@ public class MessageTranslator { } /** - * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSeraializer not using lenient mode. + * Verifies the message is valid JSON in case it's plaintext. Works around GsonComponentSerializer not using lenient mode. * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency. * * @param message Potentially lenient JSON message @@ -218,9 +216,10 @@ public class MessageTranslator { } try { - return convertMessage(message, locale); + return convertJsonMessage(message, locale); } catch (Exception ignored) { - String convertedMessage = convertMessage(convertToJavaMessage(message), locale); + // Use the default legacy serializer since message is java-legacy + String convertedMessage = convertMessage(LegacyComponentSerializer.legacySection().deserialize(message), locale); // We have to do this since Adventure strips the starting reset character if (message.startsWith(RESET) && !convertedMessage.startsWith(RESET)) { @@ -242,11 +241,10 @@ public class MessageTranslator { * @return The formatted JSON string */ public static String convertToJavaMessage(String message) { - Component component = LegacyComponentSerializer.legacySection().deserialize(message); + Component component = BEDROCK_SERIALIZER.deserialize(message); return GSON_SERIALIZER.serialize(component); } - /** * Convert legacy format message to plain text * @@ -275,7 +273,7 @@ public class MessageTranslator { * @param locale Locale to use for translation strings * @return The plain text of the message */ - public static String convertToPlainText(String message, String locale) { + public static String convertToPlainTextLenient(String message, String locale) { if (message == null) { return ""; } diff --git a/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java b/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java index bce8fe0c8..5e5c3af7a 100644 --- a/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java +++ b/core/src/test/java/org/geysermc/geyser/network/translators/chat/MessageTranslatorTest.java @@ -38,7 +38,7 @@ import java.util.Map; @TestInstance(TestInstance.Lifecycle.PER_CLASS) public class MessageTranslatorTest { - private Map messages = new HashMap<>(); + private final Map messages = new HashMap<>(); @BeforeAll public void setUp() throws Exception { @@ -70,7 +70,7 @@ public class MessageTranslatorTest { @Test public void convertMessage() { for (Map.Entry entry : messages.entrySet()) { - String bedrockMessage = MessageTranslator.convertMessage(entry.getKey(), "en_US"); + String bedrockMessage = MessageTranslator.convertJsonMessage(entry.getKey(), "en_US"); Assertions.assertEquals(entry.getValue(), bedrockMessage, "Translation of messages is incorrect"); } } @@ -85,13 +85,13 @@ public class MessageTranslatorTest { @Test public void convertToPlainText() { - Assertions.assertEquals("Many colors here", MessageTranslator.convertToPlainText("{\"extra\":[{\"color\":\"red\",\"text\":\"M\"},{\"color\":\"gold\",\"text\":\"a\"},{\"color\":\"yellow\",\"text\":\"n\"},{\"color\":\"green\",\"text\":\"y \"},{\"color\":\"aqua\",\"text\":\"c\"},{\"color\":\"dark_purple\",\"text\":\"o\"},{\"color\":\"red\",\"text\":\"l\"},{\"color\":\"gold\",\"text\":\"o\"},{\"color\":\"yellow\",\"text\":\"r\"},{\"color\":\"green\",\"text\":\"s \"},{\"color\":\"aqua\",\"text\":\"h\"},{\"color\":\"dark_purple\",\"text\":\"e\"},{\"color\":\"red\",\"text\":\"r\"},{\"color\":\"gold\",\"text\":\"e\"}],\"text\":\"\"}", "en_US"), "JSON message is not handled properly"); + Assertions.assertEquals("Many colors here", MessageTranslator.convertToPlainTextLenient("{\"extra\":[{\"color\":\"red\",\"text\":\"M\"},{\"color\":\"gold\",\"text\":\"a\"},{\"color\":\"yellow\",\"text\":\"n\"},{\"color\":\"green\",\"text\":\"y \"},{\"color\":\"aqua\",\"text\":\"c\"},{\"color\":\"dark_purple\",\"text\":\"o\"},{\"color\":\"red\",\"text\":\"l\"},{\"color\":\"gold\",\"text\":\"o\"},{\"color\":\"yellow\",\"text\":\"r\"},{\"color\":\"green\",\"text\":\"s \"},{\"color\":\"aqua\",\"text\":\"h\"},{\"color\":\"dark_purple\",\"text\":\"e\"},{\"color\":\"red\",\"text\":\"r\"},{\"color\":\"gold\",\"text\":\"e\"}],\"text\":\"\"}", "en_US"), "JSON message is not handled properly"); Assertions.assertEquals("Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e"), "Legacy formatted message is not handled properly (Colors)"); - Assertions.assertEquals("Many colors here", MessageTranslator.convertToPlainText("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e", "en_US"), "Legacy formatted message is not handled properly (Colors)"); - Assertions.assertEquals("Obf Bold Strikethrough Underline Italic Reset", MessageTranslator.convertToPlainText("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US"), "Legacy formatted message is not handled properly (Style)"); - Assertions.assertEquals("Strange", MessageTranslator.convertToPlainText("§rStrange", "en_US"), "Valid lenient JSON is not handled properly"); - Assertions.assertEquals("", MessageTranslator.convertToPlainText("", "en_US"), "Empty message is not handled properly"); - Assertions.assertEquals(" ", MessageTranslator.convertToPlainText(" ", "en_US"), "Whitespace is not preserved"); + Assertions.assertEquals("Many colors here", MessageTranslator.convertToPlainTextLenient("§cM§6a§en§ay §bc§5o§cl§6o§er§as §bh§5e§cr§6e", "en_US"), "Legacy formatted message is not handled properly (Colors)"); + Assertions.assertEquals("Obf Bold Strikethrough Underline Italic Reset", MessageTranslator.convertToPlainTextLenient("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US"), "Legacy formatted message is not handled properly (Style)"); + Assertions.assertEquals("Strange", MessageTranslator.convertToPlainTextLenient("§rStrange", "en_US"), "Valid lenient JSON is not handled properly"); + Assertions.assertEquals("", MessageTranslator.convertToPlainTextLenient("", "en_US"), "Empty message is not handled properly"); + Assertions.assertEquals(" ", MessageTranslator.convertToPlainTextLenient(" ", "en_US"), "Whitespace is not preserved"); } @Test diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 74e623f38..cbceb3e7e 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,8 +14,8 @@ protocol-connection = "3.0.0.Beta1-20230718.000033-101" raknet = "1.0.0.CR1-20230703.195238-9" mcauthlib = "d9d773e" mcprotocollib = "1.20-1-20230607.135651-6" # Temporary hack - needs to be updated to release once publishing is fixed -adventure = "4.14.0-20230424.215040-7" -adventure-platform = "4.1.2" +adventure = "4.14.0" +adventure-platform = "4.3.0" junit = "5.9.2" checkerframework = "3.19.0" log4j = "2.20.0" @@ -26,7 +26,7 @@ viaversion = "4.0.0" adapters = "1.9-SNAPSHOT" commodore = "2.2" bungeecord = "a7c6ede" -velocity = "3.0.0" +velocity = "3.1.1" sponge = "8.0.0" fabric-minecraft = "1.20" fabric-loader = "0.14.21"