From 97adae7b32f9ce27a43830660fff8b3b74fe7c2b Mon Sep 17 00:00:00 2001 From: Auxilor Date: Thu, 29 Sep 2022 16:15:19 +0100 Subject: [PATCH] More GUI improvements, improving Slot API --- .../willfp/eco/core/gui/slot/CustomSlot.java | 31 ++++++---- .../eco/core/gui/slot/ReactiveSlot.java | 61 +++++++++++++++++++ .../com/willfp/eco/core/gui/slot/Slot.java | 18 ++++++ .../willfp/eco/internal/gui/slot/EcoSlot.kt | 4 +- .../eco/internal/spigot/gui/GUIListener.kt | 31 ++++++++-- 5 files changed, 126 insertions(+), 19 deletions(-) create mode 100644 eco-api/src/main/java/com/willfp/eco/core/gui/slot/ReactiveSlot.java diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/slot/CustomSlot.java b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/CustomSlot.java index 46b0e515..af9dc5d8 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/gui/slot/CustomSlot.java +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/CustomSlot.java @@ -1,5 +1,6 @@ package com.willfp.eco.core.gui.slot; +import com.willfp.eco.core.gui.menu.Menu; import org.bukkit.entity.Player; import org.bukkit.inventory.ItemStack; import org.jetbrains.annotations.NotNull; @@ -29,17 +30,6 @@ public abstract class CustomSlot implements Slot { this.delegate = slot; } - /** - * Get the delegate slot. - *

- * This is not required to add the slot to a menu, but is instead used internally. - * - * @return The slot. - */ - public Slot getDelegate() { - return this.delegate; - } - @Override public ItemStack getItemStack(@NotNull final Player player) { if (delegate == null) { @@ -76,6 +66,25 @@ public abstract class CustomSlot implements Slot { return delegate.isCaptiveFromEmpty(); } + /** + * Get the delegate slot. + *

+ * This is not required to add the slot to a menu, but is instead used internally. + * + * @return The slot. + * @deprecated Replaced with {@link Slot#getRealSlot(Player, Menu)} + */ + @Deprecated(since = "6.43.0", forRemoval = true) + public Slot getDelegate() { + return this.delegate; + } + + @Override + public final Slot getRealSlot(@NotNull final Player player, + @NotNull final Menu menu) { + return delegate; + } + @Override public final int getRows() { return Slot.super.getRows(); diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/slot/ReactiveSlot.java b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/ReactiveSlot.java new file mode 100644 index 00000000..e165b7f4 --- /dev/null +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/ReactiveSlot.java @@ -0,0 +1,61 @@ +package com.willfp.eco.core.gui.slot; + +import com.willfp.eco.core.gui.menu.Menu; +import org.bukkit.Material; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.jetbrains.annotations.NotNull; + +/** + * Base class for custom slot implementations. + */ +public abstract class ReactiveSlot implements Slot { + /** + * Create a new reactive slot. + */ + protected ReactiveSlot() { + + } + + /** + * Get the actual slot to be shown. + * + * @param player The player. + * @param menu The menu. + * @return The slot. + */ + @NotNull + public abstract Slot getSlot(@NotNull final Player player, + @NotNull final Menu menu); + + @Override + public ItemStack getItemStack(@NotNull final Player player) { + return new ItemStack(Material.STONE); + } + + @Override + public boolean isCaptive() { + return false; + } + + @Override + public final Slot getRealSlot(@NotNull final Player player, + @NotNull final Menu menu) { + return getSlot(player, menu); + } + + @Override + public final int getRows() { + return Slot.super.getRows(); + } + + @Override + public final int getColumns() { + return Slot.super.getColumns(); + } + + @Override + public final Slot getSlotAt(int row, int column) { + return Slot.super.getSlotAt(row, column); + } +} diff --git a/eco-api/src/main/java/com/willfp/eco/core/gui/slot/Slot.java b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/Slot.java index 72a25480..1fbd8137 100644 --- a/eco-api/src/main/java/com/willfp/eco/core/gui/slot/Slot.java +++ b/eco-api/src/main/java/com/willfp/eco/core/gui/slot/Slot.java @@ -2,6 +2,7 @@ package com.willfp.eco.core.gui.slot; import com.willfp.eco.core.Eco; import com.willfp.eco.core.gui.component.GUIComponent; +import com.willfp.eco.core.gui.menu.Menu; import com.willfp.eco.core.gui.slot.functional.SlotProvider; import com.willfp.eco.core.items.TestableItem; import org.bukkit.Material; @@ -33,6 +34,23 @@ public interface Slot extends GUIComponent { */ boolean isCaptive(); + /** + * Get the real slot to be shown. + *

+ * This is mostly internal, if you want to implement custom slots you should + * turn to {@link CustomSlot} or {@link ReactiveSlot}, which abstract this + * behaviour away. + *

+ * **Never** return {@code this} from this method. Always make sure that your + * slots eventually delegate to a slot created by {@link Slot#builder()}. + * + * @param player The player. + * @param menu The menu. + * @return The slot. + */ + Slot getRealSlot(@NotNull final Player player, + @NotNull final Menu menu); + /** * If the slot is not captive for a player. * diff --git a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt index 1cb09e83..2707211d 100644 --- a/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt +++ b/eco-core/core-backend/src/main/kotlin/com/willfp/eco/internal/gui/slot/EcoSlot.kt @@ -24,8 +24,6 @@ open class EcoSlot( event: InventoryClickEvent, menu: Menu ) { - event.isCancelled = true - handlers[event.click]?.handle(event, this, menu) } @@ -47,4 +45,6 @@ open class EcoSlot( override fun isCaptive(): Boolean { return false } + + override fun getRealSlot(player: Player, menu: Menu): EcoSlot = this } diff --git a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/gui/GUIListener.kt b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/gui/GUIListener.kt index c1488e46..21be4a49 100644 --- a/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/gui/GUIListener.kt +++ b/eco-core/core-plugin/src/main/kotlin/com/willfp/eco/internal/spigot/gui/GUIListener.kt @@ -1,7 +1,6 @@ package com.willfp.eco.internal.spigot.gui import com.willfp.eco.core.EcoPlugin -import com.willfp.eco.core.gui.slot.CustomSlot import com.willfp.eco.core.gui.slot.Slot import com.willfp.eco.internal.gui.menu.EcoMenu import com.willfp.eco.internal.gui.menu.MenuHandler @@ -18,10 +17,30 @@ import org.bukkit.event.inventory.InventoryCloseEvent import org.bukkit.event.player.PlayerItemHeldEvent class GUIListener(private val plugin: EcoPlugin) : Listener { - private fun Slot.handle(event: InventoryClickEvent, menu: EcoMenu) { - when (this) { - is EcoSlot -> this.handleInventoryClick(event, menu) - is CustomSlot -> this.delegate.handle(event, menu) + // Prevents StackOverflow exceptions with poorly implemented custom slots. + private val depthLimit = 32 + + private fun Slot.handle( + player: Player, + event: InventoryClickEvent, + menu: EcoMenu, + depth: Int = 0 + ) { + // Always cancel on first depth to prevent bugs with custom slot implementations. + if (depth == 0) { + event.isCancelled = true + } + + if (depth >= depthLimit) { + return + } + + val delegate = this.getRealSlot(player, menu) + + if (delegate is EcoSlot) { + delegate.handleInventoryClick(event, menu) + } else { + delegate.handle(player, event, menu, depth + 1) } } @@ -35,7 +54,7 @@ class GUIListener(private val plugin: EcoPlugin) : Listener { val (row, column) = MenuUtils.convertSlotToRowColumn(event.slot) - menu.getSlot(row, column, player, menu).handle(event, menu) + menu.getSlot(row, column, player, menu).handle(player, event, menu) plugin.scheduler.run { rendered.render() } }