From a08ba6f7f87a9fb63fa3ebf95391048cbea7bff3 Mon Sep 17 00:00:00 2001 From: Ammo Date: Fri, 13 Jun 2025 23:59:44 +0200 Subject: [PATCH 1/4] feat: introduce new gui api, working static, outline and paginated pane with some dsl features. --- .idea/modules.xml | 12 - gradle.properties | 2 +- gradle/libs.versions.toml | 2 - .../surf-api-bukkit-api/build.gradle.kts | 1 - .../bukkit/api/inventory/SinglePlayerGui.kt | 9 - .../surfapi/bukkit/api/inventory/SurfGui.kt | 51 --- .../surfapi/bukkit/api/inventory/dsl/gui.kt | 97 +++-- .../surfapi/bukkit/api/inventory/dsl/panes.kt | 131 ------- .../bukkit/api/inventory/dsl/patterns.kt | 2 - .../surfapi/bukkit/api/inventory/dsl/slot.kt | 7 - .../surfapi/bukkit/api/inventory/gui/Gui.kt | 181 ++++++++++ .../bukkit/api/inventory/gui/NamedGui.kt | 21 ++ .../api/inventory/gui/types/ChestGui.kt | 114 ++++++ .../api/inventory/gui/utils/InventoryBased.kt | 10 + .../api/inventory/gui/utils/MergedGui.kt | 16 + .../bukkit/api/inventory/item/GuiItem.kt | 104 ++++-- .../bukkit/api/inventory/item/SurfGuiItem.kt | 24 -- .../api/inventory/item/UpdatableGuiItem.kt | 18 + .../bukkit/api/inventory/item/button.kt | 2 - .../api/inventory/listener/GuiListener.kt | 210 +++++++++++ .../surfapi/bukkit/api/inventory/pane/Pane.kt | 121 +++++++ .../api/inventory/pane/SubmitItemPane.kt | 308 ---------------- .../pane/components/PagingButtons.kt | 187 ++++++++++ .../api/inventory/pane/panes/OutlinePane.kt | 330 ++++++++++++++++++ .../api/inventory/pane/panes/PaginatedPane.kt | 276 +++++++++++++++ .../api/inventory/pane/panes/StaticPane.kt | 227 ++++++++++++ .../api/inventory/pane/utils/Flippable.kt | 8 + .../bukkit/api/inventory/pane/utils/Mask.kt | 130 +++++++ .../api/inventory/pane/utils/Orientable.kt | 11 + .../api/inventory/pane/utils/Pattern.kt | 158 +++++++++ .../api/inventory/pane/utils/Rotatable.kt | 7 + .../api/inventory/types/SurfChestGui.kt | 40 --- .../api/inventory/utils/GeometryUtils.kt | 44 +++ .../api/inventory/utils/InventoryComponent.kt | 255 ++++++++++++++ .../inventory/utils/PlayerInventoryCache.kt | 96 +++++ .../bukkit/api/inventory/utils/Priority.kt | 36 ++ .../bukkit/api/inventory/utils/Slot.kt | 60 ++++ .../api/inventory/view/InventoryViewUtil.kt | 21 ++ .../serverbound/ContainerClickPacket.kt | 24 ++ .../serverbound/ContainerClosePacket.kt | 8 + .../surfapi/bukkit/test/BukkitPluginMain.java | 2 + .../test/command/SurfApiTestCommand.java | 2 - .../gui/InventoryFrameworkTest.java | 29 -- .../surfapi/bukkit/test/command/GuiCommand.kt | 11 + .../surfapi/bukkit/test/gui/ExampleGui.kt | 81 +++++ .../surf-api-bukkit-server/build.gradle.kts | 3 +- .../serverbound/ContainerClickPacketImpl.kt | 35 ++ .../serverbound/ContainerClosePacketImpl.kt | 12 + .../bukkit/server/listener/ListenerManager.kt | 4 + .../bukkit/server/nms/nms-extensions.kt | 16 + 50 files changed, 2872 insertions(+), 684 deletions(-) delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/panes.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/patterns.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/slot.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/SurfGuiItem.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/button.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Flippable.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Orientable.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Pattern.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Rotatable.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClickPacket.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClosePacket.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/subcommands/gui/InventoryFrameworkTest.java create mode 100644 surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/GuiCommand.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/gui/ExampleGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClickPacketImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClosePacketImpl.kt diff --git a/.idea/modules.xml b/.idea/modules.xml index b3f03b10..b0a65603 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -7,16 +7,12 @@ - - - - @@ -31,31 +27,23 @@ - - - - - - - - diff --git a/gradle.properties b/gradle.properties index fc2f1726..aab0d4aa 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,5 +7,5 @@ org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled javaVersion=21 mcVersion=1.21.4 group=dev.slne.surf -version=1.21.4-2.15.1-SNAPSHOT +version=1.21.4-2.16.0-SNAPSHOT relocationPrefix=dev.slne.surf.surfapi.libs \ No newline at end of file diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index 35520229..1bfffc84 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -50,7 +50,6 @@ reflection-remapper = "0.1.1" brigadier = "1.0.18" configurate = "4.1.2" more-persistent-data-types = "2.4.0" -inventoryframework = "0.10.19" flogger = "0.8" aide-reflection = "1.3" auto-service = "1.1.1" @@ -136,7 +135,6 @@ configurate-yaml = { module = "org.spongepowered:configurate-yaml", version.ref configurate-jackson = { module = "org.spongepowered:configurate-jackson", version.ref = "configurate" } configurate-kotlin = { module = "org.spongepowered:configurate-extra-kotlin", version.ref = "configurate" } more-persistent-data-types = { module = "com.jeff_media:MorePersistentDataTypes", version.ref = "more-persistent-data-types" } -inventoryframework = { module = "com.github.stefvanschie.inventoryframework:IF", version.ref = "inventoryframework" } flogger = { module = "com.google.flogger:flogger", version.ref = "flogger" } flogger-slf4j-backend = { module = "com.google.flogger:flogger-slf4j-backend", version.ref = "flogger" } aide-reflection = { module = "tech.hiddenproject:aide-reflection", version.ref = "aide-reflection" } diff --git a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts index c1e95fac..c3353b95 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts @@ -11,7 +11,6 @@ dependencies { compileOnlyApi(libs.commandapi.bukkit) compileOnlyApi(libs.reflection.remapper) compileOnlyApi(libs.more.persistent.data.types) - compileOnlyApi(libs.inventoryframework) api(libs.commandapi.bukkit.kotlin) compileOnlyApi(libs.mccoroutine.folia.api) diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui.kt deleted file mode 100644 index 7f3cc95c..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SinglePlayerGui.kt +++ /dev/null @@ -1,9 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory - -import org.bukkit.entity.Player - -interface SinglePlayerGui : SurfGui { - val player: Player - - fun open() = gui.show(player) -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui.kt deleted file mode 100644 index 0983099b..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/SurfGui.kt +++ /dev/null @@ -1,51 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory - -import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui -import com.github.stefvanschie.inventoryframework.pane.StaticPane -import com.github.stefvanschie.inventoryframework.pane.util.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.PaneMarker -import dev.slne.surf.surfapi.bukkit.api.inventory.item.SurfGuiItem -import org.bukkit.entity.HumanEntity -import org.bukkit.event.inventory.InventoryCloseEvent -import org.bukkit.inventory.ItemStack -import org.bukkit.plugin.java.JavaPlugin - -interface SurfGui { - val parent: SurfGui? - val gui: NamedGui - - fun HumanEntity.backToParent() { - server.scheduler.runTaskLater(JavaPlugin.getProvidingPlugin(SurfGui::class.java), Runnable { - if (parent != null) { - val gui = parent!!.gui - gui.show(this) - gui.update() - } else { - closeInventory(InventoryCloseEvent.Reason.PLUGIN) - } - }, 1L) - } - - fun walkParents(): List = generateSequence(this) { it.parent }.toList() - - fun StaticPane.item( - slot: Slot, - item: ItemStack? = null, - init: (@PaneMarker SurfGuiItem).() -> Unit, - ) { - val guiItem = SurfGuiItem(item) - guiItem.init() - - if (!guiItem.condition()) { - return - } - - if (this@SurfGui is SinglePlayerGui) { - if (guiItem.itemPermission?.let { player.hasPermission(it) } == false) { - return - } - } - - addItem(guiItem, slot) - } -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt index bb8cb947..0e932ed2 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt @@ -2,11 +2,13 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.dsl -import com.github.stefvanschie.inventoryframework.gui.type.util.MergedGui -import com.github.stefvanschie.inventoryframework.pane.StaticPane -import com.github.stefvanschie.inventoryframework.pane.util.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.types.SurfChestGui -import dev.slne.surf.surfapi.bukkit.api.inventory.types.SurfChestSinglePlayerGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestSinglePlayerGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.MergedGui +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.components.PagingButtons +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot import net.kyori.adventure.text.Component import org.bukkit.entity.Player import org.jetbrains.annotations.Range @@ -18,75 +20,100 @@ import kotlin.contracts.contract @DslMarker annotation class MenuMarker +fun MergedGui.pagingButtons( + slot: Slot, + paginatedPane: PaginatedPane, + length: @Range(from = 1, to = 9) Int = 9, + init: (@PaneMarker PagingButtons).() -> Unit, +): PagingButtons { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + + return PagingButtons(slot, paginatedPane, length).apply { + init() + addPane(this) + } +} + +fun MergedGui.paginatedPane( + slot: Slot, + height: @Range(from = 1, to = 6) Int, + length: @Range(from = 1, to = 9) Int = 9, + init: (@PaneMarker PaginatedPane).() -> Unit, +): PaginatedPane { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + + return PaginatedPane(slot, length, height).apply { + init() + addPane(this) + } +} + fun MergedGui.staticPane( slot: Slot, height: @Range(from = 1, to = 6) Int, length: @Range(from = 1, to = 9) Int = 9, init: (@PaneMarker StaticPane).() -> Unit, -) { +): StaticPane { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - val pane = StaticPane(slot, length, height) - pane.init() - addPane(pane) + return StaticPane(slot, length, height).apply { + init() + addPane(this) + } } fun menu( title: Component, - rows: @Range(from = 2, to = 6) Int = 6, - init: @MenuMarker SurfChestGui.() -> Unit, -): SurfChestGui { + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestGui.() -> Unit, +): ChestGui { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - val menu = SurfChestGui(title, rows) - menu.init() - return menu + return ChestGui(title, size).apply { init() } } fun playerMenu( title: Component, player: Player, - rows: @Range(from = 2, to = 6) Int = 6, - init: @MenuMarker SurfChestSinglePlayerGui.() -> Unit, -): SurfChestSinglePlayerGui { + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestSinglePlayerGui.() -> Unit, +): ChestSinglePlayerGui { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - val menu = SurfChestSinglePlayerGui(title, player, rows) - menu.init() - return menu + return ChestSinglePlayerGui(player, title, size).apply { init() } } -fun SurfChestGui.childMenu( +fun ChestGui.childMenu( title: Component, - rows: @Range(from = 2, to = 6) Int, - init: @MenuMarker SurfChestGui.() -> Unit, -): SurfChestGui { + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestGui.() -> Unit, +): ChestGui { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - val menu = SurfChestGui(title, rows, this) - menu.init() - return menu + return ChestGui(title, size, this).apply { init() } } -fun SurfChestSinglePlayerGui.childPlayerMenu( +fun ChestSinglePlayerGui.childPlayerMenu( title: Component, - rows: @Range(from = 2, to = 6) Int, - init: @MenuMarker SurfChestSinglePlayerGui.() -> Unit, -): SurfChestSinglePlayerGui { + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestSinglePlayerGui.() -> Unit, +): ChestSinglePlayerGui { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - val menu = SurfChestSinglePlayerGui(title, player, rows, this) - menu.init() - return menu + return ChestSinglePlayerGui(player, title, size, this).apply { init() } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/panes.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/panes.kt deleted file mode 100644 index 34635aaf..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/panes.kt +++ /dev/null @@ -1,131 +0,0 @@ -@file:OptIn(ExperimentalContracts::class) - -package dev.slne.surf.surfapi.bukkit.api.inventory.dsl - -import com.github.stefvanschie.inventoryframework.gui.GuiItem -import com.github.stefvanschie.inventoryframework.pane.OutlinePane -import com.github.stefvanschie.inventoryframework.pane.Pane -import com.github.stefvanschie.inventoryframework.pane.StaticPane -import com.github.stefvanschie.inventoryframework.pane.util.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.item.guiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.SubmitItemPane -import dev.slne.surf.surfapi.bukkit.api.inventory.types.SurfChestGui -import org.bukkit.Material -import org.bukkit.inventory.ItemStack -import org.jetbrains.annotations.Range -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -@PaneMarker -class StaticPaneScope(slot: Slot, length: Int, height: Int) : StaticPane(slot, length, height) - -fun SurfChestGui.drawOutline( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int = 9, - init: @PaneMarker OutlinePane.() -> Unit -): OutlinePane { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val pane = OutlinePane(slot, length, height, Pane.Priority.LOWEST) - pane.init() - addPane(pane) - - return pane -} - -fun SurfChestGui.drawOutline( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int = 9, - item: GuiItem = guiItem(Material.GRAY_STAINED_GLASS_PANE) { isCancelled = true } -) = drawOutline(slot, height, length) { - addItem(item) - setRepeat(true) -} - -@OptIn(ExperimentalContracts::class) -fun SurfChestGui.drawOutlineRow( - row: @Range(from = 0, to = 5) Int, - length: @Range(from = 1, to = 9) Int = 9, - init: @PaneMarker OutlinePane.() -> Unit -): OutlinePane { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - return drawOutline(slot(0, row), 1, length, init) -} - -fun SurfChestGui.drawOutlineRow( - row: @Range(from = 0, to = 5) Int, - length: @Range(from = 1, to = 9) Int = 9, - item: GuiItem = guiItem(Material.GRAY_STAINED_GLASS_PANE) { isCancelled = true } -) = drawOutlineRow(row, length) { - addItem(item) - setRepeat(true) -} - -fun SurfChestGui.makeStaticPane( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int, - init: StaticPane.() -> Unit -): StaticPane { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val pane = StaticPane(slot, length, height) - pane.init() - addPane(pane) - - return pane -} - -fun SurfChestGui.makeSubmitItemPane( - slot: Slot, - length: @Range(from = 1, to = 6) Int, - height: @Range(from = 1, to = 6) Int, - filter: List, - init: SubmitItemPane.() -> Unit = {} -): SubmitItemPane { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val pane = SubmitItemPane(slot, length, height, filter) - pane.init() - addPane(pane) - - return pane -} - -fun SurfChestGui.makeSubmitItemPane( - slot: Slot, - length: @Range(from = 1, to = 6) Int, - height: @Range(from = 1, to = 6) Int, - filter: (ItemStack) -> Boolean, - init: SubmitItemPane.() -> Unit = {}, -): SubmitItemPane { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val pane = SubmitItemPane(slot, length, height, filter) - pane.init() - addPane(pane) - - return pane -} - -fun SurfChestGui.addItem( - slot: Slot, - item: GuiItem -) = addPane(StaticPane(slot, 1, 1).apply { addItem(item, 0, 0) }) - -fun SurfChestGui.addItems( - vararg items: Pair -) = items.forEach { (slot, item) -> addItem(slot, item) } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/patterns.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/patterns.kt deleted file mode 100644 index 60feb449..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/patterns.kt +++ /dev/null @@ -1,2 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.dsl - diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/slot.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/slot.kt deleted file mode 100644 index 1624fc16..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/slot.kt +++ /dev/null @@ -1,7 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.dsl - -import com.github.stefvanschie.inventoryframework.pane.util.Slot -import org.jetbrains.annotations.Range - -fun slot(x: @Range(from = 0, to = 8) Int, y: @Range(from = 0, to = 5) Int) = Slot.fromXY(x, y) -fun slot(index: Int) = Slot.fromIndex(index) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt new file mode 100644 index 00000000..8270e64a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt @@ -0,0 +1,181 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.PlayerInventoryCache +import dev.slne.surf.surfapi.core.api.util.logger +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectSet +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryDragEvent +import org.bukkit.event.inventory.InventoryEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import java.util.* + +abstract class Gui internal constructor( + val parent: Gui? = null, +) : Cloneable { + + private val log = logger() + + lateinit var backingInventory: Inventory + + internal val cache = PlayerInventoryCache() + + private var onTopClick: (InventoryClickEvent) -> Unit = {} + fun onTopClick(block: (InventoryClickEvent) -> Unit) { + onTopClick = block + } + + private var onBottomClick: (InventoryClickEvent) -> Unit = {} + fun onBottomClick(block: (InventoryClickEvent) -> Unit) { + onBottomClick = block + } + + private var onGlobalClick: (InventoryClickEvent) -> Unit = {} + fun onGlobalClick(block: (InventoryClickEvent) -> Unit) { + onGlobalClick = block + } + + private var onOutsideClick: (InventoryClickEvent) -> Unit = {} + fun onOutsideClick(block: (InventoryClickEvent) -> Unit) { + onOutsideClick = block + } + + private var onTopDrag: (InventoryDragEvent) -> Unit = {} + fun onTopDrag(block: (InventoryDragEvent) -> Unit) { + onTopDrag = block + } + + private var onBottomDrag: (InventoryDragEvent) -> Unit = {} + fun onBottomDrag(block: (InventoryDragEvent) -> Unit) { + onBottomDrag = block + } + + private var onGlobalDrag: (InventoryDragEvent) -> Unit = {} + fun onGlobalDrag(block: (InventoryDragEvent) -> Unit) { + onGlobalDrag = block + } + + var onClose: (InventoryCloseEvent) -> Unit = {} + + var shouldNavigateParentOnClose: Boolean = false + + internal var updating: Boolean = false + private set + + abstract fun show(player: Player) + abstract fun click(event: InventoryClickEvent) + abstract fun isPlayerInventoryUsed(): Boolean + abstract val viewers: ObjectSet + protected abstract fun updateAllItems(): Object2IntMap + protected abstract fun updateItem0(item: UpdatableGuiItem): Int? + + fun update() { + updating = true + + val updatedItems = updateAllItems() + for ((item, slot) in updatedItems) { + if (item.visible) { + backingInventory.setItem(slot, item.itemStack) + } else { + backingInventory.setItem(slot, ItemStack.empty()) + } + } + + for (viewer in viewers) { + val cursor = viewer.itemOnCursor + + viewer.setItemOnCursor(ItemStack.empty()) + show(viewer) + viewer.setItemOnCursor(cursor) + } + + updating = false + } + + fun updateItem(item: UpdatableGuiItem) { + if (updating) return + + val slot = updateItem0(item) ?: return + if (item.visible) { + backingInventory.setItem(slot, item.itemStack) + } else { + backingInventory.setItem(slot, ItemStack.empty()) + } + + updating = false + } + + protected fun addInventory(inventory: Inventory, gui: Gui) { + GUI_INVENTORIES[inventory] = gui + } + + internal fun callOnTopClick(event: InventoryClickEvent) { + callCallback(onTopClick, event, "onTopClick") + } + + internal fun callOnBottomClick(event: InventoryClickEvent) { + callCallback(onBottomClick, event, "onBottomClick") + } + + internal fun callOnGlobalClick(event: InventoryClickEvent) { + callCallback(onGlobalClick, event, "onGlobalClick") + } + + internal fun callOnOutsideClick(event: InventoryClickEvent) { + callCallback(onOutsideClick, event, "onOutsideClick") + } + + internal fun callOnTopDrag(event: InventoryDragEvent) { + callCallback(onTopDrag, event, "onTopDrag") + } + + internal fun callOnBottomDrag(event: InventoryDragEvent) { + callCallback(onBottomDrag, event, "onBottomDrag") + } + + internal fun callOnGlobalDrag(event: InventoryDragEvent) { + callCallback(onGlobalDrag, event, "onGlobalDrag") + } + + internal fun callOnClose(event: InventoryCloseEvent) { + callCallback(onClose, event, "onClose") + } + + private fun callCallback( + callback: (T).() -> Unit, + event: T, + name: String, + ) { + try { + callback.invoke(event) + } catch (exception: Exception) { + log.atSevere().withCause(exception).log(buildString { + append("Exception while handling $name") + + if (event is InventoryClickEvent) { + append(", slot=${event.slot}") + } + }) + } + } + + fun navigateToParent(player: Player) { + if (parent == null) return + + parent.show(player) + parent.update() + } + + fun walkParents(): Sequence = generateSequence(this) { it.parent } + + companion object { + private val GUI_INVENTORIES = WeakHashMap() + + fun getGui(inventory: Inventory) = GUI_INVENTORIES[inventory] + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt new file mode 100644 index 00000000..7d7cd6fc --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui + +import net.kyori.adventure.text.Component + +abstract class NamedGui internal constructor( + title: Component, + parent: Gui? = null, +) : Gui(parent) { + + var title: Component = Component.empty() + set(value) { + field = value + titleDirty = true + } + + protected var titleDirty = false + + init { + this.title = title + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt new file mode 100644 index 00000000..070d2b01 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt @@ -0,0 +1,114 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.types + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.MenuMarker +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.InventoryBased +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.MergedGui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.toObjectList +import dev.slne.surf.surfapi.core.api.util.toObjectSet +import it.unimi.dsi.fastutil.objects.Object2IntMap +import net.kyori.adventure.text.Component +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent + +open class ChestGui internal constructor( + title: Component, + var size: ChestGuiSize = ChestGuiSize.FIVE_ROWS, + parent: Gui? = null, +) : NamedGui(title, parent), MergedGui, InventoryBased { + + private var rows: Int = size.rows + set(value) { + inventoryComponent = InventoryComponent(9, value + 4) + + for (pane in inventoryComponent.panes) { + inventoryComponent.addPane(pane) + } + + sizeDirty = true + } + + override var inventoryComponent = InventoryComponent(9, rows + 4) + private var sizeDirty = false + + override val panes get() = inventoryComponent.panes + override val items get() = panes.flatMap { it.items }.toObjectList() + override val viewers get() = backingInventory.viewers.filterIsInstance().toObjectSet() + override fun updateAllItems(): Object2IntMap = + panes.fold(mutableObject2IntMapOf()) { acc, pane -> + acc.apply { putAll(pane.updateItems()) } + } + + override fun updateItem0(item: UpdatableGuiItem) = + panes.find { it.items.contains(item) }?.updateItem(item) + + + override fun addPane(pane: Pane) { + inventoryComponent.addPane(pane) + } + + override fun createInventory() = Bukkit.createInventory(this, rows * 9, title) + override fun getInventory() = backingInventory + + override fun show(player: Player) { + if (titleDirty || sizeDirty) { + backingInventory = createInventory() + + titleDirty = false + sizeDirty = false + } + + backingInventory.clear() + + val height = inventoryComponent.height + inventoryComponent.display() + + val topComponent = inventoryComponent.excludeRows(height - 4, height - 1) + val bottomComponent = inventoryComponent.excludeRows(0, height - 5) + + topComponent.placeItems(backingInventory, 0) + + if (bottomComponent.hasItem()) { + if (!cache.contains(player)) { + cache.storeAndClear(player) + } + + bottomComponent.placeItems(player.inventory, 0) + } + + player.openInventory(backingInventory) + } + + override fun clone() = super.clone() as ChestGui + + override fun click(event: InventoryClickEvent) { + inventoryComponent.click(this, event, event.rawSlot) + } + + override fun isPlayerInventoryUsed() = + inventoryComponent.excludeRows(0, inventoryComponent.height - 5).hasItem() + + enum class ChestGuiSize(val rows: Int) { + ONE_ROW(1), + TWO_ROWS(2), + THREE_ROWS(3), + FOUR_ROWS(4), + FIVE_ROWS(5), + SIX_ROWS(6); + } +} + +@MenuMarker +class ChestSinglePlayerGui internal constructor( + val player: Player, + title: Component, + size: ChestGuiSize = ChestGuiSize.SIX_ROWS, + parent: Gui? = null, +) : ChestGui(title, size, parent) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt new file mode 100644 index 00000000..6cd00bd6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt @@ -0,0 +1,10 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils + +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.InventoryHolder + +internal interface InventoryBased : InventoryHolder { + + fun createInventory(): Inventory + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt new file mode 100644 index 00000000..4b2c6e29 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt @@ -0,0 +1,16 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import it.unimi.dsi.fastutil.objects.ObjectList + +interface MergedGui { + + val inventoryComponent: InventoryComponent + + fun addPane(pane: Pane) + + val panes: ObjectList + val items: ObjectList +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt index 3b02b836..ddcda115 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt @@ -1,37 +1,89 @@ -@file:OptIn(ExperimentalContracts::class) - package dev.slne.surf.surfapi.bukkit.api.inventory.item -import com.github.stefvanschie.inventoryframework.gui.GuiItem -import org.bukkit.Material +import com.jeff_media.morepersistentdatatypes.DataType +import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.logger +import org.bukkit.NamespacedKey import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import org.bukkit.plugin.java.JavaPlugin +import java.util.* + +internal val GUI_ITEM_UUID_KEY = NamespacedKey( + JavaPlugin.getProvidingPlugin(GuiItem::class.java), + "sapi-uuid" +) + +@OptIn(NmsUseWithCaution::class) +open class GuiItem( + var itemStack: ItemStack, + internal var action: (InventoryClickEvent) -> Unit = {}, + val key: NamespacedKey = GUI_ITEM_UUID_KEY, + val uuid: UUID = UUID.randomUUID(), +) : Cloneable { + + var visible: Boolean = true + + init { + applyUuid() + } -fun guiItem(item: ItemStack, action: InventoryClickEvent.() -> Unit = {}): GuiItem { - contract { - callsInPlace(action, InvocationKind.UNKNOWN) + fun onClick(action: (InventoryClickEvent) -> Unit) { + this.action = action } - return GuiItem(item, action) -} + public override fun clone(): GuiItem { + val guiItem = GuiItem(itemStack.clone(), action, key, uuid) -fun guiItem( - material: Material, - item: ItemStack.() -> Unit, - action: InventoryClickEvent.() -> Unit = {} -): GuiItem { - contract { - callsInPlace(item, InvocationKind.EXACTLY_ONCE) - callsInPlace(action, InvocationKind.UNKNOWN) + guiItem.visible = visible + val meta = guiItem.itemStack.itemMeta + + if (meta != null) { + meta.persistentDataContainer.set(key, DataType.UUID, guiItem.uuid) + guiItem.itemStack.itemMeta = meta + } + + return guiItem } - return GuiItem(ItemStack(material).apply(item), action) -} + fun callAction(event: InventoryClickEvent) { + try { + action.invoke(event) + } catch (exception: Exception) { + log.atSevere().withCause(exception).log( + "Exception while handling click event in inventory '${ + InventoryViewUtil.getTitle(event.view) + }', slot=${event.slot}, item=${itemStack.type}" + ) + } + } + + fun applyUuid() { + itemStack.editMeta { + it.persistentDataContainer.set(key, DataType.UUID, uuid) + } + } -fun guiItem( - material: Material, - action: InventoryClickEvent.() -> Unit = {} -) = guiItem(material, {}, action) \ No newline at end of file + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GuiItem + + return uuid == other.uuid + } + + override fun hashCode(): Int { + return uuid.hashCode() + } + + override fun toString(): String { + return "GuiItem(itemStack=$itemStack, action=$action, key=$key, uuid=$uuid, visible=$visible)" + } + + companion object { + private val log = logger() + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/SurfGuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/SurfGuiItem.kt deleted file mode 100644 index 6a4cf99a..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/SurfGuiItem.kt +++ /dev/null @@ -1,24 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.item - -import com.github.stefvanschie.inventoryframework.gui.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.SinglePlayerGui -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.inventory.ItemStack - -class SurfGuiItem : GuiItem { - - constructor(item: ItemStack?) : super(item ?: ItemStack.empty()) - constructor() : super(ItemStack.empty()) - - var click: InventoryClickEvent.() -> Unit = {} - set(value) = setAction(value) - - var itemPermission: String? = null - private set - - var condition: () -> Boolean = { true } - - fun SinglePlayerGui.permission(permission: String) { - itemPermission = permission - } -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt new file mode 100644 index 00000000..c6b83d9c --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt @@ -0,0 +1,18 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.item + +import org.bukkit.NamespacedKey +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +class UpdatableGuiItem( + itemStack: ItemStack, + action: (InventoryClickEvent) -> Unit = {}, + internal var update: (UpdatableGuiItem) -> Boolean = { false }, + key: NamespacedKey = GUI_ITEM_UUID_KEY, + uuid: UUID = UUID.randomUUID(), +) : GuiItem(itemStack, action, key, uuid) { + fun onUpdate(update: () -> Boolean) { + this.update = { update() } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/button.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/button.kt deleted file mode 100644 index 80d60482..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/button.kt +++ /dev/null @@ -1,2 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.item - diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt new file mode 100644 index 00000000..d27a87d2 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt @@ -0,0 +1,210 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.listener + +import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin +import com.github.shynixn.mccoroutine.folia.launch +import com.github.shynixn.mccoroutine.folia.ticks +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil +import dev.slne.surf.surfapi.core.api.util.freeze +import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf +import kotlinx.coroutines.delay +import org.bukkit.entity.Player +import org.bukkit.event.EventHandler +import org.bukkit.event.EventPriority +import org.bukkit.event.Listener +import org.bukkit.event.entity.EntityPickupItemEvent +import org.bukkit.event.inventory.* +import org.bukkit.event.server.PluginDisableEvent +import org.bukkit.inventory.Inventory +import java.util.* + +class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { + + private val log = logger() + private val activeGuiInstances = mutableObjectSetOf() + + @EventHandler(ignoreCancelled = true) + fun InventoryClickEvent.onInventoryClick() { + val gui = getGui(inventory) ?: return + + val inventory = InventoryViewUtil.getInventory(view, rawSlot) ?: run { + gui.callOnOutsideClick(this) + return + } + + gui.callOnGlobalClick(this) + + if (inventory == InventoryViewUtil.getTopInventory(view)) { + gui.callOnTopClick(this) + } else { + gui.callOnBottomClick(this) + } + + gui.click(this) + + if (isCancelled) { + plugin.launch { + delay(1.ticks) + + whoClicked.inventory.setItemInOffHand(whoClicked.inventory.itemInOffHand) + } + } + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.MONITOR) + fun EntityPickupItemEvent.onEntityPickupItem() { + val player = entity as? Player ?: return + + val gui = getGui(InventoryViewUtil.getTopInventory(player.openInventory)) + if (gui == null || !gui.isPlayerInventoryUsed()) { + return + } + + val leftOver = gui.cache.add(player, item.itemStack) + + if (leftOver == 0) { + item.remove() + } else { + val itemStack = item.itemStack + itemStack.amount = leftOver + item.itemStack = itemStack + } + + isCancelled = true + } + + @EventHandler + fun InventoryDragEvent.onInventoryDrag() { + val gui = getGui(inventory) ?: return + + if (rawSlots.size > 1) { + var top = false + var bottom = false + + for (slot in rawSlots) { + val inventory = InventoryViewUtil.getInventory(view, slot) ?: continue + + if (inventory == InventoryViewUtil.getTopInventory(view)) { + top = true + } else if (inventory == InventoryViewUtil.getBottomInventory(view)) { + bottom = true + } + + if (top && bottom) break + } + + gui.callOnGlobalDrag(this) + + if (top) { + gui.callOnTopDrag(this) + } else if (bottom) { + gui.callOnBottomDrag(this) + } + } else { + val index = rawSlots.toTypedArray().first() + val slotType = InventoryViewUtil.getSlotType(view, index) + val even = type == DragType.EVEN + val clickType = if (even) ClickType.LEFT else ClickType.RIGHT + val action = if (even) InventoryAction.PLACE_SOME else InventoryAction.PLACE_ONE + + val previousCursor = InventoryViewUtil.getCursor(view) + InventoryViewUtil.setCursor(view, oldCursor) + + val clickEvent = InventoryClickEvent( + view, + slotType, + index, + clickType, + action + ) + + clickEvent.onInventoryClick() + + if (Objects.equals(InventoryViewUtil.getCursor(view), oldCursor)) { + InventoryViewUtil.setCursor(view, previousCursor) + } + + isCancelled = clickEvent.isCancelled + } + } + + @EventHandler(ignoreCancelled = true) + fun InventoryCloseEvent.onInventoryClose() { + val player = player as? Player ?: return + val gui = getGui(inventory) ?: return + + val playerInventory = player.inventory + playerInventory.setItemInOffHand(playerInventory.itemInOffHand) + + if (!gui.updating) { + gui.callOnClose(this) + gui.cache.restoreAndForget(player) + + if (gui.viewers.size == 1) { + activeGuiInstances.remove(gui) + } + + if (gui.shouldNavigateParentOnClose) { + plugin.launch { + delay(1.ticks) + + gui.navigateToParent(player) + } + } + } + } + + @EventHandler(ignoreCancelled = true) + fun InventoryOpenEvent.onInventoryOpen() { + val gui = getGui(inventory) ?: return + + activeGuiInstances.add(gui) + } + + @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) + fun PluginDisableEvent.onPluginDisable() { + if (this@GuiListener.plugin != this.plugin) return + + var counter = 0 + val maxCount = 10 + + while (!activeGuiInstances.isEmpty() && counter++ < maxCount) { + for (gui in activeGuiInstances.freeze()) { + for (viewer in gui.viewers.freeze()) { + viewer.closeInventory() + } + } + } + + if (counter == maxCount) { + log.atWarning().log( + "Unable to close GUIs on plugin disable. GUIs keep getting opened (tried: $maxCount times). " + + "This may lead to memory leaks. Please check your code for any issues." + ) + } + } + + @EventHandler(ignoreCancelled = true) + fun TradeSelectEvent.onTradeSelect() { + val gui = getGui(inventory) ?: return + + TODO("Implement TradeSelectEvent handling in GUI") + } + + private fun getGui(inventory: Inventory): Gui? { + val gui = Gui.getGui(inventory) + + if (gui != null) return gui + + val holder = inventory.holder + + if (holder is Gui) { + return holder + } + + return null + } + + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt new file mode 100644 index 00000000..25ca0296 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt @@ -0,0 +1,121 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane + +import com.jeff_media.morepersistentdatatypes.DataType +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +@OptIn(NmsUseWithCaution::class) +abstract class Pane( + var slot: Slot, + open var length: Int, + open var height: Int, + var priority: Priority = Priority.NORMAL, + val uuid: UUID = UUID.randomUUID(), +) : Cloneable { + + var onClick: ((InventoryClickEvent) -> Unit)? = null + var visible: Boolean = true + + internal abstract fun display( + component: InventoryComponent, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) + + internal abstract fun updateItems(): Object2IntMap + internal abstract fun updateItem(item: UpdatableGuiItem): Int? + + internal abstract fun click( + gui: Gui, + component: InventoryComponent, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean + + abstract val items: ObjectList + abstract val panes: ObjectList + + abstract fun clear() + + protected fun callOnClick(event: InventoryClickEvent) { + if (onClick == null) { + return + } + + try { + onClick?.invoke(event) + } catch (exception: Exception) { + throw RuntimeException(buildString { + append("Exception while handling click event in inventory '") + append(InventoryViewUtil.getTitle(event.view)) + append( + "slot=${event.slot}, for ${javaClass.simpleName}, x=${slot.getX(length)}, y=${ + slot.getY( + length + ) + }, length=$length, height=$height" + ) + }, exception) + } + } + + companion object { + @JvmStatic + protected fun matchesItem( + guiItem: GuiItem, + item: ItemStack, + ): Boolean { + val meta = item.itemMeta ?: return false + + return guiItem.uuid == meta.persistentDataContainer.get( + guiItem.key, + DataType.UUID + ) + } + + @JvmStatic + protected fun findMatchingItem( + items: Collection, + item: ItemStack, + ): T? { + for (guiItem in items) { + if (matchesItem(guiItem, item)) { + return guiItem + } + } + + return null + } + } + + public override fun clone(): Pane { + throw UnsupportedOperationException("The implementing pane has not overridden the clone method.") + } + + override fun toString(): String { + return "Pane(slot=$slot, length=$length, height=$height, priority=$priority, uuid=$uuid, onClick=$onClick, visible=$visible, items=$items, panes=$panes)" + } + + init { + if (length <= 0 || height <= 0) { + throw IllegalArgumentException("Length and height must be greater than 0 (length=$length, height=$height)") + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane.kt deleted file mode 100644 index 5d931509..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/SubmitItemPane.kt +++ /dev/null @@ -1,308 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.pane - -import com.github.stefvanschie.inventoryframework.gui.GuiItem -import com.github.stefvanschie.inventoryframework.gui.InventoryComponent -import com.github.stefvanschie.inventoryframework.gui.type.util.Gui -import com.github.stefvanschie.inventoryframework.pane.Pane -import com.github.stefvanschie.inventoryframework.pane.util.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.slot -import org.bukkit.Material -import org.bukkit.event.inventory.InventoryAction -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.inventory.ItemStack -import org.jetbrains.annotations.Range -import kotlin.math.min - - -class SubmitItemPane @JvmOverloads constructor( - slot: Slot, - length: @Range(from = 1, to = 6) Int, - height: @Range(from = 1, to = 6) Int, - private val filter: ItemStack.() -> Boolean, - priority: Priority = Priority.NORMAL -) : Pane(slot, length, height, priority) { - - constructor( - slot: Slot, - length: @Range(from = 1, to = 6) Int, - height: @Range(from = 1, to = 6) Int, - filter: List, - priority: Priority = Priority.NORMAL - ) : this(slot, length, height, { filter.contains(type) }, priority) - - private val _items = mutableMapOf() - val submittedItems get() = _items.toMap() - - override fun display( - inventoryComponent: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int - ) { -// val length = min(length, maxLength) -// val height = min(height, maxHeight) -// -// for ((location, item) in _items.filter { (_, item) -> item.isVisible }) { -// val x = location.getX(getLength()) -// val y = location.getY(getHeight()) -// -// if (x < 0 || x >= length || y < 0 || y >= height) { -// continue -// } -// -// val slot = getSlot() -// val finalRow = slot.getY(maxLength) + y + paneOffsetY -// val finalColumn = slot.getX(maxLength) + x + paneOffsetX -// -// inventoryComponent.setItem(item, finalColumn, finalRow) -// } - } - - override fun click( - gui: Gui, - inventoryComponent: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int - ): Boolean { - event.apply { - when (action) { - InventoryAction.PICKUP_ALL, InventoryAction.PICKUP_SOME, InventoryAction.PICKUP_HALF, InventoryAction.PICKUP_ONE -> { - val item = currentItem - - if (item == null || filter(item)) { - if (item == null) { - _items.remove(slot(slot)) - } else { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = item - } - } - - isCancelled = false - } else { - isCancelled = true - } - } - - InventoryAction.PLACE_ALL, InventoryAction.PLACE_SOME, InventoryAction.PLACE_ONE -> { - val item = cursor - - if (filter(item)) { - val previous = _items[slot(slot)] - if (previous == null || !previous.isSimilar(item)) { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = item - } - } else { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = previous.clone().apply { amount += item.amount } - } - } - - isCancelled = false - } else { - isCancelled = true - } - } - - InventoryAction.SWAP_WITH_CURSOR -> { - val cursorItem = cursor - val currentItem = currentItem - - if (currentItem == null || filter(currentItem)) { - if (currentItem == null) { - _items.remove(slot(slot)) - } else { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = currentItem - } - } - - isCancelled = false - } - - if (filter(cursorItem)) { - val previous = _items[slot(slot)] - if (previous == null || !previous.isSimilar(cursorItem)) { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = cursorItem - } - } else { - if (isInPane(slot, paneOffsetX, paneOffsetY, maxLength, maxHeight, inventoryComponent)) { - _items[slot(slot)] = - previous.clone().apply { amount += cursorItem.amount } - } - } - - isCancelled = false - } else { - isCancelled = true - } - } - - else -> { - isCancelled = true - return false - } - } - } - - println("current items: $_items") - - -// val itemToCheck = currentItem ?: cursor -// -// if (!filter(itemToCheck)) { -// event.isCancelled = true // Item wird blockiert -// return false -// } - - - // Wenn das geklickte Inventar unser Pane ist, fügen wir das Item hinzu oder entfernen es -// if (event.clickedInventory == event.inventory) { -// // Wenn das Slot-Item Luft ist, löschen wir es aus der Map -// if (itemToCheck.type == Material.AIR) { -// println("Removing item from slot $slot") -//// _items.remove(slot(slot)) // Stack wird entfernt -// } else { -// // Andernfalls fügen wir den Stack hinzu oder aktualisieren ihn -// println("Adding item to slot $slot with amount ${itemToCheck.amount}") -//// _items[slot(slot)] = itemToCheck // Gesamter Stack wird gespeichert -// } -// } else { -// // Wenn der Klick im Player-Inventar stattfindet, erlauben wir die Bewegung -// println("Clicked inventory is player inventory") -// } -// event.isCancelled = false // Erlaubt das Bewegen des Items - -// event.isCancelled = false // allow the user to move the item - - // if item was moved to our pane add it to the submitted items if it was removed from our pane remove it from the submitted items -// if (event.clickedInventory == event.inventory) { -// val length = min(length, maxLength) -// val height = min(height, maxHeight) -// val paneSlot = getSlot() -// -// val xPosition = paneSlot.getX(maxLength) -// val yPosition = paneSlot.getY(maxLength) -// val totalLength = inventoryComponent.length -// -// val adjustedSlot = -// slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) -// val x = adjustedSlot % totalLength -// val y = adjustedSlot / totalLength - - - //this isn't our item -// if (x < 0 || x >= length || y < 0 || y >= height) { -// return false -// } - -// for (y in 0 until height) { -// for (x in 0 until length) { -// val adjustedX = x + paneOffsetX -// val adjustedY = y + paneOffsetY -// -// val adjustedSlot1 = slot - adjustedX - totalLength * adjustedY -// val adjustedX1 = adjustedSlot1 % totalLength -// val adjustedY1 = adjustedSlot1 / totalLength -// -// if ((adjustedX1 < 0) || (adjustedX1 >= length) || (adjustedY1 < 0) || (adjustedY1 >= height)) { -// continue -// } -// -// val item = inventoryComponent.getItem(adjustedX1, adjustedY1) -// -// // update the item in the map -// if (item != null) { -// _items[slot(adjustedX1, adjustedY1)] = item -// } else { -// _items.remove(slot(adjustedX1, adjustedY1)) -// } -// } -// -// } - - // walk through all slots in the pane -// for (i in 0 until length * height) { -// -// val x1 = i % length -// val y1 = i / length -// val adjustedSlot1 = slot - (x1 + paneOffsetX) - totalLength * (y1 + paneOffsetY) -// val adjustedX = adjustedSlot1 % totalLength -// val adjustedY = adjustedSlot1 / totalLength -// -// if ((x1 < 0) || (x1 >= length) || (y1 < 0) || (y1 >= height)) { -// continue -// } -// -// val item = inventoryComponent.getItem(adjustedX, adjustedY) -// -// // update the item in the map -// if (item != null) { -// _items[slot(x1, y1)] = item -// } else { -// _items.remove(slot(x1, y1)) -// } -// } - -// println("clicked inventory is inventory") -// _items.remove(slot(slot)) -// } else { -// println("clicked inventory is not inventory") -// _items[slot(slot)] = currentItem ?: cursor -// } - - return true - } - - - private fun isInPane( - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - inventoryComponent: InventoryComponent - ): Boolean { - val length = min(length, maxLength) - val height = min(height, maxHeight) - - val paneSlot = getSlot() - - val xPosition = paneSlot.getX(maxLength) - val yPosition = paneSlot.getY(maxLength) - - val totalLength: Int = inventoryComponent.length - - val adjustedSlot = - slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) - - val x = adjustedSlot % totalLength - val y = adjustedSlot / totalLength - - return !(x < 0 || x >= length || y < 0 || y >= height) - } - - - override fun copy(): Pane { - val pane = SubmitItemPane(slot, length, height, filter, priority) - - pane._items.putAll(_items.map { (slot, item) -> slot to item.clone() }.toMap(pane._items)) - pane.isVisible = isVisible - pane.onClick = onClick - pane.uuid = uuid - - return pane - } - - override fun getItems(): MutableCollection = mutableListOf() - override fun getPanes(): MutableCollection = mutableListOf() - override fun clear() { - } -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt new file mode 100644 index 00000000..9c7cbe69 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt @@ -0,0 +1,187 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.components + +import dev.slne.surf.surfapi.bukkit.api.builder.ItemStack +import dev.slne.surf.surfapi.bukkit.api.builder.displayName +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import dev.slne.surf.surfapi.core.api.util.object2IntMapOf +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.Material +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +@OptIn(NmsUseWithCaution::class) +class PagingButtons( + slot: Slot, + val paginatedPane: PaginatedPane, + length: Int = 9, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : Pane(slot, length, 1, priority, uuid) { + + override val items: ObjectList + get() = mutableObjectListOf(backwardsButton, forwardsButton) + + override val panes: ObjectList + get() = mutableObjectListOf() + + override fun clear() { + throw UnsupportedOperationException("PagingButtons cannot be cleared.") + } + + internal var backwardsButton = GuiItem(ItemStack(Material.ARROW) { + displayName { + primary("Backwards") + } + }) + + fun setBackwardsButton( + item: GuiItem, + action: (InventoryClickEvent) -> Unit = {}, + ): PagingButtons { + this.backwardsButton = item.apply { + onClick = action + } + + return this + } + + fun setBackwardsButton( + item: ItemStack, + action: (InventoryClickEvent) -> Unit = {}, + ) = setBackwardsButton(GuiItem(item), action) + + internal var forwardsButton = GuiItem(ItemStack(Material.ARROW) { + displayName { + primary("Forwards") + } + }) + + fun setForwardsButton( + item: GuiItem, + action: (InventoryClickEvent) -> Unit = {}, + ): PagingButtons { + this.forwardsButton = item.apply { + onClick = action + } + + return this + } + + fun setForwardsButton( + item: ItemStack, + action: (InventoryClickEvent) -> Unit = {}, + ) = setForwardsButton(GuiItem(item), action) + + override fun click( + gui: Gui, + component: InventoryComponent, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + + if (matchesItem(backwardsButton, itemStack)) { + paginatedPane.previousPage() + backwardsButton.callAction(event) + gui.update() + + return true + } + + if (matchesItem(forwardsButton, itemStack)) { + paginatedPane.nextPage() + forwardsButton.callAction(event) + gui.update() + + return true + } + + return false + } + + override fun display( + component: InventoryComponent, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = length.coerceAtMost(maxLength) + + val x = super.slot.getX(length) + paneOffsetX + val y = super.slot.getY(length) + paneOffsetY + + if (paginatedPane.page > 0) { + component.setItem(backwardsButton, slot(x, y)) + } + + if (paginatedPane.page < paginatedPane.getPages() - 1) { + component.setItem(forwardsButton, slot(x + length - 1, y)) + } + } + + override fun updateItems(): Object2IntMap = object2IntMapOf() + + override fun updateItem(item: UpdatableGuiItem): Int? { + return null + } + + override fun clone(): PagingButtons { + val pagingButtons = PagingButtons( + slot, + paginatedPane, + length, + priority, + uuid + ) + + pagingButtons.visible = visible + pagingButtons.onClick = onClick + pagingButtons.backwardsButton = backwardsButton.clone() + pagingButtons.forwardsButton = forwardsButton.clone() + + return pagingButtons + } + + init { + if (length < 2) { + throw IllegalArgumentException("Length must be at least 2 to accommodate paging buttons.") + } + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt new file mode 100644 index 00000000..089bfff1 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt @@ -0,0 +1,330 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Flippable +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Orientable +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Rotatable +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.* +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import dev.slne.surf.surfapi.core.api.util.objectListOf +import it.unimi.dsi.fastutil.objects.Object2IntMap +import org.bukkit.event.inventory.InventoryClickEvent +import java.util.* + +@OptIn(NmsUseWithCaution::class) +class OutlinePane( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : Pane(slot, length, height, priority, uuid), Orientable, Flippable, Rotatable { + + override var length: Int = super.length + get() = super.length + set(value) { + field = value + + applyMask(mask.setLength(value)) + } + + override var height: Int = super.height + get() = super.height + set(value) { + field = value + + applyMask(mask.setHeight(value)) + } + + override var orientation = Orientable.Orientation.HORIZONTAL + override var flippedHorizontally = false + override var flippedVertically = false + override var rotation = 0 + set(value) { + if (length != height) { + throw IllegalArgumentException("Rotation is only allowed for square panes.") + } + + if (rotation % 90 != 0) { + throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") + } + + field = value % 360 + } + + private var gap = 0 + private var repeat = false + + private var alignment = Alignment.BEGIN + private var mask: Mask + + override val panes = objectListOf() + override val items = mutableObjectListOf(length * height) + + init { + val mask = Array(height) { "" } + val maskString = buildString { + for (i in 0 until length) { + append("1") + } + } + + Arrays.fill(mask, maskString) + + this.mask = Mask(*mask) + } + + override fun display( + component: InventoryComponent, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + var itemIndex = 0 + var gapCount = 0 + + val size = when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + height + } + + Orientable.Orientation.VERTICAL -> { + length + } + } + + for (vectorIndex in 0 until size) { + if (items.size <= itemIndex) break + + val maskLine = when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + mask.getRow(vectorIndex) + } + + Orientable.Orientation.VERTICAL -> { + mask.getColumn(vectorIndex) + } + } + + var enabled = 0 + for (bool in maskLine) { + if (bool) enabled++ + } + + val displayItems: Array = if (repeat) { + arrayOfNulls(enabled) + } else { + val remaining = gapCount + (items.size - itemIndex - 1) * (gap + 1) + 1 + + arrayOfNulls(minOf(enabled, remaining)) + } + + for (index in 0 until displayItems.size) { + if (gapCount == 0) { + displayItems[index] = items[itemIndex] + + itemIndex++ + + if (repeat && itemIndex >= items.size) { + itemIndex = 0 + } + + gapCount = gap + } else { + displayItems[index] = null + + gapCount-- + } + } + + var index = if (alignment == Alignment.BEGIN) { + 0 + } else { + -((enabled - displayItems.size) / 2) + } + for (opposingVectorIndex in 0 until maskLine.size) { + if (!maskLine[opposingVectorIndex]) { + continue + } + + if (index >= 0 && index < displayItems.size && displayItems[index] != null) { + var x: Int + var y: Int + + when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + x = opposingVectorIndex + y = vectorIndex + } + + Orientable.Orientation.VERTICAL -> { + x = vectorIndex + y = opposingVectorIndex + } + } + + if (flippedHorizontally) { + x = length - x - 1 + } + + if (flippedVertically) { + y = height - y - 1 + } + + val coordinates = GeometryUtils.processClockwiseRotation( + x, + y, + length, + height, + rotation + ) + + x = coordinates.intKey + y = coordinates.intValue + + if (x < 0 || x >= length || y < 0 || y >= height) { + continue + } + + val finalRow = slot.getY(maxLength) + y + paneOffsetY + val finalColumn = slot.getX(maxLength) + x + paneOffsetX + + val item = displayItems[index] ?: continue + if (item.visible) { + component.setItem(item, slot(finalColumn, finalRow)) + } + } + + index++ + } + } + } + + override fun updateItem(item: UpdatableGuiItem): Int? { + for ((index, guiItem) in items.withIndex()) { + if (guiItem != item) continue + if (!item.update(item)) return null + + return index + slot.getX(length) + slot.getY(length) * length + } + + return null + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + + for ((index, guiItem) in items.withIndex()) { + if (guiItem !is UpdatableGuiItem) continue + if (!guiItem.update(guiItem)) continue + + updatedItems[guiItem] = index + slot.getX(length) + slot.getY(length) * length + } + + return updatedItems + } + + override fun click( + gui: Gui, + component: InventoryComponent, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + val item = findMatchingItem(items, itemStack) ?: return false + + item.callAction(event) + + return true + } + + override fun clone(): OutlinePane { + val outlinePane = OutlinePane( + slot, + length, + height, + priority, + uuid + ) + + for (item in items) { + outlinePane.addItem(item.clone()) + } + + outlinePane.visible = visible + outlinePane.onClick = onClick + + outlinePane.orientation = orientation + outlinePane.flippedHorizontally = flippedHorizontally + outlinePane.flippedVertically = flippedVertically + outlinePane.rotation = rotation + outlinePane.gap = gap + outlinePane.repeat = repeat + outlinePane.alignment = alignment + outlinePane.mask = mask + + return outlinePane + } + + fun insertItem(item: GuiItem, index: Int) { + items.add(index, item) + } + + fun addItem(item: GuiItem) { + items.add(item) + } + + fun removeItem(item: GuiItem) { + items.remove(item) + } + + override fun clear() { + items.clear() + } + + fun applyMask(mask: Mask) { + if (length != mask.length || height != mask.height) { + throw IllegalArgumentException("Mask dimensions must match pane dimensions.") + } + + this.mask = mask + } + + enum class Alignment { + BEGIN, CENTER + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt new file mode 100644 index 00000000..4b2636b0 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt @@ -0,0 +1,276 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.* +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* +import kotlin.math.ceil + +@OptIn(NmsUseWithCaution::class) +class PaginatedPane( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : Pane(slot, length, height, priority, uuid) { + + override val panes: ObjectList + get() { + val panes = mutableObjectListOf() + + paginatedPanes.forEach { (_, paginatedPane) -> + paginatedPane.forEach { pane -> + panes.addAll(pane.panes) + } + panes.addAll(paginatedPane) + } + + return panes + } + + override val items: ObjectList + get() = panes.flatMap { it.items }.toObjectList() + + override fun clear() { + paginatedPanes.clear() + } + + private val paginatedPanes = mutableInt2ObjectMapOf>() + var page: Int = 0 + set(value) { + if (!paginatedPanes.containsKey(value)) { + throw IllegalArgumentException("Page $value does not exist.") + } + + field = value + } + + fun previousPage() { + if (page > 0) { + page-- + } + } + + fun nextPage() { + if (paginatedPanes.containsKey(page + 1)) { + page++ + } + } + + fun addPage(pane: Pane) { + val newPageList = mutableObjectListOf(pane) + + if (paginatedPanes.isEmpty()) { + paginatedPanes[0] = newPageList + return + } + + val highestPage = paginatedPanes.keys.maxOrNull() ?: 0 + + if (highestPage == Int.MAX_VALUE) { + throw ArithmeticException("Cannot add more pages, maximum reached.") + } + + paginatedPanes[highestPage + 1] = newPageList + } + + fun addPane(page: Int, pane: Pane) { + if (!paginatedPanes.containsKey(page)) { + paginatedPanes[page] = mutableObjectListOf() + } + + paginatedPanes[page]!!.add(pane) + paginatedPanes[page]!!.sortWith(Comparator.comparing(Pane::priority)) + } + + fun populateWithGuiItems(items: ObjectList) { + if (items.isEmpty()) { + return + } + + val itemsPerPage = length * height + val pagesNeeded: Int = ceil(items.size / itemsPerPage.toDouble()).coerceAtLeast(1.0).toInt() + + for (i in 0 until pagesNeeded) { + val page = OutlinePane(slot(0, 0), length, height) + + for (j in 0 until itemsPerPage) { + val index = i * itemsPerPage + j + + if (index >= items.size) { + break + } + + page.addItem(items[index]) + } + + addPane(i, page) + } + } + + fun populateWithItemStacks(items: ObjectList) = + populateWithGuiItems(items.map { GuiItem(it) }.toObjectList()) + + fun getPages() = paginatedPanes.size + + override fun display( + component: InventoryComponent, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val panes = paginatedPanes[page] ?: return + + for (pane in panes) { + if (!pane.visible) { + continue + } + + val newPaneOffsetX = paneOffsetX + slot.getX(maxLength) + val newPaneOffsetY = paneOffsetY + slot.getY(maxLength) + val newMaxLength = minOf(length, maxLength) + val newMaxHeight = minOf(height, maxHeight) + + pane.display(component, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight) + } + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + val selectedPagePanes = paginatedPanes[page] ?: return updatedItems + + for (pane in selectedPagePanes) { + if (!pane.visible) { + continue + } + + val paneItems = pane.updateItems() + for ((item, index) in paneItems) { + updatedItems[item] = + index + pane.slot.getX(length) + pane.slot.getY(length) * length + } + } + + return updatedItems + } + + override fun updateItem(item: UpdatableGuiItem): Int? { + val selectedPagePanes = paginatedPanes[page] ?: return null + + for (pane in selectedPagePanes) { + if (!pane.visible) { + continue + } + + val index = pane.updateItem(item) ?: continue + return index + pane.slot.getX(length) + pane.slot.getY(length) * length + } + + return null + } + + override fun click( + gui: Gui, + component: InventoryComponent, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + for (pane in paginatedPanes.getOrElse(page) { mutableObjectListOf() }.freeze()) { + if (!pane.visible) { + continue + } + + if (pane.click( + gui, + component, + event, + slot, + paneOffsetX + xPosition, + paneOffsetY + yPosition, + length, + height + ) + ) { + return true + } + } + + return false + } + + override fun clone(): PaginatedPane { + val paginatedPane = PaginatedPane( + slot, + length, + height, + priority, + uuid + ) + + paginatedPanes.forEach { (pageNumber, panes) -> + panes.forEach { pane -> + paginatedPane.addPane(pageNumber, pane.clone()) + } + } + + paginatedPane.page = page + paginatedPane.visible = visible + paginatedPane.onClick = onClick + + return paginatedPane + } + + fun deletePage(page: Int) { + if (paginatedPanes.remove(page) == null) { + return + } + + val newPanes = mutableInt2ObjectMapOf>() + for ((index, panes) in paginatedPanes) { + if (index > page) { + newPanes.put(index - 1, panes) + } else { + newPanes.put(index, panes) + } + } + + paginatedPanes.clear() + paginatedPanes.putAll(newPanes) + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt new file mode 100644 index 00000000..776786d8 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt @@ -0,0 +1,227 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.PaneMarker +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Flippable +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Rotatable +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.* +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf +import dev.slne.surf.surfapi.core.api.util.objectListOf +import dev.slne.surf.surfapi.core.api.util.toObjectList +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +@OptIn(NmsUseWithCaution::class) +class StaticPane( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : Pane(slot, length, height, priority, uuid), Flippable, Rotatable { + + override var flippedHorizontally: Boolean = false + override var flippedVertically: Boolean = false + override var rotation: Int = 0 + set(value) { + if (length != height) { + throw IllegalArgumentException("Rotation is only allowed for square panes.") + } + + if (rotation % 90 != 0) { + throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") + } + + field = value % 360 + } + + override val panes: ObjectList get() = objectListOf() + + private val paneItems = mutableObject2ObjectMapOf() + override val items: ObjectList get() = paneItems.values.toObjectList() + + fun item(slot: Slot, itemStack: ItemStack, init: (@PaneMarker GuiItem).() -> Unit) { + setItem(GuiItem(itemStack).apply(init), slot) + } + + fun updatableItem( + slot: Slot, + itemStack: ItemStack, + init: (@PaneMarker UpdatableGuiItem).() -> Unit, + ) { + setItem(UpdatableGuiItem(itemStack).apply(init), slot) + } + + override fun display( + component: InventoryComponent, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = minOf(this.length, maxLength) + val height = minOf(this.height, maxHeight) + + paneItems.entries.asSequence() + .filter { it.value.visible } + .forEach { (slot, guiItem) -> + var x = slot.getX(length) + var y = slot.getY(length) + + if (flippedHorizontally) { + x = length - x - 1 + } + + if (flippedVertically) { + y = height - y - 1 + } + + val coordinates = GeometryUtils.processClockwiseRotation( + x, + y, + length, + height, + rotation + ) + + x = coordinates.intKey + y = coordinates.intValue + + if (x < 0 || x >= length || y < 0 || y >= height) { + return@forEach + } + + val finalRow = this@StaticPane.slot.getY(maxLength) + y + paneOffsetY + val finalColumn = this@StaticPane.slot.getX(maxLength) + x + paneOffsetX + + component.setItem(guiItem, slot(finalColumn, finalRow)) + } + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + + for ((slot, guiItem) in paneItems.entries) { + if (guiItem !is UpdatableGuiItem) continue + if (!guiItem.update(guiItem)) continue + + val index = slot.getX(length) + slot.getY(length) * length + updatedItems[guiItem] = index + } + + return updatedItems + } + + override fun updateItem(item: UpdatableGuiItem): Int? { + for ((slot, other) in paneItems.entries) { + if (other != item) continue + if (!item.update(item)) return null + return slot.getX(length) + slot.getY(length) * length + } + return null + } + + override fun click( + gui: Gui, + component: InventoryComponent, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(this.length, maxLength) + val height = minOf(this.height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + val totalLength = component.length + + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + val clickedItem = findMatchingItem(paneItems.values, itemStack) ?: return false + + clickedItem.callAction(event) + + return true + } + + override fun clone(): StaticPane { + val clonedPane = StaticPane(slot, length, height, priority, uuid) + + for (entry in paneItems.entries) { + clonedPane.setItem(entry.value.clone(), entry.key) + } + + clonedPane.visible = visible + clonedPane.onClick = onClick + clonedPane.flippedHorizontally = flippedHorizontally + clonedPane.flippedVertically = flippedVertically + clonedPane.rotation = rotation + + return clonedPane + } + + fun fillWith(itemStack: ItemStack, action: (InventoryClickEvent) -> Unit) { + val locations = paneItems.keys + + for (y in 0 until height) { + for (x in 0 until length) { + var found = false + + for (location in locations) { + if (location.getX(length) == x && location.getY(length) == y) { + found = true + break + } + } + + if (!found) { + setItem(GuiItem(itemStack, action), slot(x, y)) + } + } + } + } + + + fun fillWith(itemStack: ItemStack) { + fillWith(itemStack) {} + } + + fun setItem(item: GuiItem, slot: Slot) { + paneItems.put(slot, item) + } + + fun removeItem(item: GuiItem) { + paneItems.values.removeIf { guiItem -> guiItem == item } + } + + fun removeItem(slot: Slot) { + paneItems.remove(slot) + } + + override fun clear() { + paneItems.clear() + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Flippable.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Flippable.kt new file mode 100644 index 00000000..be906122 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Flippable.kt @@ -0,0 +1,8 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +interface Flippable { + + var flippedVertically: Boolean + var flippedHorizontally: Boolean + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt new file mode 100644 index 00000000..602111f0 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt @@ -0,0 +1,130 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +import org.jetbrains.annotations.Contract +import java.util.* +import kotlin.math.min + + +class Mask(private val mask: Array) { + + constructor(vararg mask: String) : this(maskByString(*mask)) + + companion object { + private fun maskByString(vararg mask: String): Array { + val maskArray = Array(mask.size) { + BooleanArray(if (mask.isEmpty()) 0 else mask[0].length) + } + + for (row in mask.indices) { + val length = mask[row].length + + require(length == maskArray[row].size) { "Lengths of each string should be equal" } + + for (column in 0.. { + maskArray[row][column] = false + } + + '1' -> { + maskArray[row][column] = true + } + + else -> { + throw IllegalArgumentException("Strings may only contain '0' and '1'") + } + } + } + } + + return maskArray + } + + } + + fun setHeight(height: Int): Mask { + val newRows = Array(height) { BooleanArray(this.length) } + + for (index in 0..= getLength()) { + throw IndexOutOfBoundsException("Column index $index is out of bounds for pattern of length ${getLength()}") + } + + val column = IntArray(getHeight()) + + for (i in 0 until getHeight()) { + column[i] = pattern[i][index] + } + + return column + } + + fun contains(character: Int): Boolean { + for (row in pattern) { + for (cell in row) { + if (cell != character) { + continue + } + + return true + } + } + + return false + } + + fun getRow(index: Int): IntArray { + if (index < 0 || index >= getHeight()) { + throw IndexOutOfBoundsException("Row index $index is out of bounds for pattern of height ${getHeight()}") + } + + val row = pattern[index] + + return row.copyOf(row.size) + } + + fun getCharacter(x: Int, y: Int): Int { + if (x < 0 || x >= getLength() || y < 0 || y >= getHeight()) { + throw IndexOutOfBoundsException("Coordinates ($x, $y) are out of bounds for pattern of size ${getLength()}x${getHeight()}") + } + + return pattern[y][x] + } + + override fun hashCode() = pattern.contentDeepHashCode() + + override fun toString() = buildString { + append("Pattern{") + append("pattern=") + append(pattern.contentDeepToString()) + append('}') + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Pattern) return false + + return pattern.contentDeepEquals(other.pattern) + } + + companion object { + private fun initializePattern(vararg pattern: String): Array { + val rows = pattern.size + val zeroRows = rows == 0 + + val patternArray = Array(rows) { + IntArray( + if (zeroRows) 0 else pattern[0].codePointCount( + 0, + pattern[0].length + ) + ) + } + + if (zeroRows) { + return patternArray + } + + val globalLength = pattern[0].length + + for (index in 0 until rows) { + val row = pattern[index] + val length = row.codePointCount(0, row.length) + + if (length != globalLength) { + throw IllegalArgumentException("All rows must have the same length. Row $index has length $length, expected $globalLength") + } + + val values = mutableIntListOf() + row.codePoints().forEach(values::add) + + for (column in 0 until values.size) { + patternArray[index][column] = values.getInt(column) + } + } + + return patternArray + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Rotatable.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Rotatable.kt new file mode 100644 index 00000000..3a0d7c74 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Rotatable.kt @@ -0,0 +1,7 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +interface Rotatable { + + var rotation: Int + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui.kt deleted file mode 100644 index f8e990ac..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/types/SurfChestGui.kt +++ /dev/null @@ -1,40 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.types - -import com.github.stefvanschie.inventoryframework.adventuresupport.ComponentHolder -import com.github.stefvanschie.inventoryframework.gui.type.ChestGui -import com.github.stefvanschie.inventoryframework.gui.type.util.NamedGui -import dev.slne.surf.surfapi.bukkit.api.inventory.SinglePlayerGui -import dev.slne.surf.surfapi.bukkit.api.inventory.SurfGui -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.MenuMarker -import net.kyori.adventure.text.Component -import org.bukkit.entity.Player -import org.jetbrains.annotations.Range - -@MenuMarker -open class SurfChestGui internal constructor( - title: Component, - rows: @Range(from = 2, to = 6) Int = 6, - override val parent: SurfGui? = null -) : - ChestGui(rows, ComponentHolder.of(title)), SurfGui { - override val gui: NamedGui - get() = this - - init { - check(rows in 2..6) { "Rows must be between 2 and 6" } - - this.setOnBottomClick { event -> event.isCancelled = true } - this.setOnBottomDrag { event -> event.isCancelled = true } - this.setOnTopClick { event -> event.isCancelled = true } - this.setOnTopDrag { event -> event.isCancelled = true } - } -} - -@MenuMarker -class SurfChestSinglePlayerGui internal constructor( - title: Component, - override val player: Player, - rows: @Range(from = 2, to = 6) Int = 6, - override val parent: SurfGui? = null, -) : - SurfChestGui(title, rows, parent), SinglePlayerGui \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt new file mode 100644 index 00000000..d8278624 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt @@ -0,0 +1,44 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap +import it.unimi.dsi.fastutil.ints.Int2IntMap + +internal object GeometryUtils { + + fun processClockwiseRotation( + x: Int, + y: Int, + length: Int, + height: Int, + rotation: Int, + ): Int2IntMap.Entry { + var newX = x + var newY = y + + when (rotation) { + 90 -> { + newX = height - 1 - y + newY = x + } + + 180 -> { + newX = length - 1 - x + newY = height - 1 - y + } + + 270 -> { + newX = y + newY = length - 1 - x + } + } + + return AbstractInt2IntMap.BasicEntry(newX, newY) + } + + fun processCounterClockwiseRotation( + x: Int, y: Int, length: Int, height: Int, + rotation: Int, + ): MutableMap.MutableEntry { + return processClockwiseRotation(x, y, length, height, 360 - rotation) + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt new file mode 100644 index 00000000..844c7e99 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt @@ -0,0 +1,255 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.core.api.util.freeze +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.PlayerInventory +import java.util.* + +class InventoryComponent internal constructor( + val length: Int, + val height: Int, +) : Cloneable { + + internal val panes = mutableObjectListOf() + internal val items = Array>(length) { Array(height) { null } } + + init { + if (length < 0 || height < 0) { + throw IllegalArgumentException("Length and height must be non-negative") + } + } + + fun addPane(pane: Pane) { + val size = panes.size + + if (size == 0) { + panes.add(pane) + return + } + + val priority = pane.priority + var left = 0 + var right = size - 1 + + while (left <= right) { + val middle = (left + right) / 2 + val middlePriority = getPane(middle).priority + + if (middlePriority == priority) { + panes.add(middle, pane) + return + } + + if (middlePriority.isLessThan(priority)) { + left = middle + 1 + } else if (middlePriority.isGreaterThan(priority)) { + right = middle - 1 + } + } + + panes.add(right + 1, pane) + } + + fun display(inventory: Inventory, offset: Int) { + display() + + placeItems(inventory, offset) + } + + fun display(playerInventory: PlayerInventory, offset: Int) { + display() + + placeItems(playerInventory, offset) + } + + fun placeItems(playerInventory: PlayerInventory, offset: Int) { + for (x in 0 until length) { + for (y in 0 until height) { + val slot = if (y == height - 1) { + x + offset + } else { + (y + 1) * length + x + offset + } + + playerInventory.setItem(slot, getItem(slot(x, y))) + } + } + } + + fun placeItems(inventory: Inventory, offset: Int) { + for (x in 0 until length) { + for (y in 0 until height) { + inventory.setItem(y * length + x + offset, getItem(slot(x, y))) + } + } + } + + @OptIn(NmsUseWithCaution::class) + fun click(gui: Gui, event: InventoryClickEvent, slot: Int) { + val panes = this.panes.freeze() + + for (i in panes.indices.reversed()) { + val pane = panes[i] + val result = pane.click( + gui, + component = this, + event, + slot, + paneOffsetX = 0, + paneOffsetY = 0, + maxLength = length, + maxHeight = height + ) + + if (result) { + break + } + } + } + + public override fun clone(): InventoryComponent { + val component = InventoryComponent(length, height) + + for (x in 0 until length) { + for (y in 0 until height) { + val item = getItem(slot(x, y)) ?: continue + + component.setItem(item.clone(), slot(x, y)) + } + } + + for (pane in panes) { + component.addPane(pane.clone()) + } + + return component + } + + fun excludeRows(from: Int, end: Int): InventoryComponent { + if (from < 0 || end >= height) { + throw IllegalArgumentException("Invalid row range: $from to $end") + } + + val newHeight = height - (end - from + 1) + val newComponent = InventoryComponent(length, newHeight) + + for (pane in panes) { + newComponent.addPane(pane) + } + + for (x in 0 until length) { + var newY = 0 + + for (y in 0 until height) { + val item = getItem(slot(x, y)) + + if (y >= from && y <= end) { + continue + } + + if (item != null) { + newComponent.setItem(item, slot(x, newY)) + } + + newY++ + } + } + + return newComponent + } + + fun hasItem(): Boolean { + for (x in 0 until length) { + for (y in 0 until height) { + if (getItem(slot(x, y)) != null) { + return true + } + } + } + + return false + } + + fun display() { + clearItems() + + for (pane in panes) { + if (!pane.visible) { + continue + } + + pane.display(this, 0, 0, length, height) + } + } + + fun hasItem(slot: Slot) = getItem(slot) != null + + fun getItem(slot: Slot): ItemStack? { + val x = slot.getX(length) + val y = slot.getY(length) + + if (!inBounds(slot)) { + throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") + } + + return items[x][y] + } + + fun setItem(item: GuiItem, slot: Slot) { + val x = slot.getX(length) + val y = slot.getY(length) + + if (!inBounds(slot)) { + throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") + } + + items[x][y] = item.clone().apply { applyUuid() }.itemStack + } + + fun setItem(itemStack: ItemStack, slot: Slot) { + val x = slot.getX(length) + val y = slot.getY(length) + + if (!inBounds(slot)) { + throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") + } + + items[x][y] = itemStack + } + + val size get() = length * height + + fun clearItems() { + for (item in items) { + Arrays.fill(item, null) + } + } + + fun inBounds(slot: Slot): Boolean { + val x = slot.getX(length) + val y = slot.getY(length) + + val xBounds = inBounds(0, length - 1, x) + val yBounds = inBounds(0, height - 1, y) + + return xBounds && yBounds + } + + fun inBounds(lowerBound: Int, upperBound: Int, value: Int) = value in lowerBound..upperBound + + fun getPane(index: Int): Pane { + if (!inBounds(0, panes.size - 1, index)) { + throw IndexOutOfBoundsException("Pane index $index is out of bounds for size ${panes.size}") + } + + return panes[index] + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt new file mode 100644 index 00000000..bd10a4a1 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt @@ -0,0 +1,96 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf +import org.bukkit.entity.Player +import org.bukkit.inventory.ItemStack + +internal class PlayerInventoryCache { + + private val inventories = mutableObject2ObjectMapOf>() + + fun storeAndClear(player: Player) { + store(player) + + val inventory = player.inventory + for (i in 0 until inventory.size) { + inventory.clear(i) + } + } + + fun restoreAndForget(player: Player) { + restore(player) + clearCache(player) + } + + fun restoreAndForgetAll() { + restoreAll() + clearCache() + } + + fun add(player: Player, item: ItemStack): Int { + val items = inventories[player] + ?: throw IllegalStateException("The player ${player.uniqueId} does not have a cached inventory") + + var amountPutIn = 0 + + for (i in 0 until items.size) { + val itemStack = items[i] + + if (itemStack == null) { + items[i] = item.clone() + items[i]!!.amount = item.amount - amountPutIn + amountPutIn = item.amount + break + } + + if (!itemStack.isSimilar(item)) { + continue + } + + val additionalAmount = minOf(itemStack.maxStackSize - itemStack.amount, item.amount) + itemStack.amount = itemStack.amount + additionalAmount + amountPutIn += additionalAmount + + if (amountPutIn == item.amount) { + break + } + } + + return item.amount - amountPutIn + } + + fun store(player: Player) { + val inventory = player.inventory + val items = Array(inventory.size) { null } + + for (i in 0 until inventory.size) { + items[i] = inventory.getItem(i) + } + + inventories[player] = items + } + + private fun restore(player: Player) { + val items = inventories[player] ?: return + val inventory = player.inventory + + for (i in 0 until items.size) { + inventory.setItem(i, items[i]) + } + } + + private fun restoreAll() { + inventories.keys.forEach(this::restore) + } + + fun contains(player: Player) = inventories.containsKey(player) + + fun clearCache(player: Player) { + inventories.remove(player) + } + + fun clearCache() { + inventories.clear() + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt new file mode 100644 index 00000000..76d97918 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt @@ -0,0 +1,36 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +enum class Priority { + + LOWEST { + override fun isLessThan(priority: Priority) = this != priority + }, + + LOW { + override fun isLessThan(priority: Priority) = this != priority && priority != LOWEST + }, + + NORMAL { + override fun isLessThan(priority: Priority) = + this != priority && priority != LOWEST && priority != LOW + }, + + HIGH { + override fun isLessThan(priority: Priority) = + this != priority && priority != LOWEST && priority != LOW && priority != NORMAL + }, + + HIGHEST { + override fun isLessThan(priority: Priority) = + this != priority && priority != LOWEST && priority != LOW && priority != NORMAL && priority != HIGH + }, + + MONITOR { + override fun isLessThan(priority: Priority) = false + }; + + abstract fun isLessThan(priority: Priority): Boolean + fun isGreaterThan(priority: Priority) = !isLessThan(priority) && this != priority + + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt new file mode 100644 index 00000000..d4f9089a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt @@ -0,0 +1,60 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import org.jetbrains.annotations.Range +import java.util.* + +interface Slot { + + fun getX(length: Int): Int + fun getY(length: Int): Int + + companion object { + fun fromXY(x: Int, y: Int) = XY(x, y) + fun fromIndex(index: Int) = Indexed(index) + } + + class XY(val x: Int, val y: Int) : Slot { + override fun getX(length: Int) = x + override fun getY(length: Int) = y + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is XY) return false + + return x == other.x && y == other.y + } + + override fun hashCode() = Objects.hash(x, y) + + override fun toString() = "XY(x=$x, y=$y)" + + } + + class Indexed(val index: Int) : Slot { + override fun getX(length: Int): Int { + if (length <= 0) throw IllegalArgumentException("Length must be greater than 0") + + return index % length + } + + override fun getY(length: Int): Int { + if (length <= 0) throw IllegalArgumentException("Length must be greater than 0") + + return index / length + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is Indexed) return false + + return index == other.index + } + + override fun hashCode() = index + override fun toString() = "Indexed(index=$index)" + } + +} + +fun slot(x: @Range(from = 0, to = 8) Int, y: @Range(from = 0, to = 5) Int) = Slot.fromXY(x, y) +fun slot(index: Int) = Slot.fromIndex(index) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt new file mode 100644 index 00000000..f4843d9c --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.view + +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack + + +object InventoryViewUtil { + + fun getTitle(view: InventoryView) = view.title() + + fun getTopInventory(view: InventoryView) = view.topInventory + fun getBottomInventory(view: InventoryView) = view.bottomInventory + + fun getCursor(view: InventoryView) = view.cursor + fun setCursor(view: InventoryView, item: ItemStack?) { + view.setCursor(item) + } + + fun getInventory(view: InventoryView, slot: Int) = view.getInventory(slot) + fun getSlotType(view: InventoryView, slot: Int) = view.getSlotType(slot) +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClickPacket.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClickPacket.kt new file mode 100644 index 00000000..35e87d2c --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClickPacket.kt @@ -0,0 +1,24 @@ +package dev.slne.surf.surfapi.bukkit.api.nms.listener.packets.serverbound + +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import org.bukkit.entity.Player +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack + +@NmsUseWithCaution +interface ContainerClickPacket : NmsServerboundPacket { + val view: InventoryView + val containerId: Int + val stateId: Int + val slotNumber: Int + val buttonNumber: Int + val clickType: WindowClickType + val changedSlots: Int2ObjectMap + val carriedItem: ItemStack + val whoClicked: Player + + enum class WindowClickType { + PICKUP, QUICK_MOVE, SWAP, CLONE, THROW, QUICK_CRAFT, PICKUP_ALL, UNKNOWN; + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClosePacket.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClosePacket.kt new file mode 100644 index 00000000..06e556c2 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/nms/listener/packets/serverbound/ContainerClosePacket.kt @@ -0,0 +1,8 @@ +package dev.slne.surf.surfapi.bukkit.api.nms.listener.packets.serverbound + +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution + +@NmsUseWithCaution +interface ContainerClosePacket : NmsServerboundPacket { + val containerId: Int +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.java b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.java index e39ffb5b..6a9cf64a 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.java +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/BukkitPluginMain.java @@ -2,6 +2,7 @@ import dev.jorel.commandapi.CommandAPI; import dev.slne.surf.surfapi.bukkit.api.packet.listener.SurfBukkitPacketListenerApi; +import dev.slne.surf.surfapi.bukkit.test.command.GuiCommandKt; import dev.slne.surf.surfapi.bukkit.test.command.SurfApiTestCommand; import dev.slne.surf.surfapi.bukkit.test.command.subcommands.reflection.Reflection; import dev.slne.surf.surfapi.bukkit.test.listener.ChatListener; @@ -31,6 +32,7 @@ public void onLoad() { @Override public void onEnable() { new SurfApiTestCommand().register(); + GuiCommandKt.guiCommand(); Reflection.class.getClassLoader(); // initialize Reflection } diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java index 4a4fed60..565028ea 100644 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/SurfApiTestCommand.java @@ -10,7 +10,6 @@ import dev.slne.surf.surfapi.bukkit.test.command.subcommands.ScoreboardTest; import dev.slne.surf.surfapi.bukkit.test.command.subcommands.SmoothTimeSkip; import dev.slne.surf.surfapi.bukkit.test.command.subcommands.VisualizerTest; -import dev.slne.surf.surfapi.bukkit.test.command.subcommands.gui.InventoryFrameworkTest; public class SurfApiTestCommand extends CommandAPICommand { @@ -27,7 +26,6 @@ public SurfApiTestCommand() { new ReflectionTest("reflection"), new PrefixConfigTest("prefixconfig"), new CommandExceptionTest("commandexception"), - new InventoryFrameworkTest("inventoryframework"), new MaxStacksizeTest("maxstacksize"), new VisualizerTest("visualizer") ); diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/subcommands/gui/InventoryFrameworkTest.java b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/subcommands/gui/InventoryFrameworkTest.java deleted file mode 100644 index 8ea0bdf5..00000000 --- a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/java/dev/slne/surf/surfapi/bukkit/test/command/subcommands/gui/InventoryFrameworkTest.java +++ /dev/null @@ -1,29 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.test.command.subcommands.gui; - -import com.github.stefvanschie.inventoryframework.gui.type.ChestGui; -import com.github.stefvanschie.inventoryframework.pane.StaticPane; -import dev.jorel.commandapi.CommandAPICommand; -import org.bukkit.Material; -import org.bukkit.inventory.ItemStack; - -public class InventoryFrameworkTest extends CommandAPICommand { - - public InventoryFrameworkTest(String commandName) { - super(commandName); - - executesPlayer((player, commandArguments) -> { - final ChestGui testGui = new ChestGui(1, "Test"); - testGui.setOnGlobalClick(event -> event.setCancelled(true)); - - final StaticPane pane = new StaticPane(0, 0, 9, 1); - pane.fillWith(ItemStack.of(Material.DIAMOND), event -> { - event.setCancelled(true); - event.getWhoClicked().sendMessage("You clicked on a diamond!"); - }); - - testGui.addPane(pane); - - testGui.show(player); - }); - } -} diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/GuiCommand.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/GuiCommand.kt new file mode 100644 index 00000000..740d0d63 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/command/GuiCommand.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.bukkit.test.command + +import dev.jorel.commandapi.kotlindsl.commandAPICommand +import dev.jorel.commandapi.kotlindsl.playerExecutor +import dev.slne.surf.surfapi.bukkit.test.gui.exampleGui + +fun guiCommand() = commandAPICommand("gui") { + playerExecutor { player, args -> + exampleGui().show(player) + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/gui/ExampleGui.kt b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/gui/ExampleGui.kt new file mode 100644 index 00000000..0c3f0c68 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-plugin-test/src/main/kotlin/dev/slne/surf/surfapi/bukkit/test/gui/ExampleGui.kt @@ -0,0 +1,81 @@ +package dev.slne.surf.surfapi.bukkit.test.gui + +import dev.slne.surf.surfapi.bukkit.api.builder.ItemStack +import dev.slne.surf.surfapi.bukkit.api.builder.displayName +import dev.slne.surf.surfapi.bukkit.api.extensions.server +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.menu +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.paginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.pagingButtons +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.staticPane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.test.BukkitPluginMain +import dev.slne.surf.surfapi.core.api.messages.adventure.sendText +import dev.slne.surf.surfapi.core.api.messages.adventure.text +import dev.slne.surf.surfapi.core.api.util.toObjectList +import org.bukkit.Material + +fun exampleGui() = menu(text("Test")) { + onGlobalDrag { it.isCancelled = true } + onGlobalClick { it.isCancelled = true } + + staticPane(slot(1, 0), 1) { + updatableItem(slot(0, 0), ItemStack(Material.BARRIER) { + displayName { + primary("Tolles Item") + } + }) { + onClick { + it.whoClicked.sendText { + text("You clicked the test item!") + } + } + + onUpdate { + visible = !visible + true + } + } + + item(slot(1, 0), ItemStack(Material.DIAMOND) { + displayName { + primary("Diamond") + } + }) { + onClick { + it.whoClicked.sendText { + text("You clicked the diamond!") + } + } + } + } + + val paginatedPane = paginatedPane(slot(0, 1), 5) { + val items = (1..500).map { index -> + ItemStack(Material.STONE) { + displayName { + primary("Stone Item $index") + } + } + }.toObjectList() + + populateWithItemStacks(items) + } + + pagingButtons(slot(0, 0), paginatedPane) { + setBackwardsButton(ItemStack(Material.RED_CONCRETE) { + displayName { + primary("Previous Page") + } + }) + + setForwardsButton(ItemStack(Material.GREEN_CONCRETE) { + displayName { + primary("Next Page") + } + }) + } + + server.scheduler.runTaskTimer(BukkitPluginMain.getInstance(), Runnable { +// update() + }, 20, 20) +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts index 3147096f..f40a36e8 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts @@ -31,7 +31,6 @@ dependencies { runtimeOnly(libs.scoreboard.library.implementation) runtimeOnly(libs.scoreboard.library.modern) paperLibrary(libs.scoreboard.library.api) - api(libs.inventoryframework) api(libs.packetevents.spigot) paperLibrary(libs.guava) paperLibrary(libs.caffeine) @@ -107,7 +106,7 @@ fun NamedDomainObjectContainerScope.registerSoft( name: String, required: Boolean = false, joinClassPath: Boolean = true, - loadOrder: RelativeLoadOrder = RelativeLoadOrder.BEFORE + loadOrder: RelativeLoadOrder = RelativeLoadOrder.BEFORE, ) = register(name) { this.required = required this.joinClasspath = joinClassPath diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClickPacketImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClickPacketImpl.kt new file mode 100644 index 00000000..0215330b --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClickPacketImpl.kt @@ -0,0 +1,35 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.nms.listener.packets.serverbound + +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.listener.packets.serverbound.ContainerClickPacket +import dev.slne.surf.surfapi.bukkit.server.nms.toWindowClickType +import dev.slne.surf.surfapi.core.api.util.mutableInt2ObjectMapOf +import it.unimi.dsi.fastutil.ints.Int2ObjectMap +import net.minecraft.network.protocol.game.ServerboundContainerClickPacket +import org.bukkit.entity.Player +import org.bukkit.inventory.InventoryView +import org.bukkit.inventory.ItemStack + +@NmsUseWithCaution +class ContainerClickPacketImpl( + nmsPacket: ServerboundContainerClickPacket, + override val view: InventoryView, + override val whoClicked: Player, +) : NmsServerboundPacketImpl(nmsPacket), ContainerClickPacket { + override val containerId get() = nmsPacket.containerId + override val stateId get() = nmsPacket.stateId + override val slotNumber get() = nmsPacket.slotNum + override val buttonNumber get() = nmsPacket.buttonNum + override val clickType get() = nmsPacket.clickType.toWindowClickType() + override val changedSlots: Int2ObjectMap + get() = run { + val map = mutableInt2ObjectMapOf() + + nmsPacket.changedSlots.forEach { (key, value) -> + map[key] = value.bukkitStack + } + + map + } + override val carriedItem get() = nmsPacket.carriedItem.bukkitStack +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClosePacketImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClosePacketImpl.kt new file mode 100644 index 00000000..36c25fc6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/nms/listener/packets/serverbound/ContainerClosePacketImpl.kt @@ -0,0 +1,12 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.nms.listener.packets.serverbound + +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.api.nms.listener.packets.serverbound.ContainerClosePacket +import net.minecraft.network.protocol.game.ServerboundContainerClosePacket + +@NmsUseWithCaution +class ContainerClosePacketImpl( + nmsPacket: ServerboundContainerClosePacket, +) : NmsServerboundPacketImpl(nmsPacket), ContainerClosePacket { + override val containerId get() = nmsPacket.containerId +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt index 0b90d0d7..e7eacc9c 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt @@ -1,5 +1,7 @@ package dev.slne.surf.surfapi.bukkit.server.listener +import dev.slne.surf.surfapi.bukkit.api.event.register +import dev.slne.surf.surfapi.bukkit.api.inventory.listener.GuiListener import dev.slne.surf.surfapi.bukkit.server.plugin import org.bukkit.Bukkit @@ -9,6 +11,8 @@ object ListenerManager { */ fun registerListeners() { Bukkit.getMessenger().registerOutgoingPluginChannel(plugin, "BungeeCord") + + GuiListener(plugin).register() } /** diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt index da93a489..36761bc3 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/nms/nms-extensions.kt @@ -3,7 +3,9 @@ package dev.slne.surf.surfapi.bukkit.server.nms import dev.slne.surf.surfapi.bukkit.api.extensions.server +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution import dev.slne.surf.surfapi.bukkit.api.nms.bridges.packets.entity.SignBlockUpdateSettings +import dev.slne.surf.surfapi.bukkit.api.nms.listener.packets.serverbound.ContainerClickPacket.WindowClickType import io.papermc.paper.adventure.PaperAdventure import io.papermc.paper.math.BlockPosition import io.papermc.paper.math.FinePosition @@ -12,6 +14,7 @@ import net.minecraft.core.BlockPos import net.minecraft.network.chat.Component import net.minecraft.server.level.ServerPlayer import net.minecraft.world.entity.Display +import net.minecraft.world.inventory.ClickType import net.minecraft.world.inventory.MenuType import net.minecraft.world.item.DyeColor import net.minecraft.world.item.Item @@ -51,6 +54,7 @@ fun Material.toNmsItem(): Item = CraftMagicNumbers.getItem(this) fun BlockData.toNms(): NmsBlockState = (this as CraftBlockData).state fun Vector3f?.toNms(): NmsVector3f? = if (this == null) null else NmsVector3f(this.x(), this.y(), this.z()) + fun FinePosition.toNms() = Vec3(x(), y(), z()) fun BlockState.toNms(): NmsBlockState = (this as CraftBlockState).handle fun Quaternionf?.toNms(): NmsQuaternionf? = @@ -75,6 +79,18 @@ fun SignBlockUpdateSettings.SignText.toNms(): SignText { return SignText(lines, lines, DyeColor.BLACK, false) } +@OptIn(NmsUseWithCaution::class) +fun ClickType.toWindowClickType() = when (this) { + ClickType.PICKUP -> WindowClickType.PICKUP + ClickType.QUICK_MOVE -> WindowClickType.QUICK_MOVE + ClickType.SWAP -> WindowClickType.SWAP + ClickType.CLONE -> WindowClickType.CLONE + ClickType.THROW -> WindowClickType.THROW + ClickType.PICKUP_ALL -> WindowClickType.PICKUP_ALL + ClickType.QUICK_CRAFT -> WindowClickType.QUICK_CRAFT + else -> WindowClickType.UNKNOWN +} + fun InventoryType.toNms(): MenuType<*> = when (this) { InventoryType.ANVIL -> MenuType.ANVIL InventoryType.BEACON -> MenuType.BEACON From 21cd6c6e59ad67b86092c6628d7849dd66cf665d Mon Sep 17 00:00:00 2001 From: twisti Date: Sat, 14 Jun 2025 13:11:51 +0200 Subject: [PATCH 2/4] feat: implement new GUI handlers and rework inventory management for improved functionality --- .idea/modules.xml | 3 + .idea/vcs.xml | 6 - .../surf-api-bukkit-api/build.gradle.kts | 6 + .../bukkit/api/inventory/InventoryBridge.kt | 31 ++ .../surfapi/bukkit/api/inventory/dsl/gui.kt | 52 +-- .../bukkit/api/inventory/dsl/markers.kt | 12 + .../surfapi/bukkit/api/inventory/gui/Gui.kt | 195 ++-------- .../inventory/gui/{utils => }/MergedGui.kt | 11 +- .../bukkit/api/inventory/gui/NamedGui.kt | 22 +- .../inventory/gui/handlers/ClickHandlers.kt | 23 ++ .../inventory/gui/handlers/CloseHandlers.kt | 17 + .../inventory/gui/handlers/DragHandlers.kt | 22 ++ .../api/inventory/gui/handlers/GuiHandler.kt | 11 + .../api/inventory/gui/types/ChestGui.kt | 106 +----- .../bukkit/api/inventory/item/GuiItem.kt | 97 ++--- .../api/inventory/item/UpdatableGuiItem.kt | 27 +- .../surfapi/bukkit/api/inventory/pane/Pane.kt | 124 +------ .../pane/components/PagingButtons.kt | 185 +--------- .../api/inventory/pane/panes/OutlinePane.kt | 324 +---------------- .../api/inventory/pane/panes/PaginatedPane.kt | 274 +------------- .../api/inventory/pane/panes/StaticPane.kt | 224 +----------- .../bukkit/api/inventory/pane/utils/Mask.kt | 133 +------ .../api/inventory/pane/utils/Pattern.kt | 161 +-------- .../api/inventory/utils/InventoryComponent.kt | 270 ++------------ .../bukkit/api/inventory/utils/Priority.kt | 41 +-- .../bukkit/api/inventory/utils/Slot.kt | 64 ++-- .../api/inventory/view/InventoryViewUtil.kt | 21 -- .../surf-api-bukkit-server/build.gradle.kts | 1 + .../impl/inventory/dsl/HandlerDslImpl.kt | 27 ++ .../server/impl/inventory/gui/AbstractGui.kt | 215 +++++++++++ .../impl/inventory/gui/AbstractNamedGui.kt | 25 ++ .../impl/inventory/gui/types/ChestGuiImpl.kt | 115 ++++++ .../inventory/gui/utils/InventoryBased.kt | 6 +- .../server/impl/inventory/item/GuiItemImpl.kt | 94 +++++ .../inventory/item/UpdatableGuiItemImpl.kt | 20 ++ .../impl}/inventory/listener/GuiListener.kt | 46 ++- .../impl/inventory/pane/AbstractPane.kt | 118 ++++++ .../inventory/pane/panes/OutlinePaneImpl.kt | 335 ++++++++++++++++++ .../inventory/pane/panes/PaginatedPaneImpl.kt | 284 +++++++++++++++ .../inventory/pane/panes/StaticPaneImpl.kt | 215 +++++++++++ .../panes/components/PagingButtonsImpl.kt | 183 ++++++++++ .../impl/inventory/pane/utils/MaskImpl.kt | 128 +++++++ .../impl/inventory/pane/utils/PatternImpl.kt | 157 ++++++++ .../impl}/inventory/utils/GeometryUtils.kt | 4 +- .../inventory/utils/InventoryComponentImpl.kt | 253 +++++++++++++ .../inventory/utils/PlayerInventoryCache.kt | 4 +- .../bukkit/server/listener/ListenerManager.kt | 2 +- 47 files changed, 2569 insertions(+), 2125 deletions(-) create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt rename surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/{utils => }/MergedGui.kt (51%) create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/dsl/HandlerDslImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt rename surf-api-bukkit/{surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api => surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl}/inventory/gui/utils/InventoryBased.kt (51%) create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt rename surf-api-bukkit/{surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api => surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl}/inventory/listener/GuiListener.kt (76%) create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/AbstractPane.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/OutlinePaneImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/PaginatedPaneImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/components/PagingButtonsImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/utils/MaskImpl.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/utils/PatternImpl.kt rename surf-api-bukkit/{surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api => surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl}/inventory/utils/GeometryUtils.kt (91%) create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/InventoryComponentImpl.kt rename surf-api-bukkit/{surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api => surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl}/inventory/utils/PlayerInventoryCache.kt (96%) diff --git a/.idea/modules.xml b/.idea/modules.xml index b0a65603..bb04ac9d 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -7,8 +7,11 @@ + + + diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 4c6280eb..94a25f7f 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,11 +1,5 @@ - - - - - - diff --git a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts index c3353b95..c50ddbf4 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts @@ -21,3 +21,9 @@ description = "surf-api-bukkit-api" configurations.all { exclude(group = "org.spigotmc", module = "spigot-api") } + +kotlin { + compilerOptions { + optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") + } +} diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt new file mode 100644 index 00000000..85341cd6 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt @@ -0,0 +1,31 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Pattern +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import dev.slne.surf.surfapi.core.api.util.requiredService +import org.bukkit.NamespacedKey +import org.bukkit.inventory.Inventory +import java.util.* + +@InternalSurfApi +interface InventoryBridge { + + fun getGuiByInventory(inventory: Inventory): Gui? + fun createGuiItem(key: NamespacedKey?, uuid: UUID?, init: GuiItem.() -> Unit): GuiItem + fun createUpdatableGuiItem( + key: NamespacedKey?, + uuid: UUID?, + init: UpdatableGuiItem.() -> Unit, + ): UpdatableGuiItem + + fun createMask(vararg mask: String): Mask + fun createPattern(vararg pattern: String): Pattern + + companion object { + val instance = requiredService() + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt index 0e932ed2..a7341f9a 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt @@ -2,12 +2,12 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.dsl -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGuiImpl import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestSinglePlayerGui -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.MergedGui -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.components.PagingButtons -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.components.PagingButtonsImpl +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPaneImpl +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPaneImpl import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot import net.kyori.adventure.text.Component import org.bukkit.entity.Player @@ -22,15 +22,15 @@ annotation class MenuMarker fun MergedGui.pagingButtons( slot: Slot, - paginatedPane: PaginatedPane, + paginatedPane: PaginatedPaneImpl, length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker PagingButtons).() -> Unit, -): PagingButtons { + init: (@PaneMarker PagingButtonsImpl).() -> Unit, +): PagingButtonsImpl { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - return PagingButtons(slot, paginatedPane, length).apply { + return PagingButtonsImpl(slot, paginatedPane, length).apply { init() addPane(this) } @@ -40,13 +40,13 @@ fun MergedGui.paginatedPane( slot: Slot, height: @Range(from = 1, to = 6) Int, length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker PaginatedPane).() -> Unit, -): PaginatedPane { + init: (@PaneMarker PaginatedPaneImpl).() -> Unit, +): PaginatedPaneImpl { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - return PaginatedPane(slot, length, height).apply { + return PaginatedPaneImpl(slot, length, height).apply { init() addPane(this) } @@ -56,13 +56,13 @@ fun MergedGui.staticPane( slot: Slot, height: @Range(from = 1, to = 6) Int, length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker StaticPane).() -> Unit, -): StaticPane { + init: (@PaneMarker StaticPaneImpl).() -> Unit, +): StaticPaneImpl { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - return StaticPane(slot, length, height).apply { + return StaticPaneImpl(slot, length, height).apply { init() addPane(this) } @@ -70,20 +70,20 @@ fun MergedGui.staticPane( fun menu( title: Component, - size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestGui.() -> Unit, -): ChestGui { + size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestGuiImpl.() -> Unit, +): ChestGuiImpl { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - return ChestGui(title, size).apply { init() } + return ChestGuiImpl(title, size).apply { init() } } fun playerMenu( title: Component, player: Player, - size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, init: @MenuMarker ChestSinglePlayerGui.() -> Unit, ): ChestSinglePlayerGui { contract { @@ -94,21 +94,21 @@ fun playerMenu( } -fun ChestGui.childMenu( +fun ChestGuiImpl.childMenu( title: Component, - size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestGui.() -> Unit, -): ChestGui { + size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, + init: @MenuMarker ChestGuiImpl.() -> Unit, +): ChestGuiImpl { contract { callsInPlace(init, InvocationKind.EXACTLY_ONCE) } - return ChestGui(title, size, this).apply { init() } + return ChestGuiImpl(title, size, this).apply { init() } } fun ChestSinglePlayerGui.childPlayerMenu( title: Component, - size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, init: @MenuMarker ChestSinglePlayerGui.() -> Unit, ): ChestSinglePlayerGui { contract { diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt index eee9a825..6800560a 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt @@ -3,3 +3,15 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.dsl @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) @DslMarker annotation class PaneMarker + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class GuiDsl + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class GuiItemDsl + +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) +@DslMarker +annotation class GuiClickDsl \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt index 8270e64a..374a4053 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/Gui.kt @@ -1,181 +1,58 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui -import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.* import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.PlayerInventoryCache -import dev.slne.surf.surfapi.core.api.util.logger -import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.ObjectSet import org.bukkit.entity.Player -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.event.inventory.InventoryCloseEvent -import org.bukkit.event.inventory.InventoryDragEvent -import org.bukkit.event.inventory.InventoryEvent import org.bukkit.inventory.Inventory -import org.bukkit.inventory.ItemStack -import java.util.* +import org.jetbrains.annotations.Unmodifiable -abstract class Gui internal constructor( - val parent: Gui? = null, -) : Cloneable { +@GuiDsl +interface Gui : Cloneable { + val backingInventory: Inventory + val viewers: @Unmodifiable ObjectSet + val parent: Gui? - private val log = logger() + // region Handlers + fun onTopClick(handler: ClickHandler) - lateinit var backingInventory: Inventory + fun onTopClick(handler: ClickHandlerDsl) + fun onBottomClick(handler: ClickHandler) - internal val cache = PlayerInventoryCache() + fun onBottomClick(handler: ClickHandlerDsl) + fun onGlobalClick(handler: ClickHandler) - private var onTopClick: (InventoryClickEvent) -> Unit = {} - fun onTopClick(block: (InventoryClickEvent) -> Unit) { - onTopClick = block - } - - private var onBottomClick: (InventoryClickEvent) -> Unit = {} - fun onBottomClick(block: (InventoryClickEvent) -> Unit) { - onBottomClick = block - } - - private var onGlobalClick: (InventoryClickEvent) -> Unit = {} - fun onGlobalClick(block: (InventoryClickEvent) -> Unit) { - onGlobalClick = block - } - - private var onOutsideClick: (InventoryClickEvent) -> Unit = {} - fun onOutsideClick(block: (InventoryClickEvent) -> Unit) { - onOutsideClick = block - } - - private var onTopDrag: (InventoryDragEvent) -> Unit = {} - fun onTopDrag(block: (InventoryDragEvent) -> Unit) { - onTopDrag = block - } - - private var onBottomDrag: (InventoryDragEvent) -> Unit = {} - fun onBottomDrag(block: (InventoryDragEvent) -> Unit) { - onBottomDrag = block - } - - private var onGlobalDrag: (InventoryDragEvent) -> Unit = {} - fun onGlobalDrag(block: (InventoryDragEvent) -> Unit) { - onGlobalDrag = block - } - - var onClose: (InventoryCloseEvent) -> Unit = {} - - var shouldNavigateParentOnClose: Boolean = false - - internal var updating: Boolean = false - private set - - abstract fun show(player: Player) - abstract fun click(event: InventoryClickEvent) - abstract fun isPlayerInventoryUsed(): Boolean - abstract val viewers: ObjectSet - protected abstract fun updateAllItems(): Object2IntMap - protected abstract fun updateItem0(item: UpdatableGuiItem): Int? - - fun update() { - updating = true - - val updatedItems = updateAllItems() - for ((item, slot) in updatedItems) { - if (item.visible) { - backingInventory.setItem(slot, item.itemStack) - } else { - backingInventory.setItem(slot, ItemStack.empty()) - } - } - - for (viewer in viewers) { - val cursor = viewer.itemOnCursor - - viewer.setItemOnCursor(ItemStack.empty()) - show(viewer) - viewer.setItemOnCursor(cursor) - } - - updating = false - } + fun onGlobalClick(handler: ClickHandlerDsl) + fun onOutsideClick(handler: ClickHandler) - fun updateItem(item: UpdatableGuiItem) { - if (updating) return + fun onOutsideClick(handler: ClickHandlerDsl) + fun onTopDrag(handler: DragHandler) - val slot = updateItem0(item) ?: return - if (item.visible) { - backingInventory.setItem(slot, item.itemStack) - } else { - backingInventory.setItem(slot, ItemStack.empty()) - } + fun onTopDrag(handler: DragHandlerDsl) + fun onBottomDrag(handler: DragHandler) - updating = false - } - - protected fun addInventory(inventory: Inventory, gui: Gui) { - GUI_INVENTORIES[inventory] = gui - } - - internal fun callOnTopClick(event: InventoryClickEvent) { - callCallback(onTopClick, event, "onTopClick") - } - - internal fun callOnBottomClick(event: InventoryClickEvent) { - callCallback(onBottomClick, event, "onBottomClick") - } + fun onBottomDrag(handler: DragHandlerDsl) + fun onGlobalDrag(handler: DragHandler) - internal fun callOnGlobalClick(event: InventoryClickEvent) { - callCallback(onGlobalClick, event, "onGlobalClick") - } + fun onGlobalDrag(handler: DragHandlerDsl) + fun onClose(handler: CloseHandler) + fun onClose(handler: CloseHandlerDsl) + // endregion - internal fun callOnOutsideClick(event: InventoryClickEvent) { - callCallback(onOutsideClick, event, "onOutsideClick") - } + fun navigateToParentOnClose(enabled: Boolean = true) + fun navigateToParent(player: Player): Boolean + fun walkParents(): Sequence - internal fun callOnTopDrag(event: InventoryDragEvent) { - callCallback(onTopDrag, event, "onTopDrag") - } + fun show(player: Player) + fun update() + fun updateItem(item: UpdatableGuiItem) - internal fun callOnBottomDrag(event: InventoryDragEvent) { - callCallback(onBottomDrag, event, "onBottomDrag") - } - - internal fun callOnGlobalDrag(event: InventoryDragEvent) { - callCallback(onGlobalDrag, event, "onGlobalDrag") - } - - internal fun callOnClose(event: InventoryCloseEvent) { - callCallback(onClose, event, "onClose") - } - - private fun callCallback( - callback: (T).() -> Unit, - event: T, - name: String, - ) { - try { - callback.invoke(event) - } catch (exception: Exception) { - log.atSevere().withCause(exception).log(buildString { - append("Exception while handling $name") - - if (event is InventoryClickEvent) { - append(", slot=${event.slot}") - } - }) - } - } - - fun navigateToParent(player: Player) { - if (parent == null) return - - parent.show(player) - parent.update() - } - - fun walkParents(): Sequence = generateSequence(this) { it.parent } + public override fun clone(): Gui companion object { - private val GUI_INVENTORIES = WeakHashMap() - - fun getGui(inventory: Inventory) = GUI_INVENTORIES[inventory] + fun getGui(inventory: Inventory): Gui? = + InventoryBridge.instance.getGuiByInventory(inventory) } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt similarity index 51% rename from surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt rename to surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt index 4b2c6e29..088ea2f5 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/MergedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt @@ -1,16 +1,17 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils +package dev.slne.surf.surfapi.bukkit.api.inventory.gui -import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent import it.unimi.dsi.fastutil.objects.ObjectList +import org.jetbrains.annotations.Unmodifiable +@GuiDsl interface MergedGui { - val inventoryComponent: InventoryComponent fun addPane(pane: Pane) - val panes: ObjectList - val items: ObjectList + val panes: @Unmodifiable ObjectList + val items: @Unmodifiable ObjectList } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt index 7d7cd6fc..9802517d 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt @@ -1,21 +1,15 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder import net.kyori.adventure.text.Component -abstract class NamedGui internal constructor( - title: Component, - parent: Gui? = null, -) : Gui(parent) { +@GuiDsl +interface NamedGui : Gui { + val title: Component - var title: Component = Component.empty() - set(value) { - field = value - titleDirty = true - } + fun title(title: Component) + fun title(builder: (@GuiDsl SurfComponentBuilder).() -> Unit) - protected var titleDirty = false - - init { - this.title = title - } + override fun clone(): NamedGui } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt new file mode 100644 index 00000000..93f6934d --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt @@ -0,0 +1,23 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent + +typealias ClickHandlerDsl = ClickHandlerScope.() -> Unit + +@GuiDsl +interface ClickHandler : GuiHandler + +@JvmInline +@GuiDsl +value class ClickHandlerScope @InternalSurfApi constructor(val event: InventoryClickEvent) { + val player + get() = event.whoClicked as? Player ?: error("Click event is not triggered by a player") + + fun cancel() { + event.isCancelled = true + } +} + diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt new file mode 100644 index 00000000..f0a71042 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt @@ -0,0 +1,17 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.event.inventory.InventoryCloseEvent + +typealias CloseHandlerDsl = CloseHandlerScope.() -> Unit + +@GuiDsl +interface CloseHandler: GuiHandler + +@JvmInline +@GuiDsl +value class CloseHandlerScope @InternalSurfApi constructor(val event: InventoryCloseEvent) { + +} + diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt new file mode 100644 index 00000000..5416c91d --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt @@ -0,0 +1,22 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryDragEvent + +typealias DragHandlerDsl = DragHandlerScope.() -> Unit + +@GuiDsl +interface DragHandler : GuiHandler + +@GuiDsl +@JvmInline +value class DragHandlerScope @InternalSurfApi constructor(val event: InventoryDragEvent) { + val player + get() = event.whoClicked as? Player ?: error("Drag event is not triggered by a player") + + fun cancel() { + event.isCancelled = true + } +} diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt new file mode 100644 index 00000000..c5048510 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import org.bukkit.event.Event +import org.jetbrains.annotations.ApiStatus.OverrideOnly + +@GuiDsl +interface GuiHandler { + @OverrideOnly + fun handle(event: E) +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt index 070d2b01..91725c00 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt @@ -1,99 +1,13 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.types -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.MenuMarker -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.InventoryBased -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils.MergedGui -import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent -import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf -import dev.slne.surf.surfapi.core.api.util.toObjectList -import dev.slne.surf.surfapi.core.api.util.toObjectSet -import it.unimi.dsi.fastutil.objects.Object2IntMap -import net.kyori.adventure.text.Component -import org.bukkit.Bukkit -import org.bukkit.entity.Player -import org.bukkit.event.inventory.InventoryClickEvent -open class ChestGui internal constructor( - title: Component, - var size: ChestGuiSize = ChestGuiSize.FIVE_ROWS, - parent: Gui? = null, -) : NamedGui(title, parent), MergedGui, InventoryBased { +interface ChestGui: NamedGui, MergedGui { + val size: ChestGuiSize + fun size(size: ChestGuiSize) - private var rows: Int = size.rows - set(value) { - inventoryComponent = InventoryComponent(9, value + 4) - - for (pane in inventoryComponent.panes) { - inventoryComponent.addPane(pane) - } - - sizeDirty = true - } - - override var inventoryComponent = InventoryComponent(9, rows + 4) - private var sizeDirty = false - - override val panes get() = inventoryComponent.panes - override val items get() = panes.flatMap { it.items }.toObjectList() - override val viewers get() = backingInventory.viewers.filterIsInstance().toObjectSet() - override fun updateAllItems(): Object2IntMap = - panes.fold(mutableObject2IntMapOf()) { acc, pane -> - acc.apply { putAll(pane.updateItems()) } - } - - override fun updateItem0(item: UpdatableGuiItem) = - panes.find { it.items.contains(item) }?.updateItem(item) - - - override fun addPane(pane: Pane) { - inventoryComponent.addPane(pane) - } - - override fun createInventory() = Bukkit.createInventory(this, rows * 9, title) - override fun getInventory() = backingInventory - - override fun show(player: Player) { - if (titleDirty || sizeDirty) { - backingInventory = createInventory() - - titleDirty = false - sizeDirty = false - } - - backingInventory.clear() - - val height = inventoryComponent.height - inventoryComponent.display() - - val topComponent = inventoryComponent.excludeRows(height - 4, height - 1) - val bottomComponent = inventoryComponent.excludeRows(0, height - 5) - - topComponent.placeItems(backingInventory, 0) - - if (bottomComponent.hasItem()) { - if (!cache.contains(player)) { - cache.storeAndClear(player) - } - - bottomComponent.placeItems(player.inventory, 0) - } - - player.openInventory(backingInventory) - } - - override fun clone() = super.clone() as ChestGui - - override fun click(event: InventoryClickEvent) { - inventoryComponent.click(this, event, event.rawSlot) - } - - override fun isPlayerInventoryUsed() = - inventoryComponent.excludeRows(0, inventoryComponent.height - 5).hasItem() + override fun clone(): ChestGui enum class ChestGuiSize(val rows: Int) { ONE_ROW(1), @@ -103,12 +17,4 @@ open class ChestGui internal constructor( FIVE_ROWS(5), SIX_ROWS(6); } -} - -@MenuMarker -class ChestSinglePlayerGui internal constructor( - val player: Player, - title: Component, - size: ChestGuiSize = ChestGuiSize.SIX_ROWS, - parent: Gui? = null, -) : ChestGui(title, size, parent) \ No newline at end of file +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt index ddcda115..b6c589e2 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt @@ -1,89 +1,34 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.item -import com.jeff_media.morepersistentdatatypes.DataType -import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.logger +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandler +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl import org.bukkit.NamespacedKey -import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack -import org.bukkit.plugin.java.JavaPlugin +import org.bukkit.inventory.ItemType import java.util.* -internal val GUI_ITEM_UUID_KEY = NamespacedKey( - JavaPlugin.getProvidingPlugin(GuiItem::class.java), - "sapi-uuid" -) +@GuiItemDsl +interface GuiItem : Cloneable { + val itemStack: ItemStack + val key: NamespacedKey + val uuid: UUID + var visible: Boolean -@OptIn(NmsUseWithCaution::class) -open class GuiItem( - var itemStack: ItemStack, - internal var action: (InventoryClickEvent) -> Unit = {}, - val key: NamespacedKey = GUI_ITEM_UUID_KEY, - val uuid: UUID = UUID.randomUUID(), -) : Cloneable { + fun item(itemStack: ItemStack) + fun item(type: ItemType, block: (@GuiItemDsl ItemStack).() -> Unit = {}) - var visible: Boolean = true + fun onClick(handler: @GuiItemDsl ClickHandler) + fun onClick(handler: @GuiItemDsl ClickHandlerDsl) - init { - applyUuid() - } - - fun onClick(action: (InventoryClickEvent) -> Unit) { - this.action = action - } - - public override fun clone(): GuiItem { - val guiItem = GuiItem(itemStack.clone(), action, key, uuid) - - guiItem.visible = visible - val meta = guiItem.itemStack.itemMeta - - if (meta != null) { - meta.persistentDataContainer.set(key, DataType.UUID, guiItem.uuid) - guiItem.itemStack.itemMeta = meta - } - - return guiItem - } - - fun callAction(event: InventoryClickEvent) { - try { - action.invoke(event) - } catch (exception: Exception) { - log.atSevere().withCause(exception).log( - "Exception while handling click event in inventory '${ - InventoryViewUtil.getTitle(event.view) - }', slot=${event.slot}, item=${itemStack.type}" - ) - } - } - - fun applyUuid() { - itemStack.editMeta { - it.persistentDataContainer.set(key, DataType.UUID, uuid) - } - } - - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (javaClass != other?.javaClass) return false - - other as GuiItem - - return uuid == other.uuid - } - - override fun hashCode(): Int { - return uuid.hashCode() - } - - override fun toString(): String { - return "GuiItem(itemStack=$itemStack, action=$action, key=$key, uuid=$uuid, visible=$visible)" - } + public override fun clone(): GuiItem = super.clone() as GuiItem companion object { - private val log = logger() + operator fun invoke( + key: NamespacedKey? = null, + uuid: UUID? = null, + init: GuiItem.() -> Unit, + ): GuiItem = InventoryBridge.instance.createGuiItem(key, uuid, init) } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt index c6b83d9c..39c108a3 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt @@ -1,18 +1,23 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.item +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl import org.bukkit.NamespacedKey -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.inventory.ItemStack import java.util.* +import java.util.function.BooleanSupplier -class UpdatableGuiItem( - itemStack: ItemStack, - action: (InventoryClickEvent) -> Unit = {}, - internal var update: (UpdatableGuiItem) -> Boolean = { false }, - key: NamespacedKey = GUI_ITEM_UUID_KEY, - uuid: UUID = UUID.randomUUID(), -) : GuiItem(itemStack, action, key, uuid) { - fun onUpdate(update: () -> Boolean) { - this.update = { update() } +@GuiItemDsl +interface UpdatableGuiItem : GuiItem { + + fun onUpdate(update: @GuiItemDsl BooleanSupplier) + + override fun clone(): UpdatableGuiItem = super.clone() as UpdatableGuiItem + + companion object { + operator fun invoke( + key: NamespacedKey? = null, + uuid: UUID? = null, + init: UpdatableGuiItem.() -> Unit, + ): UpdatableGuiItem = InventoryBridge.instance.createUpdatableGuiItem(key, uuid, init) } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt index 25ca0296..f16098e2 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/Pane.kt @@ -1,121 +1,29 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane -import com.jeff_media.morepersistentdatatypes.DataType -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandler +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.ObjectList -import org.bukkit.event.inventory.InventoryClickEvent -import org.bukkit.inventory.ItemStack +import org.jetbrains.annotations.Unmodifiable import java.util.* -@OptIn(NmsUseWithCaution::class) -abstract class Pane( - var slot: Slot, - open var length: Int, - open var height: Int, - var priority: Priority = Priority.NORMAL, - val uuid: UUID = UUID.randomUUID(), -) : Cloneable { +interface Pane : Cloneable { + var slot: Slot + val uuid: UUID + val items: @Unmodifiable ObjectList + val panes: @Unmodifiable ObjectList - var onClick: ((InventoryClickEvent) -> Unit)? = null - var visible: Boolean = true + var length: Int + var height: Int + var priority: Priority + var visible: Boolean - internal abstract fun display( - component: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ) + fun onClick(handler: ClickHandler) + fun onClick(handler: ClickHandlerDsl) - internal abstract fun updateItems(): Object2IntMap - internal abstract fun updateItem(item: UpdatableGuiItem): Int? + fun clear() - internal abstract fun click( - gui: Gui, - component: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ): Boolean - - abstract val items: ObjectList - abstract val panes: ObjectList - - abstract fun clear() - - protected fun callOnClick(event: InventoryClickEvent) { - if (onClick == null) { - return - } - - try { - onClick?.invoke(event) - } catch (exception: Exception) { - throw RuntimeException(buildString { - append("Exception while handling click event in inventory '") - append(InventoryViewUtil.getTitle(event.view)) - append( - "slot=${event.slot}, for ${javaClass.simpleName}, x=${slot.getX(length)}, y=${ - slot.getY( - length - ) - }, length=$length, height=$height" - ) - }, exception) - } - } - - companion object { - @JvmStatic - protected fun matchesItem( - guiItem: GuiItem, - item: ItemStack, - ): Boolean { - val meta = item.itemMeta ?: return false - - return guiItem.uuid == meta.persistentDataContainer.get( - guiItem.key, - DataType.UUID - ) - } - - @JvmStatic - protected fun findMatchingItem( - items: Collection, - item: ItemStack, - ): T? { - for (guiItem in items) { - if (matchesItem(guiItem, item)) { - return guiItem - } - } - - return null - } - } - - public override fun clone(): Pane { - throw UnsupportedOperationException("The implementing pane has not overridden the clone method.") - } - - override fun toString(): String { - return "Pane(slot=$slot, length=$length, height=$height, priority=$priority, uuid=$uuid, onClick=$onClick, visible=$visible, items=$items, panes=$panes)" - } - - init { - if (length <= 0 || height <= 0) { - throw IllegalArgumentException("Length and height must be greater than 0 (length=$length, height=$height)") - } - } + public override fun clone(): Pane } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt index 9c7cbe69..3c0ca572 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/components/PagingButtons.kt @@ -1,187 +1,16 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane.components -import dev.slne.surf.surfapi.bukkit.api.builder.ItemStack -import dev.slne.surf.surfapi.bukkit.api.builder.displayName -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf -import dev.slne.surf.surfapi.core.api.util.object2IntMapOf -import it.unimi.dsi.fastutil.objects.Object2IntMap -import it.unimi.dsi.fastutil.objects.ObjectList -import org.bukkit.Material -import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack -import java.util.* -@OptIn(NmsUseWithCaution::class) -class PagingButtons( - slot: Slot, - val paginatedPane: PaginatedPane, - length: Int = 9, - priority: Priority = Priority.NORMAL, - uuid: UUID = UUID.randomUUID(), -) : Pane(slot, length, 1, priority, uuid) { +interface PagingButtons: Pane { + fun setBackwardsButton(item: GuiItem) + fun setBackwardsButton(item: ItemStack, action: ClickHandlerDsl = {}) - override val items: ObjectList - get() = mutableObjectListOf(backwardsButton, forwardsButton) - - override val panes: ObjectList - get() = mutableObjectListOf() - - override fun clear() { - throw UnsupportedOperationException("PagingButtons cannot be cleared.") - } - - internal var backwardsButton = GuiItem(ItemStack(Material.ARROW) { - displayName { - primary("Backwards") - } - }) - - fun setBackwardsButton( - item: GuiItem, - action: (InventoryClickEvent) -> Unit = {}, - ): PagingButtons { - this.backwardsButton = item.apply { - onClick = action - } - - return this - } - - fun setBackwardsButton( - item: ItemStack, - action: (InventoryClickEvent) -> Unit = {}, - ) = setBackwardsButton(GuiItem(item), action) - - internal var forwardsButton = GuiItem(ItemStack(Material.ARROW) { - displayName { - primary("Forwards") - } - }) - - fun setForwardsButton( - item: GuiItem, - action: (InventoryClickEvent) -> Unit = {}, - ): PagingButtons { - this.forwardsButton = item.apply { - onClick = action - } - - return this - } - - fun setForwardsButton( - item: ItemStack, - action: (InventoryClickEvent) -> Unit = {}, - ) = setForwardsButton(GuiItem(item), action) - - override fun click( - gui: Gui, - component: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ): Boolean { - val length = minOf(length, maxLength) - val height = minOf(height, maxHeight) - - val xPosition = this.slot.getX(maxLength) - val yPosition = this.slot.getY(maxLength) - - val totalLength = component.length - val adjustedSlot = - slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) - - val x = adjustedSlot % length - val y = adjustedSlot / length - - if (x < 0 || x >= length || y < 0 || y >= height) { - return false - } - - callOnClick(event) - - val itemStack = event.currentItem ?: return false - - if (matchesItem(backwardsButton, itemStack)) { - paginatedPane.previousPage() - backwardsButton.callAction(event) - gui.update() - - return true - } - - if (matchesItem(forwardsButton, itemStack)) { - paginatedPane.nextPage() - forwardsButton.callAction(event) - gui.update() - - return true - } - - return false - } - - override fun display( - component: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ) { - val length = length.coerceAtMost(maxLength) - - val x = super.slot.getX(length) + paneOffsetX - val y = super.slot.getY(length) + paneOffsetY - - if (paginatedPane.page > 0) { - component.setItem(backwardsButton, slot(x, y)) - } - - if (paginatedPane.page < paginatedPane.getPages() - 1) { - component.setItem(forwardsButton, slot(x + length - 1, y)) - } - } - - override fun updateItems(): Object2IntMap = object2IntMapOf() - - override fun updateItem(item: UpdatableGuiItem): Int? { - return null - } - - override fun clone(): PagingButtons { - val pagingButtons = PagingButtons( - slot, - paginatedPane, - length, - priority, - uuid - ) - - pagingButtons.visible = visible - pagingButtons.onClick = onClick - pagingButtons.backwardsButton = backwardsButton.clone() - pagingButtons.forwardsButton = forwardsButton.clone() - - return pagingButtons - } - - init { - if (length < 2) { - throw IllegalArgumentException("Length must be at least 2 to accommodate paging buttons.") - } - } + fun setForwardsButton(item: GuiItem) + fun setForwardsButton(item: ItemStack, action: ClickHandlerDsl = {}) + override fun clone(): PagingButtons } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt index 089bfff1..3c3d7faa 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/OutlinePane.kt @@ -1,330 +1,18 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Flippable import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Orientable import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Rotatable -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.* -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf -import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf -import dev.slne.surf.surfapi.core.api.util.objectListOf -import it.unimi.dsi.fastutil.objects.Object2IntMap -import org.bukkit.event.inventory.InventoryClickEvent -import java.util.* -@OptIn(NmsUseWithCaution::class) -class OutlinePane( - slot: Slot, - length: Int, - height: Int, - priority: Priority = Priority.NORMAL, - uuid: UUID = UUID.randomUUID(), -) : Pane(slot, length, height, priority, uuid), Orientable, Flippable, Rotatable { +interface OutlinePane : Pane, Orientable, Flippable, Rotatable { - override var length: Int = super.length - get() = super.length - set(value) { - field = value - - applyMask(mask.setLength(value)) - } - - override var height: Int = super.height - get() = super.height - set(value) { - field = value - - applyMask(mask.setHeight(value)) - } - - override var orientation = Orientable.Orientation.HORIZONTAL - override var flippedHorizontally = false - override var flippedVertically = false - override var rotation = 0 - set(value) { - if (length != height) { - throw IllegalArgumentException("Rotation is only allowed for square panes.") - } - - if (rotation % 90 != 0) { - throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") - } - - field = value % 360 - } - - private var gap = 0 - private var repeat = false - - private var alignment = Alignment.BEGIN - private var mask: Mask - - override val panes = objectListOf() - override val items = mutableObjectListOf(length * height) - - init { - val mask = Array(height) { "" } - val maskString = buildString { - for (i in 0 until length) { - append("1") - } - } - - Arrays.fill(mask, maskString) - - this.mask = Mask(*mask) - } - - override fun display( - component: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ) { - val length = minOf(length, maxLength) - val height = minOf(height, maxHeight) - - var itemIndex = 0 - var gapCount = 0 - - val size = when (orientation) { - Orientable.Orientation.HORIZONTAL -> { - height - } - - Orientable.Orientation.VERTICAL -> { - length - } - } - - for (vectorIndex in 0 until size) { - if (items.size <= itemIndex) break - - val maskLine = when (orientation) { - Orientable.Orientation.HORIZONTAL -> { - mask.getRow(vectorIndex) - } - - Orientable.Orientation.VERTICAL -> { - mask.getColumn(vectorIndex) - } - } - - var enabled = 0 - for (bool in maskLine) { - if (bool) enabled++ - } - - val displayItems: Array = if (repeat) { - arrayOfNulls(enabled) - } else { - val remaining = gapCount + (items.size - itemIndex - 1) * (gap + 1) + 1 - - arrayOfNulls(minOf(enabled, remaining)) - } - - for (index in 0 until displayItems.size) { - if (gapCount == 0) { - displayItems[index] = items[itemIndex] - - itemIndex++ - - if (repeat && itemIndex >= items.size) { - itemIndex = 0 - } - - gapCount = gap - } else { - displayItems[index] = null - - gapCount-- - } - } - - var index = if (alignment == Alignment.BEGIN) { - 0 - } else { - -((enabled - displayItems.size) / 2) - } - for (opposingVectorIndex in 0 until maskLine.size) { - if (!maskLine[opposingVectorIndex]) { - continue - } - - if (index >= 0 && index < displayItems.size && displayItems[index] != null) { - var x: Int - var y: Int - - when (orientation) { - Orientable.Orientation.HORIZONTAL -> { - x = opposingVectorIndex - y = vectorIndex - } - - Orientable.Orientation.VERTICAL -> { - x = vectorIndex - y = opposingVectorIndex - } - } - - if (flippedHorizontally) { - x = length - x - 1 - } - - if (flippedVertically) { - y = height - y - 1 - } - - val coordinates = GeometryUtils.processClockwiseRotation( - x, - y, - length, - height, - rotation - ) - - x = coordinates.intKey - y = coordinates.intValue - - if (x < 0 || x >= length || y < 0 || y >= height) { - continue - } - - val finalRow = slot.getY(maxLength) + y + paneOffsetY - val finalColumn = slot.getX(maxLength) + x + paneOffsetX - - val item = displayItems[index] ?: continue - if (item.visible) { - component.setItem(item, slot(finalColumn, finalRow)) - } - } - - index++ - } - } - } - - override fun updateItem(item: UpdatableGuiItem): Int? { - for ((index, guiItem) in items.withIndex()) { - if (guiItem != item) continue - if (!item.update(item)) return null - - return index + slot.getX(length) + slot.getY(length) * length - } - - return null - } - - override fun updateItems(): Object2IntMap { - val updatedItems = mutableObject2IntMapOf() - - for ((index, guiItem) in items.withIndex()) { - if (guiItem !is UpdatableGuiItem) continue - if (!guiItem.update(guiItem)) continue - - updatedItems[guiItem] = index + slot.getX(length) + slot.getY(length) * length - } - - return updatedItems - } - - override fun click( - gui: Gui, - component: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ): Boolean { - val length = minOf(length, maxLength) - val height = minOf(height, maxHeight) - - val xPosition = this.slot.getX(maxLength) - val yPosition = this.slot.getY(maxLength) - - val totalLength = component.length - val adjustedSlot = - slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) - - val x = adjustedSlot % length - val y = adjustedSlot / length - - if (x < 0 || x >= length || y < 0 || y >= height) { - return false - } - - callOnClick(event) - - val itemStack = event.currentItem ?: return false - val item = findMatchingItem(items, itemStack) ?: return false - - item.callAction(event) - - return true - } - - override fun clone(): OutlinePane { - val outlinePane = OutlinePane( - slot, - length, - height, - priority, - uuid - ) - - for (item in items) { - outlinePane.addItem(item.clone()) - } - - outlinePane.visible = visible - outlinePane.onClick = onClick - - outlinePane.orientation = orientation - outlinePane.flippedHorizontally = flippedHorizontally - outlinePane.flippedVertically = flippedVertically - outlinePane.rotation = rotation - outlinePane.gap = gap - outlinePane.repeat = repeat - outlinePane.alignment = alignment - outlinePane.mask = mask - - return outlinePane - } - - fun insertItem(item: GuiItem, index: Int) { - items.add(index, item) - } - - fun addItem(item: GuiItem) { - items.add(item) - } - - fun removeItem(item: GuiItem) { - items.remove(item) - } - - override fun clear() { - items.clear() - } - - fun applyMask(mask: Mask) { - if (length != mask.length || height != mask.height) { - throw IllegalArgumentException("Mask dimensions must match pane dimensions.") - } - - this.mask = mask - } - - enum class Alignment { - BEGIN, CENTER - } + fun setItem(index: Int, item: GuiItem) + fun addItem(item: GuiItem) + fun removeItem(item: GuiItem) + fun applyMask(mask: Mask) + override fun clone(): OutlinePane } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt index 4b2636b0..7683893e 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/PaginatedPane.kt @@ -1,276 +1,20 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.* -import it.unimi.dsi.fastutil.objects.Object2IntMap import it.unimi.dsi.fastutil.objects.ObjectList -import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.ItemStack -import java.util.* -import kotlin.math.ceil -@OptIn(NmsUseWithCaution::class) -class PaginatedPane( - slot: Slot, - length: Int, - height: Int, - priority: Priority = Priority.NORMAL, - uuid: UUID = UUID.randomUUID(), -) : Pane(slot, length, height, priority, uuid) { +interface PaginatedPane: Pane { + val page: Int + val totalPages: Int - override val panes: ObjectList - get() { - val panes = mutableObjectListOf() + fun page(page: Int) + fun previousPage() + fun nextPage() - paginatedPanes.forEach { (_, paginatedPane) -> - paginatedPane.forEach { pane -> - panes.addAll(pane.panes) - } - panes.addAll(paginatedPane) - } + fun populateWithGuiItems(items: ObjectList) + fun populateWithItemStacks(items: ObjectList) - return panes - } - - override val items: ObjectList - get() = panes.flatMap { it.items }.toObjectList() - - override fun clear() { - paginatedPanes.clear() - } - - private val paginatedPanes = mutableInt2ObjectMapOf>() - var page: Int = 0 - set(value) { - if (!paginatedPanes.containsKey(value)) { - throw IllegalArgumentException("Page $value does not exist.") - } - - field = value - } - - fun previousPage() { - if (page > 0) { - page-- - } - } - - fun nextPage() { - if (paginatedPanes.containsKey(page + 1)) { - page++ - } - } - - fun addPage(pane: Pane) { - val newPageList = mutableObjectListOf(pane) - - if (paginatedPanes.isEmpty()) { - paginatedPanes[0] = newPageList - return - } - - val highestPage = paginatedPanes.keys.maxOrNull() ?: 0 - - if (highestPage == Int.MAX_VALUE) { - throw ArithmeticException("Cannot add more pages, maximum reached.") - } - - paginatedPanes[highestPage + 1] = newPageList - } - - fun addPane(page: Int, pane: Pane) { - if (!paginatedPanes.containsKey(page)) { - paginatedPanes[page] = mutableObjectListOf() - } - - paginatedPanes[page]!!.add(pane) - paginatedPanes[page]!!.sortWith(Comparator.comparing(Pane::priority)) - } - - fun populateWithGuiItems(items: ObjectList) { - if (items.isEmpty()) { - return - } - - val itemsPerPage = length * height - val pagesNeeded: Int = ceil(items.size / itemsPerPage.toDouble()).coerceAtLeast(1.0).toInt() - - for (i in 0 until pagesNeeded) { - val page = OutlinePane(slot(0, 0), length, height) - - for (j in 0 until itemsPerPage) { - val index = i * itemsPerPage + j - - if (index >= items.size) { - break - } - - page.addItem(items[index]) - } - - addPane(i, page) - } - } - - fun populateWithItemStacks(items: ObjectList) = - populateWithGuiItems(items.map { GuiItem(it) }.toObjectList()) - - fun getPages() = paginatedPanes.size - - override fun display( - component: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ) { - val panes = paginatedPanes[page] ?: return - - for (pane in panes) { - if (!pane.visible) { - continue - } - - val newPaneOffsetX = paneOffsetX + slot.getX(maxLength) - val newPaneOffsetY = paneOffsetY + slot.getY(maxLength) - val newMaxLength = minOf(length, maxLength) - val newMaxHeight = minOf(height, maxHeight) - - pane.display(component, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight) - } - } - - override fun updateItems(): Object2IntMap { - val updatedItems = mutableObject2IntMapOf() - val selectedPagePanes = paginatedPanes[page] ?: return updatedItems - - for (pane in selectedPagePanes) { - if (!pane.visible) { - continue - } - - val paneItems = pane.updateItems() - for ((item, index) in paneItems) { - updatedItems[item] = - index + pane.slot.getX(length) + pane.slot.getY(length) * length - } - } - - return updatedItems - } - - override fun updateItem(item: UpdatableGuiItem): Int? { - val selectedPagePanes = paginatedPanes[page] ?: return null - - for (pane in selectedPagePanes) { - if (!pane.visible) { - continue - } - - val index = pane.updateItem(item) ?: continue - return index + pane.slot.getX(length) + pane.slot.getY(length) * length - } - - return null - } - - override fun click( - gui: Gui, - component: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ): Boolean { - val length = minOf(length, maxLength) - val height = minOf(height, maxHeight) - - val xPosition = this.slot.getX(maxLength) - val yPosition = this.slot.getY(maxLength) - - val totalLength = component.length - val adjustedSlot = - slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) - - val x = adjustedSlot % length - val y = adjustedSlot / length - - if (x < 0 || x >= length || y < 0 || y >= height) { - return false - } - - callOnClick(event) - - for (pane in paginatedPanes.getOrElse(page) { mutableObjectListOf() }.freeze()) { - if (!pane.visible) { - continue - } - - if (pane.click( - gui, - component, - event, - slot, - paneOffsetX + xPosition, - paneOffsetY + yPosition, - length, - height - ) - ) { - return true - } - } - - return false - } - - override fun clone(): PaginatedPane { - val paginatedPane = PaginatedPane( - slot, - length, - height, - priority, - uuid - ) - - paginatedPanes.forEach { (pageNumber, panes) -> - panes.forEach { pane -> - paginatedPane.addPane(pageNumber, pane.clone()) - } - } - - paginatedPane.page = page - paginatedPane.visible = visible - paginatedPane.onClick = onClick - - return paginatedPane - } - - fun deletePage(page: Int) { - if (paginatedPanes.remove(page) == null) { - return - } - - val newPanes = mutableInt2ObjectMapOf>() - for ((index, panes) in paginatedPanes) { - if (index > page) { - newPanes.put(index - 1, panes) - } else { - newPanes.put(index, panes) - } - } - - paginatedPanes.clear() - paginatedPanes.putAll(newPanes) - } + override fun clone(): PaginatedPane } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt index 776786d8..c9143f19 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt @@ -1,227 +1,19 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.PaneMarker -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem -import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Flippable import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Rotatable -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.* -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf -import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf -import dev.slne.surf.surfapi.core.api.util.objectListOf -import dev.slne.surf.surfapi.core.api.util.toObjectList -import it.unimi.dsi.fastutil.objects.Object2IntMap -import it.unimi.dsi.fastutil.objects.ObjectList -import org.bukkit.event.inventory.InventoryClickEvent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot import org.bukkit.inventory.ItemStack -import java.util.* -@OptIn(NmsUseWithCaution::class) -class StaticPane( - slot: Slot, - length: Int, - height: Int, - priority: Priority = Priority.NORMAL, - uuid: UUID = UUID.randomUUID(), -) : Pane(slot, length, height, priority, uuid), Flippable, Rotatable { +interface StaticPane: Pane, Flippable, Rotatable { - override var flippedHorizontally: Boolean = false - override var flippedVertically: Boolean = false - override var rotation: Int = 0 - set(value) { - if (length != height) { - throw IllegalArgumentException("Rotation is only allowed for square panes.") - } - - if (rotation % 90 != 0) { - throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") - } - - field = value % 360 - } - - override val panes: ObjectList get() = objectListOf() - - private val paneItems = mutableObject2ObjectMapOf() - override val items: ObjectList get() = paneItems.values.toObjectList() - - fun item(slot: Slot, itemStack: ItemStack, init: (@PaneMarker GuiItem).() -> Unit) { - setItem(GuiItem(itemStack).apply(init), slot) - } - - fun updatableItem( - slot: Slot, - itemStack: ItemStack, - init: (@PaneMarker UpdatableGuiItem).() -> Unit, - ) { - setItem(UpdatableGuiItem(itemStack).apply(init), slot) - } - - override fun display( - component: InventoryComponent, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ) { - val length = minOf(this.length, maxLength) - val height = minOf(this.height, maxHeight) - - paneItems.entries.asSequence() - .filter { it.value.visible } - .forEach { (slot, guiItem) -> - var x = slot.getX(length) - var y = slot.getY(length) - - if (flippedHorizontally) { - x = length - x - 1 - } - - if (flippedVertically) { - y = height - y - 1 - } - - val coordinates = GeometryUtils.processClockwiseRotation( - x, - y, - length, - height, - rotation - ) - - x = coordinates.intKey - y = coordinates.intValue - - if (x < 0 || x >= length || y < 0 || y >= height) { - return@forEach - } - - val finalRow = this@StaticPane.slot.getY(maxLength) + y + paneOffsetY - val finalColumn = this@StaticPane.slot.getX(maxLength) + x + paneOffsetX - - component.setItem(guiItem, slot(finalColumn, finalRow)) - } - } - - override fun updateItems(): Object2IntMap { - val updatedItems = mutableObject2IntMapOf() - - for ((slot, guiItem) in paneItems.entries) { - if (guiItem !is UpdatableGuiItem) continue - if (!guiItem.update(guiItem)) continue - - val index = slot.getX(length) + slot.getY(length) * length - updatedItems[guiItem] = index - } - - return updatedItems - } - - override fun updateItem(item: UpdatableGuiItem): Int? { - for ((slot, other) in paneItems.entries) { - if (other != item) continue - if (!item.update(item)) return null - return slot.getX(length) + slot.getY(length) * length - } - return null - } - - override fun click( - gui: Gui, - component: InventoryComponent, - event: InventoryClickEvent, - slot: Int, - paneOffsetX: Int, - paneOffsetY: Int, - maxLength: Int, - maxHeight: Int, - ): Boolean { - val length = minOf(this.length, maxLength) - val height = minOf(this.height, maxHeight) - - val xPosition = this.slot.getX(maxLength) - val yPosition = this.slot.getY(maxLength) - val totalLength = component.length - - val adjustedSlot = - slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) - - val x = adjustedSlot % length - val y = adjustedSlot / length - - if (x < 0 || x >= length || y < 0 || y >= height) { - return false - } - - callOnClick(event) - - val itemStack = event.currentItem ?: return false - val clickedItem = findMatchingItem(paneItems.values, itemStack) ?: return false - - clickedItem.callAction(event) - - return true - } - - override fun clone(): StaticPane { - val clonedPane = StaticPane(slot, length, height, priority, uuid) - - for (entry in paneItems.entries) { - clonedPane.setItem(entry.value.clone(), entry.key) - } - - clonedPane.visible = visible - clonedPane.onClick = onClick - clonedPane.flippedHorizontally = flippedHorizontally - clonedPane.flippedVertically = flippedVertically - clonedPane.rotation = rotation - - return clonedPane - } - - fun fillWith(itemStack: ItemStack, action: (InventoryClickEvent) -> Unit) { - val locations = paneItems.keys - - for (y in 0 until height) { - for (x in 0 until length) { - var found = false - - for (location in locations) { - if (location.getX(length) == x && location.getY(length) == y) { - found = true - break - } - } - - if (!found) { - setItem(GuiItem(itemStack, action), slot(x, y)) - } - } - } - } - - - fun fillWith(itemStack: ItemStack) { - fillWith(itemStack) {} - } - - fun setItem(item: GuiItem, slot: Slot) { - paneItems.put(slot, item) - } - - fun removeItem(item: GuiItem) { - paneItems.values.removeIf { guiItem -> guiItem == item } - } - - fun removeItem(slot: Slot) { - paneItems.remove(slot) - } - - override fun clear() { - paneItems.clear() - } + fun setItem(slot: Slot, item: GuiItem) + fun fillWith(item: ItemStack, handler: ClickHandlerDsl = {}) + fun removeItem(item: GuiItem) + fun removeItem(slot: Slot) + override fun clone(): StaticPane } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt index 602111f0..44c22d7b 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Mask.kt @@ -1,130 +1,19 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils -import org.jetbrains.annotations.Contract -import java.util.* -import kotlin.math.min +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +interface Mask { + val enabledSlots: Int + val length: Int + val height: Int -class Mask(private val mask: Array) { - - constructor(vararg mask: String) : this(maskByString(*mask)) + fun setHeight(height: Int): Mask + fun setLength(length: Int): Mask + fun getColumn(index: Int): BooleanArray + fun getRow(index: Int): BooleanArray + fun isEnabled(x: Int, y: Int): Boolean companion object { - private fun maskByString(vararg mask: String): Array { - val maskArray = Array(mask.size) { - BooleanArray(if (mask.isEmpty()) 0 else mask[0].length) - } - - for (row in mask.indices) { - val length = mask[row].length - - require(length == maskArray[row].size) { "Lengths of each string should be equal" } - - for (column in 0.. { - maskArray[row][column] = false - } - - '1' -> { - maskArray[row][column] = true - } - - else -> { - throw IllegalArgumentException("Strings may only contain '0' and '1'") - } - } - } - } - - return maskArray - } - - } - - fun setHeight(height: Int): Mask { - val newRows = Array(height) { BooleanArray(this.length) } - - for (index in 0..= getLength()) { - throw IndexOutOfBoundsException("Column index $index is out of bounds for pattern of length ${getLength()}") - } - - val column = IntArray(getHeight()) - - for (i in 0 until getHeight()) { - column[i] = pattern[i][index] - } - - return column - } - - fun contains(character: Int): Boolean { - for (row in pattern) { - for (cell in row) { - if (cell != character) { - continue - } - - return true - } - } - - return false - } - - fun getRow(index: Int): IntArray { - if (index < 0 || index >= getHeight()) { - throw IndexOutOfBoundsException("Row index $index is out of bounds for pattern of height ${getHeight()}") - } - - val row = pattern[index] - - return row.copyOf(row.size) - } - - fun getCharacter(x: Int, y: Int): Int { - if (x < 0 || x >= getLength() || y < 0 || y >= getHeight()) { - throw IndexOutOfBoundsException("Coordinates ($x, $y) are out of bounds for pattern of size ${getLength()}x${getHeight()}") - } - - return pattern[y][x] - } - - override fun hashCode() = pattern.contentDeepHashCode() - - override fun toString() = buildString { - append("Pattern{") - append("pattern=") - append(pattern.contentDeepToString()) - append('}') - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Pattern) return false - - return pattern.contentDeepEquals(other.pattern) - } + fun getColumn(index: Int): IntArray + fun getRow(index: Int): IntArray + fun contains(char: Char): Boolean + fun getChar(x: Int, y: Int): Char companion object { - private fun initializePattern(vararg pattern: String): Array { - val rows = pattern.size - val zeroRows = rows == 0 - - val patternArray = Array(rows) { - IntArray( - if (zeroRows) 0 else pattern[0].codePointCount( - 0, - pattern[0].length - ) - ) - } - - if (zeroRows) { - return patternArray - } - - val globalLength = pattern[0].length - - for (index in 0 until rows) { - val row = pattern[index] - val length = row.codePointCount(0, row.length) - - if (length != globalLength) { - throw IllegalArgumentException("All rows must have the same length. Row $index has length $length, expected $globalLength") - } - - val values = mutableIntListOf() - row.codePoints().forEach(values::add) - - for (column in 0 until values.size) { - patternArray[index][column] = values.getInt(column) - } - } - - return patternArray - } + operator fun invoke(vararg pattern: String): Pattern = + InventoryBridge.instance.createPattern(*pattern) } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt index 844c7e99..782b6058 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/InventoryComponent.kt @@ -1,255 +1,33 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.utils -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane -import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution -import dev.slne.surf.surfapi.core.api.util.freeze -import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf -import org.bukkit.event.inventory.InventoryClickEvent import org.bukkit.inventory.Inventory import org.bukkit.inventory.ItemStack import org.bukkit.inventory.PlayerInventory -import java.util.* - -class InventoryComponent internal constructor( - val length: Int, - val height: Int, -) : Cloneable { - - internal val panes = mutableObjectListOf() - internal val items = Array>(length) { Array(height) { null } } - - init { - if (length < 0 || height < 0) { - throw IllegalArgumentException("Length and height must be non-negative") - } - } - - fun addPane(pane: Pane) { - val size = panes.size - - if (size == 0) { - panes.add(pane) - return - } - - val priority = pane.priority - var left = 0 - var right = size - 1 - - while (left <= right) { - val middle = (left + right) / 2 - val middlePriority = getPane(middle).priority - - if (middlePriority == priority) { - panes.add(middle, pane) - return - } - - if (middlePriority.isLessThan(priority)) { - left = middle + 1 - } else if (middlePriority.isGreaterThan(priority)) { - right = middle - 1 - } - } - - panes.add(right + 1, pane) - } - - fun display(inventory: Inventory, offset: Int) { - display() - - placeItems(inventory, offset) - } - - fun display(playerInventory: PlayerInventory, offset: Int) { - display() - - placeItems(playerInventory, offset) - } - - fun placeItems(playerInventory: PlayerInventory, offset: Int) { - for (x in 0 until length) { - for (y in 0 until height) { - val slot = if (y == height - 1) { - x + offset - } else { - (y + 1) * length + x + offset - } - - playerInventory.setItem(slot, getItem(slot(x, y))) - } - } - } - - fun placeItems(inventory: Inventory, offset: Int) { - for (x in 0 until length) { - for (y in 0 until height) { - inventory.setItem(y * length + x + offset, getItem(slot(x, y))) - } - } - } - - @OptIn(NmsUseWithCaution::class) - fun click(gui: Gui, event: InventoryClickEvent, slot: Int) { - val panes = this.panes.freeze() - - for (i in panes.indices.reversed()) { - val pane = panes[i] - val result = pane.click( - gui, - component = this, - event, - slot, - paneOffsetX = 0, - paneOffsetY = 0, - maxLength = length, - maxHeight = height - ) - - if (result) { - break - } - } - } - - public override fun clone(): InventoryComponent { - val component = InventoryComponent(length, height) - - for (x in 0 until length) { - for (y in 0 until height) { - val item = getItem(slot(x, y)) ?: continue - - component.setItem(item.clone(), slot(x, y)) - } - } - - for (pane in panes) { - component.addPane(pane.clone()) - } - - return component - } - - fun excludeRows(from: Int, end: Int): InventoryComponent { - if (from < 0 || end >= height) { - throw IllegalArgumentException("Invalid row range: $from to $end") - } - - val newHeight = height - (end - from + 1) - val newComponent = InventoryComponent(length, newHeight) - - for (pane in panes) { - newComponent.addPane(pane) - } - - for (x in 0 until length) { - var newY = 0 - - for (y in 0 until height) { - val item = getItem(slot(x, y)) - - if (y >= from && y <= end) { - continue - } - - if (item != null) { - newComponent.setItem(item, slot(x, newY)) - } - - newY++ - } - } - - return newComponent - } - - fun hasItem(): Boolean { - for (x in 0 until length) { - for (y in 0 until height) { - if (getItem(slot(x, y)) != null) { - return true - } - } - } - - return false - } - - fun display() { - clearItems() - - for (pane in panes) { - if (!pane.visible) { - continue - } - - pane.display(this, 0, 0, length, height) - } - } - - fun hasItem(slot: Slot) = getItem(slot) != null - - fun getItem(slot: Slot): ItemStack? { - val x = slot.getX(length) - val y = slot.getY(length) - - if (!inBounds(slot)) { - throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") - } - - return items[x][y] - } - - fun setItem(item: GuiItem, slot: Slot) { - val x = slot.getX(length) - val y = slot.getY(length) - - if (!inBounds(slot)) { - throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") - } - - items[x][y] = item.clone().apply { applyUuid() }.itemStack - } - - fun setItem(itemStack: ItemStack, slot: Slot) { - val x = slot.getX(length) - val y = slot.getY(length) - - if (!inBounds(slot)) { - throw IllegalArgumentException("Coordinates ($x, $y) are out of bounds for inventory size $length x $height") - } - - items[x][y] = itemStack - } - - val size get() = length * height - - fun clearItems() { - for (item in items) { - Arrays.fill(item, null) - } - } - - fun inBounds(slot: Slot): Boolean { - val x = slot.getX(length) - val y = slot.getY(length) - - val xBounds = inBounds(0, length - 1, x) - val yBounds = inBounds(0, height - 1, y) - - return xBounds && yBounds - } - - fun inBounds(lowerBound: Int, upperBound: Int, value: Int) = value in lowerBound..upperBound - - fun getPane(index: Int): Pane { - if (!inBounds(0, panes.size - 1, index)) { - throw IndexOutOfBoundsException("Pane index $index is out of bounds for size ${panes.size}") - } - - return panes[index] - } +interface InventoryComponent : Cloneable { + val length: Int + val height: Int + + fun addPane(pane: Pane) + fun display(inventory: Inventory, offset: Int) + fun display(playerInventory: PlayerInventory, offset: Int) + fun placeItems(playerInventory: PlayerInventory, offset: Int) + fun placeItems(inventory: Inventory, offset: Int) + + fun excludeRows(from: Int, end: Int): InventoryComponent + fun hasItem(): Boolean + fun hasItem(slot: Slot): Boolean + fun getItem(slot: Slot): ItemStack? + fun setItem(item: GuiItem, slot: Slot) + fun setItem(itemStack: ItemStack, slot: Slot) + fun clearItems() + fun inBounds(slot: Slot): Boolean + fun inBounds(lowerBound: Int, upperBound: Int, value: Int): Boolean + fun getPane(index: Int): Pane + + fun display() + + public override fun clone(): InventoryComponent } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt index 76d97918..398c8c7f 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Priority.kt @@ -1,36 +1,13 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.utils enum class Priority { - - LOWEST { - override fun isLessThan(priority: Priority) = this != priority - }, - - LOW { - override fun isLessThan(priority: Priority) = this != priority && priority != LOWEST - }, - - NORMAL { - override fun isLessThan(priority: Priority) = - this != priority && priority != LOWEST && priority != LOW - }, - - HIGH { - override fun isLessThan(priority: Priority) = - this != priority && priority != LOWEST && priority != LOW && priority != NORMAL - }, - - HIGHEST { - override fun isLessThan(priority: Priority) = - this != priority && priority != LOWEST && priority != LOW && priority != NORMAL && priority != HIGH - }, - - MONITOR { - override fun isLessThan(priority: Priority) = false - }; - - abstract fun isLessThan(priority: Priority): Boolean - fun isGreaterThan(priority: Priority) = !isLessThan(priority) && this != priority - - + LOWEST, + LOW, + NORMAL, + HIGH, + HIGHEST, + MONITOR; + + fun isLessThan(priority: Priority): Boolean = this.ordinal < priority.ordinal + fun isGreaterThan(priority: Priority): Boolean = this.ordinal > priority.ordinal } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt index d4f9089a..169463d2 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/Slot.kt @@ -1,59 +1,43 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.utils import org.jetbrains.annotations.Range -import java.util.* interface Slot { - fun getX(length: Int): Int fun getY(length: Int): Int companion object { - fun fromXY(x: Int, y: Int) = XY(x, y) - fun fromIndex(index: Int) = Indexed(index) - } - - class XY(val x: Int, val y: Int) : Slot { - override fun getX(length: Int) = x - override fun getY(length: Int) = y - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is XY) return false - - return x == other.x && y == other.y + fun fromXY(x: Int, y: Int): Slot = XY(x, y) + fun fromIndex(index: Int): Slot = Indexed(index) + + operator fun invoke( + x: @Range(from = 0, to = 8) Int, + y: @Range(from = 0, to = 5) Int, + ): Slot { + return fromXY(x, y) } - override fun hashCode() = Objects.hash(x, y) - - override fun toString() = "XY(x=$x, y=$y)" - - } - - class Indexed(val index: Int) : Slot { - override fun getX(length: Int): Int { - if (length <= 0) throw IllegalArgumentException("Length must be greater than 0") - - return index % length + operator fun invoke(index: Int): Slot { + return fromIndex(index) } + } +} - override fun getY(length: Int): Int { - if (length <= 0) throw IllegalArgumentException("Length must be greater than 0") - - return index / length - } - - override fun equals(other: Any?): Boolean { - if (this === other) return true - if (other !is Indexed) return false - - return index == other.index - } +internal data class XY(val x: Int, val y: Int) : Slot { + override fun getX(length: Int) = x + override fun getY(length: Int) = y +} - override fun hashCode() = index - override fun toString() = "Indexed(index=$index)" +internal data class Indexed(val index: Int) : Slot { + override fun getX(length: Int): Int { + require(length > 0) { "Length must be greater than 0" } + return index % length } + override fun getY(length: Int): Int { + require(length > 0) { "Length must be greater than 0" } + return index / length + } } fun slot(x: @Range(from = 0, to = 8) Int, y: @Range(from = 0, to = 5) Int) = Slot.fromXY(x, y) diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt deleted file mode 100644 index f4843d9c..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/view/InventoryViewUtil.kt +++ /dev/null @@ -1,21 +0,0 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.view - -import org.bukkit.inventory.InventoryView -import org.bukkit.inventory.ItemStack - - -object InventoryViewUtil { - - fun getTitle(view: InventoryView) = view.title() - - fun getTopInventory(view: InventoryView) = view.topInventory - fun getBottomInventory(view: InventoryView) = view.bottomInventory - - fun getCursor(view: InventoryView) = view.cursor - fun setCursor(view: InventoryView, item: ItemStack?) { - view.setCursor(item) - } - - fun getInventory(view: InventoryView, slot: Int) = view.getInventory(slot) - fun getSlotType(view: InventoryView, slot: Int) = view.getSlotType(slot) -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts index f40a36e8..c86e453d 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-server/build.gradle.kts @@ -16,6 +16,7 @@ tasks.assemble { kotlin { compilerOptions { optIn.add("dev.slne.surf.surfapi.bukkit.api.visualizer.visualizer.ExperimentalVisualizerApi") + optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") } } diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/dsl/HandlerDslImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/dsl/HandlerDslImpl.kt new file mode 100644 index 00000000..98999ce5 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/dsl/HandlerDslImpl.kt @@ -0,0 +1,27 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.* +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryDragEvent + +@JvmInline +value class ClickHandlerScopeImpl(private val handler: ClickHandlerDsl) : ClickHandler { + override fun handle(event: InventoryClickEvent) { + ClickHandlerScope(event).handler() + } +} + +@JvmInline +value class CloseHandlerScopeImpl(private val handler: CloseHandlerDsl) : CloseHandler { + override fun handle(event: InventoryCloseEvent) { + CloseHandlerScope(event).handler() + } +} + +@JvmInline +value class DragHandlerScopeImpl(private val handler: DragHandlerDsl) : DragHandler { + override fun handle(event: InventoryDragEvent) { + DragHandlerScope(event).handler() + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt new file mode 100644 index 00000000..33965306 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt @@ -0,0 +1,215 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.* +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.ClickHandlerScopeImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.CloseHandlerScopeImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.DragHandlerScopeImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.PlayerInventoryCache +import dev.slne.surf.surfapi.core.api.util.logger +import it.unimi.dsi.fastutil.objects.Object2IntMap +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.event.inventory.InventoryCloseEvent +import org.bukkit.event.inventory.InventoryDragEvent +import org.bukkit.event.inventory.InventoryEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import java.util.* + +abstract class AbstractGui(override val parent: AbstractGui? = null) : Gui { + val cache = PlayerInventoryCache() + + override lateinit var backingInventory: Inventory + + private var onTopClick: ClickHandler? = null + private var onBottomClick: ClickHandler? = null + private var onGlobalClick: ClickHandler? = null + private var onOutsideClick: ClickHandler? = null + private var onTopDrag: DragHandler? = null + private var onBottomDrag: DragHandler? = null + private var onGlobalDrag: DragHandler? = null + private var onClose: CloseHandler? = null + + var shouldNavigateToParentOnClose: Boolean = false + + @Volatile + var updating: Boolean = false + private set + + abstract fun click(event: InventoryClickEvent) + abstract fun isPlayerInventoryUsed(): Boolean + protected abstract fun updateAllItems(): Object2IntMap + protected abstract fun updateItem0(item: UpdatableGuiItemImpl): Int? + + // region Handlers + override fun onTopClick(handler: ClickHandler) { + onTopClick = handler + } + + override fun onTopClick(handler: ClickHandlerDsl) = onTopClick(ClickHandlerScopeImpl(handler)) + + override fun onBottomClick(handler: ClickHandler) { + onBottomClick = handler + } + + override fun onBottomClick(handler: ClickHandlerDsl) = + onBottomClick(ClickHandlerScopeImpl(handler)) + + override fun onGlobalClick(handler: ClickHandler) { + onGlobalClick = handler + } + + override fun onGlobalClick(handler: ClickHandlerDsl) = + onGlobalClick(ClickHandlerScopeImpl(handler)) + + override fun onOutsideClick(handler: ClickHandler) { + onOutsideClick = handler + } + + override fun onOutsideClick(handler: ClickHandlerDsl) = + onOutsideClick(ClickHandlerScopeImpl(handler)) + + override fun onTopDrag(handler: DragHandler) { + onTopDrag = handler + } + + override fun onTopDrag(handler: DragHandlerDsl) = onTopDrag(DragHandlerScopeImpl(handler)) + + override fun onBottomDrag(handler: DragHandler) { + onBottomDrag = handler + } + + override fun onBottomDrag(handler: DragHandlerDsl) = onBottomDrag(DragHandlerScopeImpl(handler)) + + override fun onGlobalDrag(handler: DragHandler) { + onGlobalDrag = handler + } + + override fun onGlobalDrag(handler: DragHandlerDsl) = onGlobalDrag(DragHandlerScopeImpl(handler)) + + override fun onClose(handler: CloseHandler) { + onClose = handler + } + + override fun onClose(handler: CloseHandlerDsl) = onClose(CloseHandlerScopeImpl(handler)) + // endregion + + override fun navigateToParentOnClose(enabled: Boolean) { + shouldNavigateToParentOnClose = enabled + } + + override fun update() { + if (updating) return + updating = true + + val updatedItems = updateAllItems() + for ((item, slot) in updatedItems) { + if (item.visible) { + backingInventory.setItem(slot, item.itemStack) + } else { + backingInventory.setItem(slot, ItemStack.empty()) + } + } + + for (viewer in viewers) { + val cursor = viewer.itemOnCursor + + viewer.setItemOnCursor(ItemStack.empty()) + show(viewer) + viewer.setItemOnCursor(cursor) + } + + updating = false + } + + override fun updateItem(item: UpdatableGuiItemImpl) { + if (updating) return + + val slot = updateItem0(item) ?: return + if (item.visible) { + backingInventory.setItem(slot, item.itemStack) + } else { + backingInventory.setItem(slot, ItemStack.empty()) + } + + updating = false + } + + protected fun addInventory(inventory: Inventory, gui: AbstractGui) { + GUI_INVENTORIES[inventory] = gui + } + + fun callOnTopClick(event: InventoryClickEvent) { + callCallback(onTopClick, event) + } + + fun callOnBottomClick(event: InventoryClickEvent) { + callCallback(onBottomClick, event) + } + + fun callOnGlobalClick(event: InventoryClickEvent) { + callCallback(onGlobalClick, event) + } + + fun callOnOutsideClick(event: InventoryClickEvent) { + callCallback(onOutsideClick, event) + } + + fun callOnTopDrag(event: InventoryDragEvent) { + callCallback(onTopDrag, event) + } + + fun callOnBottomDrag(event: InventoryDragEvent) { + callCallback(onBottomDrag, event) + } + + fun callOnGlobalDrag(event: InventoryDragEvent) { + callCallback(onGlobalDrag, event) + } + + fun callOnClose(event: InventoryCloseEvent) { + callCallback(onClose, event) + } + + private fun callCallback( + callback: GuiHandler?, + event: E, + ) { + if (callback == null) return + + try { + callback.handle(event) + } catch (exception: Throwable) { + log.atSevere() + .withCause(exception) + .log(buildString { + append("Exception while handling $callback") + + if (event is InventoryClickEvent) { + append(", slot=${event.slot}") + } + }) + } + } + + override fun navigateToParent(player: Player): Boolean { + val parent = parent + if (parent == null) return false + + parent.show(player) + parent.update() + return true + } + + override fun walkParents(): Sequence = generateSequence(this) { it.parent } + + companion object { + private val log = logger() + private val GUI_INVENTORIES = WeakHashMap() + + fun getGui(inventory: Inventory) = GUI_INVENTORIES[inventory] + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt new file mode 100644 index 00000000..c1450251 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt @@ -0,0 +1,25 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui +import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder +import net.kyori.adventure.text.Component + +abstract class AbstractNamedGui(title: Component, parent: AbstractGui? = null) : + AbstractGui(parent), NamedGui { + + protected var titleDirty = false + + override var title: Component = title + set(value) { + field = value + titleDirty = true + } + + override fun title(title: Component) { + this.title = title + } + + override fun title(builder: @GuiDsl SurfComponentBuilder.() -> Unit) = + title(SurfComponentBuilder(builder)) +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt new file mode 100644 index 00000000..3a78aa23 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt @@ -0,0 +1,115 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.types + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractNamedGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.utils.InventoryBased +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import dev.slne.surf.surfapi.core.api.util.freeze +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf +import it.unimi.dsi.fastutil.objects.Object2IntMap +import net.kyori.adventure.text.Component +import org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent + +open class ChestGuiImpl( + title: Component, + initialSize: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.FIVE_ROWS, + parent: AbstractGui? = null, +) : AbstractNamedGui(title, parent), MergedGui, InventoryBased { + + private var rows: Int = initialSize.rows + set(value) { + inventoryComponent = InventoryComponentImpl(9, value + 4) + + for (pane in inventoryComponent.panes) { + inventoryComponent.addPane(pane) + } + + sizeDirty = true + } + + override var inventoryComponent = InventoryComponentImpl(9, rows + 4) + set(value) { + field = value + panes = value.panes.freeze() + } + + private var sizeDirty = false + + final override var panes = inventoryComponent.panes.freeze() + private set + + override val items get() = panes.flatMapTo(mutableObjectListOf()) { it.items }.freeze() + override val viewers + get() = backingInventory.viewers.filterIsInstanceTo(mutableObjectSetOf()).freeze() + + override fun updateAllItems(): Object2IntMap = + panes.fold(mutableObject2IntMapOf()) { acc, pane -> + acc.apply { putAll(pane.updateItems()) } + } + + override fun updateItem0(item: UpdatableGuiItemImpl) = + panes.find { it.items.contains(item) }?.updateItem(item) + + + override fun addPane(pane: AbstractPane) { + inventoryComponent.addPane(pane) + } + + override fun createInventory() = Bukkit.createInventory(this, rows * 9, title) + override fun getInventory() = backingInventory + + override fun show(player: Player) { + if (titleDirty || sizeDirty) { + backingInventory = createInventory() + + titleDirty = false + sizeDirty = false + } + + backingInventory.clear() + + val height = inventoryComponent.height + inventoryComponent.display() + + val topComponent = inventoryComponent.excludeRows(height - 4, height - 1) + val bottomComponent = inventoryComponent.excludeRows(0, height - 5) + + topComponent.placeItems(backingInventory, 0) + + if (bottomComponent.hasItem()) { + if (!cache.contains(player)) { + cache.storeAndClear(player) + } + + bottomComponent.placeItems(player.inventory, 0) + } + + player.openInventory(backingInventory) + } + + override fun clone() = super.clone() as ChestGuiImpl + + override fun click(event: InventoryClickEvent) { + inventoryComponent.click(this, event, event.rawSlot) + } + + override fun isPlayerInventoryUsed() = + inventoryComponent.excludeRows(0, inventoryComponent.height - 5).hasItem() +} + +//@MenuMarker +//class ChestSinglePlayerGui internal constructor( +// val player: Player, +// title: Component, +// size: ChestGuiSize = ChestGuiSize.SIX_ROWS, +// parent: AbstractGui? = null, +//) : ChestGuiImpl(title, size, parent) \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/utils/InventoryBased.kt similarity index 51% rename from surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt rename to surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/utils/InventoryBased.kt index 6cd00bd6..6ca99acb 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/utils/InventoryBased.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/utils/InventoryBased.kt @@ -1,10 +1,8 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.gui.utils +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.utils import org.bukkit.inventory.Inventory import org.bukkit.inventory.InventoryHolder -internal interface InventoryBased : InventoryHolder { - +interface InventoryBased : InventoryHolder { fun createInventory(): Inventory - } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt new file mode 100644 index 00000000..035c4cb7 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt @@ -0,0 +1,94 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.item + +import com.jeff_media.morepersistentdatatypes.DataType +import dev.slne.surf.surfapi.bukkit.api.builder.meta +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandler +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.util.key +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.ClickHandlerScopeImpl +import dev.slne.surf.surfapi.core.api.util.logger +import org.bukkit.NamespacedKey +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ItemType +import java.util.* + +open class GuiItemImpl( + override val key: NamespacedKey = DEFAULT_KEY, + override val uuid: UUID = UUID.randomUUID(), +) : GuiItem, Cloneable { + private var _itemStack: ItemStack? = null + set(value) { + field = value + applyUuid() + } + + override val itemStack: ItemStack + get() = _itemStack ?: error("ItemStack is not initialized.") + + override var visible: Boolean = true + var action: ClickHandler? = null + private set + + override fun onClick(handler: @GuiItemDsl ClickHandler) { + action = handler + } + + override fun onClick(handler: @GuiItemDsl ClickHandlerDsl) { + onClick(ClickHandlerScopeImpl(handler)) + } + + override fun item(itemStack: ItemStack) { + _itemStack = itemStack + } + + override fun item( + type: ItemType, + block: @GuiItemDsl ItemStack.() -> Unit, + ) { + item(type.createItemStack().apply { block() }) + } + + fun callAction(event: InventoryClickEvent) { + try { + action?.handle(event) + } catch (exception: Exception) { + log.atSevere() + .withCause(exception) + .log( + "Exception while handling click event in inventory '${event.view.title()}', slot=${event.slot}, item=${itemStack.type}" + ) + } + } + + fun applyUuid() { + itemStack.meta { + persistentDataContainer.set(key, DataType.UUID, uuid) + } + } + + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (javaClass != other?.javaClass) return false + + other as GuiItemImpl + + return uuid == other.uuid + } + + override fun hashCode(): Int { + return uuid.hashCode() + } + + override fun toString(): String { + return "GuiItem(itemStack=$_itemStack, action=$action, key=$key, uuid=$uuid, visible=$visible)" + } + + companion object { + private val log = logger() + val DEFAULT_KEY = key("surf-api-uuid") + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt new file mode 100644 index 00000000..e5900b0a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt @@ -0,0 +1,20 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.item + +import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import org.bukkit.NamespacedKey +import java.util.* +import java.util.function.BooleanSupplier + +class UpdatableGuiItemImpl( + key: NamespacedKey = DEFAULT_KEY, + uuid: UUID = UUID.randomUUID(), +) : GuiItemImpl(key, uuid), UpdatableGuiItem { + private var update: BooleanSupplier? = null + + override fun onUpdate(update: @GuiItemDsl BooleanSupplier) { + this.update = update + } + + fun update(): Boolean = update?.asBoolean == true +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/listener/GuiListener.kt similarity index 76% rename from surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt rename to surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/listener/GuiListener.kt index d27a87d2..b4bb5a70 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/listener/GuiListener.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/listener/GuiListener.kt @@ -1,10 +1,9 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.listener +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.listener -import com.github.shynixn.mccoroutine.folia.SuspendingJavaPlugin import com.github.shynixn.mccoroutine.folia.launch import com.github.shynixn.mccoroutine.folia.ticks -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui -import dev.slne.surf.surfapi.bukkit.api.inventory.view.InventoryViewUtil +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.plugin import dev.slne.surf.surfapi.core.api.util.freeze import dev.slne.surf.surfapi.core.api.util.logger import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf @@ -19,23 +18,22 @@ import org.bukkit.event.server.PluginDisableEvent import org.bukkit.inventory.Inventory import java.util.* -class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { - +object GuiListener : Listener { private val log = logger() - private val activeGuiInstances = mutableObjectSetOf() + private val activeGuiInstances = mutableObjectSetOf() @EventHandler(ignoreCancelled = true) fun InventoryClickEvent.onInventoryClick() { val gui = getGui(inventory) ?: return - val inventory = InventoryViewUtil.getInventory(view, rawSlot) ?: run { + val inventory = view.getInventory(rawSlot) ?: run { gui.callOnOutsideClick(this) return } gui.callOnGlobalClick(this) - if (inventory == InventoryViewUtil.getTopInventory(view)) { + if (inventory == view.topInventory) { gui.callOnTopClick(this) } else { gui.callOnBottomClick(this) @@ -56,7 +54,7 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { fun EntityPickupItemEvent.onEntityPickupItem() { val player = entity as? Player ?: return - val gui = getGui(InventoryViewUtil.getTopInventory(player.openInventory)) + val gui = getGui(player.openInventory.topInventory) if (gui == null || !gui.isPlayerInventoryUsed()) { return } @@ -83,11 +81,11 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { var bottom = false for (slot in rawSlots) { - val inventory = InventoryViewUtil.getInventory(view, slot) ?: continue + val inventory = view.getInventory(slot) ?: continue - if (inventory == InventoryViewUtil.getTopInventory(view)) { + if (inventory == view.topInventory) { top = true - } else if (inventory == InventoryViewUtil.getBottomInventory(view)) { + } else if (inventory == view.bottomInventory) { bottom = true } @@ -103,13 +101,13 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { } } else { val index = rawSlots.toTypedArray().first() - val slotType = InventoryViewUtil.getSlotType(view, index) + val slotType = view.getSlotType(index) val even = type == DragType.EVEN val clickType = if (even) ClickType.LEFT else ClickType.RIGHT val action = if (even) InventoryAction.PLACE_SOME else InventoryAction.PLACE_ONE - val previousCursor = InventoryViewUtil.getCursor(view) - InventoryViewUtil.setCursor(view, oldCursor) + val previousCursor = view.cursor + view.setCursor(oldCursor) val clickEvent = InventoryClickEvent( view, @@ -121,8 +119,8 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { clickEvent.onInventoryClick() - if (Objects.equals(InventoryViewUtil.getCursor(view), oldCursor)) { - InventoryViewUtil.setCursor(view, previousCursor) + if (Objects.equals(view.cursor, oldCursor)) { + view.setCursor(previousCursor) } isCancelled = clickEvent.isCancelled @@ -145,7 +143,7 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { activeGuiInstances.remove(gui) } - if (gui.shouldNavigateParentOnClose) { + if (gui.shouldNavigateToParentOnClose) { plugin.launch { delay(1.ticks) @@ -163,8 +161,8 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { } @EventHandler(ignoreCancelled = true, priority = EventPriority.HIGHEST) - fun PluginDisableEvent.onPluginDisable() { - if (this@GuiListener.plugin != this.plugin) return + fun onPluginDisable(event: PluginDisableEvent) { + if (plugin != event.plugin) return var counter = 0 val maxCount = 10 @@ -192,14 +190,14 @@ class GuiListener(private val plugin: SuspendingJavaPlugin) : Listener { TODO("Implement TradeSelectEvent handling in GUI") } - private fun getGui(inventory: Inventory): Gui? { - val gui = Gui.getGui(inventory) + private fun getGui(inventory: Inventory): AbstractGui? { + val gui = AbstractGui.getGui(inventory) if (gui != null) return gui val holder = inventory.holder - if (holder is Gui) { + if (holder is AbstractGui) { return holder } diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/AbstractPane.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/AbstractPane.kt new file mode 100644 index 00000000..82651c28 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/AbstractPane.kt @@ -0,0 +1,118 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane + +import com.jeff_media.morepersistentdatatypes.DataType +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandler +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.ClickHandlerScopeImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +abstract class AbstractPane( + override var slot: Slot, + override var length: Int, + override var height: Int, + override var priority: Priority = Priority.NORMAL, + override val uuid: UUID = UUID.randomUUID(), +) : Pane { + var onClick: ClickHandler? = null + override var visible: Boolean = true + + init { + require(length >= 0) { "Length must be greater than or equal to 0 (length=$length)" } + require(height >= 0) { "Height must be greater than or equal to 0 (height=$height)" } + } + + override fun onClick(handler: ClickHandler) { + onClick = handler + } + + override fun onClick(handler: ClickHandlerDsl) { + onClick(ClickHandlerScopeImpl(handler)) + } + + abstract fun display( + component: InventoryComponentImpl, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) + + abstract fun updateItems(): Object2IntMap + abstract fun updateItem(item: UpdatableGuiItemImpl): Int? + + abstract fun click( + gui: AbstractGui, + component: InventoryComponentImpl, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean + + abstract override val items: ObjectList + abstract override val panes: ObjectList + + protected fun callOnClick(event: InventoryClickEvent) { + try { + onClick?.handle(event) + } catch (exception: Exception) { + throw RuntimeException(buildString { + append("Exception while handling click event in inventory '") + append(event.view.title()) + append( + "slot=${event.slot}, for ${javaClass.simpleName}, x=${slot.getX(length)}, y=${ + slot.getY( + length + ) + }, length=$length, height=$height" + ) + }, exception) + } + } + + companion object { + @JvmStatic + protected fun matchesItem( + guiItem: GuiItem, + item: ItemStack, + ): Boolean { + return guiItem.uuid == item.persistentDataContainer.get(guiItem.key, DataType.UUID) + } + + @JvmStatic + protected fun findMatchingItem( + items: Collection, + item: ItemStack, + ): T? { + for (guiItem in items) { + if (matchesItem(guiItem, item)) { + return guiItem + } + } + + return null + } + } + + public override fun clone(): AbstractPane { + throw UnsupportedOperationException("The implementing pane has not overridden the clone method.") + } + + override fun toString(): String { + return "Pane(slot=$slot, length=$length, height=$height, priority=$priority, uuid=$uuid, onClick=$onClick, visible=$visible, items=$items, panes=$panes)" + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/OutlinePaneImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/OutlinePaneImpl.kt new file mode 100644 index 00000000..c5d75f11 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/OutlinePaneImpl.kt @@ -0,0 +1,335 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.OutlinePane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Orientable +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.utils.MaskImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.GeometryUtils +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import dev.slne.surf.surfapi.core.api.util.objectListOf +import it.unimi.dsi.fastutil.objects.Object2IntMap +import org.bukkit.event.inventory.InventoryClickEvent +import java.util.* + +class OutlinePaneImpl( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : AbstractPane(slot, length, height, priority, uuid), OutlinePane { + + override var length: Int = super.length + get() = super.length + set(value) { + field = value + + applyMask(mask.setLength(value)) + } + + override var height: Int = super.height + get() = super.height + set(value) { + field = value + + applyMask(mask.setHeight(value)) + } + + override var orientation = Orientable.Orientation.HORIZONTAL + override var flippedHorizontally = false + override var flippedVertically = false + override var rotation = 0 + set(value) { + if (length != height) { + throw IllegalArgumentException("Rotation is only allowed for square panes.") + } + + if (rotation % 90 != 0) { + throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") + } + + field = value % 360 + } + + private var gap = 0 + private var repeat = false + + private var alignment = Alignment.BEGIN + private var mask: MaskImpl + + override val panes = objectListOf() + override val items = mutableObjectListOf(length * height) + + init { + val mask = Array(height) { "" } + val maskString = buildString { + for (i in 0 until length) { + append("1") + } + } + + mask.fill(maskString) + this.mask = MaskImpl(*mask) + } + + override fun display( + component: InventoryComponentImpl, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + var itemIndex = 0 + var gapCount = 0 + + val size = when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + height + } + + Orientable.Orientation.VERTICAL -> { + length + } + } + + for (vectorIndex in 0 until size) { + if (items.size <= itemIndex) break + + val maskLine = when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + mask.getRow(vectorIndex) + } + + Orientable.Orientation.VERTICAL -> { + mask.getColumn(vectorIndex) + } + } + + var enabled = 0 + for (bool in maskLine) { + if (bool) enabled++ + } + + val displayItems: Array = if (repeat) { + arrayOfNulls(enabled) + } else { + val remaining = gapCount + (items.size - itemIndex - 1) * (gap + 1) + 1 + + arrayOfNulls(minOf(enabled, remaining)) + } + + for (index in 0 until displayItems.size) { + if (gapCount == 0) { + displayItems[index] = items[itemIndex] + + itemIndex++ + + if (repeat && itemIndex >= items.size) { + itemIndex = 0 + } + + gapCount = gap + } else { + displayItems[index] = null + + gapCount-- + } + } + + var index = if (alignment == Alignment.BEGIN) { + 0 + } else { + -((enabled - displayItems.size) / 2) + } + for (opposingVectorIndex in 0 until maskLine.size) { + if (!maskLine[opposingVectorIndex]) { + continue + } + + if (index >= 0 && index < displayItems.size && displayItems[index] != null) { + var x: Int + var y: Int + + when (orientation) { + Orientable.Orientation.HORIZONTAL -> { + x = opposingVectorIndex + y = vectorIndex + } + + Orientable.Orientation.VERTICAL -> { + x = vectorIndex + y = opposingVectorIndex + } + } + + if (flippedHorizontally) { + x = length - x - 1 + } + + if (flippedVertically) { + y = height - y - 1 + } + + val coordinates = GeometryUtils.processClockwiseRotation( + x, + y, + length, + height, + rotation + ) + + x = coordinates.intKey + y = coordinates.intValue + + if (x < 0 || x >= length || y < 0 || y >= height) { + continue + } + + val finalRow = slot.getY(maxLength) + y + paneOffsetY + val finalColumn = slot.getX(maxLength) + x + paneOffsetX + + val item = displayItems[index] ?: continue + if (item.visible) { + component.setItem(item, Slot.fromXY(finalColumn, finalRow)) + } + } + + index++ + } + } + } + + override fun updateItem(item: UpdatableGuiItemImpl): Int? { + for ((index, guiItem) in items.withIndex()) { + if (guiItem != item) continue + if (!item.update()) return null + + return index + slot.getX(length) + slot.getY(length) * length + } + + return null + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + + for ((index, guiItem) in items.withIndex()) { + if (guiItem !is UpdatableGuiItemImpl) continue + if (!guiItem.update()) continue + + updatedItems[guiItem] = index + slot.getX(length) + slot.getY(length) * length + } + + return updatedItems + } + + override fun click( + gui: AbstractGui, + component: InventoryComponentImpl, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + val item = findMatchingItem(items, itemStack) ?: return false + + item.callAction(event) + + return true + } + + override fun clone(): OutlinePaneImpl { + val outlinePane = OutlinePaneImpl( + slot, + length, + height, + priority, + uuid + ) + + for (item in items) { + outlinePane.addItem(item.clone()) + } + + outlinePane.visible = visible + outlinePane.onClick = onClick + + outlinePane.orientation = orientation + outlinePane.flippedHorizontally = flippedHorizontally + outlinePane.flippedVertically = flippedVertically + outlinePane.rotation = rotation + outlinePane.gap = gap + outlinePane.repeat = repeat + outlinePane.alignment = alignment + outlinePane.mask = mask + + return outlinePane + } + + override fun setItem(index: Int, item: GuiItem) { + require(item is GuiItemImpl) { "Item must be of type GuiItemImpl" } + items.add(index, item) + } + + override fun addItem(item: GuiItem) { + require(item is GuiItemImpl) { "Item must be of type GuiItemImpl" } + items.add(item) + } + + override fun removeItem(item: GuiItem) { + require(item is GuiItemImpl) { "Item must be of type GuiItemImpl" } + items.remove(item) + } + + override fun clear() { + items.clear() + } + + override fun applyMask(mask: Mask) { + require(mask is MaskImpl) { "Mask must be of type MaskImpl" } + if (length != mask.length || height != mask.height) { + throw IllegalArgumentException("Mask dimensions must match pane dimensions.") + } + + this.mask = mask + } + + enum class Alignment { + BEGIN, CENTER + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/PaginatedPaneImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/PaginatedPaneImpl.kt new file mode 100644 index 00000000..7fa69a09 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/PaginatedPaneImpl.kt @@ -0,0 +1,284 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import dev.slne.surf.surfapi.core.api.util.* +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* +import kotlin.math.ceil + +class PaginatedPaneImpl( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : AbstractPane(slot, length, height, priority, uuid), PaginatedPane { + + override val totalPages: Int + get() = paginatedPanes.size + + override val panes: ObjectList + get() { + val panes = mutableObjectListOf() + + paginatedPanes.forEach { (_, paginatedPane) -> + paginatedPane.forEach { pane -> + panes.addAll(pane.panes) + } + panes.addAll(paginatedPane) + } + + return panes + } + + override val items: ObjectList + get() = panes.flatMap { it.items }.toObjectList() + + override fun clear() { + paginatedPanes.clear() + } + + private val paginatedPanes = mutableInt2ObjectMapOf>() + override var page: Int = 0 + set(value) { + if (!paginatedPanes.containsKey(value)) { + throw IllegalArgumentException("Page $value does not exist.") + } + + field = value + } + + override fun page(page: Int) { + this.page = page + } + + override fun previousPage() { + if (page > 0) { + page-- + } + } + + override fun nextPage() { + if (paginatedPanes.containsKey(page + 1)) { + page++ + } + } + + fun addPage(pane: Pane) { + require(pane is AbstractPane) { "Only AbstractPane can be added to PaginatedPane." } + val newPageList = mutableObjectListOf(pane) + + if (paginatedPanes.isEmpty()) { + paginatedPanes[0] = newPageList + return + } + + val highestPage = paginatedPanes.keys.maxOrNull() ?: 0 + + if (highestPage == Int.MAX_VALUE) { + throw ArithmeticException("Cannot add more pages, maximum reached.") + } + + paginatedPanes[highestPage + 1] = newPageList + } + + fun addPane(page: Int, pane: Pane) { + require(pane is AbstractPane) { "Only AbstractPane can be added to PaginatedPane." } + + if (!paginatedPanes.containsKey(page)) { + paginatedPanes[page] = mutableObjectListOf() + } + + paginatedPanes[page]!!.add(pane) + paginatedPanes[page]!!.sortWith(Comparator.comparing(AbstractPane::priority)) + } + + override fun populateWithGuiItems(items: ObjectList) { + if (items.isEmpty()) { + return + } + + val itemsPerPage = length * height + val pagesNeeded: Int = ceil(items.size / itemsPerPage.toDouble()).coerceAtLeast(1.0).toInt() + + for (i in 0 until pagesNeeded) { + val page = OutlinePaneImpl(Slot(0, 0), length, height) + + for (j in 0 until itemsPerPage) { + val index = i * itemsPerPage + j + + if (index >= items.size) { + break + } + + page.addItem(items[index]) + } + + addPane(i, page) + } + } + + override fun populateWithItemStacks(items: ObjectList) = + populateWithGuiItems(items.map { GuiItemImpl().apply { item(it) } }.toObjectList()) + + override fun display( + component: InventoryComponentImpl, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val panes = paginatedPanes[page] ?: return + + for (pane in panes) { + if (!pane.visible) { + continue + } + + val newPaneOffsetX = paneOffsetX + slot.getX(maxLength) + val newPaneOffsetY = paneOffsetY + slot.getY(maxLength) + val newMaxLength = minOf(length, maxLength) + val newMaxHeight = minOf(height, maxHeight) + + pane.display(component, newPaneOffsetX, newPaneOffsetY, newMaxLength, newMaxHeight) + } + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + val selectedPagePanes = paginatedPanes[page] ?: return updatedItems + + for (pane in selectedPagePanes) { + if (!pane.visible) { + continue + } + + val paneItems = pane.updateItems() + for ((item, index) in paneItems) { + updatedItems[item] = + index + pane.slot.getX(length) + pane.slot.getY(length) * length + } + } + + return updatedItems + } + + override fun updateItem(item: UpdatableGuiItemImpl): Int? { + val selectedPagePanes = paginatedPanes[page] ?: return null + + for (pane in selectedPagePanes) { + if (!pane.visible) { + continue + } + + val index = pane.updateItem(item) ?: continue + return index + pane.slot.getX(length) + pane.slot.getY(length) * length + } + + return null + } + + override fun click( + gui: AbstractGui, + component: InventoryComponentImpl, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + for (pane in paginatedPanes.getOrElse(page) { mutableObjectListOf() }.freeze()) { + if (!pane.visible) { + continue + } + + if (pane.click( + gui, + component, + event, + slot, + paneOffsetX + xPosition, + paneOffsetY + yPosition, + length, + height + ) + ) { + return true + } + } + + return false + } + + override fun clone(): PaginatedPaneImpl { + val paginatedPane = PaginatedPaneImpl( + slot, + length, + height, + priority, + uuid + ) + + paginatedPanes.forEach { (pageNumber, panes) -> + panes.forEach { pane -> + paginatedPane.addPane(pageNumber, pane.clone()) + } + } + + paginatedPane.page = page + paginatedPane.visible = visible + paginatedPane.onClick = onClick + + return paginatedPane + } + + fun removePage(page: Int) { + if (paginatedPanes.remove(page) == null) { + return + } + + val newPanes = mutableInt2ObjectMapOf>() + for ((index, panes) in paginatedPanes) { + if (index > page) { + newPanes.put(index - 1, panes) + } else { + newPanes.put(index, panes) + } + } + + paginatedPanes.clear() + paginatedPanes.putAll(newPanes) + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt new file mode 100644 index 00000000..72bf84cd --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt @@ -0,0 +1,215 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.GeometryUtils +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf +import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf +import dev.slne.surf.surfapi.core.api.util.objectListOf +import dev.slne.surf.surfapi.core.api.util.toObjectList +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import java.util.* + +class StaticPaneImpl( + slot: Slot, + length: Int, + height: Int, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : AbstractPane(slot, length, height, priority, uuid), StaticPane { + + override var flippedHorizontally: Boolean = false + override var flippedVertically: Boolean = false + override var rotation: Int = 0 + set(value) { + if (length != height) { + throw IllegalArgumentException("Rotation is only allowed for square panes.") + } + + if (rotation % 90 != 0) { + throw IllegalArgumentException("Rotation must be a multiple of 90 degrees.") + } + + field = value % 360 + } + + override val panes: ObjectList = objectListOf() + + private val paneItems = mutableObject2ObjectMapOf() + override val items: ObjectList get() = paneItems.values.toObjectList() + + override fun display( + component: InventoryComponentImpl, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = minOf(this.length, maxLength) + val height = minOf(this.height, maxHeight) + + paneItems.entries.asSequence() + .filter { it.value.visible } + .forEach { (slot, guiItem) -> + var x = slot.getX(length) + var y = slot.getY(length) + + if (flippedHorizontally) { + x = length - x - 1 + } + + if (flippedVertically) { + y = height - y - 1 + } + + val coordinates = GeometryUtils.processClockwiseRotation( + x, + y, + length, + height, + rotation + ) + + x = coordinates.intKey + y = coordinates.intValue + + if (x < 0 || x >= length || y < 0 || y >= height) { + return@forEach + } + + val finalRow = this@StaticPaneImpl.slot.getY(maxLength) + y + paneOffsetY + val finalColumn = this@StaticPaneImpl.slot.getX(maxLength) + x + paneOffsetX + + component.setItem(guiItem, slot(finalColumn, finalRow)) + } + } + + override fun updateItems(): Object2IntMap { + val updatedItems = mutableObject2IntMapOf() + + for ((slot, guiItem) in paneItems.entries) { + if (guiItem !is UpdatableGuiItemImpl) continue + if (!guiItem.update()) continue + + val index = slot.getX(length) + slot.getY(length) * length + updatedItems[guiItem] = index + } + + return updatedItems + } + + override fun updateItem(item: UpdatableGuiItemImpl): Int? { + for ((slot, other) in paneItems.entries) { + if (other != item) continue + if (!item.update()) return null + return slot.getX(length) + slot.getY(length) * length + } + return null + } + + override fun click( + gui: AbstractGui, + component: InventoryComponentImpl, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(this.length, maxLength) + val height = minOf(this.height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + val totalLength = component.length + + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + val clickedItem = findMatchingItem(paneItems.values, itemStack) ?: return false + + clickedItem.callAction(event) + + return true + } + + override fun clone(): StaticPaneImpl { + val clonedPane = StaticPaneImpl(slot, length, height, priority, uuid) + + for (entry in paneItems.entries) { + clonedPane.setItem(entry.key, entry.value.clone()) + } + + clonedPane.visible = visible + clonedPane.onClick = onClick + clonedPane.flippedHorizontally = flippedHorizontally + clonedPane.flippedVertically = flippedVertically + clonedPane.rotation = rotation + + return clonedPane + } + + override fun fillWith(item: ItemStack, handler: ClickHandlerDsl) { + val locations = paneItems.keys + + for (y in 0 until height) { + for (x in 0 until length) { + var found = false + + for (location in locations) { + if (location.getX(length) == x && location.getY(length) == y) { + found = true + break + } + } + + if (!found) { + setItem(slot(x, y), GuiItemImpl().apply { + item(item) + onClick(handler) + }) + } + } + } + } + + override fun setItem(slot: Slot, item: GuiItem) { + require(item is GuiItemImpl) { "Item must be an instance of GuiItemImpl" } + paneItems.put(slot, item) + } + + override fun removeItem(item: GuiItem) { + paneItems.values.removeIf { guiItem -> guiItem == item } + } + + override fun removeItem(slot: Slot) { + paneItems.remove(slot) + } + + override fun clear() { + paneItems.clear() + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/components/PagingButtonsImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/components/PagingButtonsImpl.kt new file mode 100644 index 00000000..d57e8cd4 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/components/PagingButtonsImpl.kt @@ -0,0 +1,183 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes.components + +import dev.slne.surf.surfapi.bukkit.api.builder.displayName +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.components.PagingButtons +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Priority +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes.PaginatedPaneImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import dev.slne.surf.surfapi.core.api.util.object2IntMapOf +import it.unimi.dsi.fastutil.objects.Object2IntMap +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.ItemType +import java.util.* + +class PagingButtonsImpl( + slot: Slot, + val paginatedPane: PaginatedPaneImpl, + length: Int = 9, + priority: Priority = Priority.NORMAL, + uuid: UUID = UUID.randomUUID(), +) : AbstractPane(slot, length, 1, priority, uuid), PagingButtons { + + override val items: ObjectList + get() = mutableObjectListOf(backwardsButton, forwardsButton) + + override val panes: ObjectList + get() = mutableObjectListOf() + + override fun clear() { + throw UnsupportedOperationException("PagingButtons cannot be cleared.") + } + + var backwardsButton = GuiItemImpl().apply { + item(ItemType.ARROW) { + displayName { + primary("Backwards") + } + } + } + + override fun setBackwardsButton(item: GuiItem) { + require(item is GuiItemImpl) { "Backwards button must be an instance of GuiItemImpl." } + this.backwardsButton = item + } + + override fun setBackwardsButton(item: ItemStack, action: ClickHandlerDsl) { + setBackwardsButton(GuiItem { + item(item) + onClick(action) + }) + } + + var forwardsButton = GuiItemImpl().apply { + item(ItemType.ARROW) { + displayName { + primary("Forwards") + } + } + } + + override fun setForwardsButton(item: GuiItem) { + require(item is GuiItemImpl) { "Forwards button must be an instance of GuiItemImpl." } + this.forwardsButton = item + } + + override fun setForwardsButton(item: ItemStack, action: ClickHandlerDsl) { + setForwardsButton(GuiItem { + item(item) + onClick(action) + }) + } + + override fun click( + gui: AbstractGui, + component: InventoryComponentImpl, + event: InventoryClickEvent, + slot: Int, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ): Boolean { + val length = minOf(length, maxLength) + val height = minOf(height, maxHeight) + + val xPosition = this.slot.getX(maxLength) + val yPosition = this.slot.getY(maxLength) + + val totalLength = component.length + val adjustedSlot = + slot - (xPosition + paneOffsetX) - totalLength * (yPosition + paneOffsetY) + + val x = adjustedSlot % length + val y = adjustedSlot / length + + if (x < 0 || x >= length || y < 0 || y >= height) { + return false + } + + callOnClick(event) + + val itemStack = event.currentItem ?: return false + + if (matchesItem(backwardsButton, itemStack)) { + paginatedPane.previousPage() + backwardsButton.callAction(event) + gui.update() + + return true + } + + if (matchesItem(forwardsButton, itemStack)) { + paginatedPane.nextPage() + forwardsButton.callAction(event) + gui.update() + + return true + } + + return false + } + + override fun display( + component: InventoryComponentImpl, + paneOffsetX: Int, + paneOffsetY: Int, + maxLength: Int, + maxHeight: Int, + ) { + val length = length.coerceAtMost(maxLength) + + val x = super.slot.getX(length) + paneOffsetX + val y = super.slot.getY(length) + paneOffsetY + + if (paginatedPane.page > 0) { + component.setItem(backwardsButton, slot(x, y)) + } + + if (paginatedPane.page < paginatedPane.totalPages - 1) { + component.setItem(forwardsButton, slot(x + length - 1, y)) + } + } + + override fun updateItems(): Object2IntMap = object2IntMapOf() + + override fun updateItem(item: UpdatableGuiItemImpl): Int? { + return null + } + + override fun clone(): PagingButtonsImpl { + val pagingButtons = PagingButtonsImpl( + slot, + paginatedPane, + length, + priority, + uuid + ) + + pagingButtons.visible = visible + pagingButtons.onClick = onClick + pagingButtons.backwardsButton = backwardsButton.clone() + pagingButtons.forwardsButton = forwardsButton.clone() + + return pagingButtons + } + + init { + if (length < 2) { + throw IllegalArgumentException("Length must be at least 2 to accommodate paging buttons.") + } + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/utils/MaskImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/utils/MaskImpl.kt new file mode 100644 index 00000000..07147e60 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/utils/MaskImpl.kt @@ -0,0 +1,128 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask +import java.util.* +import kotlin.math.min + +class MaskImpl(private val mask: Array) : Mask { + + override val enabledSlots: Int get() = amountOfEnabledSlots() + override val length: Int get() = mask[0].size + override val height: Int get() = mask.size + + constructor(vararg mask: String) : this(maskByString(*mask)) + + companion object { + private fun maskByString(vararg mask: String): Array { + val maskArray = Array(mask.size) { + BooleanArray(if (mask.isEmpty()) 0 else mask[0].length) + } + + for (row in mask.indices) { + val length = mask[row].length + + require(length == maskArray[row].size) { "Lengths of each string should be equal" } + + for (column in 0.. { + maskArray[row][column] = false + } + + '1' -> { + maskArray[row][column] = true + } + + else -> { + throw IllegalArgumentException("Strings may only contain '0' and '1'") + } + } + } + } + + return maskArray + } + + } + + override fun setHeight(height: Int): MaskImpl { + val newRows = Array(height) { BooleanArray(this.length) } + + for (index in 0.. 0) { "Length must be greater than 0" } + + val newRows = Array(height) { IntArray(length) } + + for (index in 0 until height) { + val newRow = IntArray(length) + val row = pattern[index] + val minLength = minOf(length, row.size) + + System.arraycopy(row, 0, newRow, 0, minLength) + + for (column in minLength until length) { + newRow[column] = newRow[minLength - 1] + } + + newRows[index] = newRow + } + + return PatternImpl(newRows) + } + + override fun getColumn(index: Int): IntArray { + if (index < 0 || index >= length) { + throw IndexOutOfBoundsException("Column index $index is out of bounds for pattern of length $length") + } + + val column = IntArray(height) + + for (i in 0 until height) { + column[i] = pattern[i][index] + } + + return column + } + + override fun contains(char: Char): Boolean { + for (row in pattern) { + for (cell in row) { + if (cell != char.code) { + continue + } + + return true + } + } + + return false + } + + override fun getRow(index: Int): IntArray { + if (index < 0 || index >= height) { + throw IndexOutOfBoundsException("Row index $index is out of bounds for pattern of height $height") + } + + val row = pattern[index] + + return row.copyOf(row.size) + } + + override fun getChar(x: Int, y: Int): Char { + if (x < 0 || x >= length || y < 0 || y >= height) { + throw IndexOutOfBoundsException("Coordinates ($x, $y) are out of bounds for pattern of size ${length}x${height}") + } + + return pattern[y][x].toChar() + } + + override fun hashCode() = pattern.contentDeepHashCode() + + override fun toString() = buildString { + append("Pattern{") + append("pattern=") + append(pattern.contentDeepToString()) + append('}') + } + + override fun equals(other: Any?): Boolean { + if (this === other) return true + if (other !is PatternImpl) return false + + return pattern.contentDeepEquals(other.pattern) + } + + companion object { + private fun initializePattern(vararg pattern: String): Array { + val rows = pattern.size + val zeroRows = rows == 0 + + val patternArray = Array(rows) { + IntArray( + if (zeroRows) 0 else pattern[0].codePointCount( + 0, + pattern[0].length + ) + ) + } + + if (zeroRows) { + return patternArray + } + + val globalLength = pattern[0].length + + for (index in 0 until rows) { + val row = pattern[index] + val length = row.codePointCount(0, row.length) + + if (length != globalLength) { + throw IllegalArgumentException("All rows must have the same length. Row $index has length $length, expected $globalLength") + } + + val values = mutableIntListOf() + row.codePoints().forEach(values::add) + + for (column in 0 until values.size) { + patternArray[index][column] = values.getInt(column) + } + } + + return patternArray + } + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/GeometryUtils.kt similarity index 91% rename from surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt rename to surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/GeometryUtils.kt index d8278624..dec05dca 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/GeometryUtils.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/GeometryUtils.kt @@ -1,9 +1,9 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.utils +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap import it.unimi.dsi.fastutil.ints.Int2IntMap -internal object GeometryUtils { +object GeometryUtils { fun processClockwiseRotation( x: Int, diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/InventoryComponentImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/InventoryComponentImpl.kt new file mode 100644 index 00000000..f207bf96 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/InventoryComponentImpl.kt @@ -0,0 +1,253 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.slot +import dev.slne.surf.surfapi.bukkit.api.nms.NmsUseWithCaution +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane +import dev.slne.surf.surfapi.core.api.util.freeze +import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf +import org.bukkit.event.inventory.InventoryClickEvent +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.PlayerInventory + +class InventoryComponentImpl( + override val length: Int, + override val height: Int, +) : InventoryComponent { + + val panes = mutableObjectListOf() + val items = Array>(length) { Array(height) { null } } + + init { + require(length > 0) { "Length must be greater than 0" } + require(height > 0) { "Height must be greater than 0" } + } + + override fun addPane(pane: Pane) { + require(pane is AbstractPane) { "Pane must be an instance of AbstractPane" } + + val size = panes.size + + if (size == 0) { + panes.add(pane) + return + } + + val priority = pane.priority + var left = 0 + var right = size - 1 + + while (left <= right) { + val middle = (left + right) / 2 + val middlePriority = getPane(middle).priority + + if (middlePriority == priority) { + panes.add(middle, pane) + return + } + + if (middlePriority.isLessThan(priority)) { + left = middle + 1 + } else if (middlePriority.isGreaterThan(priority)) { + right = middle - 1 + } + } + + panes.add(right + 1, pane) + } + + override fun display(inventory: Inventory, offset: Int) { + display() + + placeItems(inventory, offset) + } + + override fun display(playerInventory: PlayerInventory, offset: Int) { + display() + + placeItems(playerInventory, offset) + } + + override fun placeItems(playerInventory: PlayerInventory, offset: Int) { + for (x in 0 until length) { + for (y in 0 until height) { + val slot = if (y == height - 1) { + x + offset + } else { + (y + 1) * length + x + offset + } + + playerInventory.setItem(slot, getItem(slot(x, y))) + } + } + } + + override fun placeItems(inventory: Inventory, offset: Int) { + for (x in 0 until length) { + for (y in 0 until height) { + inventory.setItem(y * length + x + offset, getItem(slot(x, y))) + } + } + } + + @OptIn(NmsUseWithCaution::class) + fun click(gui: AbstractGui, event: InventoryClickEvent, slot: Int) { + val panes = this.panes.freeze() + + for (i in panes.indices.reversed()) { + val pane = panes[i] + val result = pane.click( + gui, + component = this, + event, + slot, + paneOffsetX = 0, + paneOffsetY = 0, + maxLength = length, + maxHeight = height + ) + + if (result) { + break + } + } + } + + public override fun clone(): InventoryComponentImpl { + val component = InventoryComponentImpl(length, height) + + for (x in 0 until length) { + for (y in 0 until height) { + val item = getItem(slot(x, y)) ?: continue + + component.setItem(item.clone(), slot(x, y)) + } + } + + for (pane in panes) { + component.addPane(pane.clone()) + } + + return component + } + + override fun excludeRows(from: Int, end: Int): InventoryComponentImpl { + require(from >= 0) { "From index must be non-negative" } + require(end < height) { "End index must be less than height" } + + val newHeight = height - (end - from + 1) + val newComponent = InventoryComponentImpl(length, newHeight) + + for (pane in panes) { + newComponent.addPane(pane) + } + + for (x in 0 until length) { + var newY = 0 + + for (y in 0 until height) { + val item = getItem(slot(x, y)) + + if (y >= from && y <= end) { + continue + } + + if (item != null) { + newComponent.setItem(item, slot(x, newY)) + } + + newY++ + } + } + + return newComponent + } + + override fun hasItem(): Boolean { + for (x in 0 until length) { + for (y in 0 until height) { + if (getItem(slot(x, y)) != null) { + return true + } + } + } + + return false + } + + override fun display() { + clearItems() + + for (pane in panes) { + if (!pane.visible) { + continue + } + + pane.display(this, 0, 0, length, height) + } + } + + override fun hasItem(slot: Slot) = getItem(slot) != null + + override fun getItem(slot: Slot): ItemStack? { + val x = slot.getX(length) + val y = slot.getY(length) + + require(inBounds(slot)) { "Coordinates ($x, $y) are out of bounds for inventory size $length x $height" } + + return items[x][y] + } + + override fun setItem(item: GuiItem, slot: Slot) { + val x = slot.getX(length) + val y = slot.getY(length) + + require(inBounds(slot)) { "Coordinates ($x, $y) are out of bounds for inventory size $length x $height" } + + items[x][y] = item.clone().apply { applyUuid() }.itemStack + } + + override fun setItem(itemStack: ItemStack, slot: Slot) { + val x = slot.getX(length) + val y = slot.getY(length) + + require(inBounds(slot)) { "Coordinates ($x, $y) are out of bounds for inventory size $length x $height" } + + items[x][y] = itemStack + } + + val size get() = length * height + + override fun clearItems() { + for (item in items) { + item.fill(null) + } + } + + override fun inBounds(slot: Slot): Boolean { + val x = slot.getX(length) + val y = slot.getY(length) + + val xBounds = inBounds(0, length - 1, x) + val yBounds = inBounds(0, height - 1, y) + + return xBounds && yBounds + } + + override fun inBounds(lowerBound: Int, upperBound: Int, value: Int) = + value in lowerBound..upperBound + + override fun getPane(index: Int): AbstractPane { + if (!inBounds(0, panes.size - 1, index)) { + throw IndexOutOfBoundsException("Pane index $index is out of bounds for size ${panes.size}") + } + + return panes[index] + } + +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/PlayerInventoryCache.kt similarity index 96% rename from surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt rename to surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/PlayerInventoryCache.kt index bd10a4a1..697bc1d7 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/utils/PlayerInventoryCache.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/PlayerInventoryCache.kt @@ -1,10 +1,10 @@ -package dev.slne.surf.surfapi.bukkit.api.inventory.utils +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils import dev.slne.surf.surfapi.core.api.util.mutableObject2ObjectMapOf import org.bukkit.entity.Player import org.bukkit.inventory.ItemStack -internal class PlayerInventoryCache { +class PlayerInventoryCache { private val inventories = mutableObject2ObjectMapOf>() diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt index e7eacc9c..6d8b98d0 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/listener/ListenerManager.kt @@ -1,7 +1,7 @@ package dev.slne.surf.surfapi.bukkit.server.listener import dev.slne.surf.surfapi.bukkit.api.event.register -import dev.slne.surf.surfapi.bukkit.api.inventory.listener.GuiListener +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.listener.GuiListener import dev.slne.surf.surfapi.bukkit.server.plugin import org.bukkit.Bukkit From 8fae80a73d7b402c6a511270e2752907b6c6712c Mon Sep 17 00:00:00 2001 From: twisti Date: Sat, 14 Jun 2025 16:31:28 +0200 Subject: [PATCH 3/4] feat: remove unnecessary @GuiDsl annotations for cleaner DSL usage --- .idea/vcs.xml | 6 ++++++ .../slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt | 6 +----- .../slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt | 2 +- .../bukkit/api/inventory/gui/handlers/ClickHandlers.kt | 3 --- .../bukkit/api/inventory/gui/handlers/CloseHandlers.kt | 7 +++---- .../bukkit/api/inventory/gui/handlers/DragHandlers.kt | 3 --- .../bukkit/api/inventory/gui/handlers/GuiHandler.kt | 2 -- .../slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt | 6 +++--- .../surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt | 2 +- .../bukkit/server/impl/inventory/gui/AbstractNamedGui.kt | 3 +-- .../bukkit/server/impl/inventory/item/GuiItemImpl.kt | 7 +++---- .../server/impl/inventory/item/UpdatableGuiItemImpl.kt | 3 +-- 12 files changed, 20 insertions(+), 30 deletions(-) diff --git a/.idea/vcs.xml b/.idea/vcs.xml index 94a25f7f..4c6280eb 100644 --- a/.idea/vcs.xml +++ b/.idea/vcs.xml @@ -1,5 +1,11 @@ + + + + + + diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt index 6800560a..bb34fbda 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/markers.kt @@ -10,8 +10,4 @@ annotation class GuiDsl @Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) @DslMarker -annotation class GuiItemDsl - -@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE) -@DslMarker -annotation class GuiClickDsl \ No newline at end of file +annotation class GuiItemDsl \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt index 9802517d..1e26228d 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/NamedGui.kt @@ -9,7 +9,7 @@ interface NamedGui : Gui { val title: Component fun title(title: Component) - fun title(builder: (@GuiDsl SurfComponentBuilder).() -> Unit) + fun title(builder: SurfComponentBuilder.() -> Unit) override fun clone(): NamedGui } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt index 93f6934d..a8fefd79 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/ClickHandlers.kt @@ -1,17 +1,14 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import dev.slne.surf.surfapi.core.api.util.InternalSurfApi import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent typealias ClickHandlerDsl = ClickHandlerScope.() -> Unit -@GuiDsl interface ClickHandler : GuiHandler @JvmInline -@GuiDsl value class ClickHandlerScope @InternalSurfApi constructor(val event: InventoryClickEvent) { val player get() = event.whoClicked as? Player ?: error("Click event is not triggered by a player") diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt index f0a71042..720155e4 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/CloseHandlers.kt @@ -1,17 +1,16 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryCloseEvent typealias CloseHandlerDsl = CloseHandlerScope.() -> Unit -@GuiDsl interface CloseHandler: GuiHandler @JvmInline -@GuiDsl value class CloseHandlerScope @InternalSurfApi constructor(val event: InventoryCloseEvent) { - + val player + get() = event.player as? Player ?: error("Close event is not triggered by a player") } diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt index 5416c91d..3e531ccc 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/DragHandlers.kt @@ -1,16 +1,13 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import dev.slne.surf.surfapi.core.api.util.InternalSurfApi import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryDragEvent typealias DragHandlerDsl = DragHandlerScope.() -> Unit -@GuiDsl interface DragHandler : GuiHandler -@GuiDsl @JvmInline value class DragHandlerScope @InternalSurfApi constructor(val event: InventoryDragEvent) { val player diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt index c5048510..fdc822cf 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/handlers/GuiHandler.kt @@ -1,10 +1,8 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import org.bukkit.event.Event import org.jetbrains.annotations.ApiStatus.OverrideOnly -@GuiDsl interface GuiHandler { @OverrideOnly fun handle(event: E) diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt index b6c589e2..309dde7e 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt @@ -17,10 +17,10 @@ interface GuiItem : Cloneable { var visible: Boolean fun item(itemStack: ItemStack) - fun item(type: ItemType, block: (@GuiItemDsl ItemStack).() -> Unit = {}) + fun item(type: ItemType, block: ItemStack.() -> Unit = {}) - fun onClick(handler: @GuiItemDsl ClickHandler) - fun onClick(handler: @GuiItemDsl ClickHandlerDsl) + fun onClick(handler: ClickHandler) + fun onClick(handler: ClickHandlerDsl) public override fun clone(): GuiItem = super.clone() as GuiItem diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt index 39c108a3..26a753c6 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt @@ -9,7 +9,7 @@ import java.util.function.BooleanSupplier @GuiItemDsl interface UpdatableGuiItem : GuiItem { - fun onUpdate(update: @GuiItemDsl BooleanSupplier) + fun onUpdate(update: BooleanSupplier) override fun clone(): UpdatableGuiItem = super.clone() as UpdatableGuiItem diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt index c1450251..a8ac7c67 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt @@ -1,6 +1,5 @@ package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder import net.kyori.adventure.text.Component @@ -20,6 +19,6 @@ abstract class AbstractNamedGui(title: Component, parent: AbstractGui? = null) : this.title = title } - override fun title(builder: @GuiDsl SurfComponentBuilder.() -> Unit) = + override fun title(builder: SurfComponentBuilder.() -> Unit) = title(SurfComponentBuilder(builder)) } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt index 035c4cb7..127dc048 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/GuiItemImpl.kt @@ -2,7 +2,6 @@ package dev.slne.surf.surfapi.bukkit.server.impl.inventory.item import com.jeff_media.morepersistentdatatypes.DataType import dev.slne.surf.surfapi.bukkit.api.builder.meta -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandler import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.ClickHandlerDsl import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem @@ -32,11 +31,11 @@ open class GuiItemImpl( var action: ClickHandler? = null private set - override fun onClick(handler: @GuiItemDsl ClickHandler) { + override fun onClick(handler: ClickHandler) { action = handler } - override fun onClick(handler: @GuiItemDsl ClickHandlerDsl) { + override fun onClick(handler: ClickHandlerDsl) { onClick(ClickHandlerScopeImpl(handler)) } @@ -46,7 +45,7 @@ open class GuiItemImpl( override fun item( type: ItemType, - block: @GuiItemDsl ItemStack.() -> Unit, + block: ItemStack.() -> Unit, ) { item(type.createItemStack().apply { block() }) } diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt index e5900b0a..3b3332f6 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/item/UpdatableGuiItemImpl.kt @@ -1,6 +1,5 @@ package dev.slne.surf.surfapi.bukkit.server.impl.inventory.item -import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiItemDsl import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import org.bukkit.NamespacedKey import java.util.* @@ -12,7 +11,7 @@ class UpdatableGuiItemImpl( ) : GuiItemImpl(key, uuid), UpdatableGuiItem { private var update: BooleanSupplier? = null - override fun onUpdate(update: @GuiItemDsl BooleanSupplier) { + override fun onUpdate(update: BooleanSupplier) { this.update = update } From 3d5adf060c868d67f8d6d3c12dcf39c40990677c Mon Sep 17 00:00:00 2001 From: twisti Date: Sat, 14 Jun 2025 17:01:04 +0200 Subject: [PATCH 4/4] feat: rework GUI API with new chest menu functionality and improved item handling --- .../surf-api-bukkit-api/build.gradle.kts | 1 + .../bukkit/api/inventory/InventoryBridge.kt | 17 ++- .../surfapi/bukkit/api/inventory/dsl/gui.kt | 119 ------------------ .../bukkit/api/inventory/gui/MergedGui.kt | 50 +++++++- .../api/inventory/gui/types/ChestGui.kt | 40 +++++- .../bukkit/api/inventory/item/GuiItem.kt | 6 +- .../api/inventory/item/UpdatableGuiItem.kt | 6 +- .../api/inventory/pane/panes/StaticPane.kt | 2 + .../impl/inventory/InventoryBridgeImpl.kt | 89 +++++++++++++ .../server/impl/inventory/gui/AbstractGui.kt | 4 +- .../impl/inventory/gui/AbstractNamedGui.kt | 4 +- .../impl/inventory/gui/types/ChestGuiImpl.kt | 31 +++-- .../inventory/pane/panes/StaticPaneImpl.kt | 4 + 13 files changed, 225 insertions(+), 148 deletions(-) delete mode 100644 surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt create mode 100644 surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/InventoryBridgeImpl.kt diff --git a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts index c50ddbf4..d7df1a4e 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts +++ b/surf-api-bukkit/surf-api-bukkit-api/build.gradle.kts @@ -25,5 +25,6 @@ configurations.all { kotlin { compilerOptions { optIn.add("dev.slne.surf.surfapi.core.api.util.InternalSurfApi") + optIn.add("kotlin.contracts.ExperimentalContracts") } } diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt index 85341cd6..37ee2da7 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/InventoryBridge.kt @@ -1,10 +1,15 @@ package dev.slne.surf.surfapi.bukkit.api.inventory import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.OutlinePane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Pattern +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot import dev.slne.surf.surfapi.core.api.util.InternalSurfApi import dev.slne.surf.surfapi.core.api.util.requiredService import org.bukkit.NamespacedKey @@ -15,16 +20,22 @@ import java.util.* interface InventoryBridge { fun getGuiByInventory(inventory: Inventory): Gui? - fun createGuiItem(key: NamespacedKey?, uuid: UUID?, init: GuiItem.() -> Unit): GuiItem + fun createGuiItem(key: NamespacedKey?, uuid: UUID?): GuiItem fun createUpdatableGuiItem( key: NamespacedKey?, - uuid: UUID?, - init: UpdatableGuiItem.() -> Unit, + uuid: UUID? ): UpdatableGuiItem fun createMask(vararg mask: String): Mask fun createPattern(vararg pattern: String): Pattern + fun createChestGui(size: ChestGui.ChestGuiSize, parent: Gui? = null): ChestGui + + + fun createOutlinePane(slot: Slot, length: Int, height: Int, uuid: UUID?): OutlinePane + fun createPaginatedPane(slot: Slot, length: Int, height: Int, uuid: UUID?): PaginatedPane + fun createStaticPane(slot: Slot, length: Int, height: Int, uuid: UUID?): StaticPane + companion object { val instance = requiredService() } diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt deleted file mode 100644 index a7341f9a..00000000 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/dsl/gui.kt +++ /dev/null @@ -1,119 +0,0 @@ -@file:OptIn(ExperimentalContracts::class) - -package dev.slne.surf.surfapi.bukkit.api.inventory.dsl - -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGuiImpl -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestSinglePlayerGui -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.components.PagingButtonsImpl -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPaneImpl -import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPaneImpl -import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot -import net.kyori.adventure.text.Component -import org.bukkit.entity.Player -import org.jetbrains.annotations.Range -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract - -@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS) -@DslMarker -annotation class MenuMarker - -fun MergedGui.pagingButtons( - slot: Slot, - paginatedPane: PaginatedPaneImpl, - length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker PagingButtonsImpl).() -> Unit, -): PagingButtonsImpl { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return PagingButtonsImpl(slot, paginatedPane, length).apply { - init() - addPane(this) - } -} - -fun MergedGui.paginatedPane( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker PaginatedPaneImpl).() -> Unit, -): PaginatedPaneImpl { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return PaginatedPaneImpl(slot, length, height).apply { - init() - addPane(this) - } -} - -fun MergedGui.staticPane( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker StaticPaneImpl).() -> Unit, -): StaticPaneImpl { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return StaticPaneImpl(slot, length, height).apply { - init() - addPane(this) - } -} - -fun menu( - title: Component, - size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestGuiImpl.() -> Unit, -): ChestGuiImpl { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return ChestGuiImpl(title, size).apply { init() } -} - -fun playerMenu( - title: Component, - player: Player, - size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestSinglePlayerGui.() -> Unit, -): ChestSinglePlayerGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return ChestSinglePlayerGui(player, title, size).apply { init() } -} - - -fun ChestGuiImpl.childMenu( - title: Component, - size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestGuiImpl.() -> Unit, -): ChestGuiImpl { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return ChestGuiImpl(title, size, this).apply { init() } -} - -fun ChestSinglePlayerGui.childPlayerMenu( - title: Component, - size: ChestGuiImpl.ChestGuiSize = ChestGuiImpl.ChestGuiSize.SIX_ROWS, - init: @MenuMarker ChestSinglePlayerGui.() -> Unit, -): ChestSinglePlayerGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - return ChestSinglePlayerGui(player, title, size, this).apply { init() } -} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt index 088ea2f5..1188c80b 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt @@ -1,17 +1,63 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge import dev.slne.surf.surfapi.bukkit.api.inventory.dsl.GuiDsl +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.OutlinePane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane import dev.slne.surf.surfapi.bukkit.api.inventory.utils.InventoryComponent +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot import it.unimi.dsi.fastutil.objects.ObjectList import org.jetbrains.annotations.Unmodifiable +import java.util.* @GuiDsl interface MergedGui { val inventoryComponent: InventoryComponent + val panes: @Unmodifiable ObjectList + val items: @Unmodifiable ObjectList + fun addPane(pane: Pane) - val panes: @Unmodifiable ObjectList - val items: @Unmodifiable ObjectList + fun outlinePane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID? = null, + init: OutlinePane.() -> Unit, + ): OutlinePane { + val pane = InventoryBridge.instance.createOutlinePane(slot, length, height, uuid) + pane.init() + addPane(pane) + return pane + } + + fun paginatedPane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID? = null, + init: PaginatedPane.() -> Unit, + ): PaginatedPane { + val pane = InventoryBridge.instance.createPaginatedPane(slot, length, height, uuid) + pane.init() + addPane(pane) + return pane + } + + fun staticPane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID? = null, + init: StaticPane.() -> Unit, + ): StaticPane { + val pane = InventoryBridge.instance.createStaticPane(slot, length, height, uuid) + pane.init() + addPane(pane) + return pane + } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt index 91725c00..8988c52a 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/types/ChestGui.kt @@ -1,13 +1,17 @@ package dev.slne.surf.surfapi.bukkit.api.inventory.gui.types +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract -interface ChestGui: NamedGui, MergedGui { +interface ChestGui : NamedGui, MergedGui { val size: ChestGuiSize fun size(size: ChestGuiSize) - override fun clone(): ChestGui + override fun clone(): ChestGui = super.clone() as ChestGui enum class ChestGuiSize(val rows: Int) { ONE_ROW(1), @@ -16,5 +20,37 @@ interface ChestGui: NamedGui, MergedGui { FOUR_ROWS(4), FIVE_ROWS(5), SIX_ROWS(6); + + companion object { + fun fromRows(rows: Int): ChestGuiSize? { + return entries.find { it.rows == rows } + } + } + } +} + +fun chestMenu( + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: ChestGui.() -> Unit, +): ChestGui { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) } + + val gui = InventoryBridge.instance.createChestGui(size) + gui.init() + return gui +} + +fun Gui.childChestMenu( + size: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.SIX_ROWS, + init: ChestGui.() -> Unit, +): ChestGui { + contract { + callsInPlace(init, InvocationKind.EXACTLY_ONCE) + } + + val gui = InventoryBridge.instance.createChestGui(size, this) + gui.init() + return gui } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt index 309dde7e..cfa34692 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/GuiItem.kt @@ -29,6 +29,10 @@ interface GuiItem : Cloneable { key: NamespacedKey? = null, uuid: UUID? = null, init: GuiItem.() -> Unit, - ): GuiItem = InventoryBridge.instance.createGuiItem(key, uuid, init) + ): GuiItem { + val item = InventoryBridge.instance.createGuiItem(key, uuid) + item.init() + return item + } } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt index 26a753c6..2c275265 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/item/UpdatableGuiItem.kt @@ -18,6 +18,10 @@ interface UpdatableGuiItem : GuiItem { key: NamespacedKey? = null, uuid: UUID? = null, init: UpdatableGuiItem.() -> Unit, - ): UpdatableGuiItem = InventoryBridge.instance.createUpdatableGuiItem(key, uuid, init) + ): UpdatableGuiItem { + val item = InventoryBridge.instance.createUpdatableGuiItem(key, uuid) + item.init() + return item + } } } \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt index c9143f19..e2abbe1a 100644 --- a/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/panes/StaticPane.kt @@ -11,6 +11,8 @@ import org.bukkit.inventory.ItemStack interface StaticPane: Pane, Flippable, Rotatable { fun setItem(slot: Slot, item: GuiItem) + fun setItem(slot: Slot, init: GuiItem.() -> Unit) + fun fillWith(item: ItemStack, handler: ClickHandlerDsl = {}) fun removeItem(item: GuiItem) fun removeItem(slot: Slot) diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/InventoryBridgeImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/InventoryBridgeImpl.kt new file mode 100644 index 00000000..10ba2e3a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/InventoryBridgeImpl.kt @@ -0,0 +1,89 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory + +import com.google.auto.service.AutoService +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui +import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.OutlinePane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.PaginatedPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes.StaticPane +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Mask +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils.Pattern +import dev.slne.surf.surfapi.bukkit.api.inventory.utils.Slot +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.types.ChestGuiImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes.OutlinePaneImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes.PaginatedPaneImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.panes.StaticPaneImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.utils.MaskImpl +import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.utils.PatternImpl +import org.bukkit.NamespacedKey +import org.bukkit.inventory.Inventory +import java.util.* + +@AutoService(InventoryBridge::class) +class InventoryBridgeImpl : InventoryBridge { + override fun getGuiByInventory(inventory: Inventory): Gui? { + return AbstractGui.getGui(inventory) + } + + override fun createGuiItem( + key: NamespacedKey?, + uuid: UUID?, + ): GuiItem { + return GuiItemImpl(key ?: GuiItemImpl.DEFAULT_KEY, uuid ?: UUID.randomUUID()) + } + + override fun createUpdatableGuiItem( + key: NamespacedKey?, + uuid: UUID?, + ): UpdatableGuiItem { + return UpdatableGuiItemImpl(key ?: GuiItemImpl.DEFAULT_KEY, uuid ?: UUID.randomUUID()) + } + + override fun createMask(vararg mask: String): Mask { + return MaskImpl(*mask) + } + + override fun createPattern(vararg pattern: String): Pattern { + return PatternImpl(*pattern) + } + + override fun createChestGui( + size: ChestGui.ChestGuiSize, + parent: Gui?, + ): ChestGui { + return ChestGuiImpl(size, parent as? AbstractGui) + } + + override fun createOutlinePane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID?, + ): OutlinePane { + return OutlinePaneImpl(slot, length, height, uuid = uuid ?: UUID.randomUUID()) + } + + override fun createPaginatedPane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID?, + ): PaginatedPane { + return PaginatedPaneImpl(slot, length, height, uuid = uuid ?: UUID.randomUUID()) + } + + override fun createStaticPane( + slot: Slot, + length: Int, + height: Int, + uuid: UUID?, + ): StaticPane { + return StaticPaneImpl(slot, length, height, uuid = uuid ?: UUID.randomUUID()) + } +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt index 33965306..522114d2 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractGui.kt @@ -2,6 +2,7 @@ package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.Gui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers.* +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.ClickHandlerScopeImpl import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.CloseHandlerScopeImpl import dev.slne.surf.surfapi.bukkit.server.impl.inventory.dsl.DragHandlerScopeImpl @@ -125,7 +126,8 @@ abstract class AbstractGui(override val parent: AbstractGui? = null) : Gui { updating = false } - override fun updateItem(item: UpdatableGuiItemImpl) { + override fun updateItem(item: UpdatableGuiItem) { + require(item is UpdatableGuiItemImpl) { "Item must be an instance of UpdatableGuiItemImpl" } if (updating) return val slot = updateItem0(item) ?: return diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt index a8ac7c67..99ea3c1a 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/AbstractNamedGui.kt @@ -4,12 +4,12 @@ import dev.slne.surf.surfapi.bukkit.api.inventory.gui.NamedGui import dev.slne.surf.surfapi.core.api.messages.builder.SurfComponentBuilder import net.kyori.adventure.text.Component -abstract class AbstractNamedGui(title: Component, parent: AbstractGui? = null) : +abstract class AbstractNamedGui(parent: AbstractGui? = null) : AbstractGui(parent), NamedGui { protected var titleDirty = false - override var title: Component = title + override var title: Component = Component.empty() set(value) { field = value titleDirty = true diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt index 3a78aa23..c165e927 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/types/ChestGuiImpl.kt @@ -1,32 +1,31 @@ package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.types -import dev.slne.surf.surfapi.bukkit.api.inventory.gui.MergedGui import dev.slne.surf.surfapi.bukkit.api.inventory.gui.types.ChestGui +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractGui import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.AbstractNamedGui import dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.utils.InventoryBased import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.GuiItemImpl import dev.slne.surf.surfapi.bukkit.server.impl.inventory.item.UpdatableGuiItemImpl -import dev.slne.surf.surfapi.bukkit.server.impl.inventory.pane.AbstractPane import dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils.InventoryComponentImpl import dev.slne.surf.surfapi.core.api.util.freeze import dev.slne.surf.surfapi.core.api.util.mutableObject2IntMapOf import dev.slne.surf.surfapi.core.api.util.mutableObjectListOf import dev.slne.surf.surfapi.core.api.util.mutableObjectSetOf import it.unimi.dsi.fastutil.objects.Object2IntMap -import net.kyori.adventure.text.Component import org.bukkit.Bukkit import org.bukkit.entity.Player import org.bukkit.event.inventory.InventoryClickEvent open class ChestGuiImpl( - title: Component, initialSize: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.FIVE_ROWS, parent: AbstractGui? = null, -) : AbstractNamedGui(title, parent), MergedGui, InventoryBased { +) : AbstractNamedGui(parent), ChestGui, InventoryBased { private var rows: Int = initialSize.rows set(value) { + if (value == field) return + inventoryComponent = InventoryComponentImpl(9, value + 4) for (pane in inventoryComponent.panes) { @@ -36,6 +35,9 @@ open class ChestGuiImpl( sizeDirty = true } + override val size: ChestGui.ChestGuiSize + get() = ChestGui.ChestGuiSize.fromRows(rows) ?: error("Invalid rows count: $rows") + override var inventoryComponent = InventoryComponentImpl(9, rows + 4) set(value) { field = value @@ -51,6 +53,11 @@ open class ChestGuiImpl( override val viewers get() = backingInventory.viewers.filterIsInstanceTo(mutableObjectSetOf()).freeze() + override fun size(size: ChestGui.ChestGuiSize) { + if (this.size == size) return + rows = size.rows + } + override fun updateAllItems(): Object2IntMap = panes.fold(mutableObject2IntMapOf()) { acc, pane -> acc.apply { putAll(pane.updateItems()) } @@ -60,7 +67,7 @@ open class ChestGuiImpl( panes.find { it.items.contains(item) }?.updateItem(item) - override fun addPane(pane: AbstractPane) { + override fun addPane(pane: Pane) { inventoryComponent.addPane(pane) } @@ -96,20 +103,10 @@ open class ChestGuiImpl( player.openInventory(backingInventory) } - override fun clone() = super.clone() as ChestGuiImpl - override fun click(event: InventoryClickEvent) { inventoryComponent.click(this, event, event.rawSlot) } override fun isPlayerInventoryUsed() = inventoryComponent.excludeRows(0, inventoryComponent.height - 5).hasItem() -} - -//@MenuMarker -//class ChestSinglePlayerGui internal constructor( -// val player: Player, -// title: Component, -// size: ChestGuiSize = ChestGuiSize.SIX_ROWS, -// parent: AbstractGui? = null, -//) : ChestGuiImpl(title, size, parent) \ No newline at end of file +} \ No newline at end of file diff --git a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt index 72bf84cd..dd05218b 100644 --- a/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/pane/panes/StaticPaneImpl.kt @@ -201,6 +201,10 @@ class StaticPaneImpl( paneItems.put(slot, item) } + override fun setItem(slot: Slot, init: GuiItem.() -> Unit) { + setItem(slot, GuiItemImpl().apply(init)) + } + override fun removeItem(item: GuiItem) { paneItems.values.removeIf { guiItem -> guiItem == item } }