diff --git a/.idea/modules.xml b/.idea/modules.xml index b3f03b10..bb04ac9d 100644 --- a/.idea/modules.xml +++ b/.idea/modules.xml @@ -16,7 +16,6 @@ - @@ -31,31 +30,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..d7df1a4e 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) @@ -22,3 +21,10 @@ 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") + 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 new file mode 100644 index 00000000..37ee2da7 --- /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,42 @@ +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 +import org.bukkit.inventory.Inventory +import java.util.* + +@InternalSurfApi +interface InventoryBridge { + + fun getGuiByInventory(inventory: Inventory): Gui? + fun createGuiItem(key: NamespacedKey?, uuid: UUID?): GuiItem + fun createUpdatableGuiItem( + key: NamespacedKey?, + 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() + } +} \ 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/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 deleted file mode 100644 index bb8cb947..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,92 +0,0 @@ -@file:OptIn(ExperimentalContracts::class) - -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 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.staticPane( - slot: Slot, - height: @Range(from = 1, to = 6) Int, - length: @Range(from = 1, to = 9) Int = 9, - init: (@PaneMarker StaticPane).() -> Unit, -) { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val pane = StaticPane(slot, length, height) - pane.init() - addPane(pane) -} - -fun menu( - title: Component, - rows: @Range(from = 2, to = 6) Int = 6, - init: @MenuMarker SurfChestGui.() -> Unit, -): SurfChestGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val menu = SurfChestGui(title, rows) - menu.init() - return menu -} - -fun playerMenu( - title: Component, - player: Player, - rows: @Range(from = 2, to = 6) Int = 6, - init: @MenuMarker SurfChestSinglePlayerGui.() -> Unit, -): SurfChestSinglePlayerGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val menu = SurfChestSinglePlayerGui(title, player, rows) - menu.init() - return menu -} - - -fun SurfChestGui.childMenu( - title: Component, - rows: @Range(from = 2, to = 6) Int, - init: @MenuMarker SurfChestGui.() -> Unit, -): SurfChestGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val menu = SurfChestGui(title, rows, this) - menu.init() - return menu -} - -fun SurfChestSinglePlayerGui.childPlayerMenu( - title: Component, - rows: @Range(from = 2, to = 6) Int, - init: @MenuMarker SurfChestSinglePlayerGui.() -> Unit, -): SurfChestSinglePlayerGui { - contract { - callsInPlace(init, InvocationKind.EXACTLY_ONCE) - } - - val menu = SurfChestSinglePlayerGui(title, player, rows, this) - menu.init() - return menu -} \ 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/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..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 @@ -3,3 +3,11 @@ 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 \ 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..374a4053 --- /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,58 @@ +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.gui.handlers.* +import dev.slne.surf.surfapi.bukkit.api.inventory.item.UpdatableGuiItem +import it.unimi.dsi.fastutil.objects.ObjectSet +import org.bukkit.entity.Player +import org.bukkit.inventory.Inventory +import org.jetbrains.annotations.Unmodifiable + +@GuiDsl +interface Gui : Cloneable { + val backingInventory: Inventory + val viewers: @Unmodifiable ObjectSet + val parent: Gui? + + // region Handlers + fun onTopClick(handler: ClickHandler) + + fun onTopClick(handler: ClickHandlerDsl) + fun onBottomClick(handler: ClickHandler) + + fun onBottomClick(handler: ClickHandlerDsl) + fun onGlobalClick(handler: ClickHandler) + + fun onGlobalClick(handler: ClickHandlerDsl) + fun onOutsideClick(handler: ClickHandler) + + fun onOutsideClick(handler: ClickHandlerDsl) + fun onTopDrag(handler: DragHandler) + + fun onTopDrag(handler: DragHandlerDsl) + fun onBottomDrag(handler: DragHandler) + + fun onBottomDrag(handler: DragHandlerDsl) + fun onGlobalDrag(handler: DragHandler) + + fun onGlobalDrag(handler: DragHandlerDsl) + fun onClose(handler: CloseHandler) + fun onClose(handler: CloseHandlerDsl) + // endregion + + fun navigateToParentOnClose(enabled: Boolean = true) + fun navigateToParent(player: Player): Boolean + fun walkParents(): Sequence + + fun show(player: Player) + fun update() + fun updateItem(item: UpdatableGuiItem) + + public override fun clone(): Gui + + companion object { + 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/MergedGui.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt new file mode 100644 index 00000000..1188c80b --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/gui/MergedGui.kt @@ -0,0 +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) + + 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/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..1e26228d --- /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,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 + +@GuiDsl +interface NamedGui : Gui { + val title: Component + + fun title(title: Component) + 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 new file mode 100644 index 00000000..a8fefd79 --- /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,20 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent + +typealias ClickHandlerDsl = ClickHandlerScope.() -> Unit + +interface ClickHandler : GuiHandler + +@JvmInline +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..720155e4 --- /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,16 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryCloseEvent + +typealias CloseHandlerDsl = CloseHandlerScope.() -> Unit + +interface CloseHandler: GuiHandler + +@JvmInline +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 new file mode 100644 index 00000000..3e531ccc --- /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,19 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import dev.slne.surf.surfapi.core.api.util.InternalSurfApi +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryDragEvent + +typealias DragHandlerDsl = DragHandlerScope.() -> Unit + +interface DragHandler : GuiHandler + +@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..fdc822cf --- /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,9 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.gui.handlers + +import org.bukkit.event.Event +import org.jetbrains.annotations.ApiStatus.OverrideOnly + +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 new file mode 100644 index 00000000..8988c52a --- /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,56 @@ +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 { + val size: ChestGuiSize + fun size(size: ChestGuiSize) + + override fun clone(): ChestGui = super.clone() as ChestGui + + enum class ChestGuiSize(val rows: Int) { + ONE_ROW(1), + TWO_ROWS(2), + THREE_ROWS(3), + 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 3b02b836..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 @@ -1,37 +1,38 @@ -@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 org.bukkit.event.inventory.InventoryClickEvent +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.inventory.ItemStack -import kotlin.contracts.ExperimentalContracts -import kotlin.contracts.InvocationKind -import kotlin.contracts.contract +import org.bukkit.inventory.ItemType +import java.util.* -fun guiItem(item: ItemStack, action: InventoryClickEvent.() -> Unit = {}): GuiItem { - contract { - callsInPlace(action, InvocationKind.UNKNOWN) - } +@GuiItemDsl +interface GuiItem : Cloneable { + val itemStack: ItemStack + val key: NamespacedKey + val uuid: UUID + var visible: Boolean - return GuiItem(item, action) -} + fun item(itemStack: ItemStack) + fun item(type: ItemType, block: ItemStack.() -> Unit = {}) -fun guiItem( - material: Material, - item: ItemStack.() -> Unit, - action: InventoryClickEvent.() -> Unit = {} -): GuiItem { - contract { - callsInPlace(item, InvocationKind.EXACTLY_ONCE) - callsInPlace(action, InvocationKind.UNKNOWN) - } + fun onClick(handler: ClickHandler) + fun onClick(handler: ClickHandlerDsl) - return GuiItem(ItemStack(material).apply(item), action) -} + public override fun clone(): GuiItem = super.clone() as GuiItem -fun guiItem( - material: Material, - action: InventoryClickEvent.() -> Unit = {} -) = guiItem(material, {}, action) \ No newline at end of file + companion object { + operator fun invoke( + key: NamespacedKey? = null, + uuid: UUID? = null, + init: GuiItem.() -> Unit, + ): 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/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..2c275265 --- /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,27 @@ +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 java.util.* +import java.util.function.BooleanSupplier + +@GuiItemDsl +interface UpdatableGuiItem : GuiItem { + + fun onUpdate(update: BooleanSupplier) + + override fun clone(): UpdatableGuiItem = super.clone() as UpdatableGuiItem + + companion object { + operator fun invoke( + key: NamespacedKey? = null, + uuid: UUID? = null, + init: UpdatableGuiItem.() -> Unit, + ): 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/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/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..f16098e2 --- /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,29 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane + +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.utils.Priority +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.* + +interface Pane : Cloneable { + var slot: Slot + val uuid: UUID + val items: @Unmodifiable ObjectList + val panes: @Unmodifiable ObjectList + + var length: Int + var height: Int + var priority: Priority + var visible: Boolean + + fun onClick(handler: ClickHandler) + fun onClick(handler: ClickHandlerDsl) + + fun clear() + + 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/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..3c0ca572 --- /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,16 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.components + +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 org.bukkit.inventory.ItemStack + +interface PagingButtons: Pane { + fun setBackwardsButton(item: GuiItem) + fun setBackwardsButton(item: ItemStack, action: ClickHandlerDsl = {}) + + 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 new file mode 100644 index 00000000..3c3d7faa --- /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,18 @@ +package dev.slne.surf.surfapi.bukkit.api.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.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 + +interface OutlinePane : Pane, Orientable, Flippable, Rotatable { + + 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 new file mode 100644 index 00000000..7683893e --- /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,20 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.panes + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import it.unimi.dsi.fastutil.objects.ObjectList +import org.bukkit.inventory.ItemStack + +interface PaginatedPane: Pane { + val page: Int + val totalPages: Int + + fun page(page: Int) + fun previousPage() + fun nextPage() + + fun populateWithGuiItems(items: ObjectList) + fun populateWithItemStacks(items: ObjectList) + + 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 new file mode 100644 index 00000000..e2abbe1a --- /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,21 @@ +package dev.slne.surf.surfapi.bukkit.api.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.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.Slot +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) + + 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/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..44c22d7b --- /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,19 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge + +interface Mask { + val enabledSlots: Int + val length: Int + val height: Int + + 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 { + operator fun invoke(vararg mask: String): Mask = InventoryBridge.instance.createMask(*mask) + } +} \ 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/Orientable.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Orientable.kt new file mode 100644 index 00000000..3e47ab84 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Orientable.kt @@ -0,0 +1,11 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +interface Orientable { + + var orientation: Orientation + + enum class Orientation { + HORIZONTAL, + VERTICAL + } +} \ 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/Pattern.kt b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Pattern.kt new file mode 100644 index 00000000..bd7e969a --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-api/src/main/kotlin/dev/slne/surf/surfapi/bukkit/api/inventory/pane/utils/Pattern.kt @@ -0,0 +1,21 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.pane.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.InventoryBridge + +interface Pattern { + val length: Int + val height: Int + + fun setHeight(height: Int): Pattern + fun setLength(length: Int): 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 { + 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/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/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..782b6058 --- /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,33 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import dev.slne.surf.surfapi.bukkit.api.inventory.item.GuiItem +import dev.slne.surf.surfapi.bukkit.api.inventory.pane.Pane +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.ItemStack +import org.bukkit.inventory.PlayerInventory + +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 new file mode 100644 index 00000000..398c8c7f --- /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,13 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +enum class 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 new file mode 100644 index 00000000..169463d2 --- /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,44 @@ +package dev.slne.surf.surfapi.bukkit.api.inventory.utils + +import org.jetbrains.annotations.Range + +interface Slot { + fun getX(length: Int): Int + fun getY(length: Int): Int + + companion object { + 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) + } + + operator fun invoke(index: Int): Slot { + return fromIndex(index) + } + } +} + +internal data class XY(val x: Int, val y: Int) : Slot { + override fun getX(length: Int) = x + override fun getY(length: Int) = y +} + +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) +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/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..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") } } @@ -31,7 +32,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 +107,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/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/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..522114d2 --- /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,217 @@ +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 +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: UpdatableGuiItem) { + require(item is UpdatableGuiItemImpl) { "Item must be an instance of 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..99ea3c1a --- /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,24 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui + +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(parent: AbstractGui? = null) : + AbstractGui(parent), NamedGui { + + protected var titleDirty = false + + override var title: Component = Component.empty() + set(value) { + field = value + titleDirty = true + } + + override fun title(title: Component) { + this.title = title + } + + 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/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..c165e927 --- /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,112 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.types + +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.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 org.bukkit.Bukkit +import org.bukkit.entity.Player +import org.bukkit.event.inventory.InventoryClickEvent + +open class ChestGuiImpl( + initialSize: ChestGui.ChestGuiSize = ChestGui.ChestGuiSize.FIVE_ROWS, + parent: AbstractGui? = null, +) : 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) { + inventoryComponent.addPane(pane) + } + + 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 + 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 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()) } + } + + override fun updateItem0(item: UpdatableGuiItemImpl) = + 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 click(event: InventoryClickEvent) { + inventoryComponent.click(this, event, event.rawSlot) + } + + override fun isPlayerInventoryUsed() = + inventoryComponent.excludeRows(0, inventoryComponent.height - 5).hasItem() +} \ 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/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 new file mode 100644 index 00000000..6ca99acb --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/gui/utils/InventoryBased.kt @@ -0,0 +1,8 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.gui.utils + +import org.bukkit.inventory.Inventory +import org.bukkit.inventory.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..127dc048 --- /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,93 @@ +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.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: ClickHandler) { + action = handler + } + + override fun onClick(handler: ClickHandlerDsl) { + onClick(ClickHandlerScopeImpl(handler)) + } + + override fun item(itemStack: ItemStack) { + _itemStack = itemStack + } + + override fun item( + type: ItemType, + block: 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..3b3332f6 --- /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,19 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.item + +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: 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-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/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 new file mode 100644 index 00000000..b4bb5a70 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/listener/GuiListener.kt @@ -0,0 +1,208 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.listener + +import com.github.shynixn.mccoroutine.folia.launch +import com.github.shynixn.mccoroutine.folia.ticks +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 +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.* + +object GuiListener : Listener { + private val log = logger() + private val activeGuiInstances = mutableObjectSetOf() + + @EventHandler(ignoreCancelled = true) + fun InventoryClickEvent.onInventoryClick() { + val gui = getGui(inventory) ?: return + + val inventory = view.getInventory(rawSlot) ?: run { + gui.callOnOutsideClick(this) + return + } + + gui.callOnGlobalClick(this) + + if (inventory == view.topInventory) { + 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(player.openInventory.topInventory) + 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 = view.getInventory(slot) ?: continue + + if (inventory == view.topInventory) { + top = true + } else if (inventory == view.bottomInventory) { + 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 = 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 = view.cursor + view.setCursor(oldCursor) + + val clickEvent = InventoryClickEvent( + view, + slotType, + index, + clickType, + action + ) + + clickEvent.onInventoryClick() + + if (Objects.equals(view.cursor, oldCursor)) { + view.setCursor(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.shouldNavigateToParentOnClose) { + 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 onPluginDisable(event: PluginDisableEvent) { + if (plugin != event.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): AbstractGui? { + val gui = AbstractGui.getGui(inventory) + + if (gui != null) return gui + + val holder = inventory.holder + + if (holder is AbstractGui) { + return holder + } + + return null + } + + +} \ 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/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..dd05218b --- /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,219 @@ +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 setItem(slot: Slot, init: GuiItem.() -> Unit) { + setItem(slot, GuiItemImpl().apply(init)) + } + + 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-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/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 new file mode 100644 index 00000000..dec05dca --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/GeometryUtils.kt @@ -0,0 +1,44 @@ +package dev.slne.surf.surfapi.bukkit.server.impl.inventory.utils + +import it.unimi.dsi.fastutil.ints.AbstractInt2IntMap +import it.unimi.dsi.fastutil.ints.Int2IntMap + +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-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-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/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 new file mode 100644 index 00000000..697bc1d7 --- /dev/null +++ b/surf-api-bukkit/surf-api-bukkit-server/src/main/kotlin/dev/slne/surf/surfapi/bukkit/server/impl/inventory/utils/PlayerInventoryCache.kt @@ -0,0 +1,96 @@ +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 + +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-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..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,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.server.impl.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