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
This commit is contained in:
Konicai 2023-08-17 23:07:55 -04:00 committed by GitHub
parent 706d1b9627
commit 661a9b4741
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 63 additions and 65 deletions

View file

@ -75,7 +75,7 @@ public class AnvilContainer extends Container {
String originalName = ItemUtils.getCustomName(getInput().getNbt()); 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); String plainNewName = MessageTranslator.convertToPlainText(rename);
if (!plainOriginalName.equals(plainNewName)) { if (!plainOriginalName.equals(plainNewName)) {
// Strip out formatting since Java Edition does not allow it // Strip out formatting since Java Edition does not allow it

View file

@ -118,7 +118,7 @@ public class AnvilInventoryUpdater extends InventoryUpdater {
// Changing the item in the input slot resets the name field on Bedrock, but // Changing the item in the input slot resets the name field on Bedrock, but
// does not result in a FilterTextPacket // 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); ServerboundRenameItemPacket renameItemPacket = new ServerboundRenameItemPacket(originalName);
session.sendDownstreamPacket(renameItemPacket); session.sendDownstreamPacket(renameItemPacket);

View file

@ -28,7 +28,7 @@ package org.geysermc.geyser.text;
import net.kyori.adventure.key.Key; import net.kyori.adventure.key.Key;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.event.HoverEvent; 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 net.kyori.adventure.util.Codec;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
@ -40,9 +40,9 @@ public final class DummyLegacyHoverEventSerializer implements LegacyHoverEventSe
private final HoverEvent.ShowItem dummyShowItem; private final HoverEvent.ShowItem dummyShowItem;
public DummyLegacyHoverEventSerializer() { 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))); 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 @Override

View file

@ -43,7 +43,7 @@ public class CommandBlockBlockEntityTranslator extends BlockEntityTranslator imp
// Java and Bedrock values // Java and Bedrock values
builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue()); builder.put("conditionMet", ((ByteTag) tag.get("conditionMet")).getValue());
builder.put("auto", ((ByteTag) tag.get("auto")).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("powered", ((ByteTag) tag.get("powered")).getValue());
builder.put("Command", ((StringTag) tag.get("Command")).getValue()); builder.put("Command", ((StringTag) tag.get("Command")).getValue());
builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue()); builder.put("SuccessCount", ((IntTag) tag.get("SuccessCount")).getValue());

View file

