Overhauled GUI component-based backend to support reactive and static slots

This commit is contained in:
Auxilor
2022-09-29 15:08:34 +01:00
parent ab8c946914
commit 0caa328b1e
8 changed files with 121 additions and 31 deletions

View File

@@ -1,6 +1,12 @@
package com.willfp.eco.core.gui.component;
import com.willfp.eco.core.gui.menu.Menu;
import com.willfp.eco.core.gui.slot.FillerSlot;
import com.willfp.eco.core.gui.slot.Slot;
import org.bukkit.Material;
import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
/**
@@ -30,6 +36,28 @@ public interface GUIComponent {
* @return The slot, or null if no slot at the location.
*/
@Nullable
Slot getSlotAt(final int row,
final int column);
default Slot getSlotAt(int row,
int column) {
return new FillerSlot(new ItemStack(Material.AIR));
}
/**
* Get the slot at a certain position in the component.
* <p>
* If your component doesn't use context data (player, menu),
* then it will default to the raw slot.
*
* @param row The row (1-indexed).
* @param column The column (1-indexed).
* @param player The player.
* @param menu The menu.
* @return The slot, or null if no slot at the location.
*/
@Nullable
default Slot getSlotAt(int row,
int column,
@NotNull Player player,
@NotNull Menu menu) {
return getSlotAt(row, column);
}
}

View File

