Skip to content

Commit 73f7259

Browse files
Add proper text component parsing from NBT (#5029)
* Attempt creating a simple NBT text component parser * Fix style merging * Rename TextDecoration to ChatDecoration, use better style deserialization in ChatDecoration * Remove unused code * containsKey optimisations, update documentation, improve getStyleFromNbtMap performance slightly, more slight tweaks * Remove unnecessary deserializeStyle method
1 parent 14cf104 commit 73f7259

File tree

5 files changed

+105
-40
lines changed

5 files changed

+105
-40
lines changed

core/src/main/java/org/geysermc/geyser/item/enchantment/Enchantment.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public static Enchantment read(RegistryEntryContext context) {
6969

7070
// TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java,
7171
// but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name.
72-
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null;
72+
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(context.session(), data) : null;
7373

7474
return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel,
7575
description, anvilCost, exclusiveSet, bedrockEnchantment);

core/src/main/java/org/geysermc/geyser/level/JukeboxSong.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public static JukeboxSong read(RegistryEntryContext context) {
4444
soundEvent = "";
4545
GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject);
4646
}
47-
String description = MessageTranslator.deserializeDescription(data);
47+
String description = MessageTranslator.deserializeDescription(context.session(), data);
4848
return new JukeboxSong(soundEvent, description);
4949
}
5050
}

core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
5050
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
5151
import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry;
52-
import org.geysermc.geyser.text.TextDecoration;
52+
import org.geysermc.geyser.text.ChatDecoration;
5353
import org.geysermc.geyser.translator.level.BiomeTranslator;
5454
import org.geysermc.geyser.util.MinecraftKey;
5555
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
@@ -78,7 +78,7 @@ public final class RegistryCache {
7878
private static final Map<Key, BiConsumer<RegistryCache, List<RegistryEntry>>> REGISTRIES = new HashMap<>();
7979

8080
static {
81-
register("chat_type", cache -> cache.chatTypes, TextDecoration::readChatType);
81+
register("chat_type", cache -> cache.chatTypes, ChatDecoration::readChatType);
8282
register("dimension_type", cache -> cache.dimensions, JavaDimension::read);
8383
register("enchantment", cache -> cache.enchantments, Enchantment::read);
8484
register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read);

core/src/main/java/org/geysermc/geyser/text/TextDecoration.java renamed to core/src/main/java/org/geysermc/geyser/text/ChatDecoration.java

Lines changed: 10 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,17 +25,19 @@
2525

2626
package org.geysermc.geyser.text;
2727

28-
import net.kyori.adventure.text.format.NamedTextColor;
2928
import net.kyori.adventure.text.format.Style;
3029
import org.cloudburstmc.nbt.NbtMap;
3130
import org.cloudburstmc.nbt.NbtType;
3231
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
32+
import org.geysermc.geyser.translator.text.MessageTranslator;
3333
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
3434
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration;
3535

36-
import java.util.*;
36+
import java.util.ArrayList;
37+
import java.util.List;
38+
import java.util.Locale;
3739

38-
public record TextDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
40+
public record ChatDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
3941

4042
@Override
4143
public NbtMap style() {
@@ -53,38 +55,22 @@ public static ChatType readChatType(RegistryEntryContext context) {
5355
String translationKey = chat.getString("translation_key");
5456

5557
NbtMap styleTag = chat.getCompound("style");
56-
Style style = deserializeStyle(styleTag);
58+
Style style = MessageTranslator.getStyleFromNbtMap(styleTag);
5759

5860
List<ChatTypeDecoration.Parameter> parameters = new ArrayList<>();
5961
List<String> parametersNbt = chat.getList("parameters", NbtType.STRING);
6062
for (String parameter : parametersNbt) {
6163
parameters.add(ChatTypeDecoration.Parameter.valueOf(parameter.toUpperCase(Locale.ROOT)));
6264
}
63-
return new ChatType(new TextDecoration(translationKey, parameters, style), null);
65+
return new ChatType(new ChatDecoration(translationKey, parameters, style), null);
6466
}
6567
return new ChatType(null, null);
6668
}
6769

6870
public static Style getStyle(ChatTypeDecoration decoration) {
69-
if (decoration instanceof TextDecoration textDecoration) {
70-
return textDecoration.deserializedStyle();
71+
if (decoration instanceof ChatDecoration chatDecoration) {
72+
return chatDecoration.deserializedStyle();
7173
}
72-
return deserializeStyle(decoration.style());
73-
}
74-
75-
private static Style deserializeStyle(NbtMap styleTag) {
76-
Style.Builder builder = Style.style();
77-
if (!styleTag.isEmpty()) {
78-
String color = styleTag.getString("color", null);
79-
if (color != null) {
80-
builder.color(NamedTextColor.NAMES.value(color));
81-
}
82-
//TODO implement the rest
83-
boolean italic = styleTag.getBoolean("italic");
84-
if (italic) {
85-
builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC);
86-
}
87-
}
88-
return builder.build();
74+
return MessageTranslator.getStyleFromNbtMap(decoration.style());
8975
}
9076
}

core/src/main/java/org/geysermc/geyser/translator/text/MessageTranslator.java

Lines changed: 91 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -26,16 +26,21 @@
2626
package org.geysermc.geyser.translator.text;
2727

