From 8823943ab37890a5bfafc446f302389359c7bb1e Mon Sep 17 00:00:00 2001 From: Alepando Date: Thu, 17 Jul 2025 21:06:55 -0600 Subject: [PATCH 01/20] registry deleted, now event --- .../alepando/dev/dialogapi/DialogAPI.kt | 2 + .../dialogapi/executor/CustomKeyRegistry.kt | 65 ---------- .../dev/dialogapi/executor/PlayerOpener.kt | 3 + .../events/PlayerDialogInteractionEvent.kt | 34 ++++++ .../executor/events/PlayerOpenDialogEvent.kt | 24 ++++ .../dev/dialogapi/packets/PacketSniffer.kt | 5 +- .../dialogapi/packets/reader/ReaderManager.kt | 48 -------- .../dev/versionSupplier/VersionSupplier.kt | 10 ++ .../packet/ClientVersionSniffer.kt | 111 ++++++++++++++++++ 9 files changed, 186 insertions(+), 116 deletions(-) delete mode 100644 src/main/kotlin/alepando/dev/dialogapi/executor/CustomKeyRegistry.kt create mode 100644 src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerDialogInteractionEvent.kt create mode 100644 src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerOpenDialogEvent.kt delete mode 100644 src/main/kotlin/alepando/dev/dialogapi/packets/reader/ReaderManager.kt create mode 100644 src/main/kotlin/alepando/dev/versionSupplier/VersionSupplier.kt create mode 100644 src/main/kotlin/alepando/dev/versionSupplier/packet/ClientVersionSniffer.kt diff --git a/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt b/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt index 8c75072..a65c36e 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt @@ -2,6 +2,7 @@ package alepando.dev.dialogapi import alepando.dev.dialogapi.listeners.PlayerConnectionStatus import alepando.dev.dialogapi.listeners.ServerStatusListener +import alepando.dev.versionSupplier.packet.ClientVersionSniffer import org.bukkit.Bukkit import org.bukkit.plugin.Plugin import org.bukkit.plugin.PluginManager @@ -23,6 +24,7 @@ object DialogAPI { initialized = true val pm: PluginManager = Bukkit.getPluginManager() + ClientVersionSniffer.injectAll(plugin) pm.registerEvents(PlayerConnectionStatus(plugin), plugin) pm.registerEvents(ServerStatusListener(plugin),plugin) } diff --git a/src/main/kotlin/alepando/dev/dialogapi/executor/CustomKeyRegistry.kt b/src/main/kotlin/alepando/dev/dialogapi/executor/CustomKeyRegistry.kt deleted file mode 100644 index 837b893..0000000 --- a/src/main/kotlin/alepando/dev/dialogapi/executor/CustomKeyRegistry.kt +++ /dev/null @@ -1,65 +0,0 @@ -package alepando.dev.dialogapi.executor - -import alepando.dev.dialogapi.factory.actions.CustomAction -import alepando.dev.dialogapi.packets.reader.InputReader -import alepando.dev.dialogapi.factory.data.ResourceLocation -import java.util.* - -typealias NMSResourceLocation = net.minecraft.resources.ResourceLocation -/** - * Internal data class to hold a registered custom action and its corresponding input reader. - */ -internal data class KeyBinding(val action: CustomAction, val reader: Optional) - - - -/** - * A registry for developers to register custom actions and input readers, - * associating them with a unique ResourceLocation (namespace and path). - */ -object CustomKeyRegistry { - - private val registeredKeys = mutableMapOf() - - /** - * Registers a custom key with an associated action and input reader. - * - * @param resourceLocation location to be stored - * @param action The [CustomAction] to be executed when this key is triggered. - * @param reader The [InputReader] to process any input associated with this key. - * @throws IllegalArgumentException if the namespace or path contains invalid characters for ResourceLocation. - * @throws IllegalStateException if the key (namespace and path combination) is already registered. - */ - fun register(resourceLocation: ResourceLocation, action: CustomAction, reader: Optional = Optional.empty()) { - val location = try { - resourceLocation.toNMS() - } catch (e: Exception) { - throw IllegalArgumentException("Invalid namespace or path: '${'$'}namespace:${'$'}path'. Ensure they contain valid characters.", e) - } - - if (registeredKeys.containsKey(location)) { - throw IllegalStateException("Custom key '${'$'}location' is already registered.") - } - registeredKeys[location] = KeyBinding(action, reader) - } - - /** - * Retrieves the [KeyBinding] (action and reader) for a given ResourceLocation. - * Intended for internal use by the dialog system (e.g., ReaderManager). - * - * @param location The [ResourceLocation] of the key. - * @return The [KeyBinding] if found, otherwise null. - */ - internal fun getBinding(location: NMSResourceLocation): KeyBinding? { - return registeredKeys[location] - } - - /** - * Clears all registered custom keys. - * Mainly intended for testing or server shutdown/plugin disable scenarios. - */ - fun clearAll() { - registeredKeys.clear() - - } -} diff --git a/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt b/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt index a411f02..f08d746 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt @@ -1,5 +1,6 @@ package alepando.dev.dialogapi.executor +import alepando.dev.dialogapi.executor.events.PlayerOpenDialogEvent import alepando.dev.dialogapi.factory.Dialog import net.minecraft.core.Holder.Direct import org.bukkit.craftbukkit.entity.CraftPlayer @@ -20,6 +21,8 @@ object PlayerOpener{ val holder = Direct(dialog.toNMS()) + PlayerOpenDialogEvent(this,dialog).callEvent() + nmsPlayer.openDialog(holder) } } diff --git a/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerDialogInteractionEvent.kt b/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerDialogInteractionEvent.kt new file mode 100644 index 0000000..14f0880 --- /dev/null +++ b/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerDialogInteractionEvent.kt @@ -0,0 +1,34 @@ +package alepando.dev.dialogapi.executor.events + +import alepando.dev.dialogapi.factory.actions.CustomAction +import alepando.dev.dialogapi.packets.parser.PayloadParser +import alepando.dev.dialogapi.packets.reader.InputReader +import net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket +import org.bukkit.entity.Player +import org.bukkit.event.HandlerList +import org.bukkit.event.player.PlayerEvent +import org.bukkit.plugin.Plugin + +class PlayerDialogInteractionEvent(player: Player, packet: ServerboundCustomClickActionPacket, internal val plugin: Plugin): PlayerEvent(player) { + + val payload = PayloadParser.getValues(packet) + val id = packet.id + + override fun getHandlers(): HandlerList = handlerList + + fun read(reader:InputReader){ + reader.task(player,payload) + } + + fun action(action:CustomAction){ + action.execute(player,plugin) + } + + companion object { + @JvmStatic + private val handlerList = HandlerList() + + @JvmStatic + fun getHandlerList(): HandlerList = handlerList + } +} \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerOpenDialogEvent.kt b/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerOpenDialogEvent.kt new file mode 100644 index 0000000..2e34657 --- /dev/null +++ b/src/main/kotlin/alepando/dev/dialogapi/executor/events/PlayerOpenDialogEvent.kt @@ -0,0 +1,24 @@ +package alepando.dev.dialogapi.executor.events + +import alepando.dev.dialogapi.factory.Dialog +import alepando.dev.dialogapi.factory.actions.CustomAction +import alepando.dev.dialogapi.packets.parser.PayloadParser +import alepando.dev.dialogapi.packets.reader.InputReader +import net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket +import org.bukkit.entity.Player +import org.bukkit.event.HandlerList +import org.bukkit.event.player.PlayerEvent +import org.bukkit.plugin.Plugin + +class PlayerOpenDialogEvent(player: Player, val dialog: Dialog): PlayerEvent(player) { + + override fun getHandlers(): HandlerList = handlerList + + companion object { + @JvmStatic + private val handlerList = HandlerList() + + @JvmStatic + fun getHandlerList(): HandlerList = handlerList + } +} \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/packets/PacketSniffer.kt b/src/main/kotlin/alepando/dev/dialogapi/packets/PacketSniffer.kt index ba666e8..459564c 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/packets/PacketSniffer.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/packets/PacketSniffer.kt @@ -1,6 +1,6 @@ package alepando.dev.dialogapi.packets -import alepando.dev.dialogapi.packets.reader.ReaderManager +import alepando.dev.dialogapi.executor.events.PlayerDialogInteractionEvent import io.netty.channel.ChannelDuplexHandler import io.netty.channel.ChannelHandlerContext import net.minecraft.network.Connection @@ -42,8 +42,7 @@ internal object PacketSniffer { val handler = object : ChannelDuplexHandler() { override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { if (msg is ServerboundCustomClickActionPacket) { - ReaderManager.peekActions(player, msg, plugin) - ReaderManager.peekInputs(player, msg) + PlayerDialogInteractionEvent(player,msg,plugin).callEvent() } super.channelRead(ctx, msg) } diff --git a/src/main/kotlin/alepando/dev/dialogapi/packets/reader/ReaderManager.kt b/src/main/kotlin/alepando/dev/dialogapi/packets/reader/ReaderManager.kt deleted file mode 100644 index 83ae37c..0000000 --- a/src/main/kotlin/alepando/dev/dialogapi/packets/reader/ReaderManager.kt +++ /dev/null @@ -1,48 +0,0 @@ -package alepando.dev.dialogapi.packets.reader - -import alepando.dev.dialogapi.executor.CustomKeyRegistry // Import new registry -import alepando.dev.dialogapi.packets.parser.PayloadParser -import net.minecraft.network.protocol.common.ServerboundCustomClickActionPacket -import org.bukkit.entity.Player -import org.bukkit.plugin.Plugin - -/** - * Internal object responsible for managing the processing of incoming dialog packets. - * It uses the [CustomKeyRegistry] to find the appropriate action or input reader - * based on the packet's ID. - */ -internal object ReaderManager { - - /** - * Processes a [ServerboundCustomClickActionPacket] for input data. - * It retrieves the corresponding [InputReader] from the [CustomKeyRegistry] - * and executes its task with the parsed payload. - * - * @param player The player who sent the packet. - * @param packet The packet to process. - */ - fun peekInputs(player: Player, packet: ServerboundCustomClickActionPacket) { - val binding = CustomKeyRegistry.getBinding(packet.id) - val inputReader = binding?.reader - if (inputReader != null) { - if(inputReader.isPresent){ - binding.reader.get().task(player, PayloadParser.getValues(packet)) - } - } - } - - /** - * Processes a [ServerboundCustomClickActionPacket] for actions. - * It retrieves the corresponding [CustomAction] from the [CustomKeyRegistry] - * and executes it. - * - * @param player The player who sent the packet. - * @param packet The packet to process. - * @param plugin The plugin instance, required for executing the action. - */ - fun peekActions(player: Player, packet: ServerboundCustomClickActionPacket, plugin: Plugin) { - val binding = CustomKeyRegistry.getBinding(packet.id) - binding?.action?.execute(player, plugin) - } - -} diff --git a/src/main/kotlin/alepando/dev/versionSupplier/VersionSupplier.kt b/src/main/kotlin/alepando/dev/versionSupplier/VersionSupplier.kt new file mode 100644 index 0000000..c5f32c4 --- /dev/null +++ b/src/main/kotlin/alepando/dev/versionSupplier/VersionSupplier.kt @@ -0,0 +1,10 @@ +package alepando.dev.versionSupplier + +import alepando.dev.versionSupplier.packet.ClientVersionSniffer +import org.bukkit.entity.Player + +object VersionSupplier { + fun Player.getVersion(): Int{ + return ClientVersionSniffer.getProtocolVersionForUUID(this.uniqueId) ?: 0 + } +} \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/versionSupplier/packet/ClientVersionSniffer.kt b/src/main/kotlin/alepando/dev/versionSupplier/packet/ClientVersionSniffer.kt new file mode 100644 index 0000000..e679fff --- /dev/null +++ b/src/main/kotlin/alepando/dev/versionSupplier/packet/ClientVersionSniffer.kt @@ -0,0 +1,111 @@ +package alepando.dev.versionSupplier.packet + +import io.netty.buffer.ByteBuf +import io.netty.channel.ChannelDuplexHandler +import io.netty.channel.ChannelHandlerContext +import net.minecraft.network.Connection +import net.minecraft.server.MinecraftServer +import org.bukkit.Bukkit +import org.bukkit.craftbukkit.entity.CraftPlayer +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.Listener +import org.bukkit.event.player.PlayerJoinEvent +import org.bukkit.plugin.Plugin +import java.util.* +import java.util.concurrent.ConcurrentHashMap + +internal object ClientVersionSniffer : Listener { + + private val channelProtocols = ConcurrentHashMap() + private val clientProtocols = ConcurrentHashMap() + + fun injectAll(plugin: Plugin) { + val serverConnections = MinecraftServer.getServer().connection.connections + + serverConnections.forEach { conn -> + val channel = conn.channel + val handlerName = "${plugin.name.lowercase()}_version_sniffer" + + if (channel.pipeline().get(handlerName) == null) { + channel.pipeline().addBefore("packet_handler", handlerName, object : ChannelDuplexHandler() { + override fun channelRead(ctx: ChannelHandlerContext, msg: Any) { + if (msg is ByteBuf) { + val readerIndex = msg.readerIndex() + try { + val packetId = readVarInt(msg) + if (packetId == 0) { // Handshake packet + val protocolVersion = readVarInt(msg) + readString(msg) // hostname + msg.readUnsignedShort() // port + readVarInt(msg) // intention + + channelProtocols[ctx] = protocolVersion + } + } catch (_: Exception) { + } finally { + msg.readerIndex(readerIndex) + } + } + + super.channelRead(ctx, msg) + } + }) + } + } + + Bukkit.getPluginManager().registerEvents(this, plugin) + } + + @EventHandler + fun onJoin(event: PlayerJoinEvent) { + val player = event.player + val ctx = getContextFromPlayer(player) + + val protocol = getProtocolFromChannel(ctx) + if (protocol != null) { + clientProtocols[player.uniqueId] = protocol + } + } + + fun getProtocolVersionForUUID(uuid: UUID): Int? { + return clientProtocols[uuid] + } + + fun clear() { + clientProtocols.clear() + channelProtocols.clear() + } + + private fun getProtocolFromChannel(ctx: ChannelHandlerContext?): Int? { + return ctx?.let { channelProtocols.remove(it) } + } + + private fun getContextFromPlayer(player: Player): ChannelHandlerContext? { + val nmsPlayer = (player as CraftPlayer).handle + val connection: Connection = nmsPlayer.connection.connection + val channel = connection.channel + return channel.pipeline().context("packet_handler") + } + + private fun readVarInt(buf: ByteBuf): Int { + var numRead = 0 + var result = 0 + var read: Byte + do { + read = buf.readByte() + val value = (read.toInt() and 0b01111111) + result = result or (value shl (7 * numRead)) + numRead++ + if (numRead > 5) throw RuntimeException("VarInt too big") + } while ((read.toInt() and 0b10000000) != 0) + return result + } + + private fun readString(buf: ByteBuf): String { + val length = readVarInt(buf) + val bytes = ByteArray(length) + buf.readBytes(bytes) + return String(bytes, Charsets.UTF_8) + } +} From c9ccb1b2882b60e4fa6e1f59f247fb6b62df47c2 Mon Sep 17 00:00:00 2001 From: Alepando Date: Fri, 18 Jul 2025 00:44:25 -0600 Subject: [PATCH 02/20] via Dialog test --- .../alepando/dev/dialogapi/DialogAPI.kt | 4 + .../body/types/PlainMessageDialogBody.kt | 2 +- .../dev/dialogapi/executor/PlayerOpener.kt | 8 ++ .../dev/dialogapi/factory/button/Button.kt | 15 ++- .../factory/button/data/ButtonData.kt | 12 +- .../factory/button/data/DataContainer.kt | 13 ++- .../factory/button/data/KeyedAction.kt | 11 +- .../dev/dialogapi/factory/data/DialogData.kt | 6 +- .../factory/data/ResourceLocation.kt | 16 ++- .../dialogapi/packets/parser/PayloadParser.kt | 51 +++++++++ .../dev/dialogapi/types/ConfirmationDialog.kt | 4 +- .../dev/dialogapi/types/MultiActionDialog.kt | 6 +- .../dev/dialogapi/types/NoticeDialog.kt | 2 +- .../alepando/dev/dialogapi/util/Translator.kt | 16 ++- .../dev/viaDialog/factory/InventoryFactory.kt | 39 +++++++ .../alepando/dev/viaDialog/guis/AnvilGUI.kt | 21 ++++ .../viaDialog/inventory/DialogInventory.kt | 104 ++++++++++++++++++ .../dev/viaDialog/listeners/AnvilListener.kt | 36 ++++++ .../viaDialog/listeners/InventoryListener.kt | 41 +++++++ 19 files changed, 384 insertions(+), 23 deletions(-) create mode 100644 src/main/kotlin/alepando/dev/viaDialog/factory/InventoryFactory.kt create mode 100644 src/main/kotlin/alepando/dev/viaDialog/guis/AnvilGUI.kt create mode 100644 src/main/kotlin/alepando/dev/viaDialog/inventory/DialogInventory.kt create mode 100644 src/main/kotlin/alepando/dev/viaDialog/listeners/AnvilListener.kt create mode 100644 src/main/kotlin/alepando/dev/viaDialog/listeners/InventoryListener.kt diff --git a/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt b/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt index a65c36e..d42c753 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/DialogAPI.kt @@ -14,6 +14,8 @@ object DialogAPI { private var initialized = false + var plugin: Plugin? = null + /** * Initializes the Dialog API by registering necessary listeners and hooks. * @@ -23,6 +25,8 @@ object DialogAPI { if (initialized) return initialized = true + this.plugin = plugin + val pm: PluginManager = Bukkit.getPluginManager() ClientVersionSniffer.injectAll(plugin) pm.registerEvents(PlayerConnectionStatus(plugin), plugin) diff --git a/src/main/kotlin/alepando/dev/dialogapi/body/types/PlainMessageDialogBody.kt b/src/main/kotlin/alepando/dev/dialogapi/body/types/PlainMessageDialogBody.kt index da0ce90..2b1f350 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/body/types/PlainMessageDialogBody.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/body/types/PlainMessageDialogBody.kt @@ -10,7 +10,7 @@ import net.minecraft.server.dialog.body.PlainMessage * * @property contents The contents of the dialog body. */ -class PlainMessageDialogBody(width: Int, private val contents: Component) : DialogBody(width) { +class PlainMessageDialogBody(width: Int, val contents: Component) : DialogBody(width) { /** * Converts this dialog body to its NMS equivalent. * @return The NMS equivalent of this dialog body. diff --git a/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt b/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt index f08d746..ea9336b 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/executor/PlayerOpener.kt @@ -2,6 +2,8 @@ package alepando.dev.dialogapi.executor import alepando.dev.dialogapi.executor.events.PlayerOpenDialogEvent import alepando.dev.dialogapi.factory.Dialog +import alepando.dev.versionSupplier.packet.ClientVersionSniffer +import alepando.dev.viaDialog.inventory.DialogInventory import net.minecraft.core.Holder.Direct import org.bukkit.craftbukkit.entity.CraftPlayer import org.bukkit.entity.Player @@ -16,6 +18,12 @@ object PlayerOpener{ * @param dialog The dialog to open. */ fun Player.openDialog(dialog: Dialog) { + val protocolVersion = ClientVersionSniffer.getProtocolVersionForUUID(uniqueId) ?: 0 + if (protocolVersion < 766) { + DialogInventory().parse(this, dialog) + return + } + val craftPlayer = player as CraftPlayer val nmsPlayer = craftPlayer.handle diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/button/Button.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/button/Button.kt index 491cb2e..423e200 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/button/Button.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/button/Button.kt @@ -1,11 +1,12 @@ package alepando.dev.dialogapi.factory.button import alepando.dev.dialogapi.factory.Wrapper -import alepando.dev.dialogapi.factory.button.data.KeyedAction import alepando.dev.dialogapi.factory.button.data.ButtonData +import alepando.dev.dialogapi.factory.button.data.KeyedAction import net.minecraft.server.dialog.ActionButton import java.util.* + /** * Represents a button in a dialog. * @@ -13,8 +14,8 @@ import java.util.* * @property action The action to perform when this button is clicked. */ class Button( - private val data: ButtonData, - private val action: Optional = Optional.empty() + val data: ButtonData, + val action: Optional = Optional.empty() ): Wrapper { /** * Converts this button to its NMS equivalent. @@ -24,4 +25,12 @@ class Button( if(action.isEmpty) return ActionButton(data.toNMS(), Optional.empty()) return ActionButton(data.toNMS(),action.get().toNMS()) } + + companion object { + fun fromNMS(button: ActionButton): Button { + val buttonData = ButtonData.fromNMS(button.button) + val keyedAction = if (button.action.isPresent) Optional.of(KeyedAction.fromNMS(button.action.get())) else Optional.empty() + return Button(buttonData, keyedAction) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/ButtonData.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/ButtonData.kt index 0674647..d9800ee 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/ButtonData.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/ButtonData.kt @@ -1,6 +1,7 @@ package alepando.dev.dialogapi.factory.button.data import alepando.dev.dialogapi.factory.Wrapper +import alepando.dev.dialogapi.util.Translator import net.minecraft.network.chat.Component import net.minecraft.server.dialog.CommonButtonData import java.util.* @@ -13,7 +14,7 @@ import java.util.* * @property tooltip The tooltip of the button. */ class ButtonData( - private val label: Component, + val label: Component, private val width: Int, private val tooltip: Optional = Optional.empty() ): Wrapper { @@ -24,4 +25,13 @@ class ButtonData( override fun toNMS(): CommonButtonData{ return CommonButtonData(label,tooltip,width) } + + companion object { + fun fromNMS(buttonData: CommonButtonData): ButtonData { + val label = buttonData.label + val width = buttonData.width + val tooltip = buttonData.tooltip + return ButtonData(label, width, tooltip) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/DataContainer.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/DataContainer.kt index 316d32c..a2d150a 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/DataContainer.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/DataContainer.kt @@ -1,5 +1,8 @@ package alepando.dev.dialogapi.factory.button.data +import alepando.dev.dialogapi.util.Translator.toPersistentDataContainer +import net.minecraft.nbt.CompoundTag +import org.bukkit.Bukkit import org.bukkit.NamespacedKey import org.bukkit.craftbukkit.persistence.CraftPersistentDataContainer import org.bukkit.craftbukkit.persistence.CraftPersistentDataTypeRegistry @@ -20,7 +23,7 @@ class DataContainer { /** * The underlying [PersistentDataContainer] where all key-value pairs are stored. */ - internal val container: PersistentDataContainer = create() + internal var container: PersistentDataContainer = create() /** * Adds a new key-value pair to the container. @@ -41,4 +44,12 @@ class DataContainer { private fun create(): PersistentDataContainer { return CraftPersistentDataContainer(CraftPersistentDataTypeRegistry()) } + + companion object { + fun fromNMS(tag: CompoundTag): DataContainer { + val dataContainer = DataContainer() + dataContainer.container = tag.toPersistentDataContainer(Bukkit.getPluginManager().plugins[0]) + return dataContainer + } + } } diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/KeyedAction.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/KeyedAction.kt index 95d6953..68b2d2c 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/KeyedAction.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/button/data/KeyedAction.kt @@ -14,7 +14,7 @@ import java.util.* * @property resourceLocation The resource location identifying this action. */ class KeyedAction( - private val resourceLocation: ResourceLocation, + val resourceLocation: ResourceLocation, private val additions: Optional ):Wrapper> { @@ -28,4 +28,13 @@ class KeyedAction( return Optional.of(CustomAll(resourceLocation.toNMS(), Optional.of(additions.get().container.toCompoundTag()))) } + companion object { + fun fromNMS(action: Action): KeyedAction { + val customAction = action as CustomAll + val resourceLocation = ResourceLocation.fromNMS(customAction.id) + val additions = if (customAction.additions.isPresent) Optional.of(DataContainer.fromNMS(customAction.additions.get())) else Optional.empty() + return KeyedAction(resourceLocation, additions) + } + } + } \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/data/DialogData.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/data/DialogData.kt index ac4582e..2c043bc 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/data/DialogData.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/data/DialogData.kt @@ -31,13 +31,13 @@ typealias NMSInput = net.minecraft.server.dialog.Input * @property inputs A list of [Input] elements allowing user interaction. */ class DialogData internal constructor( - private val title: Component, + val title: Component, private val externalTitle: Optional, private val canCloseWithEscape: Boolean, private val pause: Boolean, private val afterAction: DialogAction, - private val dialogBody: List>, - private val inputs: List>, + val dialogBody: List>, + val inputs: List>, ): Wrapper { /** diff --git a/src/main/kotlin/alepando/dev/dialogapi/factory/data/ResourceLocation.kt b/src/main/kotlin/alepando/dev/dialogapi/factory/data/ResourceLocation.kt index c400d2f..de60211 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/factory/data/ResourceLocation.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/factory/data/ResourceLocation.kt @@ -1,11 +1,11 @@ package alepando.dev.dialogapi.factory.data import alepando.dev.dialogapi.factory.Wrapper -import net.minecraft.resources.ResourceLocation +import net.minecraft.resources.ResourceLocation as NMSResourceLocation /** * Represents a resource location, consisting of a namespace and a path. - * This is a wrapper around Minecraft's [ResourceLocation]. + * This is a wrapper around Minecraft's [NMSResourceLocation]. * * @property namespace The namespace of the resource location. * @property path The path of the resource location. @@ -13,10 +13,16 @@ import net.minecraft.resources.ResourceLocation class ResourceLocation( private val namespace: String, private val path: String -): Wrapper { +): Wrapper { /** * Converts this resource location to its NMS equivalent. - * @return The NMS [ResourceLocation]. + * @return The NMS [NMSResourceLocation]. */ - override fun toNMS(): ResourceLocation { return ResourceLocation.fromNamespaceAndPath(namespace,path) } + override fun toNMS(): NMSResourceLocation { return NMSResourceLocation.fromNamespaceAndPath(namespace,path) } + + companion object { + fun fromNMS(location: NMSResourceLocation): ResourceLocation { + return ResourceLocation(location.namespace, location.path) + } + } } \ No newline at end of file diff --git a/src/main/kotlin/alepando/dev/dialogapi/packets/parser/PayloadParser.kt b/src/main/kotlin/alepando/dev/dialogapi/packets/parser/PayloadParser.kt index 02d88be..d7eb24c 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/packets/parser/PayloadParser.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/packets/parser/PayloadParser.kt @@ -65,4 +65,55 @@ internal object PayloadParser { } } + fun toCompoundTag(values: InputValueList): CompoundTag { + val compound = CompoundTag() + for (value in values.list) { + val tag = when (val data = value.value) { + is Byte -> ByteTag.valueOf(data) + is Short -> ShortTag.valueOf(data) + is Int -> IntTag.valueOf(data) + is Long -> LongTag.valueOf(data) + is Float -> FloatTag.valueOf(data) + is Double -> DoubleTag.valueOf(data) + is String -> StringTag.valueOf(data) + is ByteArray -> ByteArrayTag(data) + is IntArray -> IntArrayTag(data) + is LongArray -> LongArrayTag(data) + is List<*> -> { + val tagList = ListTag() + data.forEach { item -> + when (item) { + is String -> tagList.add(StringTag.valueOf(item)) + is Int -> tagList.add(IntTag.valueOf(item)) + is Byte -> tagList.add(ByteTag.valueOf(item)) + is Short -> tagList.add(ShortTag.valueOf(item)) + is Long -> tagList.add(LongTag.valueOf(item)) + is Float -> tagList.add(FloatTag.valueOf(item)) + is Double -> tagList.add(DoubleTag.valueOf(item)) + } + } + tagList + } + + is Map<*, *> -> { + val subCompound = CompoundTag() + data.forEach { (k, v) -> + if (k is String && v is String) { + subCompound.put(k, StringTag.valueOf(v)) + } + } + subCompound + } + + else -> { + Bukkit.getLogger().warning("Unsupported input value type: ${data.javaClass.simpleName}") + null + } + } + + if (tag != null) compound.put(value.key, tag) + } + return compound + } + } diff --git a/src/main/kotlin/alepando/dev/dialogapi/types/ConfirmationDialog.kt b/src/main/kotlin/alepando/dev/dialogapi/types/ConfirmationDialog.kt index f777ebf..49d0d56 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/types/ConfirmationDialog.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/types/ConfirmationDialog.kt @@ -22,8 +22,8 @@ typealias NMSConfirmationDialog = net.minecraft.server.dialog.ConfirmationDialog * @param dynamicListener An optional [DynamicListener] for handling Bukkit events related to this dialog. */ class ConfirmationDialog( - private val yesButton: ActionButton, - private val noButton: ActionButton, data: DialogData, + val yesButton: ActionButton, + val noButton: ActionButton, data: DialogData, dynamicListener: Optional = Optional.empty() ): Dialog(data, dynamicListener){ diff --git a/src/main/kotlin/alepando/dev/dialogapi/types/MultiActionDialog.kt b/src/main/kotlin/alepando/dev/dialogapi/types/MultiActionDialog.kt index 65f9220..b270c49 100644 --- a/src/main/kotlin/alepando/dev/dialogapi/types/MultiActionDialog.kt +++ b/src/main/kotlin/alepando/dev/dialogapi/types/MultiActionDialog.kt @@ -20,9 +20,9 @@ typealias NMSMultiActionDialog = MultiActionDialog */ class MultiActionDialog( data: DialogData, - private val buttons: List