@@ -28,7 +28,10 @@ public interface Menu {
int getRows();
/**
* Get slot at given row and column.
* Get a static slot at a given row and column.
* <p>
* If the slot at the location is reactive, this will return
* an empty slot.
*
* @param row The row.
* @param column The column.
@@ -37,6 +40,24 @@ public interface Menu {
Slot getSlot(int row,
int column);
/**
* Get a slot at a given row and column.
* <p>
* Defaults to static slot if no reactive slot exists.
*
* @param row The row.
* @param column The column.
* @param player The player
* @param menu The menu.
* @return The slot.
*/
default Slot getSlot(int row,
int column,
@NotNull Player player,
@NotNull Menu menu) {
return this.getSlot(row, column);
}
/**
* Get the menu title.
*

View File

@@ -1,6 +0,0 @@
package com.willfp.eco.internal.gui.menu
data class Anchor(
val row: Int,
val column: Int
)

View File

@@ -1,12 +1,14 @@
package com.willfp.eco.internal.gui.menu
import com.willfp.eco.core.gui.component.GUIComponent
import com.willfp.eco.core.gui.menu.CloseHandler
import com.willfp.eco.core.gui.menu.Menu
import com.willfp.eco.core.gui.menu.OpenHandler
import com.willfp.eco.core.gui.slot.Slot
import com.willfp.eco.internal.gui.slot.EmptyFillerSlot
import com.willfp.eco.internal.gui.slot.EcoFillerSlot
import com.willfp.eco.util.NamespacedKeyUtils
import org.bukkit.Bukkit
import org.bukkit.Material
import org.bukkit.NamespacedKey
import org.bukkit.entity.Player
import org.bukkit.event.inventory.InventoryCloseEvent
@@ -17,18 +19,38 @@ import org.bukkit.persistence.PersistentDataType
@Suppress("UNCHECKED_CAST")
class EcoMenu(
private val rows: Int,
private val slots: Map<Anchor, Slot>,
private val componentsAtPoints: Map<Anchor, OffsetComponent>,
private val title: String,
private val onClose: CloseHandler,
private val onRender: (Player, Menu) -> Unit,
private val onOpen: OpenHandler
) : Menu {
override fun getSlot(row: Int, column: Int): Slot {
private fun getComponent(row: Int, column: Int): OffsetComponent? {
if (row < 1 || row > this.rows || column < 1 || column > 9) {
return EmptyFillerSlot
return emptyOffsetComponent
}
return slots[Anchor(row, column)] ?: EmptyFillerSlot
return componentsAtPoints[Anchor(row, column)]
}
override fun getSlot(row: Int, column: Int): Slot {
val found = getComponent(row, column) ?: return emptyFillerSlot
return found.component.getSlotAt(
found.rowOffset,
found.columnOffset
) ?: emptyFillerSlot
}
override fun getSlot(row: Int, column: Int, player: Player, menu: Menu): Slot {
val found = getComponent(row, column) ?: return emptyFillerSlot
return found.component.getSlotAt(
found.rowOffset,
found.columnOffset,
player,
menu
) ?: emptyFillerSlot
}
override fun open(player: Player): Inventory {
@@ -63,6 +85,7 @@ class EcoMenu(
return inventory.captiveItems
}
@Deprecated("Deprecated in Java", ReplaceWith("addState(player, key.toString(), value)"))
override fun <T : Any, Z : Any> writeData(
player: Player,
key: NamespacedKey,
@@ -70,9 +93,11 @@ class EcoMenu(
value: Z
) = addState(player, key.toString(), value)
@Deprecated("Deprecated in Java", ReplaceWith("getState(player, key.toString())"))
override fun <T : Any, Z : Any> readData(player: Player, key: NamespacedKey, type: PersistentDataType<T, Z>): T? =
getState(player, key.toString())
@Deprecated("Deprecated in Java")
override fun getKeys(player: Player): Set<NamespacedKey> {
val inventory = player.openInventory.topInventory.asRenderedInventory() ?: return emptySet()
return inventory.state.keys.mapNotNull { NamespacedKeyUtils.fromStringOrNull(it) }.toSet()
@@ -109,3 +134,17 @@ class EcoMenu(
fun runOnRender(player: Player) = onRender(player, this)
}
data class OffsetComponent(
val component: GUIComponent,
val rowOffset: Int,
val columnOffset: Int
)
data class Anchor(
val row: Int,
val column: Int
)
val emptyFillerSlot = EcoFillerSlot(ItemStack(Material.AIR))
val emptyOffsetComponent = OffsetComponent(emptyFillerSlot, 0, 0)

View File

@@ -64,7 +64,7 @@ class EcoMenuBuilder(private val rows: Int) : MenuBuilder {
}
override fun build(): Menu {
val slots = mutableMapOf<Anchor, Slot>()
val componentsAtPoints = mutableMapOf<Anchor, OffsetComponent>()
for (row in (1..rows)) {
for (column in (1..9)) {
@@ -84,16 +84,20 @@ class EcoMenuBuilder(private val rows: Int) : MenuBuilder {
val slot = component.getSlotAt(rowOffset, columnOffset)
if (slot != null) {
slots[Anchor(row, column)] = slot
componentsAtPoints[Anchor(row, column)] = OffsetComponent(
component,
rowOffset,
columnOffset
)
}
}
}
}
return EcoMenu(rows, slots, title, onClose, onRender, onOpen)
return EcoMenu(rows, componentsAtPoints, title, onClose, onRender, onOpen)
}
init {
maskSlots = ListUtils.create2DList(rows, 9)
}
}
}

View File

@@ -21,7 +21,7 @@ class MenuRenderedInventory(
for (row in (1..menu.rows)) {
for (column in (1..9)) {
val bukkit = MenuUtils.rowColumnToSlot(row, column)
val item = menu.getSlot(row, column).getItemStack(player)
val item = menu.getSlot(row, column, player, menu).getItemStack(player)
inventory.setItem(bukkit, item)
}
@@ -36,7 +36,9 @@ class MenuRenderedInventory(
captiveItems.clear()
for (i in 0 until inventory.size) {
val (row, column) = MenuUtils.convertSlotToRowColumn(i)
val slot = menu.getSlot(row, column)
val slot = menu.getSlot(row, column, player, menu)
if (slot.isCaptive) {
val renderedItem = slot.getItemStack(player)
val itemStack = inventory.getItem(i) ?: continue

View File

@@ -1,16 +1,16 @@
package com.willfp.eco.internal.gui.slot
import org.bukkit.Material
import com.willfp.eco.core.gui.slot.functional.SlotHandler
import org.bukkit.inventory.ItemStack
open class EcoFillerSlot(itemStack: ItemStack) : EcoSlot(
private val noop = SlotHandler { _, _, _ -> }
class EcoFillerSlot(itemStack: ItemStack) : EcoSlot(
{ _, _ -> itemStack },
{ _, _, _ -> },
{ _, _, _ -> },
{ _, _, _ -> },
{ _, _, _ -> },
{ _, _, _ -> },
noop,
noop,
noop,
noop,
noop,
{ _, _, prev -> prev }
)
object EmptyFillerSlot : EcoFillerSlot(ItemStack(Material.AIR))

View File

@@ -29,11 +29,13 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
fun handleSlotClick(event: InventoryClickEvent) {
val rendered = event.clickedInventory?.asRenderedInventory() ?: return
val player = event.whoClicked as? Player ?: return
val menu = rendered.menu
val (row, column) = MenuUtils.convertSlotToRowColumn(event.slot)
menu.getSlot(row, column).handle(event, menu)
menu.getSlot(row, column, player, menu).handle(event, menu)
plugin.scheduler.run { rendered.render() }
}
@@ -56,7 +58,7 @@ class GUIListener(private val plugin: EcoPlugin) : Listener {
val (row, column) = MenuUtils.convertSlotToRowColumn(inv.firstEmpty())
val slot = menu.getSlot(row, column)
val slot = menu.getSlot(row, column, player, menu)
if (!slot.isCaptive) {
event.isCancelled = true