2828
import net.kyori.adventure.text.Component;
29+
import net.kyori.adventure.text.JoinConfiguration;
2930
import net.kyori.adventure.text.ScoreComponent;
3031
import net.kyori.adventure.text.TranslatableComponent;
3132
import net.kyori.adventure.text.flattener.ComponentFlattener;
33+
import net.kyori.adventure.text.format.NamedTextColor;
34+
import net.kyori.adventure.text.format.Style;
3235
import net.kyori.adventure.text.format.TextColor;
36+
import net.kyori.adventure.text.format.TextDecoration;
3337
import net.kyori.adventure.text.renderer.TranslatableComponentRenderer;
3438
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
3539
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
3640
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
3741
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
3842
import org.cloudburstmc.nbt.NbtMap;
43+
import org.cloudburstmc.nbt.NbtType;
3944
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
4045
import org.geysermc.geyser.GeyserImpl;
4146
import org.geysermc.geyser.session.GeyserSession;
@@ -341,16 +346,16 @@ public static void handleChatPacket(GeyserSession session, Component message, Ho
341346
// Though, Bedrock cannot care about the signed stuff.
342347
TranslatableComponent.Builder withDecoration = Component.translatable()
343348
.key(chat.translationKey())
344-
.style(TextDecoration.getStyle(chat));
349+
.style(ChatDecoration.getStyle(chat));
345350
List<ChatTypeDecoration.Parameter> parameters = chat.parameters();
346351
List<Component> args = new ArrayList<>(3);
347-
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
352+
if (parameters.contains(ChatDecoration.Parameter.TARGET)) {
348353
args.add(targetName);
349354
}
350-
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
355+
if (parameters.contains(ChatDecoration.Parameter.SENDER)) {
351356
args.add(sender);
352357
}
353-
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
358+
if (parameters.contains(ChatDecoration.Parameter.CONTENT)) {
354359
args.add(message);
355360
}
356361
withDecoration.arguments(args);
@@ -426,17 +431,91 @@ public static String normalizeSpace(String string) {
426431
}
427432

428433
/**
429-
* Deserialize an NbtMap provided from a registry into a string.
434+
* Deserialize an NbtMap with a description text component (usually provided from a registry) into a Bedrock-formatted string.
430435
*/
431-
// This may be a Component in the future.
432-
public static String deserializeDescription(NbtMap tag) {
436+
public static String deserializeDescription(GeyserSession session, NbtMap tag) {
433437
NbtMap description = tag.getCompound("description");
434-
String translate = description.getString("translate", null);
435-
if (translate == null) {
436-
GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag);
437-
return "";
438+
Component parsed = componentFromNbtTag(description);
439+
return convertMessage(session, parsed);
440+
}
441+
442+
public static Component componentFromNbtTag(Object nbtTag) {
443+
return componentFromNbtTag(nbtTag, Style.empty());
444+
}
445+
446+
private static Component componentFromNbtTag(Object nbtTag, Style style) {
447+
if (nbtTag instanceof String literal) {
448+
return Component.text(literal).style(style);
449+
} else if (nbtTag instanceof List<?> list) {
450+
return Component.join(JoinConfiguration.noSeparators(), componentsFromNbtList(list, style));
451+
} else if (nbtTag instanceof NbtMap map) {
452+
Component component = null;
453+
String text = map.getString("text", null);
454+
if (text != null) {
455+
component = Component.text(text);
456+
} else {
457+
String translateKey = map.getString("translate", null);
458+
if (translateKey != null) {
459+
String fallback = map.getString("fallback", "");
460+
List<Component> args = new ArrayList<>();
461+
462+
Object with = map.get("with");
463+
if (with instanceof List<?> list) {
464+
args = componentsFromNbtList(list, style);
465+
} else if (with != null) {
466+
args.add(componentFromNbtTag(with, style));
467+
}
468+
component = Component.translatable(translateKey, fallback, args);
469+
}
470+
}
471+
472+
if (component != null) {
473+
Style newStyle = getStyleFromNbtMap(map, style);
474+
component = component.style(newStyle);
475+
476+
Object extra = map.get("extra");
477+
if (extra != null) {
478+
component = component.append(componentFromNbtTag(extra, newStyle));
479+
}
480+
481+
return component;
482+
}
438483
}
439-
return translate;
484+
485+
throw new IllegalArgumentException("Expected tag to be a literal string, a list of components, or a component object with a text/translate key");
486+
}
487+
488+
private static List<Component> componentsFromNbtList(List<?> list, Style style) {
489+
List<Component> components = new ArrayList<>();
490+
for (Object entry : list) {
491+
components.add(componentFromNbtTag(entry, style));
492+
}
493+
return components;
494+
}
495+
496+
public static Style getStyleFromNbtMap(NbtMap map) {
497+
Style.Builder style = Style.style();
498+
499+
String colorString = map.getString("color", null);
500+
if (colorString != null) {
501+
if (colorString.startsWith(TextColor.HEX_PREFIX)) {
502+
style.color(TextColor.fromHexString(colorString));
503+
} else {
504+
style.color(NamedTextColor.NAMES.value(colorString));
505+
}
506+
}
507+
508+
map.listenForBoolean("bold", value -> style.decoration(TextDecoration.BOLD, value));
509+
map.listenForBoolean("italic", value -> style.decoration(TextDecoration.ITALIC, value));
510+
map.listenForBoolean("underlined", value -> style.decoration(TextDecoration.UNDERLINED, value));
511+
map.listenForBoolean("strikethrough", value -> style.decoration(TextDecoration.STRIKETHROUGH, value));
512+
map.listenForBoolean("obfuscated", value -> style.decoration(TextDecoration.OBFUSCATED, value));
513+
514+
return style.build();
515+
}
516+
517+
public static Style getStyleFromNbtMap(NbtMap map, Style base) {
518+
return base.merge(getStyleFromNbtMap(map));
440519
}
441520

442521
public static void init() {

0 commit comments

Comments
 (0)