@ -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.DefaultComponentSerializer;
import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor; import com.github.steveice10.mc.protocol.data.game.scoreboard.TeamColor;
import net.kyori.adventure.text.Component; import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.ScoreComponent;
import net.kyori.adventure.text.TranslatableComponent; 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.format.TextColor;
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer; import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer; 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 // 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 GsonComponentSerializer GSON_SERIALIZER;
private static final LegacyComponentSerializer LEGACY_SERIALIZER; private static final LegacyComponentSerializer BEDROCK_SERIALIZER;
private static final String ALL_COLORS; private static final String BEDROCK_COLORS;
// Store team colors for player names // Store team colors for player names
private static final Map<TeamColor, String> TEAM_COLORS = new EnumMap<>(TeamColor.class); private static final Map<TeamColor, String> TEAM_COLORS = new EnumMap<>(TeamColor.class);
@ -99,47 +101,43 @@ public class MessageTranslator {
// Tell MCProtocolLib to use this serializer, too. // Tell MCProtocolLib to use this serializer, too.
DefaultComponentSerializer.set(GSON_SERIALIZER); DefaultComponentSerializer.set(GSON_SERIALIZER);
LegacyComponentSerializer legacySerializer; // Customize the formatting characters of our legacy serializer for bedrock edition
String allColors; List<CharacterAndFormat> formats = new ArrayList<>(CharacterAndFormat.defaults());
try { // The following two do not yet exist on Bedrock - https://bugs.mojang.com/browse/MCPE-41729
Class.forName("net.kyori.adventure.text.serializer.legacy.CharacterAndFormat"); formats.remove(CharacterAndFormat.STRIKETHROUGH);
formats.remove(CharacterAndFormat.UNDERLINED);
List<CharacterAndFormat> formats = new ArrayList<>(CharacterAndFormat.defaults()); formats.add(CharacterAndFormat.characterAndFormat('g', TextColor.color(221, 214, 5))); // Minecoin Gold
// The following two do not yet exist on Bedrock - https://bugs.mojang.com/browse/MCPE-41729 // Add the new characters implemented in 1.19.80
formats.remove(CharacterAndFormat.STRIKETHROUGH); formats.add(CharacterAndFormat.characterAndFormat('h', TextColor.color(227, 212, 209))); // Quartz
formats.remove(CharacterAndFormat.UNDERLINED); 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 // Can be removed once Adventure 1.15.0 is released (see https://github.com/KyoriPowered/adventure/pull/954)
// Add the new characters implemented in 1.19.80 ComponentFlattener flattener = ComponentFlattener.basic().toBuilder()
formats.add(CharacterAndFormat.characterAndFormat('h', TextColor.color(227, 212, 209))); // Quartz .mapper(ScoreComponent.class, component -> "")
formats.add(CharacterAndFormat.characterAndFormat('i', TextColor.color(206, 202, 202))); // Iron .build();
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
legacySerializer = LegacyComponentSerializer.legacySection().toBuilder() BEDROCK_SERIALIZER = LegacyComponentSerializer.legacySection().toBuilder()
.formats(formats) .formats(formats)
.build(); .flattener(flattener)
.build();
StringBuilder colorBuilder = new StringBuilder(); // cache all the legacy character codes
for (CharacterAndFormat format : formats) { StringBuilder colorBuilder = new StringBuilder();
if (format.format() instanceof TextColor) { for (CharacterAndFormat format : formats) {
colorBuilder.append(format.character()); 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; BEDROCK_COLORS = colorBuilder.toString();
ALL_COLORS = allColors;
} }
/** /**
@ -154,7 +152,7 @@ public class MessageTranslator {
// Translate any components that require it // Translate any components that require it
message = RENDERER.render(message, locale); message = RENDERER.render(message, locale);
String legacy = LEGACY_SERIALIZER.serialize(message); String legacy = BEDROCK_SERIALIZER.serialize(message);
StringBuilder finalLegacy = new StringBuilder(); StringBuilder finalLegacy = new StringBuilder();
char[] legacyChars = legacy.toCharArray(); char[] legacyChars = legacy.toCharArray();
@ -170,7 +168,7 @@ public class MessageTranslator {
} }
char next = legacyChars[++i]; 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 // Append this color code, as well as a necessary reset code
if (!lastFormatReset) { if (!lastFormatReset) {
finalLegacy.append(RESET); 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); return convertMessage(GSON_SERIALIZER.deserialize(message), locale);
} }
public static String convertMessage(String message) { public static String convertJsonMessage(String message) {
return convertMessage(message, GeyserLocale.getDefaultLocale()); return convertJsonMessage(message, GeyserLocale.getDefaultLocale());
} }
public static String convertMessage(Component message) { 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. * See https://wiki.vg/Chat for messages sent in lenient mode, and for a description on leniency.
* *
* @param message Potentially lenient JSON message * @param message Potentially lenient JSON message
@ -218,9 +216,10 @@ public class MessageTranslator {
} }
try { try {
return convertMessage(message, locale); return convertJsonMessage(message, locale);
} catch (Exception ignored) { } 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 // We have to do this since Adventure strips the starting reset character
if (message.startsWith(RESET) && !convertedMessage.startsWith(RESET)) { if (message.startsWith(RESET) && !convertedMessage.startsWith(RESET)) {
@ -242,11 +241,10 @@ public class MessageTranslator {
* @return The formatted JSON string * @return The formatted JSON string
*/ */
public static String convertToJavaMessage(String message) { public static String convertToJavaMessage(String message) {
Component component = LegacyComponentSerializer.legacySection().deserialize(message); Component component = BEDROCK_SERIALIZER.deserialize(message);
return GSON_SERIALIZER.serialize(component); return GSON_SERIALIZER.serialize(component);
} }
/** /**
* Convert legacy format message to plain text * Convert legacy format message to plain text
* *
@ -275,7 +273,7 @@ public class MessageTranslator {
* @param locale Locale to use for translation strings * @param locale Locale to use for translation strings
* @return The plain text of the message * @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) { if (message == null) {
return ""; return "";
} }

View file

@ -38,7 +38,7 @@ import java.util.Map;
@TestInstance(TestInstance.Lifecycle.PER_CLASS) @TestInstance(TestInstance.Lifecycle.PER_CLASS)
public class MessageTranslatorTest { public class MessageTranslatorTest {
private Map<String, String> messages = new HashMap<>(); private final Map<String, String> messages = new HashMap<>();
@BeforeAll @BeforeAll
public void setUp() throws Exception { public void setUp() throws Exception {
@ -70,7 +70,7 @@ public class MessageTranslatorTest {
@Test @Test
public void convertMessage() { public void convertMessage() {
for (Map.Entry<String, String> entry : messages.entrySet()) { for (Map.Entry<String, String> 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"); Assertions.assertEquals(entry.getValue(), bedrockMessage, "Translation of messages is incorrect");
} }
} }
@ -85,13 +85,13 @@ public class MessageTranslatorTest {
@Test @Test
public void convertToPlainText() { 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"), "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("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.convertToPlainText("§kObf §lBold §mStrikethrough §nUnderline §oItalic §rReset", "en_US"), "Legacy formatted message is not handled properly (Style)"); 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.convertToPlainText("§rStrange", "en_US"), "Valid lenient JSON is not handled properly"); Assertions.assertEquals("Strange", MessageTranslator.convertToPlainTextLenient("§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.convertToPlainTextLenient("", "en_US"), "Empty message is not handled properly");
Assertions.assertEquals(" ", MessageTranslator.convertToPlainText(" ", "en_US"), "Whitespace is not preserved"); Assertions.assertEquals(" ", MessageTranslator.convertToPlainTextLenient(" ", "en_US"), "Whitespace is not preserved");
} }
@Test @Test

View file

@ -14,8 +14,8 @@ protocol-connection = "3.0.0.Beta1-20230718.000033-101"
raknet = "1.0.0.CR1-20230703.195238-9" raknet = "1.0.0.CR1-20230703.195238-9"
mcauthlib = "d9d773e" mcauthlib = "d9d773e"
mcprotocollib = "1.20-1-20230607.135651-6" # Temporary hack - needs to be updated to release once publishing is fixed 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 = "4.14.0"
adventure-platform = "4.1.2" adventure-platform = "4.3.0"
junit = "5.9.2" junit = "5.9.2"
checkerframework = "3.19.0" checkerframework = "3.19.0"
log4j = "2.20.0" log4j = "2.20.0"
@ -26,7 +26,7 @@ viaversion = "4.0.0"
adapters = "1.9-SNAPSHOT" adapters = "1.9-SNAPSHOT"
commodore = "2.2" commodore = "2.2"
bungeecord = "a7c6ede" bungeecord = "a7c6ede"
velocity = "3.0.0" velocity = "3.1.1"
sponge = "8.0.0" sponge = "8.0.0"
fabric-minecraft = "1.20" fabric-minecraft = "1.20"
fabric-loader = "0.14.21" fabric-loader = "0.14.21"