1
0
mirror of https://github.com/GeyserMC/Geyser.git synced 2025-12-23 00:39:19 +00:00

Start working on dialog translation

This commit is contained in:
Eclipse
2025-06-02 07:27:36 +00:00
parent e6668b60b3
commit 6a6706ca1e
7 changed files with 208 additions and 2 deletions

View File

@@ -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);
}

View File

@@ -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<TrimMaterial> TRIM_MATERIAL = create("trim_material");
public static final JavaRegistryKey<TrimPattern> TRIM_PATTERN = create("trim_pattern");
public static final JavaRegistryKey<RegistryUnit> DAMAGE_TYPE = create("damage_type");
public static final JavaRegistryKey<Dialog> DIALOG = create("dialog");
public static final JavaRegistryKey<CatEntity.BuiltInVariant> CAT_VARIANT = create("cat_variant");
public static final JavaRegistryKey<FrogEntity.BuiltInVariant> FROG_VARIANT = create("frog_variant");

View File

@@ -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<T>(Key registryKey, NetworkSerializer<T> networkSe
Key keyFromNetworkId(GeyserSession session, JavaRegistryKey<T> registry, int networkId);
}
@Override
public @NotNull String toString() {
return "Java registry: " + registryKey;
}
}

View File

@@ -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<String> labels;
private final List<Object> 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<String> 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;
}
}
}

View File

@@ -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);
}
}

View File

@@ -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<ClientboundSh
@Override
public void translate(GeyserSession session, ClientboundShowDialogGamePacket packet) {
Dialog dialog;
if (packet.getDialog().isId()) {
dialog = JavaRegistries.DIALOG.fromNetworkId(session, packet.getDialog().id());
} else {
dialog = Dialog.readDialog(session, packet.getDialog().custom());
}
session.sendForm(dialog.buildForm());
}
}

View File

@@ -39,6 +39,7 @@ import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
import net.kyori.adventure.text.serializer.legacy.CharacterAndFormat;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import net.kyori.adventure.text.serializer.plain.PlainTextComponentSerializer;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.cloudburstmc.nbt.NbtMap;
import org.cloudburstmc.protocol.bedrock.packet.TextPacket;
import org.geysermc.geyser.GeyserImpl;
@@ -474,6 +475,14 @@ public class MessageTranslator {
return convertMessageForTooltip(parsed, session.locale());
}
@Nullable
public static String convertFromNullableNbtTag(GeyserSession session, @Nullable Object nbtTag) {
if (nbtTag == null) {
return null;
}
return convertMessage(session, componentFromNbtTag(nbtTag));
}
public static Component componentFromNbtTag(Object nbtTag) {
return componentFromNbtTag(nbtTag, Style.empty());
}