diff --git a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java index dc3b881c0..294d7b971 100644 --- a/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java +++ b/examples/paper/src/main/java/me/devnatan/inventoryframework/runtime/SamplePlugin.java @@ -17,6 +17,7 @@ public void onEnable() { ViewFrame viewFrame = ViewFrame.create(this) .install(AnvilInputFeature.AnvilInput) .with(new AnvilInputSample(), new Failing(), new SimplePagination(), new AutoUpdate()) + .enableDebug() .register(); getCommand("ifexample").setExecutor(new IFExampleCommandExecutor(viewFrame)); diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java index dccd17236..674733555 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/Viewer.java @@ -101,6 +101,20 @@ public interface Viewer { @ApiStatus.Internal void setSwitching(boolean switching); + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + boolean isInteractionsLocked(); + + /** + * This is an internal inventory-framework API that should not be used from outside of + * this library. No compatibility guarantees are provided. + */ + @ApiStatus.Internal + void setInteractionsLocked(boolean interactionsLocked); + /** * This is an internal inventory-framework API that should not be used from outside of * this library. No compatibility guarantees are provided. diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java index 40eef3776..01a2d4cbe 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/Pipeline.java @@ -7,6 +7,7 @@ import java.util.LinkedList; import java.util.List; import java.util.Map; +import me.devnatan.inventoryframework.IFDebug; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.TestOnly; @@ -115,6 +116,7 @@ public void execute(S subject) { public void execute(PipelinePhase phase, S subject) { final PipelineContext context = new PipelineContext<>(phase, interceptors.getOrDefault(phase, Collections.emptyList())); + IFDebug.debug("Pipeline call: %s", phase.getName()); context.execute(subject); } } diff --git a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java index 0a078a3e6..b3f085514 100644 --- a/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java +++ b/inventory-framework-api/src/main/java/me/devnatan/inventoryframework/pipeline/PipelineContext.java @@ -1,6 +1,7 @@ package me.devnatan.inventoryframework.pipeline; import java.util.List; +import me.devnatan.inventoryframework.IFDebug; import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.VisibleForTesting; @@ -40,6 +41,9 @@ private void loop() { final PipelineInterceptor nextInterceptor = safeInterceptors.get(pointer); index = pointer + 1; + IFDebug.debug( + "Interception loop #%d: %s", + index, nextInterceptor.getClass().getName()); nextInterceptor.intercept(this, subject); } while (true); } diff --git a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java index 1d4117485..481b5ef54 100644 --- a/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java +++ b/inventory-framework-core/src/main/java/me/devnatan/inventoryframework/context/AbstractIFContext.java @@ -109,7 +109,8 @@ public void removeComponent(@NotNull Component component) { private IFSlotRenderContext createSlotRenderContext(@NotNull Component component, boolean force) { if (!(this instanceof IFRenderContext)) - throw new InventoryFrameworkException("Slot render context cannot be created from non-render parent"); + throw new InventoryFrameworkException("Slot render context cannot be created from non-render parent: " + + getClass().getName()); final IFRenderContext renderContext = (IFRenderContext) this; final IFSlotRenderContext slotRender = getRoot() diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java index 16e479198..a20939fd1 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/BukkitViewer.java @@ -14,7 +14,7 @@ public final class BukkitViewer implements Viewer { private IFRenderContext activeContext; private Deque previousContexts = new LinkedList<>(); private long lastInteractionInMillis; - private boolean switching; + private boolean switching, interactionsLocked; public BukkitViewer(@NotNull Player player, IFRenderContext activeContext) { this.player = player; @@ -97,6 +97,16 @@ public void setSwitching(boolean switching) { this.switching = switching; } + @Override + public boolean isInteractionsLocked() { + return interactionsLocked || isBlockedByInteractionDelay(); + } + + @Override + public void setInteractionsLocked(boolean interactionsLocked) { + this.interactionsLocked = interactionsLocked; + } + @Override public IFRenderContext getPreviousContext() { return previousContexts.peekLast(); diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java index 71165c4b2..cfed98eb7 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/IFInventoryListener.java @@ -41,6 +41,14 @@ public void onInventoryClick(final InventoryClickEvent event) { final Viewer viewer = viewFrame.getViewer(player); if (viewer == null) return; + if (viewer.isInteractionsLocked()) { + IFDebug.debug("Interaction locked at listener level %s", player.getName()); + event.setCancelled(true); + return; + } + + viewer.setInteractionsLocked(true); + final IFRenderContext context = viewer.getActiveContext(); final Component clickedComponent = context.getComponentsAt(event.getRawSlot()).stream() .filter(Component::isVisible) @@ -55,6 +63,8 @@ public void onInventoryClick(final InventoryClickEvent event) { .createSlotClickContext(event.getRawSlot(), viewer, clickedContainer, clickedComponent, event, false); root.getPipeline().execute(StandardPipelinePhases.CLICK, clickContext); + + viewer.setInteractionsLocked(false); } @SuppressWarnings("unused") @@ -68,11 +78,15 @@ public void onInventoryClose(final InventoryCloseEvent event) { final Viewer viewer = viewFrame.getViewer(player); if (viewer == null) return; + viewer.setInteractionsLocked(true); + final IFRenderContext context = viewer.getCurrentContext(); final RootView root = context.getRoot(); final IFCloseContext closeContext = root.getElementFactory().createCloseContext(viewer, context); root.getPipeline().execute(StandardPipelinePhases.CLOSE, closeContext); + + viewer.setInteractionsLocked(false); } @SuppressWarnings("deprecation") diff --git a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java index ec3bd5531..e8e216fca 100644 --- a/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java +++ b/inventory-framework-platform-bukkit/src/main/java/me/devnatan/inventoryframework/pipeline/GlobalClickInterceptor.java @@ -2,6 +2,7 @@ import static me.devnatan.inventoryframework.ViewConfig.CANCEL_ON_CLICK; +import me.devnatan.inventoryframework.IFDebug; import me.devnatan.inventoryframework.VirtualView; import me.devnatan.inventoryframework.context.SlotClickContext; import org.bukkit.event.inventory.InventoryClickEvent; @@ -15,7 +16,10 @@ public final class GlobalClickInterceptor implements PipelineInterceptor pipeline, @NotNull VirtualView subject) { - if (!(subject instanceof SlotClickContext)) return; + if (!(subject instanceof SlotClickContext)) { + IFDebug.debug("Cannot handle global click: not a SlotClickContext"); + return; + } final SlotClickContext context = (SlotClickContext) subject; final InventoryClickEvent event = context.getClickOrigin(); diff --git a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt index dc4d74fdb..e4b264af9 100644 --- a/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt +++ b/inventory-framework-platform-minestom/src/main/kotlin/me/devnatan/inventoryframework/MinestomViewer.kt @@ -14,6 +14,7 @@ class MinestomViewer( private val previousContexts: Deque = LinkedList() private var lastInteractionInMillis: Long = 0 private var switching = false + private var interactionsLocked = false override fun getCurrentContext(): IFRenderContext = if (isSwitching()) { @@ -71,6 +72,12 @@ class MinestomViewer( this.switching = switching } + override fun isInteractionsLocked(): Boolean = interactionsLocked || isBlockedByInteractionDelay + + override fun setInteractionsLocked(interactionsLocked: Boolean) { + this.interactionsLocked = interactionsLocked + } + override fun getPreviousContext(): IFRenderContext? = previousContexts.peekLast() override fun setPreviousContext(previousContext: IFRenderContext) {