From 6a6706ca1ecec41ae548f5d3e20a3889c45aefa6 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Mon, 2 Jun 2025 07:27:36 +0000 Subject: [PATCH] Start working on dialog translation --- .../geyser/session/cache/RegistryCache.java | 8 +- .../cache/registry/JavaRegistries.java | 2 + .../cache/registry/JavaRegistryKey.java | 6 + .../geyser/session/dialog/Dialog.java | 135 ++++++++++++++++++ .../geyser/session/dialog/NoticeDialog.java | 40 ++++++ .../JavaShowDialogGameTranslator.java | 10 +- .../translator/text/MessageTranslator.java | 9 ++ 7 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java create mode 100644 core/src/main/java/org/geysermc/geyser/session/dialog/NoticeDialog.java diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java index d96890749..c3dbb55fc 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/RegistryCache.java @@ -53,6 +53,7 @@ import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; import org.geysermc.geyser.session.cache.registry.RegistryEntryData; import org.geysermc.geyser.session.cache.registry.RegistryUnit; import org.geysermc.geyser.session.cache.registry.SimpleJavaRegistry; +import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.text.ChatDecoration; import org.geysermc.geyser.translator.level.BiomeTranslator; import org.geysermc.geyser.util.MinecraftKey; @@ -87,6 +88,7 @@ public final class RegistryCache { register(JavaRegistries.TRIM_MATERIAL, TrimRecipe::readTrimMaterial); register(JavaRegistries.TRIM_PATTERN, TrimRecipe::readTrimPattern); register(JavaRegistries.DAMAGE_TYPE, RegistryReader.UNIT); + register(JavaRegistries.DIALOG, Dialog::readDialog); register(JavaRegistries.CAT_VARIANT, VariantHolder.reader(CatEntity.BuiltInVariant.class, CatEntity.BuiltInVariant.BLACK)); register(JavaRegistries.FROG_VARIANT, VariantHolder.reader(FrogEntity.BuiltInVariant.class, FrogEntity.BuiltInVariant.TEMPERATE)); @@ -135,7 +137,11 @@ public final class RegistryCache { // Java generic mess - we're sure we're putting the current readers for the correct registry types in the READERS map, so we use raw objects here to let it compile RegistryLoader reader = READERS.get(registryKey); if (reader != null) { - reader.load(session, registries.get(registryKey), packet.getEntries()); + try { + reader.load(session, registries.get(registryKey), packet.getEntries()); + } catch (Exception exception) { + GeyserImpl.getInstance().getLogger().error("Failed parsing registry entries for " + registryKey + "!", exception); + } } else { throw new IllegalStateException("Expected reader for registry " + registryKey); } diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java index ea6e14913..4808e56e7 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistries.java @@ -44,6 +44,7 @@ import org.geysermc.geyser.level.block.type.Block; import org.geysermc.geyser.registry.BlockRegistries; import org.geysermc.geyser.registry.ListRegistry; import org.geysermc.geyser.registry.Registries; +import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.util.MinecraftKey; import org.geysermc.mcprotocollib.protocol.data.game.chat.ChatType; @@ -70,6 +71,7 @@ public class JavaRegistries { public static final JavaRegistryKey TRIM_MATERIAL = create("trim_material"); public static final JavaRegistryKey TRIM_PATTERN = create("trim_pattern"); public static final JavaRegistryKey DAMAGE_TYPE = create("damage_type"); + public static final JavaRegistryKey DIALOG = create("dialog"); public static final JavaRegistryKey CAT_VARIANT = create("cat_variant"); public static final JavaRegistryKey FROG_VARIANT = create("frog_variant"); diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistryKey.java b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistryKey.java index d98a3be4d..658d44d7e 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistryKey.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/registry/JavaRegistryKey.java @@ -27,6 +27,7 @@ package org.geysermc.geyser.session.cache.registry; import net.kyori.adventure.key.Key; import org.geysermc.geyser.session.GeyserSession; +import org.jetbrains.annotations.NotNull; /** * Defines a Java registry, which can be hardcoded or data-driven. This class doesn't store registry contents itself, that is handled by {@link org.geysermc.geyser.session.cache.RegistryCache} in the case of @@ -81,4 +82,9 @@ public record JavaRegistryKey(Key registryKey, NetworkSerializer networkSe Key keyFromNetworkId(GeyserSession session, JavaRegistryKey registry, int networkId); } + + @Override + public @NotNull String toString() { + return "Java registry: " + registryKey; + } } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java new file mode 100644 index 000000000..f4842c01e --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.dialog; + +import net.kyori.adventure.key.Key; +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.cumulus.form.CustomForm; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.registry.RegistryEntryContext; +import org.geysermc.geyser.translator.text.MessageTranslator; +import org.geysermc.geyser.util.MinecraftKey; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public abstract class Dialog { + + private static final Key PLAIN_MESSAGE_BODY = MinecraftKey.key("plain_message"); + + private final String title; + private final String externalTitle; + private final AfterAction afterAction; + private final List labels; + private final List inputs; // TODO + + protected Dialog(GeyserSession session, NbtMap map) { + title = MessageTranslator.convertFromNullableNbtTag(session, map.get("title")); + externalTitle = MessageTranslator.convertFromNullableNbtTag(session, map.get("title")); + afterAction = AfterAction.fromString(map.getString("after_action")); + + Object bodyTag = map.get("body"); + if (bodyTag == null) { + labels = List.of(); + } else if (bodyTag instanceof NbtMap bodyMap) { + String body = readBody(session, bodyMap); + if (body != null) { + labels = List.of(body); + } else { + labels = List.of(); + } + } else if (bodyTag instanceof List bodyList) { + List bodies = new ArrayList<>(); + for (Object tag : bodyList) { + if (tag instanceof NbtMap bodyMap) { + String body = readBody(session, bodyMap); + if (body != null) { + bodies.add(body); + } + } else { + throw new IllegalStateException("Found non-NBT map in list of bodies, was: " + tag); + } + } + labels = List.copyOf(bodies); + } else { + throw new IllegalStateException("Expected body tag to either be a NBT map or list thereof, was: " + bodyTag); + } + + inputs = List.of(); + } + + public CustomForm buildForm() { + return createForm().build(); + } + + protected CustomForm.Builder createForm() { + CustomForm.Builder builder = CustomForm.builder() + .title(title); + for (String label : labels) { + builder.label(label); + } + return builder; + } + + private static String readBody(GeyserSession session, NbtMap tag) { + Key type = MinecraftKey.key(tag.getString("type")); + if (type.equals(PLAIN_MESSAGE_BODY)) { + return MessageTranslator.convertFromNullableNbtTag(session, tag.get("contents")); + } + // Other type is item, can't display that in forms + return null; + } + + public static Dialog readDialog(RegistryEntryContext context) { + return readDialog(context.session(), context.data()); + } + + public static Dialog readDialog(GeyserSession session, NbtMap map) { + // TYPES: notice, server_links, dialog_list, multi_action, confirmation + Key type = MinecraftKey.key(map.getString("type")); + if (type.equals(NoticeDialog.TYPE)) { + return new NoticeDialog(session, map); + } + return new Dialog(session, map) {}; + // throw new UnsupportedOperationException("Unable to read unknown dialog type " + type + "!"); // TODO put this here once all types are implemented + } + + public enum AfterAction { + CLOSE, + NONE, + WAIT_FOR_RESPONSE; + + public static AfterAction fromString(String string) { + for (AfterAction action : values()) { + if (action.name().toLowerCase(Locale.ROOT).equals(string)) { + return action; + } + } + return null; + } + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/NoticeDialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/NoticeDialog.java new file mode 100644 index 000000000..2d4f16e8d --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/NoticeDialog.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2025 GeyserMC. http://geysermc.org + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * @author GeyserMC + * @link https://github.com/GeyserMC/Geyser + */ + +package org.geysermc.geyser.session.dialog; + +import net.kyori.adventure.key.Key; +import org.cloudburstmc.nbt.NbtMap; +import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.util.MinecraftKey; + +public class NoticeDialog extends Dialog { + + public static final Key TYPE = MinecraftKey.key("notice"); + + public NoticeDialog(GeyserSession session, NbtMap map) { + super(session, map); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/dialogues/JavaShowDialogGameTranslator.java b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/dialogues/JavaShowDialogGameTranslator.java index 30431d221..1090832bf 100644 --- a/core/src/main/java/org/geysermc/geyser/translator/protocol/java/dialogues/JavaShowDialogGameTranslator.java +++ b/core/src/main/java/org/geysermc/geyser/translator/protocol/java/dialogues/JavaShowDialogGameTranslator.java @@ -26,6 +26,8 @@ package org.geysermc.geyser.translator.protocol.java.dialogues; import org.geysermc.geyser.session.GeyserSession; +import org.geysermc.geyser.session.cache.registry.JavaRegistries; +import org.geysermc.geyser.session.dialog.Dialog; import org.geysermc.geyser.translator.protocol.PacketTranslator; import org.geysermc.geyser.translator.protocol.Translator; import org.geysermc.mcprotocollib.protocol.packet.ingame.clientbound.ClientboundShowDialogGamePacket; @@ -35,6 +37,12 @@ public class JavaShowDialogGameTranslator extends PacketTranslator