From 2e699f4ceed343c85394c5defdeb1e201cd76801 Mon Sep 17 00:00:00 2001 From: Eclipse Date: Thu, 5 Jun 2025 14:29:10 +0000 Subject: [PATCH] Implement dialog_list type, small refactors with button dialogs --- .../session/cache/tags/GeyserHolderSet.java | 9 +++ .../session/dialog/ConfirmationDialog.java | 18 +++++- .../geyser/session/dialog/Dialog.java | 8 ++- .../geyser/session/dialog/DialogButton.java | 2 +- .../session/dialog/DialogListDialog.java | 57 +++++++++++++++++++ .../session/dialog/DialogWithButtons.java | 19 +++---- .../session/dialog/MultiActionDialog.java | 12 +++- .../session/dialog/ServerLinksDialog.java | 7 ++- .../session/dialog/action/DialogAction.java | 12 ++-- 9 files changed, 123 insertions(+), 21 deletions(-) create mode 100644 core/src/main/java/org/geysermc/geyser/session/dialog/DialogListDialog.java diff --git a/core/src/main/java/org/geysermc/geyser/session/cache/tags/GeyserHolderSet.java b/core/src/main/java/org/geysermc/geyser/session/cache/tags/GeyserHolderSet.java index 283d1b357..27fe7a75a 100644 --- a/core/src/main/java/org/geysermc/geyser/session/cache/tags/GeyserHolderSet.java +++ b/core/src/main/java/org/geysermc/geyser/session/cache/tags/GeyserHolderSet.java @@ -128,6 +128,8 @@ public final class GeyserHolderSet { /** * Reads a HolderSet from a NBT object. Does not support reading HolderSets that can hold inline values. * + *

Uses {@link JavaRegistryKey#keyToNetworkId(GeyserSession, Key)} to resolve registry keys to network IDs.

+ * * @param session the Geyser session. * @param registry the registry the HolderSet contains IDs from. * @param holderSet the HolderSet as a NBT object. @@ -136,6 +138,13 @@ public final class GeyserHolderSet { return readHolderSet(registry, holderSet, key -> registry.keyToNetworkId(session, key)); } + /** + * Reads a HolderSet from a NBT object. Does not support reading HolderSets that can hold inline values. + * + * @param registry the registry the HolderSet contains IDs from. + * @param holderSet the HolderSet as a NBT object. + * @param idMapper a function that maps a key in this registry to its respective network ID. + */ public static GeyserHolderSet readHolderSet(JavaRegistryKey registry, @Nullable Object holderSet, ToIntFunction idMapper) { return readHolderSet(registry, holderSet, idMapper, null); } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/ConfirmationDialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/ConfirmationDialog.java index 9ec9be3b3..c8af7f564 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/ConfirmationDialog.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/ConfirmationDialog.java @@ -30,13 +30,29 @@ import org.cloudburstmc.nbt.NbtMap; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.MinecraftKey; +import java.util.List; import java.util.Optional; public class ConfirmationDialog extends DialogWithButtons { public static final Key TYPE = MinecraftKey.key("confirmation"); + private final DialogButton yes; + private final DialogButton no; + public ConfirmationDialog(GeyserSession session, NbtMap map, IdGetter idGetter) { - super(session, map, parseOptionalList(DialogButton.read(session, map.get("yes"), idGetter), DialogButton.read(session, map.get("no"), idGetter)), Optional.empty()); + super(session, map, Optional.empty()); + yes = DialogButton.read(session, map.get("yes"), idGetter).orElseThrow(); + no = DialogButton.read(session, map.get("no"), idGetter).orElseThrow(); + } + + @Override + protected List buttons(DialogHolder holder) { + return List.of(yes, no); + } + + @Override + protected Optional onCancel() { + return Optional.of(no); } } 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 index 9f19996b3..ec2609942 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/Dialog.java @@ -59,8 +59,10 @@ public abstract class Dialog { private static final Key PLAIN_MESSAGE_BODY = MinecraftKey.key("plain_message"); + @Getter private final String title; - private final String externalTitle; + @Getter + private final Optional externalTitle; @Getter private final boolean canCloseWithEscape; @Getter @@ -72,7 +74,7 @@ public abstract class Dialog { protected Dialog(GeyserSession session, NbtMap map) { title = MessageTranslator.convertFromNullableNbtTag(session, map.get("title")); - externalTitle = MessageTranslator.convertFromNullableNbtTag(session, map.get("title")); + externalTitle = Optional.ofNullable(MessageTranslator.convertFromNullableNbtTag(session, map.get("external_title"))); canCloseWithEscape = map.getBoolean("can_close_with_escape", true); afterAction = AfterAction.fromString(map.getString("after_action")); @@ -167,6 +169,8 @@ public abstract class Dialog { Key type = MinecraftKey.key(map.getString("type")); if (type.equals(NoticeDialog.TYPE)) { return new NoticeDialog(session, map, idGetter); + } else if (type.equals(DialogListDialog.TYPE)) { + return new DialogListDialog(session, map, idGetter); } else if (type.equals(ConfirmationDialog.TYPE)) { return new ConfirmationDialog(session, map, idGetter); } else if (type.equals(MultiActionDialog.TYPE)) { diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/DialogButton.java b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogButton.java index ccf5d570a..cab427f29 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/DialogButton.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogButton.java @@ -42,7 +42,7 @@ public record DialogButton(String label, Optional action) { } List buttons = new ArrayList<>(); for (NbtMap map : tag) { - buttons.add(read(session, map, idGetter).orElseThrow()); // Should never throw + buttons.add(read(session, map, idGetter).orElseThrow()); // Should never throw because we know map is a NbtMap } return buttons; } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/DialogListDialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogListDialog.java new file mode 100644 index 000000000..a9ff1fc54 --- /dev/null +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogListDialog.java @@ -0,0 +1,57 @@ +/* + * 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.session.cache.registry.JavaRegistries; +import org.geysermc.geyser.session.cache.tags.GeyserHolderSet; +import org.geysermc.geyser.session.dialog.action.DialogAction; +import org.geysermc.geyser.util.MinecraftKey; + +import java.util.List; +import java.util.Optional; + +public class DialogListDialog extends DialogWithButtons { + + public static final Key TYPE = MinecraftKey.key("dialog_list"); + + private final GeyserHolderSet dialogs; + + public DialogListDialog(GeyserSession session, NbtMap map, IdGetter idGetter) { + super(session, map, readDefaultExitAction(session, map, idGetter)); + dialogs = GeyserHolderSet.readHolderSet(JavaRegistries.DIALOG, map.get("dialogs"), idGetter, dialog -> Dialog.readDialogFromNbt(session, dialog, idGetter)); + } + + @Override + protected List buttons(DialogHolder holder) { + return dialogs.resolve(holder.session()).stream() + .map(dialog -> new DialogButton(dialog.externalTitle().orElseGet(dialog::title), + Optional.of(new DialogAction.ShowDialog(dialog)))) + .toList(); + } +} diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/DialogWithButtons.java b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogWithButtons.java index b676ea43b..188b10a6b 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/DialogWithButtons.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/DialogWithButtons.java @@ -32,23 +32,24 @@ import org.geysermc.cumulus.form.SimpleForm; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.session.dialog.input.ParsedInputs; -import java.util.ArrayList; import java.util.List; import java.util.Optional; public abstract class DialogWithButtons extends Dialog { - protected final List buttons; protected final Optional exitAction; - protected DialogWithButtons(GeyserSession session, NbtMap map, List buttons, Optional exitAction) { + protected DialogWithButtons(GeyserSession session, NbtMap map, Optional exitAction) { super(session, map); - this.buttons = buttons; this.exitAction = exitAction; } + protected abstract List buttons(DialogHolder holder); + @Override protected void addCustomComponents(DialogHolder holder, CustomForm.Builder builder) { + List buttons = buttons(holder); + DropdownComponent.Builder dropdown = DropdownComponent.builder(); dropdown.text("Please select an option:"); for (DialogButton button : buttons) { @@ -69,6 +70,7 @@ public abstract class DialogWithButtons extends Dialog { @Override protected void addCustomComponents(DialogHolder holder, SimpleForm.Builder builder) { + List buttons = buttons(holder); for (DialogButton button : buttons) { builder.button(button.label()); } @@ -88,12 +90,7 @@ public abstract class DialogWithButtons extends Dialog { return exitAction; } - @SafeVarargs - protected static List parseOptionalList(Optional... buttons) { - List checked = new ArrayList<>(); - for (Optional button : buttons) { - checked.add(button.orElseThrow()); - } - return checked; + protected static Optional readDefaultExitAction(GeyserSession session, NbtMap map, IdGetter idGetter) { + return DialogButton.read(session, map.get("exit_action"), idGetter); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/MultiActionDialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/MultiActionDialog.java index c8c2e1f50..522a6c7b9 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/MultiActionDialog.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/MultiActionDialog.java @@ -31,11 +31,21 @@ import org.cloudburstmc.nbt.NbtType; import org.geysermc.geyser.session.GeyserSession; import org.geysermc.geyser.util.MinecraftKey; +import java.util.List; + public class MultiActionDialog extends DialogWithButtons { public static final Key TYPE = MinecraftKey.key("multi_action"); + private final List buttons; + protected MultiActionDialog(GeyserSession session, NbtMap map, IdGetter idGetter) { - super(session, map, DialogButton.readList(session, map.getList("actions", NbtType.COMPOUND), idGetter), DialogButton.read(session, map.get("exit_action"), idGetter)); + super(session, map, readDefaultExitAction(session, map, idGetter)); + buttons = DialogButton.readList(session, map.getList("actions", NbtType.COMPOUND), idGetter); + } + + @Override + protected List buttons(DialogHolder holder) { + return buttons; } } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/ServerLinksDialog.java b/core/src/main/java/org/geysermc/geyser/session/dialog/ServerLinksDialog.java index c33c81941..8c6a23ef3 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/ServerLinksDialog.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/ServerLinksDialog.java @@ -34,6 +34,11 @@ import java.util.Optional; public class ServerLinksDialog extends DialogWithButtons { protected ServerLinksDialog(GeyserSession session, NbtMap map, List buttons) { - super(session, map, buttons, Optional.empty()); + super(session, map, Optional.empty()); + } + + @Override + protected List buttons(DialogHolder holder) { + return List.of(); } } diff --git a/core/src/main/java/org/geysermc/geyser/session/dialog/action/DialogAction.java b/core/src/main/java/org/geysermc/geyser/session/dialog/action/DialogAction.java index 6487bd363..0cefdd7ea 100644 --- a/core/src/main/java/org/geysermc/geyser/session/dialog/action/DialogAction.java +++ b/core/src/main/java/org/geysermc/geyser/session/dialog/action/DialogAction.java @@ -108,22 +108,26 @@ public interface DialogAction { } } - record ShowDialog(Holder dialog) implements DialogAction { + record ShowDialog(Optional dialog, Holder holder) implements DialogAction { public static final Key TYPE = MinecraftKey.key("show_dialog"); + public ShowDialog(Dialog dialog) { + this(Optional.of(dialog), null); + } + private static ShowDialog read(Object dialog, Dialog.IdGetter idGetter) { if (dialog instanceof NbtMap map) { - return new ShowDialog(Holder.ofCustom(map)); + return new ShowDialog(Optional.empty(), Holder.ofCustom(map)); } else if (dialog instanceof String string) { - return new ShowDialog(Holder.ofId(idGetter.applyAsInt(MinecraftKey.key(string)))); + return new ShowDialog(Optional.empty(), Holder.ofId(idGetter.applyAsInt(MinecraftKey.key(string)))); } throw new IllegalArgumentException("Expected dialog in show_dialog action to be a NBT map or a resource location"); } @Override public void run(GeyserSession session, ParsedInputs inputs) { - session.getDialogManager().openDialog(dialog); + dialog.ifPresentOrElse(normal -> session.getDialogManager().openDialog(normal), () -> session.getDialogManager().openDialog(holder)); } }