mirror of
https://github.com/GeyserMC/Geyser.git
synced 2024-12-22 14:34:59 +01:00
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
This commit is contained in:
parent
14cf104cff
commit
73f7259b6d
5 changed files with 105 additions and 40 deletions
|
@ -69,7 +69,7 @@ public record Enchantment(String identifier,
|
||||||
|
|
||||||
// TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java,
|
// TODO - description is a component. So if a hardcoded literal string is given, this will display normally on Java,
|
||||||
// but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name.
|
// but Geyser will attempt to lookup the literal string as translation - and will fail, displaying an empty string as enchantment name.
|
||||||
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(data) : null;
|
String description = bedrockEnchantment == null ? MessageTranslator.deserializeDescription(context.session(), data) : null;
|
||||||
|
|
||||||
return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel,
|
return new Enchantment(context.id().asString(), effects, supportedItems, maxLevel,
|
||||||
description, anvilCost, exclusiveSet, bedrockEnchantment);
|
description, anvilCost, exclusiveSet, bedrockEnchantment);
|
||||||
|
|
|
@ -44,7 +44,7 @@ public record JukeboxSong(String soundEvent, String description) {
|
||||||
soundEvent = "";
|
soundEvent = "";
|
||||||
GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject);
|
GeyserImpl.getInstance().getLogger().debug("Sound event for " + context.id() + " was of an unexpected type! Expected string or NBT map, got " + soundEventObject);
|
||||||
}
|
}
|
||||||
String description = MessageTranslator.deserializeDescription(data);
|
String description = MessageTranslator.deserializeDescription(context.session(), data);
|
||||||
return new JukeboxSong(soundEvent, description);
|
return new JukeboxSong(soundEvent, description);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,7 +49,7 @@ import org.geysermc.geyser.session.GeyserSession;
|
||||||
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
import org.geysermc.geyser.session.cache.registry.JavaRegistry;
|
||||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||||
import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry;
|
import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry;
|
||||||
import org.geysermc.geyser.text.TextDecoration;
|
import org.geysermc.geyser.text.ChatDecoration;
|
||||||
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
import org.geysermc.geyser.translator.level.BiomeTranslator;
|
||||||
import org.geysermc.geyser.util.MinecraftKey;
|
import org.geysermc.geyser.util.MinecraftKey;
|
||||||
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
import org.geysermc.mcprotocollib.protocol.MinecraftProtocol;
|
||||||
|
@ -78,7 +78,7 @@ public final class RegistryCache {
|
||||||
private static final Map<Key, BiConsumer<RegistryCache, List<RegistryEntry>>> REGISTRIES = new HashMap<>();
|
private static final Map<Key, BiConsumer<RegistryCache, List<RegistryEntry>>> REGISTRIES = new HashMap<>();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
register("chat_type", cache -> cache.chatTypes, TextDecoration::readChatType);
|
register("chat_type", cache -> cache.chatTypes, ChatDecoration::readChatType);
|
||||||
register("dimension_type", cache -> cache.dimensions, JavaDimension::read);
|
register("dimension_type", cache -> cache.dimensions, JavaDimension::read);
|
||||||
register("enchantment", cache -> cache.enchantments, Enchantment::read);
|
register("enchantment", cache -> cache.enchantments, Enchantment::read);
|
||||||
register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read);
|
register("jukebox_song", cache -> cache.jukeboxSongs, JukeboxSong::read);
|
||||||
|
|
|
@ -25,17 +25,19 @@
|
||||||
|
|
||||||
package org.geysermc.geyser.text;
|
package org.geysermc.geyser.text;
|
||||||
|
|
||||||
import net.kyori.adventure.text.format.NamedTextColor;
|
|
||||||
import net.kyori.adventure.text.format.Style;
|
import net.kyori.adventure.text.format.Style;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
import org.cloudburstmc.nbt.NbtType;
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
import org.geysermc.geyser.session.cache.registry.RegistryEntryContext;
|
||||||
|
import org.geysermc.geyser.translator.text.MessageTranslator;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
|
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType;
|
||||||
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration;
|
import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatTypeDecoration;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
public record TextDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
|
public record ChatDecoration(String translationKey, List<Parameter> parameters, Style deserializedStyle) implements ChatTypeDecoration {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public NbtMap style() {
|
public NbtMap style() {
|
||||||
|
@ -53,38 +55,22 @@ public record TextDecoration(String translationKey, List<Parameter> parameters,
|
||||||
String translationKey = chat.getString("translation_key");
|
String translationKey = chat.getString("translation_key");
|
||||||
|
|
||||||
NbtMap styleTag = chat.getCompound("style");
|
NbtMap styleTag = chat.getCompound("style");
|
||||||
Style style = deserializeStyle(styleTag);
|
Style style = MessageTranslator.getStyleFromNbtMap(styleTag);
|
||||||
|
|
||||||
List<ChatTypeDecoration.Parameter> parameters = new ArrayList<>();
|
List<ChatTypeDecoration.Parameter> parameters = new ArrayList<>();
|
||||||
List<String> parametersNbt = chat.getList("parameters", NbtType.STRING);
|
List<String> parametersNbt = chat.getList("parameters", NbtType.STRING);
|
||||||
for (String parameter : parametersNbt) {
|
for (String parameter : parametersNbt) {
|
||||||
parameters.add(ChatTypeDecoration.Parameter.valueOf(parameter.toUpperCase(Locale.ROOT)));
|
parameters.add(ChatTypeDecoration.Parameter.valueOf(parameter.toUpperCase(Locale.ROOT)));
|
||||||
}
|
}
|
||||||
return new ChatType(new TextDecoration(translationKey, parameters, style), null);
|
return new ChatType(new ChatDecoration(translationKey, parameters, style), null);
|
||||||
}
|
}
|
||||||
return new ChatType(null, null);
|
return new ChatType(null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Style getStyle(ChatTypeDecoration decoration) {
|
public static Style getStyle(ChatTypeDecoration decoration) {
|
||||||
if (decoration instanceof TextDecoration textDecoration) {
|
if (decoration instanceof ChatDecoration chatDecoration) {
|
||||||
return textDecoration.deserializedStyle();
|
return chatDecoration.deserializedStyle();
|
||||||
}
|
}
|
||||||
return deserializeStyle(decoration.style());
|
return MessageTranslator.getStyleFromNbtMap(decoration.style());
|
||||||
}
|
|
||||||
|
|
||||||
private static Style deserializeStyle(NbtMap styleTag) {
|
|
||||||
Style.Builder builder = Style.style();
|
|
||||||
if (!styleTag.isEmpty()) {
|
|
||||||
String color = styleTag.getString("color", null);
|
|
||||||
if (color != null) {
|
|
||||||
builder.color(NamedTextColor.NAMES.value(color));
|
|
||||||
}
|
|
||||||
//TODO implement the rest
|
|
||||||
boolean italic = styleTag.getBoolean("italic");
|
|
||||||
if (italic) {
|
|
||||||
builder.decorate(net.kyori.adventure.text.format.TextDecoration.ITALIC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return builder.build();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -26,16 +26,21 @@
|
||||||
package org.geysermc.geyser.translator.text;
|
package org.geysermc.geyser.translator.text;
|
||||||
|
|
||||||
import net.kyori.adventure.text.Component;
|
import net.kyori.adventure.text.Component;
|
||||||
|
import net.kyori.adventure.text.JoinConfiguration;
|
||||||
import net.kyori.adventure.text.ScoreComponent;
|
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.flattener.ComponentFlattener;
|
||||||
|
import net.kyori.adventure.text.format.NamedTextColor;
|
||||||
|
import net.kyori.adventure.text.format.Style;
|
||||||
import net.kyori.adventure.text.format.TextColor;
|
import net.kyori.adventure.text.format.TextColor;
|
||||||
|
import net.kyori.adventure.text.format.TextDecoration;
|
||||||
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;
|
||||||
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
|
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
|
||||||
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
|
||||||
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
|
||||||
import org.cloudburstmc.nbt.NbtMap;
|
import org.cloudburstmc.nbt.NbtMap;
|
||||||
|
import org.cloudburstmc.nbt.NbtType;
|
||||||
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
|
||||||
import org.geysermc.geyser.GeyserImpl;
|
import org.geysermc.geyser.GeyserImpl;
|
||||||
import org.geysermc.geyser.session.GeyserSession;
|
import org.geysermc.geyser.session.GeyserSession;
|
||||||
|
@ -341,16 +346,16 @@ public class MessageTranslator {
|
||||||
// Though, Bedrock cannot care about the signed stuff.
|
// Though, Bedrock cannot care about the signed stuff.
|
||||||
TranslatableComponent.Builder withDecoration = Component.translatable()
|
TranslatableComponent.Builder withDecoration = Component.translatable()
|
||||||
.key(chat.translationKey())
|
.key(chat.translationKey())
|
||||||
.style(TextDecoration.getStyle(chat));
|
.style(ChatDecoration.getStyle(chat));
|
||||||
List<ChatTypeDecoration.Parameter> parameters = chat.parameters();
|
List<ChatTypeDecoration.Parameter> parameters = chat.parameters();
|
||||||
List<Component> args = new ArrayList<>(3);
|
List<Component> args = new ArrayList<>(3);
|
||||||
if (parameters.contains(TextDecoration.Parameter.TARGET)) {
|
if (parameters.contains(ChatDecoration.Parameter.TARGET)) {
|
||||||
args.add(targetName);
|
args.add(targetName);
|
||||||
}
|
}
|
||||||
if (parameters.contains(TextDecoration.Parameter.SENDER)) {
|
if (parameters.contains(ChatDecoration.Parameter.SENDER)) {
|
||||||
args.add(sender);
|
args.add(sender);
|
||||||
}
|
}
|
||||||
if (parameters.contains(TextDecoration.Parameter.CONTENT)) {
|
if (parameters.contains(ChatDecoration.Parameter.CONTENT)) {
|
||||||
args.add(message);
|
args.add(message);
|
||||||
}
|
}
|
||||||
withDecoration.arguments(args);
|
withDecoration.arguments(args);
|
||||||
|
@ -426,17 +431,91 @@ public class MessageTranslator {
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Deserialize an NbtMap provided from a registry into a string.
|
* Deserialize an NbtMap with a description text component (usually provided from a registry) into a Bedrock-formatted string.
|
||||||
*/
|
*/
|
||||||
// This may be a Component in the future.
|
public static String deserializeDescription(GeyserSession session, NbtMap tag) {
|
||||||
public static String deserializeDescription(NbtMap tag) {
|
|
||||||
NbtMap description = tag.getCompound("description");
|
NbtMap description = tag.getCompound("description");
|
||||||
String translate = description.getString("translate", null);
|
Component parsed = componentFromNbtTag(description);
|
||||||
if (translate == null) {
|
return convertMessage(session, parsed);
|
||||||
GeyserImpl.getInstance().getLogger().debug("Don't know how to read description! " + tag);
|
}
|
||||||
return "";
|
|
||||||
|
public static Component componentFromNbtTag(Object nbtTag) {
|
||||||
|
return componentFromNbtTag(nbtTag, Style.empty());
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Component componentFromNbtTag(Object nbtTag, Style style) {
|
||||||
|
if (nbtTag instanceof String literal) {
|
||||||
|
return Component.text(literal).style(style);
|
||||||
|
} else if (nbtTag instanceof List<?> list) {
|
||||||
|
return Component.join(JoinConfiguration.noSeparators(), componentsFromNbtList(list, style));
|
||||||
|
} else if (nbtTag instanceof NbtMap map) {
|
||||||
|
Component component = null;
|
||||||
|
String text = map.getString("text", null);
|
||||||
|
if (text != null) {
|
||||||
|
component = Component.text(text);
|
||||||
|
} else {
|
||||||
|
String translateKey = map.getString("translate", null);
|
||||||
|
if (translateKey != null) {
|
||||||
|
String fallback = map.getString("fallback", "");
|
||||||
|
List<Component> args = new ArrayList<>();
|
||||||
|
|
||||||
|
Object with = map.get("with");
|
||||||
|
if (with instanceof List<?> list) {
|
||||||
|
args = componentsFromNbtList(list, style);
|
||||||
|
} else if (with != null) {
|
||||||
|
args.add(componentFromNbtTag(with, style));
|
||||||
|
}
|
||||||
|
component = Component.translatable(translateKey, fallback, args);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component != null) {
|
||||||
|
Style newStyle = getStyleFromNbtMap(map, style);
|
||||||
|
component = component.style(newStyle);
|
||||||
|
|
||||||
|
Object extra = map.get("extra");
|
||||||
|
if (extra != null) {
|
||||||
|
component = component.append(componentFromNbtTag(extra, newStyle));
|
||||||
|
}
|
||||||
|
|
||||||
|
return component;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return translate;
|
|
||||||
|
throw new IllegalArgumentException("Expected tag to be a literal string, a list of components, or a component object with a text/translate key");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<Component> componentsFromNbtList(List<?> list, Style style) {
|
||||||
|
List<Component> components = new ArrayList<>();
|
||||||
|
for (Object entry : list) {
|
||||||
|
components.add(componentFromNbtTag(entry, style));
|
||||||
|
}
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Style getStyleFromNbtMap(NbtMap map) {
|
||||||
|
Style.Builder style = Style.style();
|
||||||
|
|
||||||
|
String colorString = map.getString("color", null);
|
||||||
|
if (colorString != null) {
|
||||||
|
if (colorString.startsWith(TextColor.HEX_PREFIX)) {
|
||||||
|
style.color(TextColor.fromHexString(colorString));
|
||||||
|
} else {
|
||||||
|
style.color(NamedTextColor.NAMES.value(colorString));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
map.listenForBoolean("bold", value -> style.decoration(TextDecoration.BOLD, value));
|
||||||
|
map.listenForBoolean("italic", value -> style.decoration(TextDecoration.ITALIC, value));
|
||||||
|
map.listenForBoolean("underlined", value -> style.decoration(TextDecoration.UNDERLINED, value));
|
||||||
|
map.listenForBoolean("strikethrough", value -> style.decoration(TextDecoration.STRIKETHROUGH, value));
|
||||||
|
map.listenForBoolean("obfuscated", value -> style.decoration(TextDecoration.OBFUSCATED, value));
|
||||||
|
|
||||||
|
return style.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Style getStyleFromNbtMap(NbtMap map, Style base) {
|
||||||
|
return base.merge(getStyleFromNbtMap(map));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void init() {
|
public static void init() {
|
||||||
|
|
Loading…
Reference in a new issue