From b4466ec981d104c4756d1a3b90c2ee0d6ce4e6bd Mon Sep 17 00:00:00 2001 From: Jake Potrebic Date: Sun, 6 Jul 2025 11:49:43 -0700 Subject: [PATCH] Dialog API (#12671) --- .../paper/registry/keys/DialogKeys.java | 61 ++ .../registry/keys/tags/DialogTagKeys.java | 57 ++ .../java/io/papermc/paper/dialog/Dialog.java | 47 ++ .../paper/dialog/DialogResponseView.java | 52 ++ .../io/papermc/paper/dialog/package-info.java | 7 + .../event/player/PlayerCustomClickEvent.java | 79 +++ .../papermc/paper/registry/RegistryKey.java | 7 +- .../data/InlinedRegistryBuilderProvider.java | 6 +- .../registry/data/dialog/ActionButton.java | 115 ++++ .../registry/data/dialog/DialogBase.java | 211 ++++++ .../data/dialog/DialogInstancesProvider.java | 89 +++ .../data/dialog/DialogRegistryEntry.java | 75 +++ .../data/dialog/action/DialogAction.java | 122 ++++ .../dialog/action/DialogActionCallback.java | 21 + .../data/dialog/action/package-info.java | 9 + .../registry/data/dialog/body/DialogBody.java | 77 +++ .../data/dialog/body/ItemDialogBody.java | 124 ++++ .../dialog/body/PlainMessageDialogBody.java | 28 + .../data/dialog/body/package-info.java | 9 + .../data/dialog/input/BooleanDialogInput.java | 88 +++ .../data/dialog/input/DialogInput.java | 142 ++++ .../dialog/input/NumberRangeDialogInput.java | 126 ++++ .../dialog/input/SingleOptionDialogInput.java | 130 ++++ .../data/dialog/input/TextDialogInput.java | 159 +++++ .../data/dialog/input/package-info.java | 9 + .../registry/data/dialog/package-info.java | 6 + .../data/dialog/type/ConfirmationType.java | 30 + .../data/dialog/type/DialogListType.java | 91 +++ .../registry/data/dialog/type/DialogType.java | 110 ++++ .../data/dialog/type/MultiActionType.java | 75 +++ .../registry/data/dialog/type/NoticeType.java | 21 + .../data/dialog/type/ServerLinksType.java | 40 ++ .../data/dialog/type/package-info.java | 7 + .../paper/registry/event/RegistryEvents.java | 3 + .../paper/registry/set/RegistryKeySet.java | 20 +- .../registry/set/RegistryKeySetImpl.java | 5 +- .../paper/registry/set/RegistrySet.java | 39 +- .../paper/registry/set/RegistryValueSet.java | 9 +- .../registry/set/RegistryValueSetBuilder.java | 31 + .../registry/set/RegistryValueSetImpl.java | 2 - .../paper/registry/set/package-info.java | 11 + .../main/java/org/bukkit/entity/Player.java | 8 +- .../java/io/papermc/generator/Rewriters.java | 2 + .../generator/registry/RegistryEntries.java | 6 +- .../0016-Moonrise-optimisation-patches.patch | 4 +- .../features/0028-Optimize-Hoppers.patch | 4 +- ...on-checking-in-player-move-packet-ha.patch | 18 +- .../0032-Improve-keepalive-ping-system.patch | 8 +- ...033-Optimise-EntityScheduler-ticking.patch | 6 +- .../server/MinecraftServer.java.patch | 5 +- .../server/dialog/body/ItemBody.java.patch | 13 + .../ServerCommonPacketListenerImpl.java.patch | 23 +- ...ConfigurationPacketListenerImpl.java.patch | 18 +- .../ServerGamePacketListenerImpl.java.patch | 32 +- .../paper/adventure/AdventureCodecs.java | 41 +- .../providers/ClickCallbackProviderImpl.java | 128 +++- .../PaperPlayerConfigurationConnection.java | 40 +- .../io/papermc/paper/dialog/PaperDialog.java | 29 + .../paper/dialog/PaperDialogResponseView.java | 48 ++ .../player/PaperPlayerCustomClickEvent.java | 44 ++ .../paper/event/player/package-info.java | 4 + .../paper/registry/PaperRegistries.java | 12 +- .../InlinedRegistryBuilderProviderImpl.java | 11 +- .../data/PaperInstrumentRegistryEntry.java | 4 +- .../data/PaperJukeboxSongRegistryEntry.java | 4 +- .../data/dialog/ActionButtonImpl.java | 46 ++ .../registry/data/dialog/DialogBaseImpl.java | 83 +++ .../data/dialog/PaperDialogCodecs.java | 186 ++++++ .../dialog/PaperDialogInstancesProvider.java | 155 +++++ .../data/dialog/PaperDialogRegistryEntry.java | 74 +++ .../registry/data/dialog/PaperDialogs.java | 84 +++ .../action/CommandTemplateActionImpl.java | 10 + .../dialog/action/CustomClickActionImpl.java | 24 + .../data/dialog/action/StaticActionImpl.java | 11 + .../data/dialog/body/ItemDialogBodyImpl.java | 63 ++ .../dialog/body/PlainMessageBodyImpl.java | 16 + .../dialog/input/BooleanDialogInputImpl.java | 46 ++ .../input/NumberRangeDialogInputImpl.java | 73 +++ .../input/SingleOptionDialogInputImpl.java | 60 ++ .../dialog/input/TextDialogInputImpl.java | 81 +++ .../dialog/type/ConfirmationTypeImpl.java | 6 + .../data/dialog/type/DialogListTypeImpl.java | 53 ++ .../data/dialog/type/MultiActionTypeImpl.java | 46 ++ .../data/dialog/type/NoticeTypeImpl.java | 15 + .../data/dialog/type/ServerLinksTypeImpl.java | 17 + .../paper/registry/data/util/Conversions.java | 25 +- .../registry/entry/RegistryEntryBuilder.java | 4 - .../registry/set/NamedRegistryKeySetImpl.java | 2 - .../paper/registry/set/PaperRegistrySets.java | 50 ++ .../set/RegistryValueSetBuilderImpl.java | 33 + .../io/papermc/paper/util/PaperCodecs.java | 54 ++ .../java/org/bukkit/craftbukkit/CraftArt.java | 2 +- .../bukkit/craftbukkit/CraftJukeboxSong.java | 2 +- .../craftbukkit/CraftMusicInstrument.java | 2 +- .../org/bukkit/craftbukkit/CraftRegistry.java | 20 +- .../org/bukkit/craftbukkit/CraftSound.java | 2 +- .../craftbukkit/attribute/CraftAttribute.java | 2 +- .../bukkit/craftbukkit/block/CraftBiome.java | 2 +- .../block/banner/CraftPatternType.java | 2 +- .../craftbukkit/damage/CraftDamageType.java | 2 +- .../enchantments/CraftEnchantment.java | 2 +- .../bukkit/craftbukkit/entity/CraftCat.java | 2 +- .../craftbukkit/entity/CraftChicken.java | 2 +- .../bukkit/craftbukkit/entity/CraftCow.java | 2 +- .../bukkit/craftbukkit/entity/CraftFrog.java | 2 +- .../bukkit/craftbukkit/entity/CraftPig.java | 2 +- .../craftbukkit/entity/CraftPlayer.java | 620 ++++++++++-------- .../craftbukkit/entity/CraftVillager.java | 4 +- .../bukkit/craftbukkit/entity/CraftWolf.java | 4 +- .../inventory/trim/CraftTrimMaterial.java | 2 +- .../inventory/trim/CraftTrimPattern.java | 2 +- .../craftbukkit/map/CraftMapCursor.java | 2 +- .../potion/CraftPotionEffectType.java | 2 +- ...gistry.data.dialog.DialogInstancesProvider | 1 + .../registry/RegistryConversionTest.java | 2 +- .../provider/RegistriesArgumentProvider.java | 3 + 116 files changed, 4404 insertions(+), 465 deletions(-) create mode 100644 paper-api/src/generated/java/io/papermc/paper/registry/keys/DialogKeys.java create mode 100644 paper-api/src/generated/java/io/papermc/paper/registry/keys/tags/DialogTagKeys.java create mode 100644 paper-api/src/main/java/io/papermc/paper/dialog/Dialog.java create mode 100644 paper-api/src/main/java/io/papermc/paper/dialog/DialogResponseView.java create mode 100644 paper-api/src/main/java/io/papermc/paper/dialog/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/event/player/PlayerCustomClickEvent.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/ActionButton.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogBase.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogInstancesProvider.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogRegistryEntry.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogAction.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogActionCallback.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/DialogBody.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBody.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageDialogBody.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInput.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/DialogInput.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInput.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInput.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInput.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksType.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/package-info.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilder.java create mode 100644 paper-api/src/main/java/io/papermc/paper/registry/set/package-info.java create mode 100644 paper-server/patches/sources/net/minecraft/server/dialog/body/ItemBody.java.patch create mode 100644 paper-server/src/main/java/io/papermc/paper/dialog/PaperDialog.java create mode 100644 paper-server/src/main/java/io/papermc/paper/dialog/PaperDialogResponseView.java create mode 100644 paper-server/src/main/java/io/papermc/paper/event/player/PaperPlayerCustomClickEvent.java create mode 100644 paper-server/src/main/java/io/papermc/paper/event/player/package-info.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/ActionButtonImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/DialogBaseImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogCodecs.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogInstancesProvider.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogRegistryEntry.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogs.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CommandTemplateActionImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CustomClickActionImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/StaticActionImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBodyImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageBodyImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInputImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInputImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInputImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInputImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationTypeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListTypeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionTypeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeTypeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksTypeImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilderImpl.java create mode 100644 paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java create mode 100644 paper-server/src/main/resources/META-INF/services/io.papermc.paper.registry.data.dialog.DialogInstancesProvider diff --git a/paper-api/src/generated/java/io/papermc/paper/registry/keys/DialogKeys.java b/paper-api/src/generated/java/io/papermc/paper/registry/keys/DialogKeys.java new file mode 100644 index 0000000000..ab57b44fb7 --- /dev/null +++ b/paper-api/src/generated/java/io/papermc/paper/registry/keys/DialogKeys.java @@ -0,0 +1,61 @@ +package io.papermc.paper.registry.keys; + +import static net.kyori.adventure.key.Key.key; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.generated.GeneratedFrom; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import net.kyori.adventure.key.Key; +import org.jspecify.annotations.NullMarked; + +/** + * Vanilla keys for {@link RegistryKey#DIALOG}. + * + * @apiNote The fields provided here are a direct representation of + * what is available from the vanilla game source. They may be + * changed (including removals) on any Minecraft version + * bump, so cross-version compatibility is not provided on the + * same level as it is on most of the other API. + */ +@SuppressWarnings({ + "unused", + "SpellCheckingInspection" +}) +@NullMarked +@GeneratedFrom("1.21.7-rc2") +public final class DialogKeys { + /** + * {@code minecraft:custom_options} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey CUSTOM_OPTIONS = create(key("custom_options")); + + /** + * {@code minecraft:quick_actions} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey QUICK_ACTIONS = create(key("quick_actions")); + + /** + * {@code minecraft:server_links} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TypedKey SERVER_LINKS = create(key("server_links")); + + private DialogKeys() { + } + + /** + * Creates a typed key for {@link Dialog} in the registry {@code minecraft:dialog}. + * + * @param key the value's key in the registry + * @return a new typed key + */ + public static TypedKey create(final Key key) { + return TypedKey.create(RegistryKey.DIALOG, key); + } +} diff --git a/paper-api/src/generated/java/io/papermc/paper/registry/keys/tags/DialogTagKeys.java b/paper-api/src/generated/java/io/papermc/paper/registry/keys/tags/DialogTagKeys.java new file mode 100644 index 0000000000..64d41d763f --- /dev/null +++ b/paper-api/src/generated/java/io/papermc/paper/registry/keys/tags/DialogTagKeys.java @@ -0,0 +1,57 @@ +package io.papermc.paper.registry.keys.tags; + +import static net.kyori.adventure.key.Key.key; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.generated.GeneratedFrom; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.tag.TagKey; +import net.kyori.adventure.key.Key; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; + +/** + * Vanilla tag keys for {@link RegistryKey#DIALOG}. + * + * @apiNote The fields provided here are a direct representation of + * what is available from the vanilla game source. They may be + * changed (including removals) on any Minecraft version + * bump, so cross-version compatibility is not provided on the + * same level as it is on most of the other API. + */ +@SuppressWarnings({ + "unused", + "SpellCheckingInspection" +}) +@NullMarked +@GeneratedFrom("1.21.7-rc2") +@ApiStatus.Experimental +public final class DialogTagKeys { + /** + * {@code #minecraft:pause_screen_additions} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey PAUSE_SCREEN_ADDITIONS = create(key("pause_screen_additions")); + + /** + * {@code #minecraft:quick_actions} + * + * @apiNote This field is version-dependant and may be removed in future Minecraft versions + */ + public static final TagKey QUICK_ACTIONS = create(key("quick_actions")); + + private DialogTagKeys() { + } + + /** + * Creates a tag key for {@link Dialog} in the registry {@code minecraft:dialog}. + * + * @param key the tag key's key + * @return a new tag key + */ + @ApiStatus.Experimental + public static TagKey create(final Key key) { + return TagKey.create(RegistryKey.DIALOG, key); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/dialog/Dialog.java b/paper-api/src/main/java/io/papermc/paper/dialog/Dialog.java new file mode 100644 index 0000000000..a77c23ab0a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/dialog/Dialog.java @@ -0,0 +1,47 @@ +package io.papermc.paper.dialog; + +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.InlinedRegistryBuilderProvider; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; +import java.util.function.Consumer; +import net.kyori.adventure.dialog.DialogLike; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.key.KeyPattern; +import org.bukkit.Keyed; +import org.bukkit.Registry; +import org.jetbrains.annotations.ApiStatus; + +/** + * Represents a dialog. Can be created during normal server operation via {@link #create(Consumer)}. + * Can also be created during bootstrap via {@link io.papermc.paper.registry.event.RegistryEvents#DIALOG}. + */ +@ApiStatus.NonExtendable +public interface Dialog extends Keyed, DialogLike { + + /** + * Creates a new dialog using the provided builder. + * + * @param value the builder to use for creating the dialog + * @return a new dialog instance + */ + @ApiStatus.Experimental + static Dialog create(final Consumer> value) { + return InlinedRegistryBuilderProvider.instance().createDialog(value); + } + + // Start generate - Dialog + // @GeneratedFrom 1.21.7-rc2 + Dialog CUSTOM_OPTIONS = getDialog("custom_options"); + + Dialog QUICK_ACTIONS = getDialog("quick_actions"); + + Dialog SERVER_LINKS = getDialog("server_links"); + // End generate - Dialog + + private static Dialog getDialog(@KeyPattern.Value final String value) { + final Registry registry = RegistryAccess.registryAccess().getRegistry(RegistryKey.DIALOG); + return registry.getOrThrow(Key.key(Key.MINECRAFT_NAMESPACE, value)); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/dialog/DialogResponseView.java b/paper-api/src/main/java/io/papermc/paper/dialog/DialogResponseView.java new file mode 100644 index 0000000000..10a26e788f --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/dialog/DialogResponseView.java @@ -0,0 +1,52 @@ +package io.papermc.paper.dialog; + +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +/** + * A view for a possible response to a dialog. + * There are no guarantees that this is an actual response to a + * dialog form. It is on the plugin to validate that the response + * is valid. + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +public interface DialogResponseView { + + /** + * Gets the raw payload of the response. + * + * @return the raw payload + */ + @Contract(pure = true) + BinaryTagHolder payload(); + + /** + * Gets a text value at a key. + * + * @param key the key + * @return the value (or null if it doesn't exist) + */ + @Contract(pure = true) + @Nullable String getText(String key); + + /** + * Gets a boolean value at a key. + * + * @param key the key + * @return the value (or null if it doesn't exist) + */ + @Contract(pure = true) + @Nullable Boolean getBoolean(String key); + + /** + * Gets a float value at a key. + * + * @param key the key + * @return the value (or null if it doesn't exist) + */ + @Contract(pure = true) + @Nullable Float getFloat(String key); +} diff --git a/paper-api/src/main/java/io/papermc/paper/dialog/package-info.java b/paper-api/src/main/java/io/papermc/paper/dialog/package-info.java new file mode 100644 index 0000000000..48c9a5f202 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/dialog/package-info.java @@ -0,0 +1,7 @@ +/** + * This package contains classes and interfaces related to the dialog system in Paper. + */ +@NullMarked +package io.papermc.paper.dialog; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/event/player/PlayerCustomClickEvent.java b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerCustomClickEvent.java new file mode 100644 index 0000000000..00670678e8 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/event/player/PlayerCustomClickEvent.java @@ -0,0 +1,79 @@ +package io.papermc.paper.event.player; + +import io.papermc.paper.connection.PlayerCommonConnection; +import io.papermc.paper.dialog.DialogResponseView; +import io.papermc.paper.registry.data.dialog.action.DialogActionCallback; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.event.ClickCallback; +import org.bukkit.entity.Player; +import org.bukkit.event.Event; +import org.bukkit.event.HandlerList; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; + +/** + * This event is fired for any custom click events. + * @see net.kyori.adventure.text.event.ClickEvent#custom(Key, BinaryTagHolder) + * @see io.papermc.paper.registry.data.dialog.action.DialogAction#customClick(DialogActionCallback, ClickCallback.Options) + */ +@ApiStatus.Experimental +@ApiStatus.NonExtendable +@NullMarked +public abstract class PlayerCustomClickEvent extends Event { + + private final Key identifier; + private final PlayerCommonConnection commonConnection; + + @ApiStatus.Internal + protected PlayerCustomClickEvent(final Key identifier, final PlayerCommonConnection commonConnection) { + this.identifier = identifier; + this.commonConnection = commonConnection; + } + + /** + * The identifier of the custom click event. + * + * @return the identifier + */ + public final Key getIdentifier() { + return this.identifier; + } + + /** + * The tag payload of the custom click event. + * + * @return the tag (if any) + */ + public abstract @Nullable BinaryTagHolder getTag(); + + /** + * The dialog response view of the custom click event. + * + * @return the dialog response view + */ + public abstract @Nullable DialogResponseView getDialogResponseView(); + + /** + * The common connection of the player. + * + * @return the common connection + */ + public final PlayerCommonConnection getCommonConnection() { + return this.commonConnection; + } + + @Override + public HandlerList getHandlers() { + // this will be how handler lists will work on interfaces + return PlayerCustomClickEvent.getHandlerList(); + } + + public static HandlerList getHandlerList() { + final class Holder { + private static final HandlerList HANDLER_LIST = new HandlerList(); + } + return Holder.HANDLER_LIST; + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java b/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java index ef057b02d8..fe22a5fadc 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/RegistryKey.java @@ -1,6 +1,7 @@ package io.papermc.paper.registry; import io.papermc.paper.datacomponent.DataComponentType; +import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.tag.TagKey; import net.kyori.adventure.key.Key; import net.kyori.adventure.key.KeyPattern; @@ -215,7 +216,11 @@ public sealed interface RegistryKey extends Keyed permits RegistryKeyImpl { * @see io.papermc.paper.registry.keys.PigVariantKeys */ RegistryKey PIG_VARIANT = create("pig_variant"); - + /** + * Data-driven registry for dialogs. + * @see io.papermc.paper.registry.keys.DialogKeys + */ + RegistryKey DIALOG = create("dialog"); /* ******************* * diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProvider.java b/paper-api/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProvider.java index 68b56dcd11..33257a4147 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProvider.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProvider.java @@ -1,6 +1,8 @@ package io.papermc.paper.registry.data; +import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; import java.util.Optional; import java.util.ServiceLoader; import java.util.function.Consumer; @@ -12,11 +14,13 @@ import org.jetbrains.annotations.ApiStatus; public interface InlinedRegistryBuilderProvider { static InlinedRegistryBuilderProvider instance() { - class Holder { + final class Holder { static final Optional INSTANCE = ServiceLoader.load(InlinedRegistryBuilderProvider.class).findFirst(); } return Holder.INSTANCE.orElseThrow(); } MusicInstrument createInstrument(Consumer> value); + + Dialog createDialog(Consumer> value); } diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/ActionButton.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/ActionButton.java new file mode 100644 index 0000000000..127a224965 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/ActionButton.java @@ -0,0 +1,115 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * Represents an action button in a dialog, which can be used to trigger actions or navigate within the dialog. + * Action buttons can have labels, tooltips, and associated actions. + */ +@ApiStatus.NonExtendable +public interface ActionButton { + + /** + * Creates a new action button with the specified label, tooltip, width, and action. + * + * @param label the label of the button + * @param tooltip the tooltip to display when hovering over the button, or null if no tooltip is needed + * @param width the width of the button + * @param action the action to perform when the button is clicked, or null if no action is associated + * @return a new ActionButton instance + */ + @Contract(value = "_, _, _, _ -> new", pure = true) + static ActionButton create(final Component label, final @Nullable Component tooltip, final int width, final @Nullable DialogAction action) { + return builder(label).tooltip(tooltip).width(width).action(action).build(); + } + + /** + * Creates a new action button builder with the specified label. + * + * @param label the label of the button + * @return a new ActionButton.Builder instance + */ + @Contract(pure = true, value = "_ -> new") + static ActionButton.Builder builder(final Component label) { + return DialogInstancesProvider.instance().actionButtonBuilder(label); + } + + /** + * Returns the label of the action button. + * + * @return the label of the button + */ + @Contract(pure = true) + Component label(); + + /** + * Returns the tooltip of the action button, or null if no tooltip is set. + * + * @return the tooltip of the button, or null + */ + @Contract(pure = true) + @Nullable Component tooltip(); + + /** + * Returns the width of the action button. + * + * @return the width of the button + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int width(); + + /** + * Returns the action associated with this button, or null if no action is associated. + * + * @return the action to perform when the button is clicked, or null + */ + @Contract(pure = true) + @Nullable DialogAction action(); + + /** + * A builder for creating ActionButton instances. + */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the tooltip of the action button, or null if no tooltip is desired. + * + * @param tooltip the tooltip of the button, or null + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder tooltip(@Nullable Component tooltip); + + /** + * Sets the width of the action button. + * + * @param width the width of the button + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder width(@Range(from = 1, to = 1024) int width); + + /** + * Sets the action associated with this button, or null if no action is desired. + * + * @param action the action to perform when the button is clicked, or null + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder action(@Nullable DialogAction action); + + /** + * Builds the ActionButton instance with the configured values. + * + * @return a new ActionButton instance + */ + @Contract(value = "-> new", pure = true) + ActionButton build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogBase.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogBase.java new file mode 100644 index 0000000000..d8c71a538e --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogBase.java @@ -0,0 +1,211 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.registry.data.dialog.body.DialogBody; +import io.papermc.paper.registry.data.dialog.input.DialogInput; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.util.Index; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; + +/** + * Represents the base of all dialogs. + */ +@ApiStatus.NonExtendable +public interface DialogBase { + + /** + * Creates a new dialog base. + * + * @param title the title of the dialog + * @param externalTitle the external title of the dialog, or null if not set + * @param canCloseWithEscape if the dialog can be closed with the "escape" keybind + * @param pause if the dialog should pause the game when opened (single-player only) + * @param afterAction the action to take after the dialog is closed + * @param body the body of the dialog + * @param inputs the inputs of the dialog + * @return a new dialog base instance + */ + @Contract(value = "_, _, _, _, _, _, _ -> new", pure = true) + static DialogBase create( + final Component title, + final @Nullable Component externalTitle, + final boolean canCloseWithEscape, + final boolean pause, + final DialogAfterAction afterAction, + final List body, + final List inputs + ) { + return builder(title).externalTitle(externalTitle).canCloseWithEscape(canCloseWithEscape).pause(pause).afterAction(afterAction).body(body).inputs(inputs).build(); + } + + /** + * Creates a new dialog base builder. + * + * @param title the title of the dialog + * @return a new dialog base builder + */ + @Contract(value = "_ -> new", pure = true) + static Builder builder(final Component title) { + return DialogInstancesProvider.instance().dialogBaseBuilder(title); + } + + /** + * The title of the dialog. + * + * @return the title + */ + @Contract(pure = true) + Component title(); + + /** + * The external title of the dialog. This title + * is used on buttons that open this dialog. + * + * @return the external title or null + */ + @Contract(pure = true) + @Nullable Component externalTitle(); + + /** + * Returns if this dialog can be closed with the "escape" keybind. + * + * @return if the dialog can be closed with "escape" + */ + @Contract(pure = true) + boolean canCloseWithEscape(); + + /** + * Returns if this dialog should pause the game when opened (single-player only). + * + * @return if the dialog pauses the game + */ + @Contract(pure = true) + boolean pause(); + + /** + * The action to take after the dialog is closed. + * + * @return the action to take after the dialog is closed + */ + @Contract(pure = true) + DialogAfterAction afterAction(); + + /** + * The body of the dialog. + *

+ * The body is a list of {@link DialogBody} elements that will be displayed in the dialog. + * + * @return the body of the dialog + */ + @Contract(pure = true) + @Unmodifiable List body(); + + /** + * The inputs of the dialog. + *

+ * The inputs are a list of {@link DialogInput} elements that will be displayed in the dialog. + * + * @return the inputs of the dialog + */ + @Contract(pure = true) + @Unmodifiable List inputs(); + + /** + * Actions to take after the dialog is closed. + */ + enum DialogAfterAction { + /** + * Closes the dialog and returns to the previous non-dialog screen (if any). + */ + CLOSE("close"), + /** + * Does nothing (keeps the current screen open). + */ + NONE("none"), + /** + * Replaces dialog with a "waiting for response" screen. + */ + WAIT_FOR_RESPONSE("wait_for_response"); + + public static final Index NAMES = Index.create(DialogAfterAction.class, e -> e.name); + + private final String name; + + DialogAfterAction(final String name) { + this.name = name; + } + } + + /** + * Builder interface for creating dialog bases. + */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the external title of the dialog. + * This title is used on buttons that open this dialog. + * + * @param externalTitle the external title of the dialog, or null if not set + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder externalTitle(@Nullable Component externalTitle); + + /** + * Sets whether the dialog can be closed with the "escape" keybind. + * + * @param canCloseWithEscape if the dialog can be closed with "escape" + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder canCloseWithEscape(boolean canCloseWithEscape); + + /** + * Sets whether the dialog should pause the game when opened (single-player only). + * + * @param pause if the dialog should pause the game + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder pause(boolean pause); + + /** + * Sets the action to take after the dialog is closed. + * + * @param afterAction the action to take after the dialog is closed + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder afterAction(DialogAfterAction afterAction); + + /** + * Sets the body of the dialog. + * + * @param body the body of the dialog + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder body(List body); + + /** + * Sets the inputs of the dialog. + * + * @param inputs the inputs of the dialog + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder inputs(List inputs); + + /** + * Builds the dialog base. + * + * @return the built dialog base + */ + @Contract(pure = true, value = "-> new") + DialogBase build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogInstancesProvider.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogInstancesProvider.java new file mode 100644 index 0000000000..ae8ba50f5c --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogInstancesProvider.java @@ -0,0 +1,89 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import io.papermc.paper.registry.data.dialog.action.DialogActionCallback; +import io.papermc.paper.registry.data.dialog.body.ItemDialogBody; +import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody; +import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput; +import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput; +import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput; +import io.papermc.paper.registry.data.dialog.input.TextDialogInput; +import io.papermc.paper.registry.data.dialog.type.ConfirmationType; +import io.papermc.paper.registry.data.dialog.type.DialogListType; +import io.papermc.paper.registry.data.dialog.type.MultiActionType; +import io.papermc.paper.registry.data.dialog.type.NoticeType; +import io.papermc.paper.registry.data.dialog.type.ServerLinksType; +import io.papermc.paper.registry.set.RegistrySet; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickCallback; +import net.kyori.adventure.text.event.ClickEvent; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.Nullable; + +/** + * @hidden + */ +@SuppressWarnings("MissingJavadoc") +@ApiStatus.Internal +public interface DialogInstancesProvider { + + static DialogInstancesProvider instance() { + final class Holder { + static final Optional INSTANCE = ServiceLoader.load(DialogInstancesProvider.class).findFirst(); + } + return Holder.INSTANCE.orElseThrow(); + } + + DialogBase.Builder dialogBaseBuilder(Component title); + + ActionButton.Builder actionButtonBuilder(Component label); + + // actions + DialogAction.CustomClickAction register(DialogActionCallback callback, ClickCallback.Options options); + + DialogAction.StaticAction staticAction(ClickEvent value); + + DialogAction.CommandTemplateAction commandTemplate(String template); + + DialogAction.CustomClickAction customClick(Key id, @Nullable BinaryTagHolder additions); + + // bodies + ItemDialogBody.Builder itemDialogBodyBuilder(ItemStack itemStack); + + PlainMessageDialogBody plainMessageDialogBody(Component component); + + PlainMessageDialogBody plainMessageDialogBody(Component component, int width); + + // inputs + BooleanDialogInput.Builder booleanBuilder(String key, Component label); + + NumberRangeDialogInput.Builder numberRangeBuilder(String key, Component label, float start, float end); + + SingleOptionDialogInput.Builder singleOptionBuilder(String key, Component label, List entries); + + SingleOptionDialogInput.OptionEntry singleOptionEntry(String id, @Nullable Component display, boolean initial); + + TextDialogInput.Builder textBuilder(String key, Component label); + + TextDialogInput.MultilineOptions multilineOptions(@Nullable Integer maxLines, @Nullable Integer height); + + // types + ConfirmationType confirmation(ActionButton yesButton, ActionButton noButton); + + DialogListType.Builder dialogList(RegistrySet

dialogs); + + MultiActionType.Builder multiAction(List actions); + + NoticeType notice(); + + NoticeType notice(ActionButton action); + + ServerLinksType serverLinks(@Nullable ActionButton exitAction, int columns, int buttonWidth); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogRegistryEntry.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogRegistryEntry.java new file mode 100644 index 0000000000..62f691161a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/DialogRegistryEntry.java @@ -0,0 +1,75 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.data.dialog.type.DialogListType; +import io.papermc.paper.registry.data.dialog.type.DialogType; +import io.papermc.paper.registry.set.RegistryValueSetBuilder; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * A data-centric version-specific registry entry for the {@link io.papermc.paper.dialog.Dialog} type. + */ +@ApiStatus.NonExtendable +public interface DialogRegistryEntry { + + /** + * The base dialog for this entry. + * + * @return the base dialog + */ + @Contract(pure = true) + DialogBase base(); + + /** + * The type of dialog for this entry. + * + * @return the dialog type + */ + @Contract(pure = true) + DialogType type(); + + /** + * A mutable builder for the {@link DialogRegistryEntry} plugins may change in applicable registry events. + *

+ * The following values are required for each builder: + *

    + *
  • {@link #base(DialogBase)}
  • + *
  • {@link #type(DialogType)}
  • + *
+ */ + @ApiStatus.NonExtendable + interface Builder extends DialogRegistryEntry, RegistryBuilder { + + /** + * Provides a builder for dialog {@link io.papermc.paper.registry.set.RegistryValueSet} which + * can be used inside {@link DialogListType}. + *

Not a part of the registry entry.

+ * + * @return a new registry value set builder + */ + @Contract(value = "-> new", pure = true) + RegistryValueSetBuilder registryValueSet(); + + /** + * Sets the base dialog for this entry. + * + * @param dialogBase the base dialog + * @return this builder instance + * @see DialogRegistryEntry#base() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder base(DialogBase dialogBase); + + /** + * Sets the specialty dialog for this entry. + * + * @param dialogType the specialty dialog + * @return this builder instance + * @see DialogRegistryEntry#type() + */ + @Contract(value = "_ -> this", mutates = "this") + Builder type(DialogType dialogType); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogAction.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogAction.java new file mode 100644 index 0000000000..0f3323c0c0 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogAction.java @@ -0,0 +1,122 @@ +package io.papermc.paper.registry.data.dialog.action; + +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.event.ClickCallback; +import net.kyori.adventure.text.event.ClickEvent; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +/** + * Represents an action that can be performed in a dialog. + */ +public sealed interface DialogAction permits DialogAction.CommandTemplateAction, DialogAction.CustomClickAction, DialogAction.StaticAction { + + /** + * Creates a new command template action. The template format + * looks for {@code $(variable_name)} to substitute variables. + * {@code variable_name} should correspond to a {@link io.papermc.paper.registry.data.dialog.input.DialogInput#key()}. + * + * @param template the command template to execute + * @return a new command template action instance + */ + @Contract(pure = true, value = "_ -> new") + static CommandTemplateAction commandTemplate(final String template) { + return DialogInstancesProvider.instance().commandTemplate(template); + } + + /** + * Creates a new static action that performs a click event. + * + * @param value the click event to perform + * @return a new static action instance + */ + @Contract(pure = true, value = "_ -> new") + static StaticAction staticAction(final ClickEvent value) { + return DialogInstancesProvider.instance().staticAction(value); + } + + /** + * Creates a new custom click action that executes a custom action. + * Each {@link io.papermc.paper.registry.data.dialog.input.DialogInput#key()} is added + * to the compound binary tag holder. + * + * @param id the identifier of the custom action + * @param additions additional data to be sent with the action, or null if not needed + * @return a new custom all action instance + */ + @Contract(pure = true, value = "_, _ -> new") + static CustomClickAction customClick(final Key id, final @Nullable BinaryTagHolder additions) { + return DialogInstancesProvider.instance().customClick(id, additions); + } + + /** + * Creates a new custom click action that executes a custom action. + * + * @param callback the custom action to execute + * @param options the options for the custom action + * @return a new custom all action instance + */ + @Contract(pure = true, value = "_, _ -> new") + static CustomClickAction customClick(final DialogActionCallback callback, final ClickCallback.Options options) { + return DialogInstancesProvider.instance().register(callback, options); + } + + /** + * Represents an action that executes a command template. + */ + @ApiStatus.NonExtendable + non-sealed interface CommandTemplateAction extends DialogAction { + + /** + * The command template to execute. + * + * @return the command template + */ + @Contract(pure = true) + String template(); + } + + /** + * Represents an action that performs a static click event. + */ + @ApiStatus.NonExtendable + non-sealed interface StaticAction extends DialogAction { + + /** + * The click event to perform. + * + * @return the click event + */ + @Contract(pure = true) + ClickEvent value(); + } + + + /** + * Represents an action that executes a custom action with additional data. + */ + @ApiStatus.NonExtendable + non-sealed interface CustomClickAction extends DialogAction { + + /** + * The identifier of the custom action. + * + * @return the identifier + */ + @Contract(pure = true) + Key id(); + + /** + * Additional data to be sent with the action. + * This is a compound binary tag holder that can contain + * various data related to the action. + * + * @return the additional data, or null if not needed + */ + @Contract(pure = true) + @Nullable BinaryTagHolder additions(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogActionCallback.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogActionCallback.java new file mode 100644 index 0000000000..48f4bf7d6a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/DialogActionCallback.java @@ -0,0 +1,21 @@ +package io.papermc.paper.registry.data.dialog.action; + +import io.papermc.paper.dialog.DialogResponseView; +import net.kyori.adventure.audience.Audience; +import org.jetbrains.annotations.ApiStatus; + +/** + * A callback for a dialog action. + */ +@FunctionalInterface +public interface DialogActionCallback { + + /** + * Handles a dialog action. + * + * @param response the response to the action + * @param audience the audience to send the response to + */ + @ApiStatus.OverrideOnly + void accept(DialogResponseView response, Audience audience); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/package-info.java new file mode 100644 index 0000000000..1f545bdae3 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/action/package-info.java @@ -0,0 +1,9 @@ +/** + * This package contains classes related to dialog actions in the Paper API. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.registry.data.dialog.action; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/DialogBody.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/DialogBody.java new file mode 100644 index 0000000000..8059a32b20 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/DialogBody.java @@ -0,0 +1,77 @@ +package io.papermc.paper.registry.data.dialog.body; + +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import net.kyori.adventure.text.Component; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * Represents the body of a dialog. + */ +public sealed interface DialogBody permits ItemDialogBody, PlainMessageDialogBody { + + /** + * Creates an item body for a dialog. + * + * @param item the item to display in the dialog + * @param description the description of the body, or null if not set + * @param showDecorations whether to show decorations around the item + * @param showTooltip whether to show a tooltip for the item + * @param width the width of the item body + * @param height the height of the item body + * @return a new item body instance + */ + @Contract(pure = true, value = "_, _, _, _, _, _ -> new") + static ItemDialogBody item( + final ItemStack item, + final @Nullable PlainMessageDialogBody description, + final boolean showDecorations, + final boolean showTooltip, + final int width, + final int height + ) { + return item(item) + .description(description) + .showDecorations(showDecorations) + .showTooltip(showTooltip) + .width(width) + .height(height) + .build(); + } + + /** + * Creates a new item dialog body builder. + * + * @param item the item to display in the dialog + * @return a new item dialog body builder instance + */ + @Contract(pure = true, value = "_ -> new") + static ItemDialogBody.Builder item(final ItemStack item) { + return DialogInstancesProvider.instance().itemDialogBodyBuilder(item); + } + + /** + * Creates a plain message body for a dialog. + * + * @param contents the contents of the message + * @return a new plain message body instance + */ + @Contract(pure = true, value = "_, -> new") + static PlainMessageDialogBody plainMessage(final Component contents) { + return DialogInstancesProvider.instance().plainMessageDialogBody(contents); + } + + /** + * Creates a plain message body for a dialog. + * + * @param contents the contents of the message + * @param width the width of the message body + * @return a new plain message body instance + */ + @Contract(pure = true, value = "_, _ -> new") + static PlainMessageDialogBody plainMessage(final Component contents, final @Range(from = 1, to = 1024) int width) { + return DialogInstancesProvider.instance().plainMessageDialogBody(contents, width); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBody.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBody.java new file mode 100644 index 0000000000..252858c27e --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBody.java @@ -0,0 +1,124 @@ +package io.papermc.paper.registry.data.dialog.body; + +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * An item body for a dialog. + *

Created via {@link DialogBody#item(ItemStack, PlainMessageDialogBody, boolean, boolean, int, int)}

+ */ +@ApiStatus.NonExtendable +public non-sealed interface ItemDialogBody extends DialogBody { + + /** + * The item to display in the dialog. + * + * @return the item stack + */ + @Contract(pure = true) + ItemStack item(); + + /** + * The description of the body, or null if not set. + * + * @return the description body + */ + @Contract(pure = true) + @Nullable PlainMessageDialogBody description(); + + /** + * Whether to show decorations around the item. + *

Decorations include damage, itemstack count, etc.

+ * + * @return true if decorations should be shown + */ + @Contract(pure = true) + boolean showDecorations(); + + /** + * Whether to show a tooltip for the item. + * + * @return true if a tooltip should be shown + */ + @Contract(pure = true) + boolean showTooltip(); + + /** + * The width of the item body. + * + * @return the width + */ + @Contract(pure = true) + int width(); + + /** + * The height of the item body. + * + * @return the height + */ + @Contract(pure = true) + int height(); + + /** + * A builder for an item dialog body. + */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the description of the item dialog body, or null if not set. + * + * @param description the description of the body, or null + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder description(@Nullable PlainMessageDialogBody description); + + /** + * Sets whether to show decorations around the item. + * + * @param showDecorations true to show decorations, false otherwise + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder showDecorations(boolean showDecorations); + + /** + * Sets whether to show a tooltip for the item. + * + * @param showTooltip true to show a tooltip, false otherwise + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder showTooltip(boolean showTooltip); + + /** + * Sets the width of the item body. + * + * @param width the width, must be between 1 and 256 + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder width(@Range(from = 1, to = 256) int width); + + /** + * Sets the height of the item body. + * + * @param height the height, must be between 1 and 256 + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder height(@Range(from = 1, to = 256) int height); + + /** + * Builds a new instance of {@link ItemDialogBody}. + * + * @return a new item dialog body instance + */ + @Contract(value = "-> new", pure = true) + ItemDialogBody build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageDialogBody.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageDialogBody.java new file mode 100644 index 0000000000..c0e1bc20df --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageDialogBody.java @@ -0,0 +1,28 @@ +package io.papermc.paper.registry.data.dialog.body; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; + +/** + * A plain message body for a dialog. + *

Created via {@link DialogBody#plainMessage(Component, int)}

+ */ +public non-sealed interface PlainMessageDialogBody extends DialogBody { + + /** + * The contents of the plain message body. + * + * @return the component contents + */ + @Contract(pure = true) + Component contents(); + + /** + * The width of the plain message body. + * + * @return the width + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int width(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/package-info.java new file mode 100644 index 0000000000..f7a4219439 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/body/package-info.java @@ -0,0 +1,9 @@ +/** + * This package contains classes related to dialog bodies in the Paper API. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.registry.data.dialog.body; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInput.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInput.java new file mode 100644 index 0000000000..6761df9400 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInput.java @@ -0,0 +1,88 @@ +package io.papermc.paper.registry.data.dialog.input; + +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * A boolean dialog input. + *

Created via {@link DialogInput#bool(String, Component, boolean, String, String)}

+ */ +@ApiStatus.NonExtendable +public non-sealed interface BooleanDialogInput extends DialogInput { + + /** + * The label for the input. + * + * @return the label component + */ + @Contract(pure = true) + Component label(); + + /** + * The initial value of the input. + * + * @return true if the input is initially true, false otherwise + */ + @Contract(pure = true) + boolean initial(); + + /** + * The input's value in a template when the value is true. + * + * @return the string to use when the input is true + */ + @Contract(pure = true) + String onTrue(); + + /** + * The input's value in a template when the value is false. + * + * @return the string to use when the input is false + */ + @Contract(pure = true) + String onFalse(); + + /** + * A builder for a boolean dialog input. + *

Created via {@link DialogInput#bool(String, Component)}

+ */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the initial value of the input. + * + * @param initial true if the input is initially true, false otherwise + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder initial(boolean initial); + + /** + * Sets the input's value in a template when the value is true. + * + * @param onTrue the string to use when the input is true + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder onTrue(String onTrue); + + /** + * Sets the input's value in a template when the value is false. + * + * @param onFalse the string to use when the input is false + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder onFalse(String onFalse); + + /** + * Builds the instance with the configured values. + * + * @return a new instance + */ + @Contract(value = "-> new", pure = true) + BooleanDialogInput build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/DialogInput.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/DialogInput.java new file mode 100644 index 0000000000..65ce9f2884 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/DialogInput.java @@ -0,0 +1,142 @@ +package io.papermc.paper.registry.data.dialog.input; + +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import java.util.List; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +/** + * Represents an input for dialog. + */ +public sealed interface DialogInput permits BooleanDialogInput, NumberRangeDialogInput, SingleOptionDialogInput, TextDialogInput { + + /** + * Creates a boolean dialog input. + * + * @param key the key identifier for the input + * @param label the label for the input + * @param initial the initial value of the input + * @param onTrue the input's value in a template when the value is true + * @param onFalse the input's value in a template when the value is false + * @return a new boolean dialog input instance + */ + @Contract(pure = true, value = "_, _, _, _, _ -> new") + static BooleanDialogInput bool(final String key, final Component label, final boolean initial, final String onTrue, final String onFalse) { + return bool(key, label) + .initial(initial) + .onTrue(onTrue) + .onFalse(onFalse) + .build(); + } + + /** + * Creates a new builder for a boolean dialog input. + * + * @param key the key identifier for the input + * @param label the label for the input + * @return a new builder instance + */ + @Contract(pure = true, value = "_, _ -> new") + static BooleanDialogInput.Builder bool(final String key, final Component label) { + return DialogInstancesProvider.instance().booleanBuilder(key, label); + } + + /** + * Creates a number range dialog input. + * + * @param key the key identifier for the input + * @param width the width of the input + * @param label the label for the input + * @param labelFormat the format for the label (a translation key or format string) + * @param start the start of the range + * @param end the end of the range + * @param initial the initial value, or null if not set + * @param step the step size, or null if not set + * @return a new number range dialog input instance + */ + @Contract(pure = true, value = "_, _, _, _, _, _, _, _ -> new") + static NumberRangeDialogInput numberRange(final String key, final int width, final Component label, final String labelFormat, final float start, final float end, final @Nullable Float initial, final @Nullable Float step) { + return numberRange(key, label, start, end).width(width).labelFormat(labelFormat).initial(initial).step(step).build(); + } + + /** + * Creates a new builder for a number range dialog input. + * + * @param key the key identifier for the input + * @param label the label for the input + * @param start the start of the range + * @param end the end of the range + * @return a new builder instance + */ + @Contract(value = "_, _, _, _ -> new", pure = true) + static NumberRangeDialogInput.Builder numberRange(final String key, final Component label, final float start, final float end) { + return DialogInstancesProvider.instance().numberRangeBuilder(key, label, start, end); + } + + /** + * Creates a single option dialog input (radio input). + * + * @param key the key identifier for the input + * @param width the width of the input + * @param entries the list of options for the input + * @param label the label for the input + * @param labelVisible whether the label should be visible + * @return a new single option dialog input instance + */ + @Contract(pure = true, value = "_, _, _, _, _ -> new") + static SingleOptionDialogInput singleOption(final String key, final int width, final List entries, final Component label, final boolean labelVisible) { + return singleOption(key, label, entries).width(width).labelVisible(labelVisible).build(); + } + + /** + * Creates a new builder for a single option dialog input. + * + * @param key the key identifier for the input + * @param label the label for the input + * @param entries the list of options for the input + * @return a new builder instance + */ + @Contract(value = "_, _, _ -> new", pure = true) + static SingleOptionDialogInput.Builder singleOption(final String key, final Component label, final List entries) { + return DialogInstancesProvider.instance().singleOptionBuilder(key, label, entries); + } + + /** + * Creates a text dialog input. + * + * @param key the key identifier for the input + * @param width the width of the input + * @param label the label for the input + * @param labelVisible whether the label should be visible + * @param initial the initial value of the input + * @param maxLength the maximum length of the input + * @param multilineOptions the multiline options + * @return a new text dialog input instance + */ + @Contract(pure = true, value = "_, _, _, _, _, _, _ -> new") + static TextDialogInput text(final String key, final int width, final Component label, final boolean labelVisible, final String initial, final int maxLength, final TextDialogInput.@Nullable MultilineOptions multilineOptions) { + return text(key, label).width(width).labelVisible(labelVisible).initial(initial).maxLength(maxLength).multiline(multilineOptions).build(); + } + + /** + * Creates a new builder for a text dialog input. + * + * @param key the key identifier for the input + * @param label the label for the input + * @return a new builder instance + */ + @Contract(value = "_, _ -> new", pure = true) + static TextDialogInput.Builder text(final String key, final Component label) { + return DialogInstancesProvider.instance().textBuilder(key, label); + } + + /** + * Gets the key for this input. + *

Used in dialog actions to identify this dialog input's value

+ * + * @return the key + */ + @Contract(pure = true, value = " -> new") + String key(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInput.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInput.java new file mode 100644 index 0000000000..dfe11a5cde --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInput.java @@ -0,0 +1,126 @@ +package io.papermc.paper.registry.data.dialog.input; + +import net.kyori.adventure.text.Component; +import org.checkerframework.checker.index.qual.Positive; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; + +/** + * A number range dialog input. + *

Created via {@link DialogInput#numberRange(String, int, Component, String, float, float, Float, Float)}

+ */ +@ApiStatus.NonExtendable +public non-sealed interface NumberRangeDialogInput extends DialogInput { + + /** + * The width of the input. + * + * @return the width + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int width(); + + /** + * The label for the input. + * + * @return the label component + */ + @Contract(pure = true) + Component label(); + + /** + * The format for the label, which can be a translation key or a format string. + *

Example: {@code "%s: %s"} or {@code "options.generic_value"}

+ * + * @return the label format + */ + @Contract(pure = true) + String labelFormat(); + + /** + * The start of the range. + * + * @return the start value + */ + @Contract(pure = true) + float start(); + + /** + * The end of the range. + * + * @return the end value + */ + @Contract(pure = true) + float end(); + + /** + * The initial value of the input, or null if not set. + * + * @return the initial value, or null + */ + @Contract(pure = true) + @Nullable Float initial(); + + /** + * The step size for the input, or null if not set. + * + * @return the step size, or null + */ + @Contract(pure = true) + @Positive @Nullable Float step(); + + /** + * A builder for creating instances of {@link NumberRangeDialogInput}. + *

Created via {@link DialogInput#numberRange(String, Component, float, float)}

+ */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the width of the input. + * + * @param width the width + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder width(@Range(from = 1, to = 1024) int width); + + /** + * Sets the format for the label. + *

Example: {@code "%s: %s"} or {@code "options.generic_value"}

+ * + * @param labelFormat the label format + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder labelFormat(String labelFormat); + + /** + * Sets the initial value for the input. + * + * @param initial the initial value, or null if not set + * @return this builder + */ + @Contract(value = "_ -> this", pure = true) + Builder initial(@Nullable Float initial); + + /** + * Sets the step of the range. + * + * @param step the step size, or null if not set + * @return this builder + */ + @Contract(value = "_ -> this", pure = true) + Builder step(@Positive @Nullable Float step); + + /** + * Builds the instance with the configured values. + * + * @return a new instance + */ + @Contract(pure = true, value = "-> new") + NumberRangeDialogInput build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInput.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInput.java new file mode 100644 index 0000000000..67cf17f797 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInput.java @@ -0,0 +1,130 @@ +package io.papermc.paper.registry.data.dialog.input; + +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import java.util.List; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; +import org.jetbrains.annotations.Unmodifiable; + +/** + * A single option dialog input. + *

Created via {@link DialogInput#singleOption(String, int, List, Component, boolean)}

+ */ +@ApiStatus.NonExtendable +public non-sealed interface SingleOptionDialogInput extends DialogInput { + + /** + * The width of the input. + * + * @return the width + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int width(); + + /** + * The list of options for the input. + * + * @return the list of option entries + */ + @Contract(pure = true) + @Unmodifiable List entries(); + + /** + * The label for the input. + * + * @return the label component + */ + @Contract(pure = true) + Component label(); + + /** + * Whether the label should be visible. + * + * @return true if the label is visible, false otherwise + */ + @Contract(pure = true) + boolean labelVisible(); + + /** + * Represents a single option entry in a single option dialog input. + *

Only 1 option is allowed to have initial selected.

+ */ + @ApiStatus.NonExtendable + interface OptionEntry { + + /** + * Creates a new option entry. + * + * @param id the unique identifier for the option + * @param display the display name for the option, or null if not set + * @param initial whether this option is initially selected + * @return a new option entry instance + */ + @Contract(pure = true, value = "_, _, _ -> new") + static OptionEntry create(final String id, final @Nullable Component display, final boolean initial) { + return DialogInstancesProvider.instance().singleOptionEntry(id, display, initial); + } + + /** + * The unique identifier for the option. + * + * @return the option ID + */ + @Contract(pure = true) + String id(); + + /** + * The display name for the option, or null if not set. + * + * @return the display component, or null + */ + @Contract(pure = true) + @Nullable Component display(); + + /** + * Whether this option is initially selected. + *

Only 1 option is allowed to have initial selected.

+ * + * @return true if the option is initially selected, false otherwise + */ + @Contract(pure = true) + boolean initial(); + } + + /** + * A builder for creating a {@link SingleOptionDialogInput}. + *

Created via {@link DialogInput#singleOption(String, Component, List)}

+ */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the width of the input. + * + * @param width the width + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder width(@Range(from = 1, to = 1024) int width); + + /** + * Sets whether the label should be visible. + * + * @param labelVisible whether the label should be visible + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder labelVisible(boolean labelVisible); + + /** + * Builds the {@link SingleOptionDialogInput}. + * + * @return the built dialog input + */ + @Contract(value = "-> new", pure = true) + SingleOptionDialogInput build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInput.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInput.java new file mode 100644 index 0000000000..518dffc8a1 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInput.java @@ -0,0 +1,159 @@ +package io.papermc.paper.registry.data.dialog.input; + +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * A text dialog input. + *

Created via {@link DialogInput#text(String, int, Component, boolean, String, int, MultilineOptions)}

+ */ +@ApiStatus.NonExtendable +public non-sealed interface TextDialogInput extends DialogInput { + + /** + * The width of the input. + * + * @return the width of the input + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int width(); + + /** + * The label for the input. + * + * @return the label component + */ + @Contract(pure = true) + Component label(); + + /** + * Whether the label should be visible. + * + * @return true if the label is visible, false otherwise + */ + @Contract(pure = true) + boolean labelVisible(); + + /** + * The initial value of the input. + * + * @return the initial text + */ + @Contract(pure = true) + String initial(); + + /** + * The format for the label (a translation key or format string). + * + * @return the label format + */ + @Contract(pure = true) + @Range(from = 1, to = Integer.MAX_VALUE) int maxLength(); + + /** + * The multiline options for the input, or null if not set. + * + * @return the multiline options + */ + @Contract(pure = true) + @Nullable MultilineOptions multiline(); + + /** + * Represents the multiline options for a text dialog input. + */ + @ApiStatus.NonExtendable + interface MultilineOptions { + + /** + * Creates a new multiline options instance. + * + * @param maxLines the maximum number of lines, or null if not set + * @param height the height of the input, or null if not set + * @return a new MultilineOptions instance + */ + static MultilineOptions create(final @Nullable Integer maxLines, final @Nullable Integer height) { + return DialogInstancesProvider.instance().multilineOptions(maxLines, height); + } + + /** + * Gets the maximum number of lines. + * + * @return the maximum number of lines, or null if not set + */ + @Contract(pure = true) + @Nullable Integer maxLines(); + + /** + * Gets the height of the input. + * + * @return the height of the input, or null if not set + */ + @Contract(pure = true) + @Nullable Integer height(); + } + + /** + * A builder for a text dialog input. + *

Created via {@link DialogInput#text(String, Component)}

+ */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the width of the input. + * + * @param width the width of the input + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder width(final @Range(from = 1, to = 1024) int width); + + /** + * Sets whether the label should be visible. + * + * @param labelVisible true if the label should be visible, false otherwise + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder labelVisible(final boolean labelVisible); + + /** + * Sets the initial value of the input. + * + * @param initial the initial value of the input + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder initial(final String initial); + + /** + * Sets the maximum length of the input. + * + * @param maxLength the maximum length of the input + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder maxLength(final @Range(from = 1, to = Integer.MAX_VALUE) int maxLength); + + /** + * Sets the multiline options for the input. + * + * @param multiline the multiline options + * @return this builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder multiline(final @Nullable MultilineOptions multiline); + + /** + * Builds the text dialog input. + * + * @return the text dialog input + */ + @Contract(value = "-> new", pure = true) + TextDialogInput build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/package-info.java new file mode 100644 index 0000000000..f47e48a001 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/input/package-info.java @@ -0,0 +1,9 @@ +/** + * Dialog inputs for Paper API. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.registry.data.dialog.input; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/package-info.java new file mode 100644 index 0000000000..a4eca44898 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/package-info.java @@ -0,0 +1,6 @@ +@NullMarked +@ApiStatus.Experimental +package io.papermc.paper.registry.data.dialog; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationType.java new file mode 100644 index 0000000000..3e1a586aac --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationType.java @@ -0,0 +1,30 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.registry.data.dialog.ActionButton; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * Represents a confirmation dialog. + * This interface defines the structure for a confirmation dialog with "confirm" and "deny" buttons. + * @see DialogType#confirmation(ActionButton, ActionButton) + */ +@ApiStatus.NonExtendable +public non-sealed interface ConfirmationType extends DialogType { + + /** + * Gets the button for confirming the action. + * + * @return the confirmation button + */ + @Contract(pure = true) + ActionButton yesButton(); + + /** + * Gets the button for denying the action. + * + * @return the denial button + */ + @Contract(pure = true) + ActionButton noButton(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListType.java new file mode 100644 index 0000000000..20e6729845 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListType.java @@ -0,0 +1,91 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.ActionButton; +import io.papermc.paper.registry.set.RegistrySet; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * Represents a dialog that displays a list of dialogs. + * @see DialogType#dialogList(RegistrySet, ActionButton, int, int) + */ +@ApiStatus.NonExtendable +public non-sealed interface DialogListType extends DialogType { + + /** + * Returns the set of dialogs to display in the dialog list. + * + * @return the set of dialogs + */ + @Contract(pure = true) + RegistrySet dialogs(); + + /** + * Returns the action button to exit the dialog, or null if there is no exit action. + * + * @return the exit action button, or null + */ + @Contract(pure = true) + @Nullable ActionButton exitAction(); + + /** + * Returns the number of columns to display in the dialog list. + * + * @return the number of columns + */ + @Contract(pure = true) + @Range(from = 1, to = Integer.MAX_VALUE) int columns(); + + /** + * Returns the width of each button in the dialog list. + * + * @return the width of the buttons + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int buttonWidth(); + + /** + * A builder for creating a dialog list type. + */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the action button to exit the dialog, or null if there is no exit action. + * + * @param exitAction the exit action button, or null + * @return the builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder exitAction(final @Nullable ActionButton exitAction); + + /** + * Sets the number of columns to display in the dialog list. + * + * @param columns the number of columns + * @return the builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder columns(final @Range(from = 1, to = Integer.MAX_VALUE) int columns); + + /** + * Sets the width of each button in the dialog list. + * + * @param buttonWidth the width of the buttons + * @return the builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder buttonWidth(final @Range(from = 1, to = 1024) int buttonWidth); + + /** + * Builds the dialog list type. + * + * @return the built dialog list type + */ + @Contract(value = "-> new", pure = true) + DialogListType build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogType.java new file mode 100644 index 0000000000..7552d538d2 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogType.java @@ -0,0 +1,110 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.ActionButton; +import io.papermc.paper.registry.data.dialog.DialogInstancesProvider; +import io.papermc.paper.registry.set.RegistrySet; +import java.util.List; +import org.jetbrains.annotations.Contract; +import org.jspecify.annotations.Nullable; + +/** + * Represents a type of dialog. + */ +public sealed interface DialogType permits ConfirmationType, DialogListType, MultiActionType, NoticeType, ServerLinksType { + + /** + * Creates a confirmation dialog with the specified yes and no buttons. + * + * @param yesButton the button to confirm the action + * @param noButton the button to cancel the action + * @return a new instance + */ + @Contract(value = "_, _ -> new", pure = true) + static ConfirmationType confirmation(final ActionButton yesButton, final ActionButton noButton) { + return DialogInstancesProvider.instance().confirmation(yesButton, noButton); + } + + /** + * Creates a dialog list dialog with the specified dialogs, exit action, columns, and button width. + * + * @param dialogs the set of dialogs to display + * @param exitAction the action button to exit the dialog + * @param columns the number of columns to display in the dialog + * @param buttonWidth the width of each button in the dialog + * @return a new instance + */ + @Contract(value = "_, _, _, _ -> new", pure = true) + static DialogListType dialogList(final RegistrySet dialogs, final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) { + return dialogList(dialogs).exitAction(exitAction).columns(columns).buttonWidth(buttonWidth).build(); + } + + /** + * Creates a dialog list builder with the specified dialogs. + * + * @param dialogs the set of dialogs to display + * @return a new builder instance + */ + @Contract(value = "_ -> new", pure = true) + static DialogListType.Builder dialogList(final RegistrySet dialogs) { + return DialogInstancesProvider.instance().dialogList(dialogs); + } + + /** + * Creates a multi-action dialog with the specified actions, exit action, and number of columns. + * + * @param actions the list of action buttons to display + * @param exitAction the action button to exit the dialog + * @param columns the number of columns to display in the dialog + * @return a new instance + */ + @Contract(value = "_, _, _ -> new", pure = true) + static MultiActionType multiAction(final List actions, final @Nullable ActionButton exitAction, final int columns) { + return multiAction(actions).exitAction(exitAction).columns(columns).build(); + } + + /** + * Creates a multi-action dialog builder with the specified actions. + * + * @param actions the list of action buttons to display + * @return a new builder instance + */ + @Contract(value = "_ -> new", pure = true) + static MultiActionType.Builder multiAction(final List actions) { + return DialogInstancesProvider.instance().multiAction(actions); + } + + /** + * Creates a notice dialog with the default action button. + * + * @return a new instance + */ + @Contract(value = "-> new", pure = true) + static NoticeType notice() { + return DialogInstancesProvider.instance().notice(); + } + + /** + * Creates a notice dialog with the specified action button. + * + * @param action the action button to display in the notice + * @return a new instance + */ + @Contract(value = "_ -> new", pure = true) + static NoticeType notice(final ActionButton action) { + return DialogInstancesProvider.instance().notice(action); + } + + /** + * Creates a server links dialog with the specified exit action, number of columns, and button width. + * + * @param exitAction the action button to exit the dialog + * @param columns the number of columns to display in the dialog + * @param buttonWidth the width of each button in the dialog + * @return a new instance + */ + @Contract(value = "_, _, _ -> new", pure = true) + static ServerLinksType serverLinks(final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) { + return DialogInstancesProvider.instance().serverLinks(exitAction, columns, buttonWidth); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionType.java new file mode 100644 index 0000000000..e015e10a0a --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionType.java @@ -0,0 +1,75 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.registry.data.dialog.ActionButton; +import java.util.List; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Nullable; +import org.jetbrains.annotations.Range; +import org.jetbrains.annotations.Unmodifiable; + +/** + * Represents a dialog that allows multiple actions to be performed. + * This dialog is used to create dialogs with multiple action buttons, allowing users to choose from several options. + * @see DialogType#multiAction(List, ActionButton, int) + */ +@ApiStatus.NonExtendable +public non-sealed interface MultiActionType extends DialogType { + + /** + * Returns the list of action buttons available in this multi-action dialog. + * + * @return an unmodifiable list of action buttons + */ + @Contract(pure = true) + @Unmodifiable List actions(); + + /** + * Returns the action button to exit the dialog, or null if there is no exit action. + * + * @return the exit action button, or null + */ + @Contract(pure = true) + @Nullable ActionButton exitAction(); + + /** + * Returns the number of columns to display in the dialog. + * + * @return the number of columns + */ + @Contract(pure = true) + @Range(from = 1, to = Integer.MAX_VALUE) int columns(); + + /** + * A builder for creating a multi-action dialog. + */ + @ApiStatus.NonExtendable + interface Builder { + + /** + * Sets the action button to exit the dialog, or null if there is no exit action. + * + * @param exitAction the exit action button, or null + * @return the builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder exitAction(final @Nullable ActionButton exitAction); + + /** + * Sets the number of columns to display in the dialog. + * + * @param columns the number of columns + * @return the builder + */ + @Contract(value = "_ -> this", mutates = "this") + Builder columns(final @Range(from = 1, to = Integer.MAX_VALUE) int columns); + + /** + * Builds the multi-action dialog. + * + * @return a new instance + */ + @Contract(value = "-> new", pure = true) + MultiActionType build(); + } +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeType.java new file mode 100644 index 0000000000..5357dabd1b --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeType.java @@ -0,0 +1,21 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.registry.data.dialog.ActionButton; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; + +/** + * Represents a notice dialog. + * @see DialogType#notice(ActionButton) + */ +@ApiStatus.NonExtendable +public non-sealed interface NoticeType extends DialogType { + + /** + * Returns the action button associated with this notice type. + * + * @return the action button + */ + @Contract(pure = true) + ActionButton action(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksType.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksType.java new file mode 100644 index 0000000000..d76d818542 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksType.java @@ -0,0 +1,40 @@ +package io.papermc.paper.registry.data.dialog.type; + + +import io.papermc.paper.registry.data.dialog.ActionButton; +import org.jetbrains.annotations.ApiStatus; +import org.jetbrains.annotations.Contract; +import org.jetbrains.annotations.Range; +import org.jspecify.annotations.Nullable; + +/** + * Represents a server links dialog that displays links. + * @see DialogType#serverLinks(ActionButton, int, int) + */ +@ApiStatus.NonExtendable +public non-sealed interface ServerLinksType extends DialogType { + + /** + * Returns the action button to exit the dialog, or null if there is no exit action. + * + * @return the exit action button, or null + */ + @Contract(pure = true) + @Nullable ActionButton exitAction(); + + /** + * Returns the number of columns to display in the server links dialog. + * + * @return the number of columns + */ + @Contract(pure = true) + @Range(from = 1, to = Integer.MAX_VALUE) int columns(); + + /** + * Returns the width of each button in the server links dialog. + * + * @return the width of the buttons + */ + @Contract(pure = true) + @Range(from = 1, to = 1024) int buttonWidth(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/package-info.java new file mode 100644 index 0000000000..f319b993a2 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/data/dialog/type/package-info.java @@ -0,0 +1,7 @@ +/** + * Dialog types for the Paper API. + */ +@NullMarked +package io.papermc.paper.registry.data.dialog.type; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java b/paper-api/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java index eb7d5efd04..2c652f3f13 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/event/RegistryEvents.java @@ -1,5 +1,6 @@ package io.papermc.paper.registry.event; +import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.data.BannerPatternRegistryEntry; import io.papermc.paper.registry.data.CatTypeRegistryEntry; @@ -14,6 +15,7 @@ import io.papermc.paper.registry.data.JukeboxSongRegistryEntry; import io.papermc.paper.registry.data.PaintingVariantRegistryEntry; import io.papermc.paper.registry.data.PigVariantRegistryEntry; import io.papermc.paper.registry.data.WolfVariantRegistryEntry; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; import org.bukkit.Art; import org.bukkit.GameEvent; import org.bukkit.JukeboxSong; @@ -51,6 +53,7 @@ public final class RegistryEvents { public static final RegistryEventProvider CHICKEN_VARIANT = create(RegistryKey.CHICKEN_VARIANT); public static final RegistryEventProvider COW_VARIANT = create(RegistryKey.COW_VARIANT); public static final RegistryEventProvider PIG_VARIANT = create(RegistryKey.PIG_VARIANT); + public static final RegistryEventProvider DIALOG = create(RegistryKey.DIALOG); // End generate - RegistryEvents private RegistryEvents() { diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java index e383014a7f..2d1ca43425 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySet.java @@ -1,17 +1,29 @@ package io.papermc.paper.registry.set; +import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.registry.tag.Tag; +import io.papermc.paper.registry.tag.TagKey; import java.util.Collection; import java.util.Iterator; -import io.papermc.paper.registry.tag.TagKey; import org.bukkit.Keyed; import org.bukkit.Registry; import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Unmodifiable; -import org.jspecify.annotations.NullMarked; -@ApiStatus.Experimental -@NullMarked +/** + * Represents a collection tied to a registry. + *

+ * There are 2 types of registry key sets: + *

    + *
  • {@link Tag} which is a tag from vanilla or a datapack. + * These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.
  • + *
  • {@link RegistryKeySet} which is a set of keys linked to values that are present in the registry. These are + * created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.
  • + *
+ * + * @param registry value type + */ @ApiStatus.NonExtendable public non-sealed interface RegistryKeySet extends Iterable>, RegistrySet { // TODO remove Keyed diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java index fe4a0817d7..2ea3854452 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryKeySetImpl.java @@ -12,12 +12,9 @@ import org.bukkit.Keyed; import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; -import org.jspecify.annotations.Nullable; @ApiStatus.Internal -@NullMarked -record RegistryKeySetImpl(RegistryKey registryKey, List> values) implements RegistryKeySet { // TODO remove Keyed +record RegistryKeySetImpl(RegistryKey registryKey, List> values) implements RegistryKeySet { // TODO remove Keyed static RegistryKeySet create(final RegistryKey registryKey, final Iterable values) { // TODO remove Keyed final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistrySet.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistrySet.java index c6fdeb0524..b1ad0aefa0 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistrySet.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistrySet.java @@ -5,43 +5,38 @@ import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.tag.Tag; import org.bukkit.Keyed; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Contract; -import org.jspecify.annotations.NullMarked; /** * Represents a collection tied to a registry. *

- * There are 2 types of registry sets: + * There are 3 types of registry sets: *

    *
  • {@link Tag} which is a tag from vanilla or a datapack. * These are obtained via {@link org.bukkit.Registry#getTag(io.papermc.paper.registry.tag.TagKey)}.
  • - *
  • {@link RegistryKeySet} which is a set of of keys linked to values that are present in the registry. These are + *
  • {@link RegistryKeySet} which is a set of keys linked to values that are present in the registry. These are * created via {@link #keySet(RegistryKey, Iterable)} or {@link #keySetFromValues(RegistryKey, Iterable)}.
  • - * + *
  • {@link RegistryValueSet} which is a set of values which are anonymous (don't have keys in the registry). These are + * created via {@link #valueSet(RegistryKey, Iterable)}.
  • *
* * @param registry value type */ -@ApiStatus.Experimental -@NullMarked public sealed interface RegistrySet permits RegistryKeySet, RegistryValueSet { - // TODO uncomment when direct holder sets need to be exposed to the API - // /** - // * Creates a {@link RegistryValueSet} from anonymous values. - // *

All values provided must not have keys in the given registry.

- // * - // * @param registryKey the registry key for the type of these values - // * @param values the values - // * @return a new registry set - // * @param the type of the values - // */ - // @Contract(value = "_, _ -> new", pure = true) - // static RegistryValueSet valueSet(final RegistryKey registryKey, final Iterable values) { - // return RegistryValueSetImpl.create(registryKey, values); - // } + /** + * Creates a {@link RegistryValueSet} from anonymous values. + *

All values provided must not have keys in the given registry.

+ * + * @param registryKey the registry key for the type of these values + * @param values the values + * @return a new registry set + * @param the type of the values + */ + @Contract(value = "_, _ -> new", pure = true) + static RegistryValueSet valueSet(final RegistryKey registryKey, final Iterable values) { + return RegistryValueSetImpl.create(registryKey, values); + } /** * Creates a {@link RegistryKeySet} from registry-backed values. diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java index 9f7761535c..74ae9cba4f 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSet.java @@ -1,19 +1,18 @@ package io.papermc.paper.registry.set; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; import java.util.Collection; import java.util.Iterator; -import org.jetbrains.annotations.ApiStatus; import org.jetbrains.annotations.Unmodifiable; -import org.jspecify.annotations.NullMarked; /** * A collection of anonymous values relating to a registry. These * are values of the same type as the registry, but will not be found - * in the registry, hence, anonymous. + * in the registry, hence, anonymous. Created via {@link RegistrySet#valueSet(io.papermc.paper.registry.RegistryKey, Iterable)} or + * in the context of a {@link io.papermc.paper.registry.RegistryBuilder}, + * there are methods to create them like {@link DialogRegistryEntry.Builder#registryValueSet()}. * @param registry value type */ -@ApiStatus.Experimental -@NullMarked public sealed interface RegistryValueSet extends Iterable, RegistrySet permits RegistryValueSetImpl { @Override diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilder.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilder.java new file mode 100644 index 0000000000..974d2c3b47 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilder.java @@ -0,0 +1,31 @@ +package io.papermc.paper.registry.set; + +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.RegistryBuilderFactory; +import java.util.function.Consumer; +import org.jetbrains.annotations.ApiStatus; + +/** + * A builder for a {@link RegistryValueSet}. + * + * @param the API type + * @param the type of the entry builder, + */ +@ApiStatus.NonExtendable +public interface RegistryValueSetBuilder> { + + /** + * Adds a value to the registry value set. + * + * @param builder the builder for the value to add + * @return this builder for chaining + */ + RegistryValueSetBuilder add(Consumer> builder); + + /** + * Builds the {@link RegistryValueSet}. + * + * @return the built registry value set + */ + RegistryValueSet build(); +} diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java index 1bcc6b4b9f..4ce5b26a1f 100644 --- a/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/RegistryValueSetImpl.java @@ -4,10 +4,8 @@ import com.google.common.collect.Lists; import io.papermc.paper.registry.RegistryKey; import java.util.List; import org.jetbrains.annotations.ApiStatus; -import org.jspecify.annotations.NullMarked; @ApiStatus.Internal -@NullMarked record RegistryValueSetImpl(RegistryKey registryKey, List values) implements RegistryValueSet { RegistryValueSetImpl { diff --git a/paper-api/src/main/java/io/papermc/paper/registry/set/package-info.java b/paper-api/src/main/java/io/papermc/paper/registry/set/package-info.java new file mode 100644 index 0000000000..c1160276d4 --- /dev/null +++ b/paper-api/src/main/java/io/papermc/paper/registry/set/package-info.java @@ -0,0 +1,11 @@ +/** + * This package contains the API for registry sets in Paper. + *

+ * Registry sets are collections of keys or inlined values of a type that has a registry. + */ +@ApiStatus.Experimental +@NullMarked +package io.papermc.paper.registry.set; + +import org.jetbrains.annotations.ApiStatus; +import org.jspecify.annotations.NullMarked; diff --git a/paper-api/src/main/java/org/bukkit/entity/Player.java b/paper-api/src/main/java/org/bukkit/entity/Player.java index 3584ca0a89..672d78811b 100644 --- a/paper-api/src/main/java/org/bukkit/entity/Player.java +++ b/paper-api/src/main/java/org/bukkit/entity/Player.java @@ -1,5 +1,9 @@ package org.bukkit.entity; +import io.papermc.paper.connection.PlayerGameConnection; +import io.papermc.paper.entity.LookAnchor; +import io.papermc.paper.entity.PlayerGiveResult; +import io.papermc.paper.math.Position; import java.net.InetAddress; import java.net.InetSocketAddress; import java.time.Duration; @@ -10,10 +14,6 @@ import java.util.List; import java.util.Map; import java.util.UUID; import java.util.concurrent.CompletableFuture; -import io.papermc.paper.connection.PlayerGameConnection; -import io.papermc.paper.entity.LookAnchor; -import io.papermc.paper.entity.PlayerGiveResult; -import io.papermc.paper.math.Position; import org.bukkit.BanEntry; import org.bukkit.DyeColor; import org.bukkit.Effect; diff --git a/paper-generator/src/main/java/io/papermc/generator/Rewriters.java b/paper-generator/src/main/java/io/papermc/generator/Rewriters.java index be47f326c3..5570229d13 100644 --- a/paper-generator/src/main/java/io/papermc/generator/Rewriters.java +++ b/paper-generator/src/main/java/io/papermc/generator/Rewriters.java @@ -23,6 +23,7 @@ import io.papermc.generator.rewriter.types.simple.trial.VillagerProfessionRewrit import io.papermc.generator.types.goal.MobGoalNames; import io.papermc.generator.utils.Formatting; import io.papermc.paper.datacomponent.item.consumable.ItemUseAnimation; +import io.papermc.paper.dialog.Dialog; import io.papermc.typewriter.preset.EnumCloneRewriter; import io.papermc.typewriter.preset.model.EnumValue; import io.papermc.typewriter.replace.SearchMetadata; @@ -184,6 +185,7 @@ public final class Rewriters { .register("ChickenVariant", Chicken.Variant.class, new RegistryFieldRewriter<>(Registries.CHICKEN_VARIANT, "getVariant")) .register("CowVariant", Cow.Variant.class, new RegistryFieldRewriter<>(Registries.COW_VARIANT, "getVariant")) .register("PigVariant", Pig.Variant.class, new RegistryFieldRewriter<>(Registries.PIG_VARIANT, "getVariant")) + .register("Dialog", Dialog.class, new RegistryFieldRewriter<>(Registries.DIALOG, "getDialog")) .register("MemoryKey", MemoryKey.class, new MemoryKeyRewriter()) // .register("ItemType", ItemType.class, new ItemTypeRewriter()) - disable for now, lynx want the generic type .register("BlockType", BlockType.class, new BlockTypeRewriter()) diff --git a/paper-generator/src/main/java/io/papermc/generator/registry/RegistryEntries.java b/paper-generator/src/main/java/io/papermc/generator/registry/RegistryEntries.java index e792cd5669..a4e46283c7 100644 --- a/paper-generator/src/main/java/io/papermc/generator/registry/RegistryEntries.java +++ b/paper-generator/src/main/java/io/papermc/generator/registry/RegistryEntries.java @@ -3,6 +3,7 @@ package io.papermc.generator.registry; import io.papermc.generator.utils.ClassHelper; import io.papermc.paper.datacomponent.DataComponentType; import io.papermc.paper.datacomponent.DataComponentTypes; +import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.data.BannerPatternRegistryEntry; import io.papermc.paper.registry.data.CatTypeRegistryEntry; import io.papermc.paper.registry.data.ChickenVariantRegistryEntry; @@ -17,6 +18,7 @@ import io.papermc.paper.registry.data.PaintingVariantRegistryEntry; import io.papermc.paper.registry.data.PigVariantRegistryEntry; import io.papermc.paper.registry.data.SoundEventRegistryEntry; import io.papermc.paper.registry.data.WolfVariantRegistryEntry; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; import java.lang.reflect.Field; import java.lang.reflect.Modifier; import java.lang.reflect.Type; @@ -34,6 +36,7 @@ import net.minecraft.core.component.DataComponents; import net.minecraft.core.particles.ParticleTypes; import net.minecraft.core.registries.Registries; import net.minecraft.resources.ResourceKey; +import net.minecraft.server.dialog.Dialogs; import net.minecraft.sounds.SoundEvents; import net.minecraft.world.damagesource.DamageTypes; import net.minecraft.world.effect.MobEffects; @@ -181,7 +184,8 @@ public final class RegistryEntries { entry(Registries.FROG_VARIANT, FrogVariants.class, Frog.Variant.class).writableApiRegistryBuilder(FrogVariantRegistryEntry.Builder.class, "PaperFrogVariantRegistryEntry.PaperBuilder").delayed(), entry(Registries.CHICKEN_VARIANT, ChickenVariants.class, Chicken.Variant.class).writableApiRegistryBuilder(ChickenVariantRegistryEntry.Builder.class, "PaperChickenVariantRegistryEntry.PaperBuilder"), entry(Registries.COW_VARIANT, CowVariants.class, Cow.Variant.class).writableApiRegistryBuilder(CowVariantRegistryEntry.Builder.class, "PaperCowVariantRegistryEntry.PaperBuilder"), - entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class).writableApiRegistryBuilder(PigVariantRegistryEntry.Builder.class, "PaperPigVariantRegistryEntry.PaperBuilder") + entry(Registries.PIG_VARIANT, PigVariants.class, Pig.Variant.class).writableApiRegistryBuilder(PigVariantRegistryEntry.Builder.class, "PaperPigVariantRegistryEntry.PaperBuilder"), + entry(Registries.DIALOG, Dialogs.class, Dialog.class, "Paper").allowDirect().writableApiRegistryBuilder(DialogRegistryEntry.Builder.class, "PaperDialogRegistryEntry.PaperBuilder") ); public static final List> API_ONLY = List.of( diff --git a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch index f29705d6b2..235f2c0160 100644 --- a/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch +++ b/paper-server/patches/features/0016-Moonrise-optimisation-patches.patch @@ -23867,7 +23867,7 @@ index 46de98a6bbbae48c4837e1e588ba198a363d2dde..fd3553bdc1c3cdbf6aa3dc00e0a4987f thread1 -> { DedicatedServer dedicatedServer1 = new DedicatedServer( diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a718d12b8 100644 +index 03a616fc9b0325aa163fe4950ec4ce9ffdd3a9ea..b6aee251027cf124d6597137abefc7fd177358c9 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -173,7 +173,7 @@ import net.minecraft.world.phys.Vec2; @@ -24059,7 +24059,7 @@ index 75aba65cbe1a943f21c7464ff9465e64f63e8e5b..32475c0958fd7e0f1f9b494b0cc78a4a return true; } else { boolean ret = false; // Paper - force execution of all worlds, do not just bias the first -@@ -2478,6 +2566,12 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop 0; // Paper - BlockPhysicsEvent serverLevel.hasEntityMoveEvent = io.papermc.paper.event.entity.EntityMoveEvent.getHandlerList().getRegisteredListeners().length > 0; // Paper - Add EntityMoveEvent serverLevel.updateLagCompensationTick(); // Paper - lag compensation diff --git a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch index c02eb1f83a..6c1ade7364 100644 --- a/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch +++ b/paper-server/patches/features/0030-Optimise-collision-checking-in-player-move-packet-ha.patch @@ -6,10 +6,10 @@ Subject: [PATCH] Optimise collision checking in player move packet handling Move collision logic to just the hasNewCollision call instead of getCubes + hasNewCollision diff --git a/net/minecraft/server/network/ServerGamePacketListenerImpl.java b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c8857c064d55 100644 +index 08aba415735733f5968fd032ab7ca249cdcf6cde..ee4397711625344622c81424afd11fd6d967efba 100644 --- a/net/minecraft/server/network/ServerGamePacketListenerImpl.java +++ b/net/minecraft/server/network/ServerGamePacketListenerImpl.java -@@ -594,6 +594,7 @@ public class ServerGamePacketListenerImpl +@@ -606,6 +606,7 @@ public class ServerGamePacketListenerImpl } rootVehicle.move(MoverType.PLAYER, new Vec3(d3, d4, d5)); @@ -17,7 +17,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 double verticalDelta = d4; d3 = d - rootVehicle.getX(); d4 = d1 - rootVehicle.getY(); -@@ -605,12 +606,21 @@ public class ServerGamePacketListenerImpl +@@ -617,12 +618,21 @@ public class ServerGamePacketListenerImpl d7 = d3 * d3 + d4 * d4 + d5 * d5; boolean flag1 = false; if (d7 > org.spigotmc.SpigotConfig.movedWronglyThreshold) { // Spigot @@ -42,7 +42,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 rootVehicle.absSnapTo(x, y, z, f, f1); this.send(ClientboundMoveVehiclePacket.fromEntity(rootVehicle)); rootVehicle.removeLatestMovementRecording(); -@@ -689,9 +699,32 @@ public class ServerGamePacketListenerImpl +@@ -701,9 +711,32 @@ public class ServerGamePacketListenerImpl } private boolean noBlocksAround(Entity entity) { @@ -78,7 +78,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 } @Override -@@ -1467,7 +1500,7 @@ public class ServerGamePacketListenerImpl +@@ -1479,7 +1512,7 @@ public class ServerGamePacketListenerImpl } } @@ -87,7 +87,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 d3 = d - this.lastGoodX; // Paper - diff on change, used for checking large move vectors above d4 = d1 - this.lastGoodY; // Paper - diff on change, used for checking large move vectors above d5 = d2 - this.lastGoodZ; // Paper - diff on change, used for checking large move vectors above -@@ -1506,6 +1539,7 @@ public class ServerGamePacketListenerImpl +@@ -1518,6 +1551,7 @@ public class ServerGamePacketListenerImpl boolean flag1 = this.player.verticalCollisionBelow; this.player.move(MoverType.PLAYER, new Vec3(d3, d4, d5)); this.player.onGround = packet.isOnGround(); // CraftBukkit - SPIGOT-5810, SPIGOT-5835, SPIGOT-6828: reset by this.player.move @@ -95,7 +95,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 // Paper start - prevent position desync if (this.awaitingPositionFromClient != null) { return; // ... thanks Mojang for letting move calls teleport across dimensions. -@@ -1538,7 +1572,17 @@ public class ServerGamePacketListenerImpl +@@ -1550,7 +1584,17 @@ public class ServerGamePacketListenerImpl } // Paper start - Add fail move event @@ -114,7 +114,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 if (!allowMovement) { io.papermc.paper.event.player.PlayerFailMoveEvent event = fireFailMove(io.papermc.paper.event.player.PlayerFailMoveEvent.FailReason.CLIPPED_INTO_BLOCK, toX, toY, toZ, toYaw, toPitch, false); -@@ -1674,7 +1718,7 @@ public class ServerGamePacketListenerImpl +@@ -1686,7 +1730,7 @@ public class ServerGamePacketListenerImpl private boolean updateAwaitingTeleport() { if (this.awaitingPositionFromClient != null) { @@ -123,7 +123,7 @@ index ff2f83dc1cbf10baccd07e65c894dff1d2f9a3cf..63d01c3753b865532a222b5b7408c885 this.awaitingTeleportTime = this.tickCount; this.teleport( this.awaitingPositionFromClient.x, -@@ -1693,6 +1737,33 @@ public class ServerGamePacketListenerImpl +@@ -1705,6 +1749,33 @@ public class ServerGamePacketListenerImpl } } diff --git a/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch index d5c7f4793d..1ba600dca8 100644 --- a/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch +++ b/paper-server/patches/features/0032-Improve-keepalive-ping-system.patch @@ -100,7 +100,7 @@ index 962084054c0208470d0c3c99c5dca6327c9b8752..2abc21102bbd2da79dc0c50826cff7da } } diff --git a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java -index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f20319544c2fb 100644 +index 1b42d5e9f9fd07f99009de6f4483648f416db733..08c59d603fca038fc2dde36384eea1b6c971e659 100644 --- a/net/minecraft/server/network/ServerCommonPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerCommonPacketListenerImpl.java @@ -38,12 +38,13 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @@ -137,7 +137,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031 // Paper end } -@@ -93,13 +95,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -99,13 +101,41 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack @Override public void handleKeepAlive(ServerboundKeepAlivePacket packet) { @@ -185,7 +185,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031 } @Override -@@ -220,20 +250,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -232,20 +262,23 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack protected void keepConnectionAlive() { Profiler.get().push("keepAlive"); long millis = Util.getMillis(); @@ -223,7 +223,7 @@ index 140f0ac42626662354e9b5386d91f1b096a945a3..2e7c4c74818befd25e296b58ef9f2031 } } -@@ -413,6 +446,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack +@@ -425,6 +458,6 @@ public abstract class ServerCommonPacketListenerImpl implements ServerCommonPack } protected CommonListenerCookie createCookie(ClientInformation clientInformation) { diff --git a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch index ac688a653e..bd58b3e269 100644 --- a/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch +++ b/paper-server/patches/features/0033-Optimise-EntityScheduler-ticking.patch @@ -20,7 +20,7 @@ index 5f2deeb5cc01d8bbeb7449bd4e59c466b3dfdf57..82824ae7ffbced513a8bcace684af949 @Override diff --git a/net/minecraft/server/MinecraftServer.java b/net/minecraft/server/MinecraftServer.java -index 0a260fdf6b198a8ab52e60bf6db2fb5eab719c48..52fa5112cd90ba766c94512a02401dd3aee82cc9 100644 +index 382d2b6b53bd144f4d56dccdc603ed0da8fe07a7..7aac2a6889af3edaebfaf94deecbf00d00758b68 100644 --- a/net/minecraft/server/MinecraftServer.java +++ b/net/minecraft/server/MinecraftServer.java @@ -1654,33 +1654,22 @@ public abstract class MinecraftServer extends ReentrantBlockableEventLoop list = new ObjectArrayList<>(min); int randomInt = Mth.nextInt(this.random, 0, players.size() - min); -@@ -1040,17 +_,75 @@ +@@ -1040,17 +_,76 @@ protected void tickChildren(BooleanSupplier hasTimeLeft) { ProfilerFiller profilerFiller = Profiler.get(); this.getPlayerList().getPlayers().forEach(serverPlayer1 -> serverPlayer1.connection.suspendFlushing()); @@ -1012,7 +1012,8 @@ + } + }); + // Paper end - Folia scheduler API -+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.handleQueue(this.tickCount); // Paper ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.handleQueue(this.tickCount); // Paper ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.handleQueue(this.tickCount); // Paper profilerFiller.push("commandFunctions"); this.getFunctions().tick(); profilerFiller.popPush("levels"); diff --git a/paper-server/patches/sources/net/minecraft/server/dialog/body/ItemBody.java.patch b/paper-server/patches/sources/net/minecraft/server/dialog/body/ItemBody.java.patch new file mode 100644 index 0000000000..d541a55a09 --- /dev/null +++ b/paper-server/patches/sources/net/minecraft/server/dialog/body/ItemBody.java.patch @@ -0,0 +1,13 @@ +--- a/net/minecraft/server/dialog/body/ItemBody.java ++++ b/net/minecraft/server/dialog/body/ItemBody.java +@@ -15,8 +_,8 @@ + PlainMessage.CODEC.optionalFieldOf("description").forGetter(ItemBody::description), + Codec.BOOL.optionalFieldOf("show_decorations", true).forGetter(ItemBody::showDecorations), + Codec.BOOL.optionalFieldOf("show_tooltip", true).forGetter(ItemBody::showTooltip), +- ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemBody::width), +- ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemBody::height) ++ ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemBody::width), // Paper - diff on change - update builder defaults/limits ++ ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemBody::height) // Paper - diff on change - update builder defaults/limits + ) + .apply(instance, ItemBody::new) + ); diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch index 9a0a082f70..af1564a46b 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerCommonPacketListenerImpl.java.patch @@ -27,7 +27,7 @@ public ServerCommonPacketListenerImpl(MinecraftServer server, Connection connection, CommonListenerCookie cookie) { this.server = server; -@@ -52,6 +_,11 @@ +@@ -52,7 +_,18 @@ this.keepAliveTime = Util.getMillis(); this.latency = cookie.latency(); this.transferred = cookie.transferred(); @@ -37,8 +37,15 @@ + this.pluginMessagerChannels = cookie.channels(); + // Paper end } ++ ++ // Paper start - configuration phase API ++ public abstract io.papermc.paper.connection.PlayerCommonConnection getApiConnection(); ++ ++ public abstract net.kyori.adventure.audience.Audience getAudience(); ++ // Paper end - configuration phase API private void close() { + if (!this.closed) { @@ -82,7 +_,7 @@ this.latency = (this.latency * 3 + i) / 4; this.keepAlivePending = false; @@ -48,7 +55,7 @@ } } -@@ -90,8 +_,76 @@ +@@ -90,14 +_,88 @@ public void handlePong(ServerboundPongPacket packet) { } @@ -124,6 +131,18 @@ + // Paper end } + @Override + public void handleCustomClickAction(ServerboundCustomClickActionPacket packet) { + PacketUtils.ensureRunningOnSameThread(packet, this, this.server); + this.server.handleCustomClickAction(packet.id(), packet.payload()); ++ // Paper start - Implement click callbacks with custom click action ++ final io.papermc.paper.event.player.PaperPlayerCustomClickEvent event = new io.papermc.paper.event.player.PaperPlayerCustomClickEvent(io.papermc.paper.adventure.PaperAdventure.asAdventure(packet.id()), this.getApiConnection(), packet.payload().orElse(null)); ++ event.callEvent(); ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.tryRunCallback(this.getAudience(), packet.id(), packet.payload()); ++ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.ADVENTURE_CLICK_MANAGER.tryRunCallback(this.getAudience(), packet.id(), packet.payload()); ++ // Paper end - Implement click callbacks with custom click action + } + @Override @@ -105,21 +_,46 @@ PacketUtils.ensureRunningOnSameThread(packet, this, this.server); diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch index 13554d7436..26de214826 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java.patch @@ -1,6 +1,6 @@ --- a/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java +++ b/net/minecraft/server/network/ServerConfigurationPacketListenerImpl.java -@@ -47,11 +_,13 @@ +@@ -47,12 +_,26 @@ public ClientInformation clientInformation; @Nullable private SynchronizeRegistriesTask synchronizeRegistriesTask; @@ -10,10 +10,24 @@ super(server, connection, cookie); this.gameProfile = cookie.gameProfile(); this.clientInformation = cookie.clientInformation(); +- } + this.paperConnection = new io.papermc.paper.connection.PaperPlayerConfigurationConnection(this); // Paper - } ++ } ++ ++ // Paper start - configuration phase API ++ @Override ++ public io.papermc.paper.connection.PlayerCommonConnection getApiConnection() { ++ return this.paperConnection; ++ } ++ ++ @Override ++ public net.kyori.adventure.audience.Audience getAudience() { ++ return this.paperConnection.getAudience(); ++ } ++ // Paper end - configuration phase API @Override + protected GameProfile playerProfile() { @@ -61,6 +_,11 @@ @Override diff --git a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch index 1308853cfa..e0381f9bef 100644 --- a/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch +++ b/paper-server/patches/sources/net/minecraft/server/network/ServerGamePacketListenerImpl.java.patch @@ -83,17 +83,31 @@ public ServerGamePacketListenerImpl(MinecraftServer server, Connection connection, ServerPlayer player, CommonListenerCookie cookie) { super(server, connection, cookie); -@@ -268,7 +_,9 @@ +@@ -268,8 +_,22 @@ player.connection = this; player.getTextFilter().join(); this.signedMessageDecoder = SignedMessageChain.Decoder.unsigned(player.getUUID(), server::enforceSecureProfile); - this.chatMessageChain = new FutureChain(server); +- } + this.chatMessageChain = new FutureChain(server.chatExecutor); // CraftBukkit - async chat + this.tickEndEvent = new io.papermc.paper.event.packet.ClientTickEndEvent(player.getBukkitEntity()); // Paper - add client tick end event + this.playerGameConnection = new io.papermc.paper.connection.PaperPlayerGameConnection(this); // Paper - } ++ } ++ ++ // Paper start - configuration phase API ++ @Override ++ public io.papermc.paper.connection.PlayerCommonConnection getApiConnection() { ++ return this.playerGameConnection; ++ } ++ ++ @Override ++ public net.kyori.adventure.audience.Audience getAudience() { ++ return this.getCraftPlayer(); ++ } ++ // Paper end - configuration phase API @Override + public void tick() { @@ -288,8 +_,8 @@ this.knownMovePacketCount = this.receivedMovePacketCount; if (this.clientIsFloating && !this.player.isSleeping() && !this.player.isPassenger() && !this.player.isDeadOrDying()) { @@ -2610,7 +2624,7 @@ if (!this.receivedMovementThisTick) { this.player.setKnownMovement(Vec3.ZERO); } -@@ -2078,4 +_,92 @@ +@@ -2078,4 +_,80 @@ interface EntityInteraction { InteractionResult run(ServerPlayer player, Entity entity, InteractionHand hand); } @@ -2628,18 +2642,6 @@ + } + // Paper end - Add fail move event + -+ // Paper start - Implement click callbacks with custom click action -+ @Override -+ public void handleCustomClickAction(final net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket packet) { -+ super.handleCustomClickAction(packet); -+ if (packet.id().equals(io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CLICK_CALLBACK_RESOURCE_LOCATION)) { -+ packet.payload().ifPresent(tag -> -+ io.papermc.paper.adventure.providers.ClickCallbackProviderImpl.CALLBACK_MANAGER.tryRunCallback(this.player.getBukkitEntity(), tag) -+ ); -+ } -+ } -+ // Paper end - Implement click callbacks with custom click action -+ + // Paper start - add utilities + public org.bukkit.craftbukkit.entity.CraftPlayer getCraftPlayer() { + return this.player == null ? null : this.player.getBukkitEntity(); diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java index dbc03f9e06..bc9641a987 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/AdventureCodecs.java @@ -5,6 +5,9 @@ import com.mojang.datafixers.util.Either; import com.mojang.serialization.Codec; import com.mojang.serialization.DataResult; import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.PaperDialogCodecs; import java.net.URI; import java.util.Collections; import java.util.List; @@ -13,7 +16,7 @@ import java.util.Optional; import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Predicate; -import com.mojang.serialization.codecs.RecordCodecBuilder; +import java.util.function.Supplier; import net.kyori.adventure.key.Key; import net.kyori.adventure.nbt.api.BinaryTagHolder; import net.kyori.adventure.text.BlockNBTComponent; @@ -36,12 +39,12 @@ import net.kyori.adventure.text.format.ShadowColor; import net.kyori.adventure.text.format.Style; import net.kyori.adventure.text.format.TextColor; import net.kyori.adventure.text.format.TextDecoration; +import net.kyori.adventure.util.Index; import net.minecraft.commands.arguments.selector.SelectorPattern; import net.minecraft.core.UUIDUtil; import net.minecraft.core.registries.BuiltInRegistries; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; -import net.minecraft.nbt.TagParser; import net.minecraft.network.RegistryFriendlyByteBuf; import net.minecraft.network.chat.ComponentSerialization; import net.minecraft.network.chat.contents.KeybindContents; @@ -49,7 +52,6 @@ import net.minecraft.network.chat.contents.ScoreContents; import net.minecraft.network.chat.contents.TranslatableContents; import net.minecraft.network.codec.ByteBufCodecs; import net.minecraft.network.codec.StreamCodec; -import net.minecraft.resources.ResourceLocation; import net.minecraft.util.ExtraCodecs; import net.minecraft.util.StringRepresentable; import net.minecraft.world.item.Item; @@ -98,7 +100,7 @@ public final class AdventureCodecs { } }); - static final Codec KEY_CODEC = Codec.STRING.comapFlatMap(s -> { + public static final Codec KEY_CODEC = Codec.STRING.comapFlatMap(s -> { return Key.parseable(s) ? DataResult.success(Key.key(s)) : DataResult.error(() -> "Cannot convert " + s + " to adventure Key"); }, Key::asString); @@ -129,6 +131,10 @@ public final class AdventureCodecs { static final MapCodec COPY_TO_CLIPBOARD_CODEC = mapCodec((instance) -> instance.group( Codec.STRING.fieldOf("value").forGetter(TEXT_PAYLOAD_EXTRACTOR) ).apply(instance, ClickEvent::copyToClipboard)); + // needs to be lazy loaded due to depending on PaperDialogCodecs static init + static final MapCodec SHOW_DIALOG_CODEC = MapCodec.recursive("show_dialog", ignored -> mapCodec((instance) -> instance.group( + PaperDialogCodecs.DIALOG_CODEC.fieldOf("dialog").forGetter(a -> (Dialog) ((ClickEvent.Payload.Dialog) a.payload()).dialog()) + ).apply(instance, ClickEvent::showDialog))); static final MapCodec CUSTOM_CODEC = mapCodec((instance) -> instance.group( KEY_CODEC.fieldOf("id").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).key()), BINARY_TAG_HOLDER_CODEC.fieldOf("payload").forGetter(a -> ((ClickEvent.Payload.Custom) a.payload()).nbt()) @@ -140,17 +146,19 @@ public final class AdventureCodecs { static final ClickEventType SUGGEST_COMMAND_CLICK_EVENT_TYPE = new ClickEventType(SUGGEST_COMMAND_CODEC, "suggest_command"); static final ClickEventType CHANGE_PAGE_CLICK_EVENT_TYPE = new ClickEventType(CHANGE_PAGE_CODEC, "change_page"); static final ClickEventType COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE = new ClickEventType(COPY_TO_CLIPBOARD_CODEC, "copy_to_clipboard"); + static final ClickEventType SHOW_DIALOG_CLICK_EVENT_TYPE = new ClickEventType(SHOW_DIALOG_CODEC, "show_dialog"); static final ClickEventType CUSTOM_CLICK_EVENT_TYPE = new ClickEventType(CUSTOM_CODEC, "custom"); - static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(() -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE}); + public static final Supplier CLICK_EVENT_TYPES = () -> new ClickEventType[]{OPEN_URL_CLICK_EVENT_TYPE, OPEN_FILE_CLICK_EVENT_TYPE, RUN_COMMAND_CLICK_EVENT_TYPE, SUGGEST_COMMAND_CLICK_EVENT_TYPE, CHANGE_PAGE_CLICK_EVENT_TYPE, COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE, SHOW_DIALOG_CLICK_EVENT_TYPE, CUSTOM_CLICK_EVENT_TYPE}; + static final Codec CLICK_EVENT_TYPE_CODEC = StringRepresentable.fromValues(CLICK_EVENT_TYPES); - record ClickEventType(MapCodec codec, String id) implements StringRepresentable { + public record ClickEventType(MapCodec codec, String id) implements StringRepresentable { @Override public String getSerializedName() { return this.id; } } - private static final Function GET_CLICK_EVENT_TYPE = + public static final Function GET_CLICK_EVENT_TYPE = he -> switch (he.action()) { case OPEN_URL -> OPEN_URL_CLICK_EVENT_TYPE; case OPEN_FILE -> OPEN_FILE_CLICK_EVENT_TYPE; @@ -158,7 +166,7 @@ public final class AdventureCodecs { case SUGGEST_COMMAND -> SUGGEST_COMMAND_CLICK_EVENT_TYPE; case CHANGE_PAGE -> CHANGE_PAGE_CLICK_EVENT_TYPE; case COPY_TO_CLIPBOARD -> COPY_TO_CLIPBOARD_CLICK_EVENT_TYPE; - case SHOW_DIALOG -> throw new UnsupportedOperationException(); // todo: dialog codec with dialog "api" + case SHOW_DIALOG -> SHOW_DIALOG_CLICK_EVENT_TYPE; case CUSTOM -> CUSTOM_CLICK_EVENT_TYPE; }; @@ -450,6 +458,23 @@ public final class AdventureCodecs { return component; } + public static final Codec BINARY_TAG_HOLDER_COMPOUND_CODEC = CompoundTag.CODEC.flatComapMap(PaperAdventure::asBinaryTagHolder, api -> { + try { + final Tag tag = api.get(PaperAdventure.NBT_CODEC); + if (!(tag instanceof final CompoundTag compoundTag)) { + return DataResult.error(() -> "Expected a CompoundTag, but got " + tag.getClass().getSimpleName()); + } + return DataResult.success(compoundTag); + } catch (CommandSyntaxException e) { + return DataResult.error(e::getMessage); + } + }); + + public static Codec indexCodec(final Index index) { + return Codec.of(Codec.STRING.comap(index::keyOrThrow), Codec.STRING.map(index::valueOrThrow)); + + } + private AdventureCodecs() { } } diff --git a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java index 28a3817d46..e62cfa5f47 100644 --- a/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/adventure/providers/ClickCallbackProviderImpl.java @@ -1,15 +1,22 @@ package io.papermc.paper.adventure.providers; +import com.google.common.base.Predicates; import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.dialog.PaperDialogResponseView; +import io.papermc.paper.registry.data.dialog.action.DialogActionCallback; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.Queue; import java.util.UUID; import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.function.Consumer; +import java.util.function.Predicate; import net.kyori.adventure.audience.Audience; import net.kyori.adventure.key.Key; import net.kyori.adventure.text.event.ClickCallback; import net.kyori.adventure.text.event.ClickEvent; +import net.minecraft.core.UUIDUtil; import net.minecraft.nbt.CompoundTag; import net.minecraft.nbt.Tag; import net.minecraft.resources.ResourceLocation; @@ -17,30 +24,81 @@ import org.jetbrains.annotations.NotNull; @SuppressWarnings("UnstableApiUsage") // permitted provider public class ClickCallbackProviderImpl implements ClickCallback.Provider { - private static final Key CLICK_CALLBACK_KEY = Key.key("paper", "click_callback"); - private static final String ID_KEY = "id"; + private static final Key ADVENTURE_CLICK_CALLBACK_KEY = Key.key("paper", "click_callback"); + public static final Key DIALOG_CLICK_CALLBACK_KEY = Key.key("paper", "dialog_click_callback"); + public static final String ID_KEY = "id"; - public static final ResourceLocation CLICK_CALLBACK_RESOURCE_LOCATION = PaperAdventure.asVanilla(CLICK_CALLBACK_KEY); - public static final CallbackManager CALLBACK_MANAGER = new CallbackManager(); + public static final AdventureClick ADVENTURE_CLICK_MANAGER = new AdventureClick(); + public static final DialogClickManager DIALOG_CLICK_MANAGER = new DialogClickManager(); @Override public @NotNull ClickEvent create(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { final CompoundTag tag = new CompoundTag(); - tag.putString(ID_KEY, CALLBACK_MANAGER.addCallback(callback, options).toString()); - return ClickEvent.custom(CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag)); + tag.store(ID_KEY, UUIDUtil.CODEC, ADVENTURE_CLICK_MANAGER.addCallback(callback, options)); + return ClickEvent.custom(ADVENTURE_CLICK_CALLBACK_KEY, PaperAdventure.asBinaryTagHolder(tag)); } - public static final class CallbackManager { + public static final class AdventureClick extends CallbackManager, UUID> { - private final Map callbacks = new HashMap<>(); - private final Queue queue = new ConcurrentLinkedQueue<>(); - - private CallbackManager() { + private AdventureClick() { + super(PaperAdventure.asVanilla(ADVENTURE_CLICK_CALLBACK_KEY)::equals); } public UUID addCallback(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options) { - final UUID id = UUID.randomUUID(); - this.queue.add(new StoredCallback(callback, options, id)); + return this.addCallback(UUID.randomUUID(), callback, options); + } + + @Override + void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag) { + tag.asCompound().ifPresent(t -> { + final Optional id = t.read(ID_KEY, UUIDUtil.CODEC); + if (id.isEmpty()) { + return; + } + this.tryConsumeCallback(id.get(), callback -> { + callback.accept(audience); + }); + }); + } + } + + public static final class DialogClickManager extends CallbackManager { + + public DialogClickManager() { + super(PaperAdventure.asVanilla(DIALOG_CLICK_CALLBACK_KEY)::equals); + } + + @Override + void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag) { + tag.asCompound().ifPresent(t -> { + final Optional id = t.read(ID_KEY, UUIDUtil.CODEC); + if (id.isEmpty()) { + return; + } + this.tryConsumeCallback(id.get(), callback -> { + callback.accept(PaperDialogResponseView.createUnvalidatedResponse(t), audience); + }); + }); + } + } + + + abstract static class CallbackManager { + + private final Predicate locationPredicate; + protected final Map> callbacks = new HashMap<>(); + private final Queue> queue = new ConcurrentLinkedQueue<>(); + + protected CallbackManager(final Predicate locationPredicate) { + this.locationPredicate = locationPredicate; + } + + protected CallbackManager() { + this.locationPredicate = Predicates.alwaysTrue(); + } + + public I addCallback(final I id, final @NotNull C callback, final ClickCallback.@NotNull Options options) { + this.queue.add(new StoredCallback<>(callback, options, id)); return id; } @@ -51,38 +109,36 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { } // Add entries from queue - StoredCallback callback; + StoredCallback callback; while ((callback = this.queue.poll()) != null) { this.callbacks.put(callback.id(), callback); } } - public void tryRunCallback(final @NotNull Audience audience, final Tag tag) { - tag.asCompound().flatMap(t -> t.getString(ID_KEY)).ifPresent(s -> { - final UUID id; - try { - id = UUID.fromString(s); - } catch (final IllegalArgumentException ignored) { - return; - } + final void tryConsumeCallback(final I key, final Consumer callbackConsumer) { + final StoredCallback callback = this.callbacks.get(key); + if (callback != null && callback.valid()) { + callback.takeUse(); + callbackConsumer.accept(callback.callback); + } + } - final StoredCallback callback = this.callbacks.get(id); - if (callback != null && callback.valid()) { - callback.takeUse(); - callback.callback.accept(audience); - } - }); + abstract void doRunCallback(final @NotNull Audience audience, final Key key, final Tag tag); + + public final void tryRunCallback(final @NotNull Audience audience, final ResourceLocation key, final Optional tag) { + if (!this.locationPredicate.test(key) || tag.isEmpty()) return; + this.doRunCallback(audience, PaperAdventure.asAdventure(key), tag.get()); } } - private static final class StoredCallback { + public static final class StoredCallback { private final long startedAt = System.nanoTime(); - private final ClickCallback callback; + private final C callback; private final long lifetime; - private final UUID id; + private final I id; private int remainingUses; - private StoredCallback(final @NotNull ClickCallback callback, final ClickCallback.@NotNull Options options, final UUID id) { + private StoredCallback(final @NotNull C callback, final ClickCallback.@NotNull Options options, final I id) { long lifetimeValue; this.callback = callback; try { @@ -95,7 +151,7 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { this.id = id; } - public void takeUse() { + private void takeUse() { if (this.remainingUses != ClickCallback.UNLIMITED_USES) { this.remainingUses--; } @@ -110,11 +166,11 @@ public class ClickCallbackProviderImpl implements ClickCallback.Provider { return System.nanoTime() - this.startedAt >= this.lifetime; } - public boolean valid() { - return hasRemainingUses() && !expired(); + private boolean valid() { + return this.hasRemainingUses() && !this.expired(); } - public UUID id() { + public I id() { return this.id; } } diff --git a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java index ff450bb493..7adb48a2b9 100644 --- a/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java +++ b/paper-server/src/main/java/io/papermc/paper/connection/PaperPlayerConfigurationConnection.java @@ -3,26 +3,31 @@ package io.papermc.paper.connection; import com.destroystokyo.paper.profile.CraftPlayerProfile; import com.destroystokyo.paper.profile.PlayerProfile; import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.dialog.PaperDialog; +import java.util.ArrayList; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import java.util.UUID; import net.kyori.adventure.audience.Audience; +import net.kyori.adventure.dialog.DialogLike; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.pointer.Pointers; import net.kyori.adventure.resource.ResourcePackCallback; import net.kyori.adventure.resource.ResourcePackInfo; import net.kyori.adventure.resource.ResourcePackRequest; +import net.minecraft.network.chat.Component; import net.minecraft.network.protocol.common.ClientboundResourcePackPopPacket; import net.minecraft.network.protocol.common.ClientboundResourcePackPushPacket; +import net.minecraft.network.protocol.common.ClientboundShowDialogPacket; import net.minecraft.network.protocol.configuration.ClientboundResetChatPacket; import net.minecraft.server.level.ClientInformation; import net.minecraft.server.network.ConfigurationTask; import net.minecraft.server.network.ServerConfigurationPacketListenerImpl; import org.jspecify.annotations.Nullable; -import java.util.HashSet; -import java.util.List; -import java.util.Optional; -import java.util.Set; -import java.util.UUID; - public class PaperPlayerConfigurationConnection extends PaperCommonConnection implements PlayerConfigurationConnection, Audience, PluginMessageBridgeImpl { private @Nullable Pointers adventurePointers; @@ -37,13 +42,13 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection packs = new java.util.ArrayList<>(request.packs().size()); + public void sendResourcePacks(final ResourcePackRequest request) { + final List packs = new ArrayList<>(request.packs().size()); if (request.replace()) { this.clearResourcePacks(); } - final net.minecraft.network.chat.Component prompt = PaperAdventure.asVanilla(request.prompt()); - for (final java.util.Iterator iter = request.packs().iterator(); iter.hasNext(); ) { + final Component prompt = PaperAdventure.asVanilla(request.prompt()); + for (final Iterator iter = request.packs().iterator(); iter.hasNext(); ) { final ResourcePackInfo pack = iter.next(); packs.add(new ClientboundResourcePackPushPacket(pack.id(), pack.uri().toASCIIString(), pack.hash(), request.required(), iter.hasNext() ? Optional.empty() : Optional.ofNullable(prompt))); if (request.callback() != ResourcePackCallback.noOp()) { @@ -54,7 +59,7 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others).forEach(this.handle::send); } @@ -63,13 +68,18 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection this.handle.getOwner().getName()) - .withDynamic(Identity.UUID, () -> this.handle.getOwner().getId()) - .build(); + .withDynamic(Identity.NAME, () -> this.handle.getOwner().getName()) + .withDynamic(Identity.UUID, () -> this.handle.getOwner().getId()) + .build(); } return this.adventurePointers; @@ -92,7 +102,7 @@ public class PaperPlayerConfigurationConnection extends PaperCommonConnection implements Dialog { + + public static net.minecraft.server.dialog.Dialog bukkitToMinecraft(final Dialog bukkit) { + return CraftRegistry.bukkitToMinecraft(bukkit); + } + + public static Holder bukkitToMinecraftHolder(final Dialog bukkit) { + return CraftRegistry.bukkitToMinecraftHolder(bukkit); + } + + public static Dialog minecraftToBukkit(final net.minecraft.server.dialog.Dialog minecraft) { + return CraftRegistry.minecraftToBukkit(minecraft, Registries.DIALOG); + } + + public static Dialog minecraftHolderToBukkit(final Holder minecraft) { + return CraftRegistry.minecraftHolderToBukkit(minecraft, Registries.DIALOG); + } + + public PaperDialog(final Holder holder) { + super(holder); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/dialog/PaperDialogResponseView.java b/paper-server/src/main/java/io/papermc/paper/dialog/PaperDialogResponseView.java new file mode 100644 index 0000000000..fef4e81eb3 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/dialog/PaperDialogResponseView.java @@ -0,0 +1,48 @@ +package io.papermc.paper.dialog; + +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.minecraft.nbt.CompoundTag; +import org.jspecify.annotations.Nullable; + +public class PaperDialogResponseView implements DialogResponseView { + + private final CompoundTag payload; + + private PaperDialogResponseView(final CompoundTag payload) { + this.payload = payload; + } + + public static DialogResponseView createUnvalidatedResponse(final CompoundTag tag) { + return new PaperDialogResponseView(tag); + } + + @Override + public BinaryTagHolder payload() { + return BinaryTagHolder.encode(this.payload, PaperAdventure.NBT_CODEC); + } + + @Override + public @Nullable String getText(final String key) { + if (!this.payload.contains(key)) { + return null; + } + return this.payload.getString(key).orElse(null); + } + + @Override + public @Nullable Boolean getBoolean(final String key) { + if (!this.payload.contains(key)) { + return null; + } + return this.payload.getBoolean(key).orElse(null); + } + + @Override + public @Nullable Float getFloat(final String key) { + if (!this.payload.contains(key)) { + return null; + } + return this.payload.getFloat(key).orElse(null); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/event/player/PaperPlayerCustomClickEvent.java b/paper-server/src/main/java/io/papermc/paper/event/player/PaperPlayerCustomClickEvent.java new file mode 100644 index 0000000000..b63d844315 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/event/player/PaperPlayerCustomClickEvent.java @@ -0,0 +1,44 @@ +package io.papermc.paper.event.player; + +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.connection.PlayerCommonConnection; +import io.papermc.paper.dialog.DialogResponseView; +import io.papermc.paper.dialog.PaperDialogResponseView; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import org.bukkit.entity.Player; +import org.jspecify.annotations.Nullable; + +public class PaperPlayerCustomClickEvent extends PlayerCustomClickEvent { + + private final @Nullable Tag payload; + private @Nullable BinaryTagHolder apiPayload; + + private @Nullable DialogResponseView rawResponse; + + public PaperPlayerCustomClickEvent(final Key key, final PlayerCommonConnection commonConnection, final @Nullable Tag payload) { + super(key, commonConnection); + this.payload = payload; + } + + @Override + public @Nullable BinaryTagHolder getTag() { + if (this.apiPayload == null && this.payload != null) { + this.apiPayload = BinaryTagHolder.encode(this.payload, PaperAdventure.NBT_CODEC); + } + return this.apiPayload; + } + + @Override + public @Nullable DialogResponseView getDialogResponseView() { + if (this.payload == null || !(this.payload instanceof final CompoundTag compoundPayload)) { + return null; + } + if (this.rawResponse == null) { + this.rawResponse = PaperDialogResponseView.createUnvalidatedResponse(compoundPayload); + } + return this.rawResponse; + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/event/player/package-info.java b/paper-server/src/main/java/io/papermc/paper/event/player/package-info.java new file mode 100644 index 0000000000..b3d1dbeeab --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/event/player/package-info.java @@ -0,0 +1,4 @@ +@NullMarked +package io.papermc.paper.event.player; + +import org.jspecify.annotations.NullMarked; diff --git a/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistries.java b/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistries.java index 4a0d5da676..804d6408eb 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistries.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/PaperRegistries.java @@ -4,6 +4,8 @@ import com.google.common.base.Preconditions; import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.datacomponent.DataComponentTypes; import io.papermc.paper.datacomponent.PaperDataComponentType; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.dialog.PaperDialog; import io.papermc.paper.registry.data.PaperBannerPatternRegistryEntry; import io.papermc.paper.registry.data.PaperCatTypeRegistryEntry; import io.papermc.paper.registry.data.PaperChickenVariantRegistryEntry; @@ -18,6 +20,7 @@ import io.papermc.paper.registry.data.PaperPaintingVariantRegistryEntry; import io.papermc.paper.registry.data.PaperPigVariantRegistryEntry; import io.papermc.paper.registry.data.PaperSoundEventRegistryEntry; import io.papermc.paper.registry.data.PaperWolfVariantRegistryEntry; +import io.papermc.paper.registry.data.dialog.PaperDialogRegistryEntry; import io.papermc.paper.registry.entry.RegistryEntry; import io.papermc.paper.registry.entry.RegistryEntryMeta; import io.papermc.paper.registry.tag.TagKey; @@ -132,6 +135,7 @@ public final class PaperRegistries { start(Registries.CHICKEN_VARIANT, RegistryKey.CHICKEN_VARIANT).craft(Chicken.Variant.class, CraftChicken.CraftVariant::new).writable(PaperChickenVariantRegistryEntry.PaperBuilder::new), start(Registries.COW_VARIANT, RegistryKey.COW_VARIANT).craft(Cow.Variant.class, CraftCow.CraftVariant::new).writable(PaperCowVariantRegistryEntry.PaperBuilder::new), start(Registries.PIG_VARIANT, RegistryKey.PIG_VARIANT).craft(Pig.Variant.class, CraftPig.CraftVariant::new).writable(PaperPigVariantRegistryEntry.PaperBuilder::new), + start(Registries.DIALOG, RegistryKey.DIALOG).craft(Dialog.class, PaperDialog::new, true).writable(PaperDialogRegistryEntry.PaperBuilder::new), // api-only start(Registries.ENTITY_TYPE, RegistryKey.ENTITY_TYPE).apiOnly(PaperSimpleRegistry::entityType), @@ -161,13 +165,13 @@ public final class PaperRegistries { } @SuppressWarnings("unchecked") - public static > RegistryEntryMeta.Buildable getBuildableMeta(final ResourceKey> resourceKey) { - final RegistryEntry entry = getEntry(resourceKey); + public static > RegistryEntryMeta.Buildable getBuildableMeta(final RegistryKey registryKey) { + final RegistryEntry entry = getEntry(registryKey); if (entry == null) { - throw new IllegalArgumentException("No registry entry for " + resourceKey); + throw new IllegalArgumentException("No registry entry for " + registryKey); } if (!(entry.meta() instanceof final RegistryEntryMeta.Buildable buildableMeta)) { - throw new IllegalArgumentException("Registry entry for " + resourceKey + " is not buildable"); + throw new IllegalArgumentException("Registry entry for " + registryKey + " is not buildable"); } return (RegistryEntryMeta.Buildable) buildableMeta; } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProviderImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProviderImpl.java index 41539dc045..8927a5fc4f 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProviderImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/InlinedRegistryBuilderProviderImpl.java @@ -1,15 +1,22 @@ package io.papermc.paper.registry.data; +import io.papermc.paper.dialog.Dialog; import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.dialog.DialogRegistryEntry; import io.papermc.paper.registry.data.util.Conversions; import java.util.function.Consumer; -import net.minecraft.core.registries.Registries; import org.bukkit.MusicInstrument; public final class InlinedRegistryBuilderProviderImpl implements InlinedRegistryBuilderProvider { @Override public MusicInstrument createInstrument(final Consumer> value) { - return Conversions.global().createApiInstanceFromBuilder(Registries.INSTRUMENT, value); + return Conversions.global().createApiInstanceFromBuilder(RegistryKey.INSTRUMENT, value); + } + + @Override + public Dialog createDialog(final Consumer> value) { + return Conversions.global().createApiInstanceFromBuilder(RegistryKey.DIALOG, value); } } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/PaperInstrumentRegistryEntry.java b/paper-server/src/main/java/io/papermc/paper/registry/data/PaperInstrumentRegistryEntry.java index 7d3e798047..e8a43e93bb 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/data/PaperInstrumentRegistryEntry.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/PaperInstrumentRegistryEntry.java @@ -3,13 +3,13 @@ package io.papermc.paper.registry.data; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.PaperRegistryBuilder; import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.util.Conversions; import io.papermc.paper.registry.holder.PaperRegistryHolders; import io.papermc.paper.registry.holder.RegistryHolder; import java.util.function.Consumer; import net.minecraft.core.Holder; -import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.item.Instrument; @@ -77,7 +77,7 @@ public class PaperInstrumentRegistryEntry implements InstrumentRegistryEntry { @Override public Builder soundEvent(final Consumer> soundEvent) { - this.soundEvent = this.conversions.createHolderFromBuilder(Registries.SOUND_EVENT, asArgument(soundEvent, "soundEvent")); + this.soundEvent = this.conversions.createHolderFromBuilder(RegistryKey.SOUND_EVENT, asArgument(soundEvent, "soundEvent")); return this; } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/PaperJukeboxSongRegistryEntry.java b/paper-server/src/main/java/io/papermc/paper/registry/data/PaperJukeboxSongRegistryEntry.java index 7e8764436d..50a286fd2f 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/data/PaperJukeboxSongRegistryEntry.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/PaperJukeboxSongRegistryEntry.java @@ -3,6 +3,7 @@ package io.papermc.paper.registry.data; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.PaperRegistryBuilder; import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; import io.papermc.paper.registry.data.util.Conversions; import io.papermc.paper.registry.holder.PaperRegistryHolders; @@ -10,7 +11,6 @@ import io.papermc.paper.registry.holder.RegistryHolder; import java.util.OptionalInt; import java.util.function.Consumer; import net.minecraft.core.Holder; -import net.minecraft.core.registries.Registries; import net.minecraft.network.chat.Component; import net.minecraft.sounds.SoundEvent; import net.minecraft.world.item.JukeboxSong; @@ -80,7 +80,7 @@ public class PaperJukeboxSongRegistryEntry implements JukeboxSongRegistryEntry { @Override public JukeboxSongRegistryEntry.Builder soundEvent(final Consumer> soundEvent) { - this.soundEvent = this.conversions.createHolderFromBuilder(Registries.SOUND_EVENT, asArgument(soundEvent, "soundEvent")); + this.soundEvent = this.conversions.createHolderFromBuilder(RegistryKey.SOUND_EVENT, asArgument(soundEvent, "soundEvent")); return this; } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/ActionButtonImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/ActionButtonImpl.java new file mode 100644 index 0000000000..1ee4c85fe2 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/ActionButtonImpl.java @@ -0,0 +1,46 @@ +package io.papermc.paper.registry.data.dialog; + +import com.google.common.base.Preconditions; +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import net.kyori.adventure.text.Component; +import net.minecraft.server.dialog.CommonButtonData; +import org.jspecify.annotations.Nullable; + +public record ActionButtonImpl(Component label, @Nullable Component tooltip, int width, @Nullable DialogAction action) implements ActionButton { + + public static final class BuilderImpl implements ActionButton.Builder { + + private final Component label; + private @Nullable Component tooltip; + private int width = CommonButtonData.DEFAULT_WIDTH; + private @Nullable DialogAction action; + + public BuilderImpl(final Component label) { + this.label = label; + } + + @Override + public Builder tooltip(final @Nullable Component tooltip) { + this.tooltip = tooltip; + return this; + } + + @Override + public Builder width(final int width) { + Preconditions.checkArgument(width >= 1 && width <= 1024, "Width must be between 1 and 1024"); + this.width = width; + return this; + } + + @Override + public Builder action(final @Nullable DialogAction action) { + this.action = action; + return this; + } + + @Override + public ActionButton build() { + return new ActionButtonImpl(this.label, this.tooltip, this.width, this.action); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/DialogBaseImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/DialogBaseImpl.java new file mode 100644 index 0000000000..ff541efc46 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/DialogBaseImpl.java @@ -0,0 +1,83 @@ +package io.papermc.paper.registry.data.dialog; + +import com.google.common.base.Preconditions; +import io.papermc.paper.registry.data.dialog.body.DialogBody; +import io.papermc.paper.registry.data.dialog.input.DialogInput; +import java.util.Collections; +import java.util.List; +import net.kyori.adventure.text.Component; +import org.jetbrains.annotations.Unmodifiable; +import org.jspecify.annotations.Nullable; + +public record DialogBaseImpl( + Component title, + @Nullable Component externalTitle, + boolean canCloseWithEscape, + boolean pause, + DialogAfterAction afterAction, + @Unmodifiable List body, + @Unmodifiable List inputs +) implements DialogBase { + + public DialogBaseImpl { + Preconditions.checkArgument(!pause || afterAction != DialogAfterAction.NONE); + body = List.copyOf(body); + inputs = List.copyOf(inputs); + } + + public static final class BuilderImpl implements DialogBase.Builder { + + private final Component title; + private @Nullable Component externalTitle; + private boolean canCloseWithEscape = true; + private boolean pause = true; + private DialogAfterAction afterAction = DialogAfterAction.CLOSE; + private List body = Collections.emptyList(); + private List inputs = Collections.emptyList(); + + public BuilderImpl(final Component title) { + this.title = title; + } + + @Override + public BuilderImpl externalTitle(final @Nullable Component externalTitle) { + this.externalTitle = externalTitle; + return this; + } + + @Override + public BuilderImpl canCloseWithEscape(final boolean canCloseWithEscape) { + this.canCloseWithEscape = canCloseWithEscape; + return this; + } + + @Override + public BuilderImpl pause(final boolean pause) { + this.pause = pause; + return this; + } + + @Override + public BuilderImpl afterAction(final DialogAfterAction afterAction) { + this.afterAction = afterAction; + return this; + } + + @Override + public BuilderImpl body(final List body) { + this.body = List.copyOf(body); + return this; + } + + @Override + public BuilderImpl inputs(final List inputs) { + this.inputs = List.copyOf(inputs); + return this; + } + + @Override + public DialogBase build() { + return new DialogBaseImpl(this.title, this.externalTitle, this.canCloseWithEscape, this.pause, this.afterAction, this.body, this.inputs); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogCodecs.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogCodecs.java new file mode 100644 index 0000000000..9e2a26aeb2 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogCodecs.java @@ -0,0 +1,186 @@ +package io.papermc.paper.registry.data.dialog; + +import com.mojang.serialization.Codec; +import com.mojang.serialization.Lifecycle; +import com.mojang.serialization.MapCodec; +import com.mojang.serialization.codecs.RecordCodecBuilder; +import io.papermc.paper.adventure.AdventureCodecs; +import io.papermc.paper.dialog.PaperDialog; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import io.papermc.paper.registry.data.dialog.body.DialogBody; +import io.papermc.paper.registry.data.dialog.body.ItemDialogBody; +import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody; +import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput; +import io.papermc.paper.registry.data.dialog.input.DialogInput; +import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput; +import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput; +import io.papermc.paper.registry.data.dialog.input.TextDialogInput; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import net.minecraft.Util; +import net.minecraft.core.MappedRegistry; +import net.minecraft.core.Registry; +import net.minecraft.resources.ResourceKey; +import net.minecraft.resources.ResourceLocation; +import net.minecraft.server.dialog.CommonButtonData; +import net.minecraft.server.dialog.Dialog; +import net.minecraft.server.dialog.action.ParsedTemplate; +import net.minecraft.server.dialog.body.PlainMessage; +import net.minecraft.server.dialog.input.TextInput; +import net.minecraft.util.ExtraCodecs; +import net.minecraft.world.item.ItemStack; +import org.bukkit.craftbukkit.inventory.CraftItemStack; + +import static io.papermc.paper.util.PaperCodecs.registryFileDecoderFor; + +public final class PaperDialogCodecs { + + private PaperDialogCodecs() { + } + + // dialog actions + private static final MapCodec COMMAND_TEMPLATE_ACTION_CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group(Codec.STRING.fieldOf("template").forGetter(DialogAction.CommandTemplateAction::template)) + .apply(instance, DialogAction::commandTemplate) + ); + private static final MapCodec CUSTOM_ALL_ACTION_CODEC = RecordCodecBuilder.mapCodec(instance -> + instance.group(AdventureCodecs.KEY_CODEC.fieldOf("id").forGetter(DialogAction.CustomClickAction::id), AdventureCodecs.BINARY_TAG_HOLDER_COMPOUND_CODEC.optionalFieldOf("additions").forGetter(action -> Optional.ofNullable(action.additions()))) + .apply(instance, (key, binaryTagHolder) -> DialogAction.customClick(key, binaryTagHolder.orElse(null))) + ); + private static final Map> STATIC_ACTION_CODECS = Arrays.stream(AdventureCodecs.CLICK_EVENT_TYPES.get()).collect(Collectors.toMap(Function.identity(), type -> type.codec().xmap(DialogAction::staticAction, DialogAction.StaticAction::value))); + private static final Registry> DIALOG_ACTION_TYPES = Util.make(() -> { + final MappedRegistry> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_action_type")), Lifecycle.experimental()); + STATIC_ACTION_CODECS.forEach((clickType, actionCodec) -> { + Registry.register(registry, clickType.getSerializedName(), actionCodec); + }); + Registry.register(registry, "dynamic/run_command", COMMAND_TEMPLATE_ACTION_CODEC); + Registry.register(registry, "dynamic/custom", CUSTOM_ALL_ACTION_CODEC); + return registry.freeze(); + }); + private static final Function> GET_DIALOG_ACTION_TYPE = dialogAction -> switch (dialogAction) { + case DialogAction.CommandTemplateAction $ -> COMMAND_TEMPLATE_ACTION_CODEC; + case DialogAction.CustomClickAction $ -> CUSTOM_ALL_ACTION_CODEC; + case DialogAction.StaticAction action -> STATIC_ACTION_CODECS.get(AdventureCodecs.GET_CLICK_EVENT_TYPE.apply(action.value())); + }; + private static final Codec DIALOG_ACTION_CODEC = DIALOG_ACTION_TYPES.byNameCodec().dispatch(GET_DIALOG_ACTION_TYPE, Function.identity()); + + // buttons + public static final Codec ACTION_BUTTON_CODEC = RecordCodecBuilder.create(instance -> instance.group( + AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(ActionButton::label), + AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("tooltip").forGetter(button -> Optional.ofNullable(button.tooltip())), + Dialog.WIDTH_CODEC.optionalFieldOf("width", CommonButtonData.DEFAULT_WIDTH).forGetter(ActionButton::width), + DIALOG_ACTION_CODEC.optionalFieldOf("action").forGetter(button -> Optional.ofNullable(button.action())) + ).apply(instance, (label, tooltip, width, action) -> ActionButton.create(label, tooltip.orElse(null), width, action.orElse(null)))); + + // dialog bodies + private static final MapCodec PLAIN_MESSAGE_BODY_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + AdventureCodecs.COMPONENT_CODEC.fieldOf("contents").forGetter(PlainMessageDialogBody::contents), + Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(PlainMessageDialogBody::width) + ).apply(instance, DialogBody::plainMessage) + ); + private static final Codec SIMPLE_PLAIN_MESSAGE_BODY_CODEC = Codec.withAlternative(PLAIN_MESSAGE_BODY_CODEC.codec(), AdventureCodecs.COMPONENT_CODEC, component -> DialogBody.plainMessage(component, PlainMessage.DEFAULT_WIDTH)); + private static final MapCodec ITEM_BODY_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ItemStack.STRICT_CODEC.xmap(CraftItemStack::asBukkitCopy, CraftItemStack::asNMSCopy).fieldOf("item").forGetter(ItemDialogBody::item), + SIMPLE_PLAIN_MESSAGE_BODY_CODEC.optionalFieldOf("description").forGetter(body -> Optional.ofNullable(body.description())), + Codec.BOOL.optionalFieldOf("show_decorations", true).forGetter(ItemDialogBody::showDecorations), + Codec.BOOL.optionalFieldOf("show_tooltip", true).forGetter(ItemDialogBody::showTooltip), + ExtraCodecs.intRange(1, 256).optionalFieldOf("width", 16).forGetter(ItemDialogBody::width), + ExtraCodecs.intRange(1, 256).optionalFieldOf("height", 16).forGetter(ItemDialogBody::height) + ).apply(instance, (itemStack, plainMessageBody, showDecorations, showTooltip, width, height) -> DialogBody.item(itemStack, plainMessageBody.orElse(null), showDecorations, showTooltip, width, height)) + ); + private static final Registry> DIALOG_BODY_TYPES = Util.make(() -> { + final MappedRegistry> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_body_type")), Lifecycle.experimental()); + Registry.register(registry, "item", ITEM_BODY_CODEC); + Registry.register(registry, "plain_message", PLAIN_MESSAGE_BODY_CODEC); + return registry.freeze(); + }); + private static final Function> GET_DIALOG_BODY_TYPE = dialogAction -> switch (dialogAction) { + case PlainMessageDialogBody $ -> PLAIN_MESSAGE_BODY_CODEC; + case ItemDialogBody $ -> ITEM_BODY_CODEC; + }; + private static final Codec DIALOG_BODY_CODEC = DIALOG_BODY_TYPES.byNameCodec().dispatch(GET_DIALOG_BODY_TYPE, Function.identity()); + private static final Codec> DIALOG_BODY_LIST_CODEC = ExtraCodecs.compactListCodec(DIALOG_BODY_CODEC); + + // input types + private static final MapCodec BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key), + AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(BooleanDialogInput::label), + Codec.BOOL.optionalFieldOf("initial", false).forGetter(BooleanDialogInput::initial), + Codec.STRING.optionalFieldOf("on_true", "true").forGetter(BooleanDialogInput::onTrue), + Codec.STRING.optionalFieldOf("on_false", "false").forGetter(BooleanDialogInput::onFalse) + ).apply(instance, DialogInput::bool)); + private static final MapCodec NUMBER_RANGE_INPUT_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key), + Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(NumberRangeDialogInput::width), + AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(NumberRangeDialogInput::label), + Codec.STRING.optionalFieldOf("label_format", "options.generic_value").forGetter(NumberRangeDialogInput::labelFormat), + Codec.FLOAT.fieldOf("start").forGetter(NumberRangeDialogInput::start), + Codec.FLOAT.fieldOf("end").forGetter(NumberRangeDialogInput::end), + Codec.FLOAT.optionalFieldOf("initial").forGetter(type -> Optional.ofNullable(type.initial())), + ExtraCodecs.POSITIVE_FLOAT.optionalFieldOf("step").forGetter(type -> Optional.ofNullable(type.step())) + ).apply(instance, (key, width, label, labelFormat, start, end, initial, step) -> DialogInput.numberRange(key, width, label, labelFormat, start, end, initial.orElse(null), step.orElse(null)))); + private static final Codec SINGLE_OPTION_DIALOG_INPUT_ENTRY_FULL_CODEC = RecordCodecBuilder.create(instance -> instance.group( + Codec.STRING.fieldOf("id").forGetter(SingleOptionDialogInput.OptionEntry::id), + AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("display").forGetter(entry -> Optional.ofNullable(entry.display())), + Codec.BOOL.optionalFieldOf("initial", false).forGetter(SingleOptionDialogInput.OptionEntry::initial) + ).apply(instance, (id, display, initial) -> SingleOptionDialogInput.OptionEntry.create(id, display.orElse(null), initial))); + private static final Codec SINGLE_OPTION_DIALOG_INPUT_ENTRY_CODEC = Codec.withAlternative( + SINGLE_OPTION_DIALOG_INPUT_ENTRY_FULL_CODEC, Codec.STRING, string -> SingleOptionDialogInput.OptionEntry.create(string, null, false) + ); + private static final MapCodec SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key), + Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(SingleOptionDialogInput::width), + ExtraCodecs.nonEmptyList(SINGLE_OPTION_DIALOG_INPUT_ENTRY_CODEC.listOf()).fieldOf("options").forGetter(SingleOptionDialogInput::entries), + AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(SingleOptionDialogInput::label), + Codec.BOOL.optionalFieldOf("label_visible", true).forGetter(SingleOptionDialogInput::labelVisible) + ).apply(instance, DialogInput::singleOption)); + private static final Codec TEXT_DIALOG_INPUT_MULTILINE_OPTIONS_CODEC = RecordCodecBuilder.create(instance -> instance.group( + ExtraCodecs.POSITIVE_INT.optionalFieldOf("max_lines").forGetter(options -> Optional.ofNullable(options.maxLines())), + ExtraCodecs.intRange(1, TextInput.MultilineOptions.MAX_HEIGHT).optionalFieldOf("height").forGetter(options -> Optional.ofNullable(options.height())) + ).apply(instance, (maxLines, height) -> TextDialogInput.MultilineOptions.create(maxLines.orElse(null), height.orElse(null)))); + private static final MapCodec TEXT_DIALOG_INPUT_TYPE_MAP_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + ParsedTemplate.VARIABLE_CODEC.fieldOf("key").forGetter(DialogInput::key), + Dialog.WIDTH_CODEC.optionalFieldOf("width", PlainMessage.DEFAULT_WIDTH).forGetter(TextDialogInput::width), + AdventureCodecs.COMPONENT_CODEC.fieldOf("label").forGetter(TextDialogInput::label), + Codec.BOOL.optionalFieldOf("label_visible", true).forGetter(TextDialogInput::labelVisible), + Codec.STRING.optionalFieldOf("initial", "").forGetter(TextDialogInput::initial), + ExtraCodecs.POSITIVE_INT.optionalFieldOf("max_length", 32).forGetter(TextDialogInput::maxLength), + TEXT_DIALOG_INPUT_MULTILINE_OPTIONS_CODEC.optionalFieldOf("multiline").forGetter(inputType -> Optional.ofNullable(inputType.multiline())) + ).apply(instance, (key, width, label, labelVisible, initial, maxLength, multilineOptions) -> + DialogInput.text(key, width, label, labelVisible, initial, maxLength, multilineOptions.orElse(null)) + )); + private static final Registry> DIALOG_INPUT_TYPES = Util.make(() -> { + final MappedRegistry> registry = new MappedRegistry<>(ResourceKey.createRegistryKey(ResourceLocation.fromNamespaceAndPath(ResourceLocation.PAPER_NAMESPACE, "dialog_input_type")), Lifecycle.experimental()); + Registry.register(registry, "boolean", BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC); + Registry.register(registry, "number_range", NUMBER_RANGE_INPUT_MAP_CODEC); + Registry.register(registry, "single_option", SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC); + Registry.register(registry, "text", TEXT_DIALOG_INPUT_TYPE_MAP_CODEC); + return registry.freeze(); + }); + private static final Function> GET_DIALOG_INPUT_TYPE_TYPE = dialogAction -> switch (dialogAction) { + case TextDialogInput $ -> TEXT_DIALOG_INPUT_TYPE_MAP_CODEC; + case SingleOptionDialogInput $ -> SINGLE_OPTION_DIALOG_INPUT_TYPE_MAP_CODEC; + case NumberRangeDialogInput $ -> NUMBER_RANGE_INPUT_MAP_CODEC; + case BooleanDialogInput $ -> BOOLEAN_DIALOG_INPUT_TYPE_MAP_CODEC; + }; + private static final Codec DIALOG_INPUT_CODEC = DIALOG_INPUT_TYPES.byNameCodec().dispatchMap(GET_DIALOG_INPUT_TYPE_TYPE, Function.identity()).codec(); + + // dialog base / common dialog data + public static final Codec DIALOG_BASE_CODEC = RecordCodecBuilder.mapCodec(instance -> instance.group( + AdventureCodecs.COMPONENT_CODEC.fieldOf("title").forGetter(DialogBase::title), + AdventureCodecs.COMPONENT_CODEC.optionalFieldOf("external_title").forGetter(base -> Optional.ofNullable(base.externalTitle())), + Codec.BOOL.optionalFieldOf("can_close_with_escape", true).forGetter(DialogBase::canCloseWithEscape), + Codec.BOOL.optionalFieldOf("pause", true).forGetter(DialogBase::pause), + AdventureCodecs.indexCodec(DialogBase.DialogAfterAction.NAMES).optionalFieldOf("after_action", DialogBase.DialogAfterAction.CLOSE).forGetter(DialogBase::afterAction), + DIALOG_BODY_LIST_CODEC.optionalFieldOf("body", List.of()).forGetter(DialogBase::body), + DIALOG_INPUT_CODEC.listOf().optionalFieldOf("inputs", List.of()).forGetter(DialogBase::inputs) + ).apply(instance, (title, externalTitle, canCloseWithEsc, pause, afterAction, body, inputs) -> DialogBase.create(title, externalTitle.orElse(null), canCloseWithEsc, pause, afterAction, body, inputs)) + ).codec(); + + public static final Codec DIALOG_CODEC = Codec.of(Dialog.CODEC.comap(PaperDialog::bukkitToMinecraftHolder), registryFileDecoderFor(Dialog.DIRECT_CODEC, PaperDialog::minecraftHolderToBukkit, RegistryKey.DIALOG, true)); +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogInstancesProvider.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogInstancesProvider.java new file mode 100644 index 0000000000..4731d52d8f --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogInstancesProvider.java @@ -0,0 +1,155 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.adventure.PaperAdventure; +import io.papermc.paper.adventure.providers.ClickCallbackProviderImpl; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.action.CommandTemplateActionImpl; +import io.papermc.paper.registry.data.dialog.action.CustomClickActionImpl; +import io.papermc.paper.registry.data.dialog.action.DialogAction; +import io.papermc.paper.registry.data.dialog.action.DialogActionCallback; +import io.papermc.paper.registry.data.dialog.action.StaticActionImpl; +import io.papermc.paper.registry.data.dialog.body.ItemDialogBody; +import io.papermc.paper.registry.data.dialog.body.ItemDialogBodyImpl; +import io.papermc.paper.registry.data.dialog.body.PlainMessageBodyImpl; +import io.papermc.paper.registry.data.dialog.body.PlainMessageDialogBody; +import io.papermc.paper.registry.data.dialog.input.BooleanDialogInput; +import io.papermc.paper.registry.data.dialog.input.BooleanDialogInputImpl; +import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInput; +import io.papermc.paper.registry.data.dialog.input.NumberRangeDialogInputImpl; +import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInput; +import io.papermc.paper.registry.data.dialog.input.SingleOptionDialogInputImpl; +import io.papermc.paper.registry.data.dialog.input.TextDialogInput; +import io.papermc.paper.registry.data.dialog.input.TextDialogInputImpl; +import io.papermc.paper.registry.data.dialog.type.ConfirmationType; +import io.papermc.paper.registry.data.dialog.type.ConfirmationTypeImpl; +import io.papermc.paper.registry.data.dialog.type.DialogListType; +import io.papermc.paper.registry.data.dialog.type.DialogListTypeImpl; +import io.papermc.paper.registry.data.dialog.type.MultiActionType; +import io.papermc.paper.registry.data.dialog.type.MultiActionTypeImpl; +import io.papermc.paper.registry.data.dialog.type.NoticeType; +import io.papermc.paper.registry.data.dialog.type.NoticeTypeImpl; +import io.papermc.paper.registry.data.dialog.type.ServerLinksType; +import io.papermc.paper.registry.data.dialog.type.ServerLinksTypeImpl; +import io.papermc.paper.registry.set.RegistrySet; +import java.util.List; +import java.util.UUID; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.kyori.adventure.text.Component; +import net.kyori.adventure.text.event.ClickCallback; +import net.kyori.adventure.text.event.ClickEvent; +import net.minecraft.core.UUIDUtil; +import net.minecraft.nbt.CompoundTag; +import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.Nullable; + +public final class PaperDialogInstancesProvider implements DialogInstancesProvider { + + @Override + public DialogBase.Builder dialogBaseBuilder(final Component title) { + return new DialogBaseImpl.BuilderImpl(title); + } + + @Override + public ActionButton.Builder actionButtonBuilder(final Component label) { + return new ActionButtonImpl.BuilderImpl(label); + } + + @Override + public DialogAction.CustomClickAction register(final DialogActionCallback callback, final ClickCallback.Options options) { + final UUID id = ClickCallbackProviderImpl.DIALOG_CLICK_MANAGER.addCallback(UUID.randomUUID(), callback, options); + final CompoundTag tag = new CompoundTag(); + tag.store(ClickCallbackProviderImpl.ID_KEY, UUIDUtil.CODEC, id); + return DialogAction.customClick(ClickCallbackProviderImpl.DIALOG_CLICK_CALLBACK_KEY, BinaryTagHolder.encode(tag, PaperAdventure.NBT_CODEC)); + } + + @Override + public DialogAction.StaticAction staticAction(final ClickEvent value) { + return new StaticActionImpl(value); + } + + @Override + public DialogAction.CommandTemplateAction commandTemplate(final String template) { + return new CommandTemplateActionImpl(template); + } + + @Override + public DialogAction.CustomClickAction customClick(final Key id, final @Nullable BinaryTagHolder additions) { + return new CustomClickActionImpl(id, additions); + } + + @Override + public ItemDialogBody.Builder itemDialogBodyBuilder(final ItemStack itemStack) { + return new ItemDialogBodyImpl.BuilderImpl(itemStack); + } + + @Override + public PlainMessageDialogBody plainMessageDialogBody(final Component component) { + return new PlainMessageBodyImpl(component); + } + + @Override + public PlainMessageDialogBody plainMessageDialogBody(final Component component, final int width) { + return new PlainMessageBodyImpl(component, width); + } + + @Override + public BooleanDialogInput.Builder booleanBuilder(final String key, final Component label) { + return new BooleanDialogInputImpl.BuilderImpl(key, label); + } + + @Override + public NumberRangeDialogInput.Builder numberRangeBuilder(final String key, final Component label, final float start, final float end) { + return new NumberRangeDialogInputImpl.BuilderImpl(key, label, start, end); + } + + @Override + public SingleOptionDialogInput.Builder singleOptionBuilder(final String key, final Component label, final List entries) { + return new SingleOptionDialogInputImpl.BuilderImpl(key, entries, label); + } + + @Override + public SingleOptionDialogInput.OptionEntry singleOptionEntry(final String id, final @Nullable Component display, final boolean initial) { + return new SingleOptionDialogInputImpl.SingleOptionEntryImpl(id, display, initial); + } + + @Override + public TextDialogInput.Builder textBuilder(final String key, final Component label) { + return new TextDialogInputImpl.BuilderImpl(key, label); + } + + @Override + public TextDialogInput.MultilineOptions multilineOptions(final @Nullable Integer maxLines, final @Nullable Integer height) { + return new TextDialogInputImpl.MultilineOptionsImpl(maxLines, height); + } + + @Override + public ConfirmationType confirmation(final ActionButton yesButton, final ActionButton noButton) { + return new ConfirmationTypeImpl(yesButton, noButton); + } + + @Override + public DialogListType.Builder dialogList(final RegistrySet

dialogs) { + return new DialogListTypeImpl.BuilderImpl(dialogs); + } + + @Override + public MultiActionType.Builder multiAction(final List actions) { + return new MultiActionTypeImpl.BuilderImpl(actions); + } + + @Override + public NoticeType notice() { + return new NoticeTypeImpl(); + } + + @Override + public NoticeType notice(final ActionButton action) { + return new NoticeTypeImpl(action); + } + + @Override + public ServerLinksType serverLinks(final @Nullable ActionButton exitAction, final int columns, final int buttonWidth) { + return new ServerLinksTypeImpl(exitAction, columns, buttonWidth); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogRegistryEntry.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogRegistryEntry.java new file mode 100644 index 0000000000..e4d246110e --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogRegistryEntry.java @@ -0,0 +1,74 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.registry.PaperRegistryBuilder; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.dialog.type.DialogType; +import io.papermc.paper.registry.data.util.Conversions; +import io.papermc.paper.registry.set.RegistryValueSetBuilder; +import io.papermc.paper.registry.set.RegistryValueSetBuilderImpl; +import net.minecraft.server.dialog.CommonDialogData; +import net.minecraft.server.dialog.Dialog; +import org.jspecify.annotations.Nullable; + +import static io.papermc.paper.registry.data.util.Checks.asArgument; +import static io.papermc.paper.registry.data.util.Checks.asConfigured; + +public class PaperDialogRegistryEntry implements DialogRegistryEntry { + + protected @Nullable DialogBase dialogBase; + protected @Nullable DialogType dialogType; + + protected final Conversions conversions; + + public PaperDialogRegistryEntry( + final Conversions conversions, + final @Nullable Dialog internal + ) { + this.conversions = conversions; + if (internal == null) return; + + final CommonDialogData common = internal.common(); + this.dialogBase = conversions.convert(common, PaperDialogCodecs.DIALOG_BASE_CODEC, CommonDialogData.MAP_CODEC.codec()); + + this.dialogType = PaperDialogs.extractSpecialty(internal, conversions); + } + + @Override + public DialogBase base() { + return asConfigured(this.dialogBase, "dialogBase"); + } + + @Override + public DialogType type() { + return asConfigured(this.dialogType, "dialogSpecialty"); + } + + public static final class PaperBuilder extends PaperDialogRegistryEntry implements Builder, PaperRegistryBuilder { + + public PaperBuilder(final Conversions conversions, final @Nullable Dialog internal) { + super(conversions, internal); + } + + @Override + public RegistryValueSetBuilder registryValueSet() { + return new RegistryValueSetBuilderImpl<>(RegistryKey.DIALOG, this.conversions); + } + + @Override + public Builder base(final DialogBase dialogBase) { + this.dialogBase = asArgument(dialogBase, "dialogBase"); + return this; + } + + @Override + public Builder type(final DialogType dialogType) { + this.dialogType = asArgument(dialogType, "dialogSpecialty"); + return this; + } + + @Override + public Dialog build() { + return PaperDialogs.constructDialog(this.base(), this.type(), this.conversions); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogs.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogs.java new file mode 100644 index 0000000000..e7cb7281e9 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/PaperDialogs.java @@ -0,0 +1,84 @@ +package io.papermc.paper.registry.data.dialog; + +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.dialog.type.ConfirmationType; +import io.papermc.paper.registry.data.dialog.type.DialogListType; +import io.papermc.paper.registry.data.dialog.type.DialogType; +import io.papermc.paper.registry.data.dialog.type.MultiActionType; +import io.papermc.paper.registry.data.dialog.type.NoticeType; +import io.papermc.paper.registry.data.dialog.type.ServerLinksType; +import io.papermc.paper.registry.data.util.Conversions; +import io.papermc.paper.registry.set.PaperRegistrySets; +import io.papermc.paper.registry.set.RegistrySet; +import java.util.Optional; +import java.util.function.Function; +import net.minecraft.core.registries.Registries; +import net.minecraft.server.dialog.CommonDialogData; +import net.minecraft.server.dialog.ConfirmationDialog; +import net.minecraft.server.dialog.Dialog; +import net.minecraft.server.dialog.DialogListDialog; +import net.minecraft.server.dialog.MultiActionDialog; +import net.minecraft.server.dialog.NoticeDialog; +import net.minecraft.server.dialog.ServerLinksDialog; + +public final class PaperDialogs { + + private PaperDialogs() { + } + + public static DialogType extractSpecialty(final Dialog nmsDialog, final Conversions conversions) { + final Function convertButton = button -> conversions.convert(button, PaperDialogCodecs.ACTION_BUTTON_CODEC, net.minecraft.server.dialog.ActionButton.CODEC); + return switch (nmsDialog) { + case final ConfirmationDialog conf -> + DialogType.confirmation(convertButton.apply(conf.yesButton()), convertButton.apply(conf.noButton())); + case final DialogListDialog list -> { + final RegistrySet apiSet = PaperRegistrySets.convertToApiWithDirects(RegistryKey.DIALOG, list.dialogs()); + yield DialogType.dialogList( + apiSet, + list.exitAction().map(convertButton).orElse(null), + list.columns(), + list.buttonWidth() + ); + } + case final MultiActionDialog multi -> + DialogType.multiAction(multi.actions().stream().map(convertButton).toList(), multi.exitAction().map(convertButton).orElse(null), multi.columns()); + case final NoticeDialog notice -> DialogType.notice(convertButton.apply(notice.action())); + case final ServerLinksDialog links -> + DialogType.serverLinks(links.exitAction().map(convertButton).orElse(null), links.columns(), links.buttonWidth()); + default -> throw new IllegalArgumentException("Unsupported dialog type: " + nmsDialog.getClass().getName()); + }; + } + + public static Dialog constructDialog(final DialogBase dialogBase, final DialogType dialogType, final Conversions conversions) { + final Function convertButton = button -> conversions.convert(button, net.minecraft.server.dialog.ActionButton.CODEC, PaperDialogCodecs.ACTION_BUTTON_CODEC); + final CommonDialogData common = conversions.convert(dialogBase, CommonDialogData.MAP_CODEC.codec(), PaperDialogCodecs.DIALOG_BASE_CODEC); + switch (dialogType) { + case final ConfirmationType conf -> { + return new ConfirmationDialog(common, convertButton.apply(conf.yesButton()), convertButton.apply(conf.noButton())); + } + case final DialogListType list -> { + return new DialogListDialog( + common, + PaperRegistrySets.convertToNmsWithDirects(Registries.DIALOG, conversions.lookup(), list.dialogs()), + Optional.ofNullable(list.exitAction()).map(convertButton), + list.columns(), + list.buttonWidth() + ); + } + case final MultiActionType multi -> { + return new MultiActionDialog( + common, + multi.actions().stream().map(convertButton).toList(), + Optional.ofNullable(multi.exitAction()).map(convertButton), + multi.columns() + ); + } + case final NoticeType notice -> { + return new NoticeDialog(common, convertButton.apply(notice.action())); + } + case final ServerLinksType links -> { + return new ServerLinksDialog(common, Optional.ofNullable(links.exitAction()).map(convertButton), links.columns(), links.buttonWidth()); + } + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CommandTemplateActionImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CommandTemplateActionImpl.java new file mode 100644 index 0000000000..0822fd19e3 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CommandTemplateActionImpl.java @@ -0,0 +1,10 @@ +package io.papermc.paper.registry.data.dialog.action; + +import net.minecraft.commands.functions.StringTemplate; + +public record CommandTemplateActionImpl(String template) implements DialogAction.CommandTemplateAction { + + public CommandTemplateActionImpl { + StringTemplate.fromString(template); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CustomClickActionImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CustomClickActionImpl.java new file mode 100644 index 0000000000..453287b722 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/CustomClickActionImpl.java @@ -0,0 +1,24 @@ +package io.papermc.paper.registry.data.dialog.action; + +import com.google.common.base.Preconditions; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import io.papermc.paper.adventure.PaperAdventure; +import net.kyori.adventure.key.Key; +import net.kyori.adventure.nbt.api.BinaryTagHolder; +import net.minecraft.nbt.CompoundTag; +import net.minecraft.nbt.Tag; +import org.jspecify.annotations.Nullable; + +public record CustomClickActionImpl(Key id, @Nullable BinaryTagHolder additions) implements DialogAction.CustomClickAction { + + public CustomClickActionImpl { + if (additions != null) { + try { + final Tag tag = additions.get(PaperAdventure.NBT_CODEC); + Preconditions.checkArgument(tag instanceof CompoundTag, "Additions must be a compound tag"); + } catch (final CommandSyntaxException e) { + throw new RuntimeException(e); + } + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/StaticActionImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/StaticActionImpl.java new file mode 100644 index 0000000000..2d34aaa7f0 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/action/StaticActionImpl.java @@ -0,0 +1,11 @@ +package io.papermc.paper.registry.data.dialog.action; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.event.ClickEvent; + +public record StaticActionImpl(ClickEvent value) implements DialogAction.StaticAction { + + public StaticActionImpl { + Preconditions.checkArgument(value.action().readable(), "action must be readable"); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBodyImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBodyImpl.java new file mode 100644 index 0000000000..ad7aee57c8 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/ItemDialogBodyImpl.java @@ -0,0 +1,63 @@ +package io.papermc.paper.registry.data.dialog.body; + +import com.google.common.base.Preconditions; +import org.bukkit.craftbukkit.inventory.CraftItemStack; +import org.bukkit.inventory.ItemStack; +import org.jspecify.annotations.Nullable; + +public record ItemDialogBodyImpl( + ItemStack item, @Nullable PlainMessageDialogBody description, boolean showDecorations, boolean showTooltip, int width, int height +) implements ItemDialogBody { + + public static final class BuilderImpl implements ItemDialogBody.Builder { + + private final ItemStack item; + private @Nullable PlainMessageDialogBody description; + private boolean showDecorations = true; + private boolean showTooltip = true; + private int width = 16; + private int height = 16; + + public BuilderImpl(final ItemStack item) { + net.minecraft.world.item.ItemStack.validateStrict(CraftItemStack.unwrap(item)).getOrThrow(); + this.item = item; + } + + @Override + public ItemDialogBody.Builder description(final @Nullable PlainMessageDialogBody description) { + this.description = description; + return this; + } + + @Override + public ItemDialogBody.Builder showDecorations(final boolean showDecorations) { + this.showDecorations = showDecorations; + return this; + } + + @Override + public ItemDialogBody.Builder showTooltip(final boolean showTooltip) { + this.showTooltip = showTooltip; + return this; + } + + @Override + public ItemDialogBody.Builder width(final int width) { + Preconditions.checkArgument(width >= 1 && width <= 256, "Width must be between 1 and 256"); + this.width = width; + return this; + } + + @Override + public ItemDialogBody.Builder height(final int height) { + Preconditions.checkArgument(this.width >= 1 && this.width <= 256, "Width must be between 1 and 256"); + this.height = height; + return this; + } + + @Override + public ItemDialogBody build() { + return new ItemDialogBodyImpl(this.item, this.description, this.showDecorations, this.showTooltip, this.width, this.height); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageBodyImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageBodyImpl.java new file mode 100644 index 0000000000..d1b3aa36a1 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/body/PlainMessageBodyImpl.java @@ -0,0 +1,16 @@ +package io.papermc.paper.registry.data.dialog.body; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.Component; +import net.minecraft.server.dialog.body.PlainMessage; + +public record PlainMessageBodyImpl(Component contents, int width) implements PlainMessageDialogBody { + + public PlainMessageBodyImpl { + Preconditions.checkArgument(width >= 1 && width <= 1024, "Width must be between 1 and 1024"); + } + + public PlainMessageBodyImpl(final Component contents) { + this(contents, PlainMessage.DEFAULT_WIDTH); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInputImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInputImpl.java new file mode 100644 index 0000000000..45ca833dfc --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/BooleanDialogInputImpl.java @@ -0,0 +1,46 @@ +package io.papermc.paper.registry.data.dialog.input; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.functions.StringTemplate; + +public record BooleanDialogInputImpl(String key, Component label, boolean initial, String onTrue, String onFalse) implements BooleanDialogInput { + + public static final class BuilderImpl implements BooleanDialogInput.Builder { + + private final String key; + private final Component label; + private boolean initial = false; + private String onTrue = "true"; + private String onFalse = "false"; + + public BuilderImpl(final String key, final Component label) { + Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name"); + this.key = key; + this.label = label; + } + + @Override + public BooleanDialogInput.Builder initial(final boolean initial) { + this.initial = initial; + return this; + } + + @Override + public BooleanDialogInput.Builder onTrue(final String onTrue) { + this.onTrue = onTrue; + return this; + } + + @Override + public BooleanDialogInput.Builder onFalse(final String onFalse) { + this.onFalse = onFalse; + return this; + } + + @Override + public BooleanDialogInput build() { + return new BooleanDialogInputImpl(this.key, this.label, this.initial, this.onTrue, this.onFalse); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInputImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInputImpl.java new file mode 100644 index 0000000000..b611d73fbb --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/NumberRangeDialogInputImpl.java @@ -0,0 +1,73 @@ +package io.papermc.paper.registry.data.dialog.input; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.functions.StringTemplate; +import net.minecraft.server.dialog.body.PlainMessage; +import org.jspecify.annotations.Nullable; + +public record NumberRangeDialogInputImpl( + String key, + int width, + Component label, + String labelFormat, + float start, + float end, + @Nullable Float initial, + @Nullable Float step +) implements NumberRangeDialogInput { + + public static final class BuilderImpl implements NumberRangeDialogInput.Builder { + + private final String key; + private final Component label; + private final float start; + private final float end; + private int width = PlainMessage.DEFAULT_WIDTH; + private String labelFormat = "options.generic_value"; + private @Nullable Float initial = null; + private @Nullable Float step = null; + + public BuilderImpl(final String key, final Component label, final float start, final float end) { + Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name"); + this.key = key; + this.label = label; + this.start = start; + this.end = end; + } + + @Override + public BuilderImpl width(final int width) { + Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024"); + this.width = width; + return this; + } + + @Override + public BuilderImpl labelFormat(final String labelFormat) { + this.labelFormat = labelFormat; + return this; + } + + @Override + public BuilderImpl initial(final @Nullable Float initial) { + if (initial != null) { + Preconditions.checkArgument(initial >= this.start && initial <= this.end, "initial must be within the range"); + } + this.initial = initial; + return this; + } + + @Override + public BuilderImpl step(final @Nullable Float step) { + Preconditions.checkArgument(step == null || step > 0, "step must be null or greater than 0"); + this.step = step; + return this; + } + + @Override + public NumberRangeDialogInput build() { + return new NumberRangeDialogInputImpl(this.key, this.width, this.label, this.labelFormat, this.start, this.end, this.initial, this.step); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInputImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInputImpl.java new file mode 100644 index 0000000000..f28825c20f --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/SingleOptionDialogInputImpl.java @@ -0,0 +1,60 @@ +package io.papermc.paper.registry.data.dialog.input; + +import com.google.common.base.Preconditions; +import java.util.List; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.functions.StringTemplate; +import net.minecraft.server.dialog.body.PlainMessage; +import org.jetbrains.annotations.Nullable; + +public record SingleOptionDialogInputImpl( + String key, + int width, + List entries, + Component label, + boolean labelVisible +) implements SingleOptionDialogInput { + + public SingleOptionDialogInputImpl { + entries = List.copyOf(entries); + } + + public record SingleOptionEntryImpl(String id, @Nullable Component display, boolean initial) implements OptionEntry { + } + + public static final class BuilderImpl implements SingleOptionDialogInput.Builder { + + private final String key; + private int width = PlainMessage.DEFAULT_WIDTH; + private final List entries; + private final Component label; + private boolean labelVisible = true; + + public BuilderImpl(final String key, final List entries, final Component label) { + Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name"); + this.key = key; + Preconditions.checkArgument(!entries.isEmpty(), "entries must not be empty"); + Preconditions.checkArgument(entries.stream().filter(OptionEntry::initial).count() <= 1, "only 1 option can be initially selected"); + this.entries = entries; + this.label = label; + } + + @Override + public BuilderImpl width(final int width) { + Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024"); + this.width = width; + return this; + } + + @Override + public BuilderImpl labelVisible(final boolean labelVisible) { + this.labelVisible = labelVisible; + return this; + } + + @Override + public SingleOptionDialogInput build() { + return new SingleOptionDialogInputImpl(this.key, this.width, this.entries, this.label, this.labelVisible); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInputImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInputImpl.java new file mode 100644 index 0000000000..bc483f97aa --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/input/TextDialogInputImpl.java @@ -0,0 +1,81 @@ +package io.papermc.paper.registry.data.dialog.input; + +import com.google.common.base.Preconditions; +import net.kyori.adventure.text.Component; +import net.minecraft.commands.functions.StringTemplate; +import net.minecraft.server.dialog.body.PlainMessage; +import net.minecraft.server.dialog.input.TextInput; +import org.jspecify.annotations.Nullable; + +public record TextDialogInputImpl( + String key, + int width, + Component label, + boolean labelVisible, + String initial, + int maxLength, + TextDialogInput.@Nullable MultilineOptions multiline +) implements TextDialogInput { + + public record MultilineOptionsImpl(@Nullable Integer maxLines, @Nullable Integer height) implements MultilineOptions { + public MultilineOptionsImpl { + Preconditions.checkArgument(maxLines == null || maxLines > 0, "maxLines must be null or greater than 0"); + Preconditions.checkArgument(height == null || (height >= 1 && height <= TextInput.MultilineOptions.MAX_HEIGHT), "height must be null or between 1 and 512"); + } + } + + public static final class BuilderImpl implements TextDialogInput.Builder { + + private final String key; + private int width = PlainMessage.DEFAULT_WIDTH; + private final Component label; + private boolean labelVisible = true; + private String initial = ""; + private int maxLength = 32; + private TextDialogInput.@Nullable MultilineOptions multiline = null; + + public BuilderImpl(final String key, final Component label) { + Preconditions.checkArgument(StringTemplate.isValidVariableName(key), "key must be a valid input name"); + this.key = key; + this.label = label; + } + + @Override + public TextDialogInput.Builder width(final int width) { + Preconditions.checkArgument(width >= 1 && width <= 1024, "width must be between 1 and 1024"); + this.width = width; + return this; + } + + @Override + public TextDialogInput.Builder labelVisible(final boolean labelVisible) { + this.labelVisible = labelVisible; + return this; + } + + @Override + public TextDialogInput.Builder initial(final String initial) { + this.initial = initial; + return this; + } + + @Override + public TextDialogInput.Builder maxLength(final int maxLength) { + Preconditions.checkArgument(maxLength > 0, "maxLength must be greater than 0"); + this.maxLength = maxLength; + return this; + } + + @Override + public TextDialogInput.Builder multiline(final TextDialogInput.@Nullable MultilineOptions multiline) { + this.multiline = multiline; + return this; + } + + @Override + public TextDialogInput build() { + Preconditions.checkState(this.initial.length() <= this.maxLength, "The initial value must be less than or equal to the maximum length."); + return new TextDialogInputImpl(this.key, this.width, this.label, this.labelVisible, this.initial, this.maxLength, this.multiline); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationTypeImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationTypeImpl.java new file mode 100644 index 0000000000..efa55b0dc9 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ConfirmationTypeImpl.java @@ -0,0 +1,6 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.registry.data.dialog.ActionButton; + +public record ConfirmationTypeImpl(ActionButton yesButton, ActionButton noButton) implements ConfirmationType { +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListTypeImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListTypeImpl.java new file mode 100644 index 0000000000..26fbcb0e65 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/DialogListTypeImpl.java @@ -0,0 +1,53 @@ +package io.papermc.paper.registry.data.dialog.type; + +import com.google.common.base.Preconditions; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.registry.data.dialog.ActionButton; +import io.papermc.paper.registry.set.RegistrySet; +import net.minecraft.server.dialog.CommonButtonData; +import org.jspecify.annotations.Nullable; + +public record DialogListTypeImpl( + RegistrySet dialogs, + @Nullable ActionButton exitAction, + int columns, + int buttonWidth +) implements DialogListType { + + public static final class BuilderImpl implements DialogListType.Builder { + + private final RegistrySet dialogs; + private @Nullable ActionButton exitAction; + private int columns = 2; + private int buttonWidth = CommonButtonData.DEFAULT_WIDTH; + + public BuilderImpl(final RegistrySet dialogs) { + this.dialogs = dialogs; + } + + @Override + public DialogListType.Builder exitAction(final @Nullable ActionButton exitAction) { + this.exitAction = exitAction; + return this; + } + + @Override + public DialogListType.Builder columns(final int columns) { + Preconditions.checkArgument(columns > 0, "columns must be greater than 0"); + this.columns = columns; + return this; + } + + @Override + public DialogListType.Builder buttonWidth(final int buttonWidth) { + Preconditions.checkArgument(buttonWidth >= 1 && buttonWidth <= 1024, "buttonWidth must be between 1 and 1024"); + this.buttonWidth = buttonWidth; + return this; + } + + @Override + public DialogListType build() { + return new DialogListTypeImpl(this.dialogs, this.exitAction, this.columns, this.buttonWidth); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionTypeImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionTypeImpl.java new file mode 100644 index 0000000000..26a6cc26a5 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/MultiActionTypeImpl.java @@ -0,0 +1,46 @@ +package io.papermc.paper.registry.data.dialog.type; + +import com.google.common.base.Preconditions; +import io.papermc.paper.registry.data.dialog.ActionButton; +import java.util.List; +import org.jspecify.annotations.Nullable; + +public record MultiActionTypeImpl( + List actions, + @Nullable ActionButton exitAction, + int columns +) implements MultiActionType { + + public MultiActionTypeImpl { + actions = List.copyOf(actions); + } + + public static final class BuilderImpl implements MultiActionType.Builder { + private final List actions; + private @Nullable ActionButton exitAction = null; + private int columns = 2; + + public BuilderImpl(final List actions) { + Preconditions.checkArgument(!actions.isEmpty(), "actions cannot be empty"); + this.actions = actions; + } + + @Override + public Builder exitAction(final @org.jetbrains.annotations.Nullable ActionButton exitAction) { + this.exitAction = exitAction; + return this; + } + + @Override + public Builder columns(final int columns) { + Preconditions.checkArgument(columns > 0, "columns must be greater than 0"); + this.columns = columns; + return this; + } + + @Override + public MultiActionType build() { + return new MultiActionTypeImpl(this.actions, this.exitAction, this.columns); + } + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeTypeImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeTypeImpl.java new file mode 100644 index 0000000000..b730401210 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/NoticeTypeImpl.java @@ -0,0 +1,15 @@ +package io.papermc.paper.registry.data.dialog.type; + +import io.papermc.paper.registry.data.dialog.ActionButton; +import net.minecraft.server.dialog.CommonButtonData; + +import static net.kyori.adventure.text.Component.translatable; + +public record NoticeTypeImpl(ActionButton action) implements NoticeType { + + public static final ActionButton DEFAULT_ACTION = ActionButton.builder(translatable("gui.ok")).width(CommonButtonData.DEFAULT_WIDTH).build(); + + public NoticeTypeImpl() { + this(DEFAULT_ACTION); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksTypeImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksTypeImpl.java new file mode 100644 index 0000000000..316d582ab8 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/dialog/type/ServerLinksTypeImpl.java @@ -0,0 +1,17 @@ +package io.papermc.paper.registry.data.dialog.type; + +import com.google.common.base.Preconditions; +import io.papermc.paper.registry.data.dialog.ActionButton; +import org.jspecify.annotations.Nullable; + +public record ServerLinksTypeImpl( + @Nullable ActionButton exitAction, + int columns, + int buttonWidth +) implements ServerLinksType { + + public ServerLinksTypeImpl { + Preconditions.checkArgument(columns > 0, "columns must be greater than 0"); + Preconditions.checkArgument(buttonWidth >= 1 && buttonWidth <= 1024, "buttonWidth must be between 1 and 1024"); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/registry/data/util/Conversions.java b/paper-server/src/main/java/io/papermc/paper/registry/data/util/Conversions.java index 710286a19f..1120f50d5c 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/data/util/Conversions.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/data/util/Conversions.java @@ -1,12 +1,14 @@ package io.papermc.paper.registry.data.util; import com.google.common.base.Preconditions; +import com.mojang.serialization.Codec; import com.mojang.serialization.JavaOps; import io.papermc.paper.adventure.PaperAdventure; import io.papermc.paper.adventure.WrapperAwareSerializer; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.PaperRegistryBuilder; import io.papermc.paper.registry.PaperRegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.data.client.ClientTextureAsset; import io.papermc.paper.registry.entry.RegistryEntryMeta; import java.util.function.Consumer; @@ -36,10 +38,20 @@ public class Conversions { private final RegistryOps.RegistryInfoLookup lookup; private final WrapperAwareSerializer serializer; + private final RegistryOps javaOps; public Conversions(final RegistryOps.RegistryInfoLookup lookup) { this.lookup = lookup; this.serializer = new WrapperAwareSerializer(() -> RegistryOps.create(JavaOps.INSTANCE, lookup)); + this.javaOps = RegistryOps.create(JavaOps.INSTANCE, lookup); + } + + public OUT convert(final IN in, final Codec outCodec, final Codec inCodec) { + final Object obj = inCodec.encodeStart(this.javaOps, in) + .getOrThrow(s -> new RuntimeException("Failed to encode input: " + in + "; " + s)); + return outCodec.decode(this.javaOps, obj) + .getOrThrow(s -> new RuntimeException("Failed to decode to output: " + obj + "; " + s)) + .getFirst(); } public RegistryOps.RegistryInfoLookup lookup() { @@ -74,20 +86,20 @@ public class Conversions { ); } - private static > RegistryEntryMeta.Buildable getDirectHolderBuildableMeta(final ResourceKey> registryKey) { + private static > RegistryEntryMeta.Buildable getDirectHolderBuildableMeta(final RegistryKey registryKey) { final RegistryEntryMeta.Buildable buildableMeta = PaperRegistries.getBuildableMeta(registryKey); Preconditions.checkArgument(buildableMeta.registryTypeMapper().supportsDirectHolders(), "Registry type mapper must support direct holders"); return buildableMeta; } - public > A createApiInstanceFromBuilder(final ResourceKey> registryKey, final Consumer> value) { + public > A createApiInstanceFromBuilder(final RegistryKey registryKey, final Consumer> value) { final RegistryEntryMeta.Buildable meta = getDirectHolderBuildableMeta(registryKey); final PaperRegistryBuilderFactory builderFactory = this.createRegistryBuilderFactory(registryKey, meta); value.accept(builderFactory); return meta.registryTypeMapper().createBukkit(Holder.direct(builderFactory.requireBuilder().build())); } - public > Holder createHolderFromBuilder(final ResourceKey> registryKey, final Consumer> value) { + public > Holder createHolderFromBuilder(final RegistryKey registryKey, final Consumer> value) { final RegistryEntryMeta.Buildable meta = getDirectHolderBuildableMeta(registryKey); final PaperRegistryBuilderFactory builderFactory = this.createRegistryBuilderFactory(registryKey, meta); value.accept(builderFactory); @@ -95,11 +107,12 @@ public class Conversions { } private > PaperRegistryBuilderFactory createRegistryBuilderFactory( - final ResourceKey> registryKey, + final RegistryKey registryKey, final RegistryEntryMeta.Buildable buildableMeta ) { - final HolderLookup.RegistryLookup lookupForBuilders = this.lookup.lookupForValueCopyViaBuilders().lookupOrThrow(registryKey); - return new PaperRegistryBuilderFactory<>(registryKey, this, buildableMeta.builderFiller(), lookupForBuilders::getValueForCopying); + final ResourceKey> resourceRegistryKey = PaperRegistries.registryToNms(registryKey); + final HolderLookup.RegistryLookup lookupForBuilders = this.lookup.lookupForValueCopyViaBuilders().lookupOrThrow(resourceRegistryKey); + return new PaperRegistryBuilderFactory<>(resourceRegistryKey, this, buildableMeta.builderFiller(), lookupForBuilders::getValueForCopying); } } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/entry/RegistryEntryBuilder.java b/paper-server/src/main/java/io/papermc/paper/registry/entry/RegistryEntryBuilder.java index 04bdb095ae..51c91cf018 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/entry/RegistryEntryBuilder.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/entry/RegistryEntryBuilder.java @@ -37,10 +37,6 @@ public class RegistryEntryBuilder { // TODO remove Keyed return new RegistryEntryImpl<>(new RegistryEntryMeta.ApiOnly<>(this.mcKey, this.apiKey, apiRegistrySupplier)); } - public CraftStage craft(final Class classToPreload, final BiFunction minecraftToBukkit) { - return new CraftStage<>(this.mcKey, this.apiKey, classToPreload, new RegistryTypeMapper<>(minecraftToBukkit)); - } - public CraftStage craft(final Class classToPreload, final Function, ? extends A> minecraftToBukkit) { return this.craft(classToPreload, minecraftToBukkit, false); } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java index 79499fa902..7fbde6cd68 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/set/NamedRegistryKeySetImpl.java @@ -18,9 +18,7 @@ import org.bukkit.NamespacedKey; import org.bukkit.Registry; import org.bukkit.craftbukkit.util.CraftNamespacedKey; import org.jetbrains.annotations.Unmodifiable; -import org.jspecify.annotations.NullMarked; -@NullMarked public record NamedRegistryKeySetImpl( // TODO remove Keyed TagKey tagKey, HolderSet.Named namedSet diff --git a/paper-server/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java b/paper-server/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java index cb92f53cf9..e865932d41 100644 --- a/paper-server/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java +++ b/paper-server/src/main/java/io/papermc/paper/registry/set/PaperRegistrySets.java @@ -3,6 +3,7 @@ package io.papermc.paper.registry.set; import io.papermc.paper.registry.PaperRegistries; import io.papermc.paper.registry.RegistryKey; import io.papermc.paper.registry.TypedKey; +import io.papermc.paper.util.Holderable; import java.util.ArrayList; import java.util.List; import net.minecraft.core.Holder; @@ -11,6 +12,7 @@ import net.minecraft.core.Registry; import net.minecraft.resources.RegistryOps; import net.minecraft.resources.ResourceKey; import org.bukkit.Keyed; +import org.bukkit.craftbukkit.CraftRegistry; public final class PaperRegistrySets { @@ -25,6 +27,30 @@ public final class PaperRegistrySets { } } + public static HolderSet convertToNmsWithDirects(final ResourceKey> resourceKey, final RegistryOps.RegistryInfoLookup lookup, final RegistrySet registrySet) { // TODO remove Keyed + if (registrySet instanceof NamedRegistryKeySetImpl) { + return ((NamedRegistryKeySetImpl) registrySet).namedSet(); + } else if (registrySet.isEmpty()) { + return HolderSet.empty(); + } else if (registrySet instanceof final RegistryValueSet valueSet) { + final List> directs = new ArrayList<>(valueSet.values().size()); + for (final A value : valueSet) { + if (!(value instanceof final Holderable holderable)) { + throw new UnsupportedOperationException("Cannot convert a registry set containing non-holderable values"); + } + directs.add(((Holderable) holderable).getHolder()); + } + return HolderSet.direct(directs); + } else if (registrySet instanceof final RegistryKeySet keySet) { + final RegistryOps.RegistryInfo registryInfo = lookup.lookup(resourceKey).orElseThrow(); + return HolderSet.direct(key -> { + return registryInfo.getter().getOrThrow(PaperRegistries.toNms(key)); + }, keySet.values()); + } else { + throw new UnsupportedOperationException("Cannot convert a registry set of type " + registrySet.getClass().getName()); + } + } + public static RegistryKeySet convertToApi(final RegistryKey registryKey, final HolderSet holders) { // TODO remove Keyed if (holders instanceof final HolderSet.Named named) { return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named); @@ -40,6 +66,30 @@ public final class PaperRegistrySets { } } + public static RegistrySet convertToApiWithDirects(final RegistryKey registryKey, final HolderSet holders) { // TODO remove Keyed + if (holders instanceof final HolderSet.Named named) { + return new NamedRegistryKeySetImpl<>(PaperRegistries.fromNms(named.key()), named); + } else { + if (holders.size() == 0) { + return RegistrySet.keySet(registryKey); + } + if (holders.get(0) instanceof Holder.Direct) { + final List directs = new ArrayList<>(holders.size()); + final ResourceKey> nmsRegistryKey = PaperRegistries.registryToNms(registryKey); + for (final Holder holder : holders) { + directs.add(CraftRegistry.minecraftHolderToBukkit(holder, nmsRegistryKey)); + } + return RegistrySet.valueSet(registryKey, directs); + } else { + final List> keys = new ArrayList<>(holders.size()); + for (final Holder holder : holders) { + keys.add(PaperRegistries.fromNms(((Holder.Reference) holder).key())); + } + return RegistrySet.keySet(registryKey, keys); + } + } + } + private PaperRegistrySets() { } } diff --git a/paper-server/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilderImpl.java b/paper-server/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilderImpl.java new file mode 100644 index 0000000000..dabd34b289 --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/registry/set/RegistryValueSetBuilderImpl.java @@ -0,0 +1,33 @@ +package io.papermc.paper.registry.set; + +import io.papermc.paper.registry.RegistryBuilder; +import io.papermc.paper.registry.RegistryBuilderFactory; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.data.util.Conversions; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Consumer; +import org.bukkit.Keyed; + +public class RegistryValueSetBuilderImpl> implements RegistryValueSetBuilder { // TODO remove Keyed + + private final RegistryKey registryKey; + private final Conversions conversions; + private final List instances = new ArrayList<>(); + + public RegistryValueSetBuilderImpl(final RegistryKey registryKey, final Conversions conversions) { + this.registryKey = registryKey; + this.conversions = conversions; + } + + @Override + public RegistryValueSetBuilder add(final Consumer> builder) { + this.instances.add(this.conversions.createApiInstanceFromBuilder(this.registryKey, builder)); + return this; + } + + @Override + public RegistryValueSet build() { + return RegistrySet.valueSet(this.registryKey, this.instances); + } +} diff --git a/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java b/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java new file mode 100644 index 0000000000..22f24d35ef --- /dev/null +++ b/paper-server/src/main/java/io/papermc/paper/util/PaperCodecs.java @@ -0,0 +1,54 @@ +package io.papermc.paper.util; + +import com.mojang.datafixers.util.Pair; +import com.mojang.serialization.DataResult; +import com.mojang.serialization.Decoder; +import com.mojang.serialization.DynamicOps; +import io.papermc.paper.adventure.AdventureCodecs; +import io.papermc.paper.registry.RegistryAccess; +import io.papermc.paper.registry.RegistryKey; +import io.papermc.paper.registry.TypedKey; +import java.util.function.Function; +import net.kyori.adventure.key.Key; +import net.minecraft.core.Holder; +import net.minecraft.resources.RegistryOps; +import org.bukkit.Keyed; +import org.bukkit.Registry; +import org.jspecify.annotations.NullMarked; + +@NullMarked +public final class PaperCodecs { + + public static Decoder registryFileDecoderFor(final Decoder directNmsDecoder, final Function, A> directHolderConverter, final RegistryKey registryKey, final boolean allowInline) { // TODO remove Keyed + final Decoder.Terminal terminalDecoder = directNmsDecoder.map(nms -> directHolderConverter.apply(Holder.direct(nms))).terminal(); + return Decoder.ofTerminal(new Decoder.Terminal<>() { + @Override + public DataResult decode(final DynamicOps ops, final T input) { + // logic based on RegistryFileCodec + if (ops instanceof RegistryOps) { + // Pretty sure we can just use our RegistryAccess here. These codecs aren't ever + // used for deserialization, so we don't need to rely on different implementations + // of HolderGetter like vanilla's RegistryFileCodec does. We are always just + // getting existing dialog elements, never creating empty reference holders. + final Registry registry = RegistryAccess.registryAccess().getRegistry(registryKey); + final DataResult> keyDataResult = AdventureCodecs.KEY_CODEC.decode(ops, input); + if (keyDataResult.result().isEmpty()) { + return !allowInline ? DataResult.error(() -> "Inline definitions not allowed here") : terminalDecoder.decode(ops, input); + } + final Pair pair = keyDataResult.result().get(); + final TypedKey elementKey = TypedKey.create(registryKey, pair.getFirst()); + final A value = registry.get(elementKey); + if (value == null) { + return DataResult.error(() -> "Failed to get element " + elementKey); + } + return DataResult.success(value); + } else { + return terminalDecoder.decode(ops, input); + } + } + }); + } + + private PaperCodecs() { + } +} diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java index 38d3859867..d837b95337 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftArt.java @@ -25,7 +25,7 @@ public class CraftArt extends OldEnumHolderable implements } public static Holder bukkitToMinecraftHolder(Art bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.PAINTING_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftArt(Holder paintingVariant) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftJukeboxSong.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftJukeboxSong.java index 7fdc65da18..efdbed8772 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftJukeboxSong.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftJukeboxSong.java @@ -21,7 +21,7 @@ public class CraftJukeboxSong extends HolderableBase bukkitToMinecraftHolder(JukeboxSong bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.JUKEBOX_SONG); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftJukeboxSong(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java index 81a738b05d..ba1bc2ff70 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftMusicInstrument.java @@ -28,7 +28,7 @@ public class CraftMusicInstrument extends MusicInstrument implements io.papermc. } public static Holder bukkitToMinecraftHolder(MusicInstrument bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.INSTRUMENT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static Object bukkitToString(MusicInstrument bukkit) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java index 4b08d061c6..889d8c3fbc 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftRegistry.java @@ -99,28 +99,18 @@ public class CraftRegistry implements Registry { * @param bukkit the bukkit representation * @return the minecraft representation of the bukkit value */ - public static M bukkitToMinecraft(B bukkit) { + @SuppressWarnings("unchecked") + public static M bukkitToMinecraft(final B bukkit) { Preconditions.checkArgument(bukkit != null); return ((Handleable) bukkit).getHandle(); } - public static Holder bukkitToMinecraftHolder(B bukkit, ResourceKey> registryKey) { + @SuppressWarnings("unchecked") + public static Holder bukkitToMinecraftHolder(final B bukkit) { Preconditions.checkArgument(bukkit != null); - // Paper start - support direct Holder - if (bukkit instanceof io.papermc.paper.util.Holderable) { - return ((io.papermc.paper.util.Holderable) bukkit).getHolder(); - } - // Paper end - support direct Holder - net.minecraft.core.Registry registry = CraftRegistry.getMinecraftRegistry(registryKey); - - if (registry.wrapAsHolder(CraftRegistry.bukkitToMinecraft(bukkit)) instanceof Holder.Reference holder) { - return holder; - } - - throw new IllegalArgumentException("No Reference holder found for " + bukkit - + ", this can happen if a plugin creates its own registry entry with out properly registering it."); + return ((Holderable) bukkit).getHolder(); } // Paper start - fixup upstream being dum diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java index 9c8d305f57..b39fb97c70 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/CraftSound.java @@ -23,7 +23,7 @@ public class CraftSound extends OldEnumHolderable implements } public static Holder bukkitToMinecraftHolder(Sound bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.SOUND_EVENT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftSound(Holder soundEffect) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java b/paper-server/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java index fd77390bf3..ef4e24219c 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/attribute/CraftAttribute.java @@ -43,7 +43,7 @@ public class CraftAttribute extends OldEnumHolderable bukkitToMinecraftHolder(Attribute bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.ATTRIBUTE); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static String bukkitToString(Attribute bukkit) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java index b25b1d3652..7dd35a6d0c 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/CraftBiome.java @@ -36,7 +36,7 @@ public class CraftBiome extends OldEnumHolderable holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java index 5865892ad8..fd6acd6acf 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/block/banner/CraftPatternType.java @@ -24,7 +24,7 @@ public class CraftPatternType extends OldEnumHolderable bukkitToMinecraftHolder(PatternType bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.BANNER_PATTERN); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftPatternType(Holder bannerPatternType) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageType.java index 0d82d21e08..ecd0ef7abc 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/damage/CraftDamageType.java @@ -79,7 +79,7 @@ public class CraftDamageType extends HolderableBase bukkitToMinecraftHolder(DamageType bukkitDamageType) { - return CraftRegistry.bukkitToMinecraftHolder(bukkitDamageType, Registries.DAMAGE_TYPE); + return CraftRegistry.bukkitToMinecraftHolder(bukkitDamageType); } public static net.minecraft.world.damagesource.DamageType bukkitToMinecraft(DamageType bukkitDamageType) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java b/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java index 5dba681497..286ed00c01 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/enchantments/CraftEnchantment.java @@ -33,7 +33,7 @@ public class CraftEnchantment extends Enchantment implements Holderable bukkitToMinecraftHolder(Enchantment bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.ENCHANTMENT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static String bukkitToString(Enchantment bukkit) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java index 7b518e1423..c3e28e291e 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCat.java @@ -59,7 +59,7 @@ public class CraftCat extends CraftTameableAnimal implements Cat { } public static Holder bukkitToMinecraftHolder(Type bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.CAT_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftType(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java index ec6e42a617..5b1353d87a 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftChicken.java @@ -49,7 +49,7 @@ public class CraftChicken extends CraftAnimals implements Chicken { } public static Holder bukkitToMinecraftHolder(Variant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.CHICKEN_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java index d21aa1b8b1..c93885fdd0 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftCow.java @@ -49,7 +49,7 @@ public class CraftCow extends CraftAbstractCow implements Cow { } public static Holder bukkitToMinecraftHolder(Variant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.COW_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftVariant(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java index 7d2e9495f8..08df77201f 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftFrog.java @@ -63,7 +63,7 @@ public class CraftFrog extends CraftAnimals implements org.bukkit.entity.Frog { } public static Holder bukkitToMinecraftHolder(Variant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.FROG_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftVariant(final Holder holder) { super(holder, count++); diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java index 91f1e61e37..387eed7722 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPig.java @@ -97,7 +97,7 @@ public class CraftPig extends CraftAnimals implements Pig { } public static Holder bukkitToMinecraftHolder(Variant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.PIG_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftVariant(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java index 1e2e8d81e6..9d6235390c 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftPlayer.java @@ -10,6 +10,8 @@ import com.mojang.datafixers.util.Pair; import io.papermc.paper.FeatureHooks; import io.papermc.paper.connection.PlayerGameConnection; import io.papermc.paper.connection.PluginMessageBridgeImpl; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.dialog.PaperDialog; import io.papermc.paper.entity.LookAnchor; import io.papermc.paper.entity.PaperPlayerGiveResult; import io.papermc.paper.entity.PlayerGiveResult; @@ -43,6 +45,7 @@ import java.util.concurrent.CompletableFuture; import java.util.logging.Level; import java.util.logging.Logger; import java.util.stream.Collectors; +import net.kyori.adventure.dialog.DialogLike; import net.kyori.adventure.identity.Identity; import net.kyori.adventure.pointer.PointersSupplier; import net.kyori.adventure.util.TriState; @@ -211,22 +214,102 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa .resolving(Identity.DISPLAY_NAME, Player::displayName) .resolving(Identity.LOCALE, Player::locale) .build(); - - private long firstPlayed = 0; - private long lastPlayed = 0; - private boolean hasPlayedBefore = false; + private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + private static final net.kyori.adventure.text.Component DEFAULT_KICK_COMPONENT = net.kyori.adventure.text.Component.translatable("multiplayer.disconnect.kicked"); private final ConversationTracker conversationTracker = new ConversationTracker(); private final Map>> invertedVisibilityEntities = new HashMap<>(); private final Set unlistedEntities = new HashSet<>(); // Paper - Add Listing API for Player - private static final WeakHashMap> pluginWeakReferences = new WeakHashMap<>(); + public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API + private long firstPlayed = 0; + private long lastPlayed = 0; + private boolean hasPlayedBefore = false; private int hash = 0; private double health = 20; + // Paper end + // Spigot start + private final Player.Spigot spigot = new Player.Spigot() { + + @Override + public InetSocketAddress getRawAddress() { + return (InetSocketAddress) CraftPlayer.this.getHandle().connection.getRawAddress(); + } + + @Override + public void respawn() { + if (CraftPlayer.this.getHealth() <= 0 && CraftPlayer.this.isOnline()) { + CraftPlayer.this.server.getServer().getPlayerList().respawn(CraftPlayer.this.getHandle(), false, Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN); + } + } + + @Override + public Set getHiddenPlayers() { + Set ret = new HashSet<>(); + for (Player player : CraftPlayer.this.getServer().getOnlinePlayers()) { + if (!CraftPlayer.this.canSee(player)) { + ret.add(player); + } + } + + return java.util.Collections.unmodifiableSet(ret); + } + + @Override + public void sendMessage(BaseComponent component) { + this.sendMessage(new BaseComponent[]{component}); + } + + @Override + public void sendMessage(BaseComponent... components) { + this.sendMessage(net.md_5.bungee.api.ChatMessageType.SYSTEM, components); + } + + @Override + public void sendMessage(UUID sender, BaseComponent component) { + this.sendMessage(net.md_5.bungee.api.ChatMessageType.CHAT, sender, component); + } + + @Override + public void sendMessage(UUID sender, BaseComponent... components) { + this.sendMessage(net.md_5.bungee.api.ChatMessageType.CHAT, sender, components); + } + + @Override + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, BaseComponent component) { + this.sendMessage(position, new BaseComponent[]{component}); + } + + @Override + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, BaseComponent... components) { + this.sendMessage(position, null, components); + } + + @Override + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, UUID sender, BaseComponent component) { + this.sendMessage(position, sender, new BaseComponent[]{component}); + } + + @Override + public void sendMessage(net.md_5.bungee.api.ChatMessageType position, UUID sender, BaseComponent... components) { + if (CraftPlayer.this.getHandle().connection == null) return; + + CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR)); + } + + // Paper start + @Override + public int getPing() { + return CraftPlayer.this.getPing(); + } + // Paper end + }; private boolean scaledHealth = false; private double healthScale = 20; private CraftWorldBorder clientWorldBorder = null; private BorderChangeListener clientWorldBorderListener = this.createWorldBorderListener(); - public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status resourcePackStatus; // Paper - more resource pack API private long lastSaveTime; // Paper - getLastPlayed replacement API + private net.kyori.adventure.text.Component playerListHeader; // Paper - Adventure + private net.kyori.adventure.text.Component playerListFooter; // Paper - Adventure + private @Nullable Set activeBossBars; public CraftPlayer(CraftServer server, ServerPlayer entity) { super(server, entity); @@ -234,6 +317,37 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.firstPlayed = System.currentTimeMillis(); } + public static net.minecraft.world.entity.Relative deltaRelativeToNMS(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { + return switch (apiFlag) { + case VELOCITY_X -> net.minecraft.world.entity.Relative.DELTA_X; + case VELOCITY_Y -> net.minecraft.world.entity.Relative.DELTA_Y; + case VELOCITY_Z -> net.minecraft.world.entity.Relative.DELTA_Z; + case VELOCITY_ROTATION -> net.minecraft.world.entity.Relative.ROTATE_DELTA; + }; + } + + public static @org.jetbrains.annotations.Nullable io.papermc.paper.entity.TeleportFlag.Relative deltaRelativeToAPI(net.minecraft.world.entity.Relative nmsFlag) { + return switch (nmsFlag) { + case DELTA_X -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_X; + case DELTA_Y -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_Y; + case DELTA_Z -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_Z; + case ROTATE_DELTA -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_ROTATION; + default -> null; + }; + } + + private static @Nullable WeakReference getPluginWeakReference(@Nullable Plugin plugin) { + return (plugin == null) ? null : CraftPlayer.pluginWeakReferences.computeIfAbsent(plugin, WeakReference::new); + } + + private static int ticks(final java.time.Duration duration) { + if (duration == null) { + return -1; + } + return (int) (duration.toMillis() / 50L); + } + // Paper end + @Override public ServerPlayer getHandle() { return (ServerPlayer) this.entity; @@ -262,8 +376,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa @Override public void remove() { if (this.getHandle().getClass().equals(ServerPlayer.class)) { // special case for NMS plugins inheriting - // Will lead to an inconsistent player state if we remove the player as any other entity. - throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); + // Will lead to an inconsistent player state if we remove the player as any other entity. + throw new UnsupportedOperationException(String.format("Cannot remove player %s, use Player#kickPlayer(String) instead.", this.getName())); } else { super.remove(); } @@ -291,13 +405,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public boolean isOnline() { return this.server.getPlayer(this.getUniqueId()) != null; } + // Paper end // Paper start @Override public boolean isConnected() { return !this.getHandle().hasDisconnected(); } - // Paper end @Override public InetSocketAddress getAddress() { @@ -318,6 +432,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa return this.getHandle().connection.connection.haProxyAddress instanceof final InetSocketAddress inetSocketAddress ? inetSocketAddress : null; } + // Paper end - Add API to get player's proxy address @Override public boolean isTransferred() { @@ -351,7 +466,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa if (getHandle().connection == null) return null; return getHandle().connection.connection.virtualHost; } - // Paper end @Override public double getEyeHeight(boolean ignorePose) { @@ -431,33 +545,36 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa @Override public void setPlayerListHeaderFooter(BaseComponent @Nullable [] header, BaseComponent @Nullable [] footer) { - if (header != null) { - String headerJson = CraftChatMessage.bungeeToJson(header); - playerListHeader = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(headerJson); - } else { - playerListHeader = null; - } + if (header != null) { + String headerJson = CraftChatMessage.bungeeToJson(header); + playerListHeader = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(headerJson); + } else { + playerListHeader = null; + } if (footer != null) { - String footerJson = CraftChatMessage.bungeeToJson(footer); - playerListFooter = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(footerJson); + String footerJson = CraftChatMessage.bungeeToJson(footer); + playerListFooter = net.kyori.adventure.text.serializer.gson.GsonComponentSerializer.gson().deserialize(footerJson); } else { - playerListFooter = null; - } + playerListFooter = null; + } - updatePlayerListHeaderFooter(); + updatePlayerListHeaderFooter(); } @Override public void setPlayerListHeaderFooter(@Nullable BaseComponent header, @Nullable BaseComponent footer) { - this.setPlayerListHeaderFooter(header == null ? null : new BaseComponent[]{header}, - footer == null ? null : new BaseComponent[]{footer}); + this.setPlayerListHeaderFooter( + header == null ? null : new BaseComponent[]{header}, + footer == null ? null : new BaseComponent[]{footer} + ); } @Override public void setTitleTimes(int fadeInTicks, int stayTicks, int fadeOutTicks) { getHandle().connection.send(new ClientboundSetTitlesAnimationPacket(fadeInTicks, stayTicks, fadeOutTicks)); } + // Paper end @Override public void setSubtitle(BaseComponent[] subtitle) { @@ -517,7 +634,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public void hideTitle() { getHandle().connection.send(new ClientboundClearTitlesPacket(false)); } - // Paper end @Override public String getDisplayName() { @@ -542,18 +658,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } } } + @Override public net.kyori.adventure.text.Component playerListName() { return getHandle().listName == null ? net.kyori.adventure.text.Component.text(getName()) : io.papermc.paper.adventure.PaperAdventure.asAdventure(getHandle().listName); } + @Override public net.kyori.adventure.text.Component playerListHeader() { return playerListHeader; } + @Override public net.kyori.adventure.text.Component playerListFooter() { return playerListFooter; } + // Paper end @Override public String getPlayerListName() { @@ -594,25 +714,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa // Paper end - Send update packet } - private net.kyori.adventure.text.Component playerListHeader; // Paper - Adventure - private net.kyori.adventure.text.Component playerListFooter; // Paper - Adventure - @Override public String getPlayerListHeader() { return (this.playerListHeader == null) ? null : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.playerListHeader); } - @Override - public String getPlayerListFooter() { - return (this.playerListFooter == null) ? null : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.playerListFooter); // Paper - Adventure - } - @Override public void setPlayerListHeader(String header) { this.playerListHeader = header == null ? null : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(header); // Paper - Adventure this.updatePlayerListHeaderFooter(); } + @Override + public String getPlayerListFooter() { + return (this.playerListFooter == null) ? null : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().serialize(this.playerListFooter); // Paper - Adventure + } + @Override public void setPlayerListFooter(String footer) { this.playerListFooter = footer == null ? null : net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer.legacySection().deserialize(footer); // Paper - Adventure @@ -639,8 +756,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().connection.disconnect(CraftChatMessage.fromStringOrEmpty(message, true), org.bukkit.event.player.PlayerKickEvent.Cause.PLUGIN); // Paper - kick event cause } - private static final net.kyori.adventure.text.Component DEFAULT_KICK_COMPONENT = net.kyori.adventure.text.Component.translatable("multiplayer.disconnect.kicked"); - @Override public void kick() { this.kick(DEFAULT_KICK_COMPONENT); @@ -702,6 +817,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa )); } + @Override + public Location getCompassTarget() { + return this.getHandle().compassTarget; + } + @Override public void setCompassTarget(Location loc) { Preconditions.checkArgument(loc != null, "Location cannot be null"); @@ -712,11 +832,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().connection.send(new ClientboundSetDefaultSpawnPositionPacket(CraftLocation.toBlockPosition(loc), loc.getYaw())); } - @Override - public Location getCompassTarget() { - return this.getHandle().compassTarget; - } - @Override public void chat(String msg) { Preconditions.checkArgument(msg != null, "msg cannot be null"); @@ -802,7 +917,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.playSound0(loc, Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), volume, pitch, seed); } - private void playSound0(Location loc, Holder soundEffectHolder, net.minecraft.sounds.SoundSource categoryNMS, float volume, float pitch, long seed) { + private void playSound0( + Location loc, + Holder soundEffectHolder, + net.minecraft.sounds.SoundSource categoryNMS, + float volume, + float pitch, + long seed + ) { Preconditions.checkArgument(loc != null, "Location cannot be null"); if (this.getHandle().connection == null) return; @@ -845,7 +967,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.playSound0(entity, Holder.direct(SoundEvent.createVariableRangeEvent(ResourceLocation.parse(sound))), net.minecraft.sounds.SoundSource.valueOf(category.name()), volume, pitch, seed); } - private void playSound0(org.bukkit.entity.Entity entity, Holder soundEffectHolder, net.minecraft.sounds.SoundSource categoryNMS, float volume, float pitch, long seed) { + private void playSound0( + org.bukkit.entity.Entity entity, + Holder soundEffectHolder, + net.minecraft.sounds.SoundSource categoryNMS, + float volume, + float pitch, + long seed + ) { Preconditions.checkArgument(entity != null, "Entity cannot be null"); Preconditions.checkArgument(soundEffectHolder != null, "Holder of SoundEffect cannot be null"); Preconditions.checkArgument(categoryNMS != null, "SoundCategory cannot be null"); @@ -999,13 +1128,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } } - private record ChunkSectionChanges(ShortSet positions, List blockData) { - - public ChunkSectionChanges() { - this(new ShortArraySet(), new ArrayList<>()); - } - } - @Override public void sendBlockDamage(Location loc, float progress) { this.sendBlockDamage(loc, progress, this.getEntityId()); @@ -1050,6 +1172,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa Component[] components = CraftSign.sanitizeLines(lines); this.sendSignChange0(components, loc, dyeColor, hasGlowingText); } + // Paper end @Override public void sendSignChange(Location loc, @Nullable String @Nullable [] lines) { @@ -1218,10 +1341,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public void onBorderSetDamagePerBlock(net.minecraft.world.level.border.WorldBorder border, double damagePerBlock) {} // NO OP + public void onBorderSetDamagePerBlock(net.minecraft.world.level.border.WorldBorder border, double damagePerBlock) { + } // NO OP @Override - public void onBorderSetDamageSafeZOne(net.minecraft.world.level.border.WorldBorder border, double safeZoneRadius) {} // NO OP + public void onBorderSetDamageSafeZOne(net.minecraft.world.level.border.WorldBorder border, double safeZoneRadius) { + } // NO OP }; } @@ -1250,6 +1375,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa // Paper start - Add target entity to sendHurtAnimation this.sendHurtAnimation(yaw, this); } + public void sendHurtAnimation(float yaw, org.bukkit.entity.Entity target) { // Paper end - Add target entity to sendHurtAnimation if (this.getHandle().connection == null) { @@ -1284,6 +1410,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public void removeCustomChatCompletions(Collection completions) { this.sendCustomChatCompletionPacket(completions, ClientboundCustomChatCompletionsPacket.Action.REMOVE); } + // Paper end @Override public void setCustomChatCompletions(Collection completions) { @@ -1314,7 +1441,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public void setHasSeenWinScreen(boolean hasSeenWinScreen) { getHandle().seenCredits = hasSeenWinScreen; } - // Paper end @Override public void setRotation(float yaw, float pitch) { @@ -1335,25 +1461,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().lookAt(toNmsAnchor(playerAnchor), ((CraftEntity) entity).getHandle(), toNmsAnchor(entityAnchor)); } - public static net.minecraft.world.entity.Relative deltaRelativeToNMS(io.papermc.paper.entity.TeleportFlag.Relative apiFlag) { - return switch (apiFlag) { - case VELOCITY_X -> net.minecraft.world.entity.Relative.DELTA_X; - case VELOCITY_Y -> net.minecraft.world.entity.Relative.DELTA_Y; - case VELOCITY_Z -> net.minecraft.world.entity.Relative.DELTA_Z; - case VELOCITY_ROTATION -> net.minecraft.world.entity.Relative.ROTATE_DELTA; - }; - } - - public static @org.jetbrains.annotations.Nullable io.papermc.paper.entity.TeleportFlag.Relative deltaRelativeToAPI(net.minecraft.world.entity.Relative nmsFlag) { - return switch (nmsFlag) { - case DELTA_X -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_X; - case DELTA_Y -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_Y; - case DELTA_Z -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_Z; - case ROTATE_DELTA -> io.papermc.paper.entity.TeleportFlag.Relative.VELOCITY_ROTATION; - default -> null; - }; - } - @Override public boolean teleport(Location location, org.bukkit.event.player.PlayerTeleportEvent.TeleportCause cause, io.papermc.paper.entity.TeleportFlag... flags) { Set relativeArguments; @@ -1444,9 +1551,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa for (final io.papermc.paper.entity.TeleportFlag.Relative bukkit : relativeArguments) { nms.add(deltaRelativeToNMS(bukkit)); } - entity.connection.internalTeleport(new net.minecraft.world.entity.PositionMoveRotation( - io.papermc.paper.util.MCUtil.toVec3(to), net.minecraft.world.phys.Vec3.ZERO, to.getYaw(), to.getPitch() - ), nms); + entity.connection.internalTeleport( + new net.minecraft.world.entity.PositionMoveRotation( + io.papermc.paper.util.MCUtil.toVec3(to), net.minecraft.world.phys.Vec3.ZERO, to.getYaw(), to.getPitch() + ), nms + ); // Paper end - Teleport API } else { entity.portalProcess = null; // SPIGOT-7785: there is no need to carry this over as it contains the old world/location and we might run into trouble if there is a portal in the same spot in both worlds @@ -1457,13 +1566,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public void setSneaking(boolean sneak) { - this.getHandle().setShiftKeyDown(sneak); + public boolean isSneaking() { + return this.getHandle().isShiftKeyDown(); } @Override - public boolean isSneaking() { - return this.getHandle().isShiftKeyDown(); + public void setSneaking(boolean sneak) { + this.getHandle().setShiftKeyDown(sneak); } @Override @@ -1493,14 +1602,14 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public void setSleepingIgnored(boolean isSleeping) { - this.getHandle().fauxSleeping = isSleeping; - ((CraftWorld) this.getWorld()).getHandle().updateSleepingPlayerList(); + public boolean isSleepingIgnored() { + return this.getHandle().fauxSleeping; } @Override - public boolean isSleepingIgnored() { - return this.getHandle().fauxSleeping; + public void setSleepingIgnored(boolean isSleeping) { + this.getHandle().fauxSleeping = isSleeping; + ((CraftWorld) this.getWorld()).getHandle().updateSleepingPlayerList(); } @Override @@ -1701,13 +1810,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public void setPlayerWeather(WeatherType type) { - this.getHandle().setPlayerWeather(type, true); + public WeatherType getPlayerWeather() { + return this.getHandle().weatherType; } @Override - public WeatherType getPlayerWeather() { - return this.getHandle().weatherType; + public void setPlayerWeather(WeatherType type) { + this.getHandle().setPlayerWeather(type, true); } @Override @@ -1746,7 +1855,12 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public BanEntry ban(String reason, Date expires, String source, boolean kickPlayer) { // Paper - fix ban list API + public BanEntry ban( + String reason, + Date expires, + String source, + boolean kickPlayer + ) { // Paper - fix ban list API BanEntry banEntry = ((ProfileBanList) this.server.getBanList(BanList.Type.PROFILE)).addBan(this.getPlayerProfile(), reason, expires, source); // Paper - fix ban list API if (kickPlayer) { this.kickPlayer(reason); @@ -1755,12 +1869,22 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public BanEntry ban(String reason, Instant instant, String source, boolean kickPlayer) { // Paper - fix ban list API + public BanEntry ban( + String reason, + Instant instant, + String source, + boolean kickPlayer + ) { // Paper - fix ban list API return this.ban(reason, instant != null ? Date.from(instant) : null, source, kickPlayer); } @Override - public BanEntry ban(String reason, Duration duration, String source, boolean kickPlayer) { // Paper - fix ban list API + public BanEntry ban( + String reason, + Duration duration, + String source, + boolean kickPlayer + ) { // Paper - fix ban list API return this.ban(reason, duration != null ? Instant.now().plus(duration) : null, source, kickPlayer); } @@ -1798,6 +1922,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } } + @Override + public GameMode getGameMode() { + return GameMode.getByValue(this.getHandle().gameMode.getGameModeForPlayer().getId()); + } + @Override public void setGameMode(GameMode mode) { Preconditions.checkArgument(mode != null, "GameMode cannot be null"); @@ -1806,11 +1935,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().setGameMode(GameType.byId(mode.getValue()), org.bukkit.event.player.PlayerGameModeChangeEvent.Cause.PLUGIN, null); // Paper - Expand PlayerGameModeChangeEvent } - @Override - public GameMode getGameMode() { - return GameMode.getByValue(this.getHandle().gameMode.getGameModeForPlayer().getId()); - } - @Override public GameMode getPreviousGameMode() { GameType previousGameMode = this.getHandle().gameMode.getPreviousGameModeForPlayer(); @@ -1828,7 +1952,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa final net.minecraft.world.item.ItemStack itemstack = stackEntry.map(net.minecraft.world.item.enchantment.EnchantedItemInUse::itemStack).orElse(net.minecraft.world.item.ItemStack.EMPTY); if (!itemstack.isEmpty() && itemstack.getItem().components().has(net.minecraft.core.component.DataComponents.MAX_DAMAGE)) { net.minecraft.world.entity.ExperienceOrb orb = net.minecraft.world.entity.EntityType.EXPERIENCE_ORB.create(handle.level(), net.minecraft.world.entity.EntitySpawnReason.COMMAND); - orb.setValue(amount);; + orb.setValue(amount); + ; orb.spawnReason = org.bukkit.entity.ExperienceOrb.SpawnReason.CUSTOM; orb.setPosRaw(handle.getX(), handle.getY(), handle.getZ()); @@ -1895,6 +2020,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa Preconditions.checkArgument(exp >= 0, "Total experience points must not be negative (%s)", exp); this.getHandle().totalExperience = exp; } + // Paper start @Override public int calculateTotalExperiencePoints() { @@ -1911,6 +2037,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().experienceProgress = (float) remainingPoints / this.getExperiencePointsNeededForNextLevel(); this.getHandle().lastSentExp = -1; } + // Paper end @Override public int getExperiencePointsNeededForNextLevel() { @@ -1937,7 +2064,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa return (int) Math.floor((325.0 / 18.0) + Math.sqrt((2.0 / 9.0) * (points - (54215.0 / 72.0)))); } } - // Paper end @Override public void sendExperienceChange(float progress) { @@ -1957,10 +2083,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().connection.send(packet); } - private static @Nullable WeakReference getPluginWeakReference(@Nullable Plugin plugin) { - return (plugin == null) ? null : CraftPlayer.pluginWeakReferences.computeIfAbsent(plugin, WeakReference::new); - } - @Override @Deprecated public void hidePlayer(Player player) { @@ -2020,6 +2142,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa server.getPluginManager().callEvent(new PlayerHideEntityEvent(this, entity)); } + private void unregisterEntity(Entity other) { // Paper end ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; @@ -2102,6 +2225,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa // Paper start - uuid override this.trackAndShowEntity(entity, null); } + // Paper end + private void trackAndShowEntity(org.bukkit.entity.Entity entity, final @Nullable UUID uuidOverride) { // Paper end ChunkMap tracker = ((ServerLevel) this.getHandle().level()).getChunkSource().chunkMap; @@ -2127,6 +2252,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.server.getPluginManager().callEvent(new PlayerShowEntityEvent(this, entity)); } + + void resetAndShowEntity(org.bukkit.entity.Entity entity) { + // SPIGOT-7312: Can't show/hide self + if (this.equals(entity)) { + return; + } + + if (this.invertedVisibilityEntities.remove(entity.getUniqueId()) == null) { + this.trackAndShowEntity(entity); + } + } + + // Paper start + public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { + return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); + } + // Paper end + // Paper start @Override public void setPlayerProfile(com.destroystokyo.paper.profile.PlayerProfile profile) { @@ -2159,22 +2302,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa // Refresh misc player things AFTER sending game profile this.refreshPlayer(); } - // Paper end - - void resetAndShowEntity(org.bukkit.entity.Entity entity) { - // SPIGOT-7312: Can't show/hide self - if (this.equals(entity)) { - return; - } - - if (this.invertedVisibilityEntities.remove(entity.getUniqueId()) == null) { - this.trackAndShowEntity(entity); - } - } - // Paper start - public com.destroystokyo.paper.profile.PlayerProfile getPlayerProfile() { - return new com.destroystokyo.paper.profile.CraftPlayerProfile(this).clone(); - } private void refreshPlayer() { ServerPlayer handle = this.getHandle(); @@ -2197,7 +2324,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa connection.send(new net.minecraft.network.protocol.game.ClientboundUpdateMobEffectPacket(handle.getId(), mobEffect, false)); } } - // Paper end public void onEntityRemove(Entity entity) { this.invertedVisibilityEntities.remove(entity.getUUID()); @@ -2224,6 +2350,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public boolean isListed(Player other) { return !this.unlistedEntities.contains(other.getUniqueId()); } + // Paper end - Add Listing API for Player @Override public boolean unlistPlayer(@NonNull Player other) { @@ -2252,7 +2379,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa return false; } } - // Paper end - Add Listing API for Player @Override public Map serialize() { @@ -2273,6 +2399,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa return this.firstPlayed; } + public void setFirstPlayed(long firstPlayed) { + this.firstPlayed = firstPlayed; + } + @Override public long getLastPlayed() { return this.lastPlayed; @@ -2282,10 +2412,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public boolean hasPlayedBefore() { return this.hasPlayedBefore; } - - public void setFirstPlayed(long firstPlayed) { - this.firstPlayed = firstPlayed; - } + // Paper end - getLastPlayed replacement API // Paper start - getLastPlayed replacement API @Override @@ -2297,7 +2424,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public long getLastSeen() { return this.isOnline() ? System.currentTimeMillis() : this.lastSaveTime; } - // Paper end - getLastPlayed replacement API public void readExtraData(ValueInput input) { this.hasPlayedBefore = true; @@ -2437,7 +2563,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa // Paper start - adventure @Override - public void setResourcePack(final UUID uuid, final String url, final byte @Nullable [] hashBytes, final net.kyori.adventure.text.Component prompt, final boolean force) { + public void setResourcePack( + final UUID uuid, + final String url, + final byte @Nullable [] hashBytes, + final net.kyori.adventure.text.Component prompt, + final boolean force + ) { Preconditions.checkArgument(uuid != null, "Resource pack UUID cannot be null"); Preconditions.checkArgument(url != null, "Resource pack URL cannot be null"); final String hash; @@ -2464,7 +2596,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.clearResourcePacks(); } final Component prompt = io.papermc.paper.adventure.PaperAdventure.asVanilla(request.prompt()); - for (final java.util.Iterator iter = request.packs().iterator(); iter.hasNext();) { + for (final java.util.Iterator iter = request.packs().iterator(); iter.hasNext(); ) { final net.kyori.adventure.resource.ResourcePackInfo pack = iter.next(); packs.add(new ClientboundResourcePackPushPacket(pack.id(), pack.uri().toASCIIString(), pack.hash(), request.required(), iter.hasNext() ? Optional.empty() : Optional.ofNullable(prompt))); if (request.callback() != net.kyori.adventure.resource.ResourcePackCallback.noOp()) { @@ -2480,20 +2612,26 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa if (this.getHandle().connection == null) return; this.sendBundle(net.kyori.adventure.util.MonkeyBars.nonEmptyArrayToList(pack -> new ClientboundResourcePackPopPacket(Optional.of(pack)), id, others)); } + // Paper end - adventure @Override public void clearResourcePacks() { if (this.getHandle().connection == null) return; this.getHandle().connection.send(new ClientboundResourcePackPopPacket(Optional.empty())); } - // Paper end - adventure + // Paper end - more resource pack API + + @Override + public void showDialog(final DialogLike dialog) { + if (this.getHandle().connection == null) return; + this.getHandle().openDialog(PaperDialog.bukkitToMinecraftHolder((Dialog) dialog)); + } // Paper start - more resource pack API @Override public org.bukkit.event.player.PlayerResourcePackStatusEvent.Status getResourcePackStatus() { return this.resourcePackStatus; } - // Paper end - more resource pack API @Override public void removeResourcePack(UUID id) { @@ -2614,6 +2752,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().getAbilities().mayfly = value; this.getHandle().onUpdateAbilities(); } + // Paper end - flying fall damage // Paper start - flying fall damage @Override @@ -2625,7 +2764,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public @NonNull TriState hasFlyingFallDamage() { return getHandle().flyingFallDamage; } - // Paper end - flying fall damage + + @Override + public float getFlySpeed() { + return (float) this.getHandle().getAbilities().flyingSpeed * 2f; + } @Override public void setFlySpeed(float value) { @@ -2636,6 +2779,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } + @Override + public float getWalkSpeed() { + return this.getHandle().getAbilities().walkingSpeed * 2f; + } + @Override public void setWalkSpeed(float value) { this.validateSpeed(value); @@ -2645,16 +2793,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.getHandle().getAttribute(Attributes.MOVEMENT_SPEED).setBaseValue(player.getAbilities().walkingSpeed); // SPIGOT-5833: combination of the two in 1.16+ } - @Override - public float getFlySpeed() { - return (float) this.getHandle().getAbilities().flyingSpeed * 2f; - } - - @Override - public float getWalkSpeed() { - return this.getHandle().getAbilities().walkingSpeed * 2f; - } - private void validateSpeed(float value) { Preconditions.checkArgument(value <= 1f && value >= -1f, "Speed value (%s) need to be between -1f and 1f", value); } @@ -2688,6 +2826,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa this.server.getScoreboardManager().setPlayerBoard(this, craftScoreboard); } + @Override + public double getHealthScale() { + return this.healthScale; + } + @Override public void setHealthScale(double value) { Preconditions.checkArgument(value > 0F, "Health value (%s) must be greater than 0", value); @@ -2697,8 +2840,8 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public double getHealthScale() { - return this.healthScale; + public boolean isHealthScaled() { + return this.scaledHealth; } @Override @@ -2708,11 +2851,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } } - @Override - public boolean isHealthScaled() { - return this.scaledHealth; - } - public float getScaledHealth() { return (float) (this.isHealthScaled() ? this.getHealth() * this.getHealthScale() / this.getMaxHealth() : this.getHealth()); } @@ -2778,7 +2916,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } public AttributeInstance getScaledMaxHealth() { - AttributeInstance dummy = new AttributeInstance(Attributes.MAX_HEALTH, (attribute) -> { }); + AttributeInstance dummy = new AttributeInstance(Attributes.MAX_HEALTH, (attribute) -> {}); double healthMod = this.scaledHealth ? this.healthScale : this.getMaxHealth(); if (healthMod >= Float.MAX_VALUE || healthMod <= 0) { healthMod = 20; // Reset health @@ -2883,17 +3021,50 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data) { + public void spawnParticle( + Particle particle, + double x, + double y, + double z, + int count, + double offsetX, + double offsetY, + double offsetZ, + double extra, + T data + ) { this.spawnParticle(particle, x, y, z, count, offsetX, offsetY, offsetZ, extra, data, false); } @Override - public void spawnParticle(Particle particle, Location location, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { + public void spawnParticle( + Particle particle, + Location location, + int count, + double offsetX, + double offsetY, + double offsetZ, + double extra, + T data, + boolean force + ) { this.spawnParticle(particle, location.getX(), location.getY(), location.getZ(), count, offsetX, offsetY, offsetZ, extra, data, force); } @Override - public void spawnParticle(Particle particle, double x, double y, double z, int count, double offsetX, double offsetY, double offsetZ, double extra, T data, boolean force) { + public void spawnParticle( + Particle particle, + double x, + double y, + double z, + int count, + double offsetX, + double offsetY, + double offsetZ, + double extra, + T data, + boolean force + ) { ClientboundLevelParticlesPacket packetplayoutworldparticles = new ClientboundLevelParticlesPacket(CraftParticle.createParticleParam(particle, data), force, false, x, y, z, (float) offsetX, (float) offsetY, (float) offsetZ, (float) extra, count); // Paper - fix x/y/z precision loss this.getHandle().connection.send(packetplayoutworldparticles); } @@ -2919,6 +3090,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public java.util.Locale locale() { return getHandle().adventure$locale; } + // Paper end @Override public int getPing() { @@ -2932,17 +3104,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa return locale != null ? locale : "en_us"; // Paper end } - - // Paper start - public void setAffectsSpawning(boolean affects) { - this.getHandle().affectsSpawning = affects; - } + // Paper end @Override public boolean getAffectsSpawning() { return this.getHandle().affectsSpawning; } - // Paper end + + // Paper start + public void setAffectsSpawning(boolean affects) { + this.getHandle().affectsSpawning = affects; + } @Override public void updateCommands() { @@ -3043,20 +3215,24 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } else { super.sendMessage(signedMessage, boundChatType); } -// net.minecraft.network.chat.PlayerChatMessage playerChatMessage = new net.minecraft.network.chat.PlayerChatMessage( -// null, // TODO: -// new net.minecraft.network.chat.MessageSignature(signedMessage.signature().bytes()), -// null, // TODO -// io.papermc.paper.adventure.PaperAdventure.asVanilla(signedMessage.unsignedContent()), -// net.minecraft.network.chat.FilterMask.PASS_THROUGH -// ); -// -// this.getHandle().sendChatMessage(net.minecraft.network.chat.OutgoingChatMessage.create(playerChatMessage), this.getHandle().isTextFilteringEnabled(), this.toHandle(boundChatType)); + // net.minecraft.network.chat.PlayerChatMessage playerChatMessage = new net.minecraft.network.chat.PlayerChatMessage( + // null, // TODO: + // new net.minecraft.network.chat.MessageSignature(signedMessage.signature().bytes()), + // null, // TODO + // io.papermc.paper.adventure.PaperAdventure.asVanilla(signedMessage.unsignedContent()), + // net.minecraft.network.chat.FilterMask.PASS_THROUGH + // ); + // + // this.getHandle().sendChatMessage(net.minecraft.network.chat.OutgoingChatMessage.create(playerChatMessage), this.getHandle().isTextFilteringEnabled(), this.toHandle(boundChatType)); } @Deprecated(forRemoval = true) @Override - public void sendMessage(final net.kyori.adventure.identity.Identity identity, final net.kyori.adventure.text.Component message, final net.kyori.adventure.audience.MessageType type) { + public void sendMessage( + final net.kyori.adventure.identity.Identity identity, + final net.kyori.adventure.text.Component message, + final net.kyori.adventure.audience.MessageType type + ) { if (getHandle().connection == null) return; final net.minecraft.core.Registry chatTypeRegistry = this.getHandle().level().registryAccess().lookupOrThrow(net.minecraft.core.registries.Registries.CHAT_TYPE); this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(message, false)); @@ -3115,10 +3291,10 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa java.util.Objects.requireNonNull(part, "part"); java.util.Objects.requireNonNull(value, "value"); if (part == net.kyori.adventure.title.TitlePart.TITLE) { - final ClientboundSetTitleTextPacket tp = new ClientboundSetTitleTextPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla((net.kyori.adventure.text.Component)value)); + final ClientboundSetTitleTextPacket tp = new ClientboundSetTitleTextPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla((net.kyori.adventure.text.Component) value)); this.getHandle().connection.send(tp); } else if (part == net.kyori.adventure.title.TitlePart.SUBTITLE) { - final ClientboundSetSubtitleTextPacket sp = new ClientboundSetSubtitleTextPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla((net.kyori.adventure.text.Component)value)); + final ClientboundSetSubtitleTextPacket sp = new ClientboundSetSubtitleTextPacket(io.papermc.paper.adventure.PaperAdventure.asVanilla((net.kyori.adventure.text.Component) value)); this.getHandle().connection.send(sp); } else if (part == net.kyori.adventure.title.TitlePart.TIMES) { final net.kyori.adventure.title.Title.Times times = (net.kyori.adventure.title.Title.Times) value; @@ -3128,22 +3304,13 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa } } - private static int ticks(final java.time.Duration duration) { - if (duration == null) { - return -1; - } - return (int) (duration.toMillis() / 50L); - } + // resetTitle implemented above @Override public void clearTitle() { this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundClearTitlesPacket(false)); } - // resetTitle implemented above - - private @Nullable Set activeBossBars; - @Override public @NonNull Iterable activeBossBars() { if (this.activeBossBars != null) { @@ -3237,96 +3404,20 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public void resetCooldown() { getHandle().resetAttackStrengthTicker(); } - // Paper end - // Spigot start - private final Player.Spigot spigot = new Player.Spigot() { - - @Override - public InetSocketAddress getRawAddress() { - return (InetSocketAddress) CraftPlayer.this.getHandle().connection.getRawAddress(); - } - - @Override - public void respawn() { - if (CraftPlayer.this.getHealth() <= 0 && CraftPlayer.this.isOnline()) { - CraftPlayer.this.server.getServer().getPlayerList().respawn(CraftPlayer.this.getHandle(), false, Entity.RemovalReason.KILLED, org.bukkit.event.player.PlayerRespawnEvent.RespawnReason.PLUGIN); - } - } - - @Override - public Set getHiddenPlayers() { - Set ret = new HashSet<>(); - for (Player player : CraftPlayer.this.getServer().getOnlinePlayers()) { - if (!CraftPlayer.this.canSee(player)) { - ret.add(player); - } - } - - return java.util.Collections.unmodifiableSet(ret); - } - - @Override - public void sendMessage(BaseComponent component) { - this.sendMessage(new BaseComponent[]{component}); - } - - @Override - public void sendMessage(BaseComponent... components) { - this.sendMessage(net.md_5.bungee.api.ChatMessageType.SYSTEM, components); - } - - @Override - public void sendMessage(UUID sender, BaseComponent component) { - this.sendMessage(net.md_5.bungee.api.ChatMessageType.CHAT, sender, component); - } - - @Override - public void sendMessage(UUID sender, BaseComponent... components) { - this.sendMessage(net.md_5.bungee.api.ChatMessageType.CHAT, sender, components); - } - - @Override - public void sendMessage(net.md_5.bungee.api.ChatMessageType position, BaseComponent component) { - this.sendMessage(position, new BaseComponent[]{component}); - } - - @Override - public void sendMessage(net.md_5.bungee.api.ChatMessageType position, BaseComponent... components) { - this.sendMessage(position, null, components); - } - - @Override - public void sendMessage(net.md_5.bungee.api.ChatMessageType position, UUID sender, BaseComponent component) { - this.sendMessage(position, sender, new BaseComponent[]{component}); - } - - @Override - public void sendMessage(net.md_5.bungee.api.ChatMessageType position, UUID sender, BaseComponent... components) { - if (CraftPlayer.this.getHandle().connection == null) return; - - CraftPlayer.this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundSystemChatPacket(components, position == net.md_5.bungee.api.ChatMessageType.ACTION_BAR)); - } - - // Paper start - @Override - public int getPing() { - return CraftPlayer.this.getPing(); - } - // Paper end - }; // Paper start - brand support @Override public String getClientBrandName() { return getHandle().connection.playerBrand; } - // Paper end // Paper start @Override public void showElderGuardian(boolean silent) { - if (getHandle().connection != null) getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); + if (getHandle().connection != null) + getHandle().connection.send(new ClientboundGameEventPacket(ClientboundGameEventPacket.GUARDIAN_ELDER_EFFECT, silent ? 0F : 1F)); } + // Paper end @Override public int getWardenWarningCooldown() { @@ -3362,19 +3453,18 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public void increaseWardenWarningLevel() { this.getHandle().wardenSpawnTracker.increaseWarningLevel(); } - // Paper end // Paper start @Override public Duration getIdleDuration() { return Duration.ofMillis(net.minecraft.Util.getMillis() - this.getHandle().getLastActionTime()); } + // Paper end @Override public void resetIdleDuration() { this.getHandle().resetLastActionTime(); } - // Paper end // Paper start - Add chunk view API @Override @@ -3382,6 +3472,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa org.spigotmc.AsyncCatcher.catchOp("accessing sent chunks"); return FeatureHooks.getSentChunkKeys(this.getHandle()); } + // Paper end @Override public Set getSentChunks() { @@ -3394,17 +3485,17 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa org.spigotmc.AsyncCatcher.catchOp("accessing sent chunks"); return FeatureHooks.isChunkSent(this.getHandle(), chunkKey); } - // Paper end public Player.Spigot spigot() { return this.spigot; } - // Spigot end + // Paper end @Override public int getViewDistance() { return ca.spottedleaf.moonrise.common.PlatformHooks.get().getViewDistance(this.getHandle()); } + // Spigot end @Override public void setViewDistance(final int viewDistance) { @@ -3440,7 +3531,6 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa Preconditions.checkArgument(effect.isApplicableTo(target), "Entity effect cannot apply to the target"); this.getHandle().connection.send(new net.minecraft.network.protocol.game.ClientboundEntityEventPacket(((CraftEntity) target).getHandle(), effect.getData())); } - // Paper end - entity effect API @Override public @NonNull PlayerGiveResult give(final @NonNull Collection<@NonNull ItemStack> items, final boolean dropIfFull) { @@ -3472,6 +3562,7 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa handle.containerMenu.broadcastChanges(); return new PaperPlayerGiveResult(leftovers.build(), drops.build()); } + // Paper end - entity effect API @Override public float getSidewaysMovement() { @@ -3503,4 +3594,11 @@ public class CraftPlayer extends CraftHumanEntity implements Player, PluginMessa public PlayerGameConnection getConnection() { return this.getHandle().connection.playerGameConnection; } + + private record ChunkSectionChanges(ShortSet positions, List blockData) { + + public ChunkSectionChanges() { + this(new ShortArraySet(), new ArrayList<>()); + } + } } diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java index 66f0089ee3..df332b6938 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftVillager.java @@ -178,7 +178,7 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { } public static Holder bukkitToMinecraftHolder(Type bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.VILLAGER_TYPE); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftType(final Holder holder){ @@ -194,7 +194,7 @@ public class CraftVillager extends CraftAbstractVillager implements Villager { } public static Holder bukkitToMinecraftHolder(Profession bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.VILLAGER_PROFESSION); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static Profession minecraftToBukkit(VillagerProfession minecraft) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java index 33866c5a55..723a8e6594 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/entity/CraftWolf.java @@ -105,7 +105,7 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { } public static Holder bukkitToMinecraftHolder(Variant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.WOLF_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftVariant(final Holder holder) { @@ -128,7 +128,7 @@ public class CraftWolf extends CraftTameableAnimal implements Wolf { } public static Holder bukkitToMinecraftHolder(SoundVariant bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.WOLF_SOUND_VARIANT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftSoundVariant(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java index 4a4e2c88c8..5a7a0520f5 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimMaterial.java @@ -25,7 +25,7 @@ public class CraftTrimMaterial extends HolderableBase bukkitToMinecraftHolder(TrimMaterial bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_MATERIAL); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static Object bukkitToObject(TrimMaterial bukkit) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java index ad4be97577..89925377dc 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/inventory/trim/CraftTrimPattern.java @@ -25,7 +25,7 @@ public class CraftTrimPattern extends HolderableBase bukkitToMinecraftHolder(TrimPattern bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.TRIM_PATTERN); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public static Object bukkitToObject(TrimPattern bukkit) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/map/CraftMapCursor.java b/paper-server/src/main/java/org/bukkit/craftbukkit/map/CraftMapCursor.java index be61490892..c0cf9065de 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/map/CraftMapCursor.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/map/CraftMapCursor.java @@ -26,7 +26,7 @@ public final class CraftMapCursor { } public static Holder bukkitToMinecraftHolder(MapCursor.Type bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.MAP_DECORATION_TYPE); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } public CraftType(final Holder holder) { diff --git a/paper-server/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java b/paper-server/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java index d1c1a24e53..add1575ef4 100644 --- a/paper-server/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java +++ b/paper-server/src/main/java/org/bukkit/craftbukkit/potion/CraftPotionEffectType.java @@ -38,7 +38,7 @@ public class CraftPotionEffectType extends PotionEffectType implements Holderabl } public static Holder bukkitToMinecraftHolder(PotionEffectType bukkit) { - return CraftRegistry.bukkitToMinecraftHolder(bukkit, Registries.MOB_EFFECT); + return CraftRegistry.bukkitToMinecraftHolder(bukkit); } private final Holder holder; diff --git a/paper-server/src/main/resources/META-INF/services/io.papermc.paper.registry.data.dialog.DialogInstancesProvider b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.registry.data.dialog.DialogInstancesProvider new file mode 100644 index 0000000000..a7dd359922 --- /dev/null +++ b/paper-server/src/main/resources/META-INF/services/io.papermc.paper.registry.data.dialog.DialogInstancesProvider @@ -0,0 +1 @@ +io.papermc.paper.registry.data.dialog.PaperDialogInstancesProvider diff --git a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java index f5d5b112ca..87396938f8 100644 --- a/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java +++ b/paper-server/src/test/java/org/bukkit/registry/RegistryConversionTest.java @@ -270,7 +270,7 @@ public class RegistryConversionTest { Joiner.on('\n').withKeyValueSeparator(" got: ").join(notMatching))); } - static final Set> IGNORE_FOR_DIRECT_HOLDER = Set.of(RegistryKey.TRIM_MATERIAL, RegistryKey.TRIM_PATTERN, RegistryKey.INSTRUMENT, RegistryKey.BANNER_PATTERN, RegistryKey.SOUND_EVENT); // Paper + static final Set> IGNORE_FOR_DIRECT_HOLDER = Set.of(RegistryKey.TRIM_MATERIAL, RegistryKey.TRIM_PATTERN, RegistryKey.INSTRUMENT, RegistryKey.BANNER_PATTERN, RegistryKey.SOUND_EVENT, RegistryKey.DIALOG); // Paper /** * Minecraft registry can return a default key / value diff --git a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java index 1c0be44709..919f13143d 100644 --- a/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java +++ b/paper-server/src/test/java/org/bukkit/support/provider/RegistriesArgumentProvider.java @@ -1,6 +1,8 @@ package org.bukkit.support.provider; import com.google.common.collect.Lists; +import io.papermc.paper.dialog.Dialog; +import io.papermc.paper.dialog.PaperDialog; import io.papermc.paper.registry.RegistryKey; import java.util.List; import java.util.stream.Stream; @@ -115,6 +117,7 @@ public class RegistriesArgumentProvider implements ArgumentsProvider { register(RegistryKey.CHICKEN_VARIANT, Chicken.Variant.class, Registries.CHICKEN_VARIANT, CraftChicken.CraftVariant.class, ChickenVariant.class); register(RegistryKey.COW_VARIANT, Cow.Variant.class, Registries.COW_VARIANT, CraftCow.CraftVariant.class, CowVariant.class); register(RegistryKey.PIG_VARIANT, Pig.Variant.class, Registries.PIG_VARIANT, CraftPig.CraftVariant.class, PigVariant.class); + register(RegistryKey.DIALOG, Dialog.class, Registries.DIALOG, PaperDialog.class, net.minecraft.server.dialog.Dialog.class); } private static void register(RegistryKey registryKey, Class bukkit, ResourceKey registry, Class craft, Class minecraft) { // Paper