From beafc32323f3cfebd2239341cfd3e0bc0ba1be73 Mon Sep 17 00:00:00 2001 From: bobhenl <18662086+bobhenl@users.noreply.github.com> Date: Sun, 8 Nov 2026 21:42:20 +0100 Subject: [PATCH 1/2] fix(anvil): finish click results on the next tick Closing the anvil inside the click left the client a predicted cursor item that creative players could keep. --- .../promptui/v26_1/FrameworkAnvilScreen.java | 25 +++++++++++++++++-- .../promptui/v26_2/FrameworkAnvilScreen.java | 25 +++++++++++++++++-- .../promptui/v26_3/FrameworkAnvilScreen.java | 25 +++++++++++++++++-- 3 files changed, 69 insertions(+), 6 deletions(-) diff --git a/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java b/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java index 28f08bb..a8fbfd7 100644 --- a/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java +++ b/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java @@ -124,7 +124,8 @@ private void openOnPlayerThread() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen result: player={}", player.getName()); - complete(ScreenResult.answer(answer), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.answer(answer)); }); anvilGui.createInventory(); @@ -241,7 +242,8 @@ private void setupItems() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen cancel: player={}", player.getName()); - complete(ScreenResult.manualCancel(), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.manualCancel()); }); anvilGui.getSecondItemComponent().addItem(cancelGuiItem, 0, 0); } @@ -295,6 +297,24 @@ public void close() { scheduleCleanup(gui, impl, true); } + /** + * Finishes a click-triggered result on the next tick. Closing the anvil inside the click event + * left the client holding its predicted cursor item (a copy of the input), which creative-mode + * players could then keep. Waiting one tick lets the server resync the cancelled click first. + */ + private void completeAfterClick(ScreenResult result) { + try { + ScheduledTask task = + player.getScheduler().run(plugin, ignored -> complete(result, true), null); + if (task != null) { + return; + } + } catch (Throwable ignored) { + // Fall through and finish now. + } + complete(result, true); + } + private void complete(ScreenResult result, boolean closePlayer) { AnvilGui gui; AnvilInventoryImpl impl; @@ -399,6 +419,7 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); + player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory diff --git a/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java b/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java index 8628a30..10ee6d8 100644 --- a/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java +++ b/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java @@ -119,7 +119,8 @@ private void openOnPlayerThread() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen result: player={}", player.getName()); - complete(ScreenResult.answer(answer), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.answer(answer)); }); anvilGui.createInventory(); @@ -238,7 +239,8 @@ private void setupItems() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen cancel: player={}", player.getName()); - complete(ScreenResult.manualCancel(), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.manualCancel()); }); anvilGui.getSecondItemComponent().addItem(cancelGuiItem, 0, 0); } @@ -292,6 +294,24 @@ public void close() { scheduleCleanup(gui, impl, true); } + /** + * Finishes a click-triggered result on the next tick. Closing the anvil inside the click event + * left the client holding its predicted cursor item (a copy of the input), which creative-mode + * players could then keep. Waiting one tick lets the server resync the cancelled click first. + */ + private void completeAfterClick(ScreenResult result) { + try { + ScheduledTask task = + player.getScheduler().run(plugin, ignored -> complete(result, true), null); + if (task != null) { + return; + } + } catch (Throwable ignored) { + // Fall through and finish now. + } + complete(result, true); + } + private void complete(ScreenResult result, boolean closePlayer) { AnvilGui gui; AnvilInventoryImpl impl; @@ -393,6 +413,7 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); + player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory diff --git a/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java b/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java index 72b7dcd..73ec110 100644 --- a/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java +++ b/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java @@ -119,7 +119,8 @@ private void openOnPlayerThread() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen result: player={}", player.getName()); - complete(ScreenResult.answer(answer), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.answer(answer)); }); anvilGui.createInventory(); @@ -238,7 +239,8 @@ private void setupItems() { plugin .getSLF4JLogger() .debug("FrameworkAnvilScreen cancel: player={}", player.getName()); - complete(ScreenResult.manualCancel(), true); + event.setCancelled(true); + completeAfterClick(ScreenResult.manualCancel()); }); anvilGui.getSecondItemComponent().addItem(cancelGuiItem, 0, 0); } @@ -292,6 +294,24 @@ public void close() { scheduleCleanup(gui, impl, true); } + /** + * Finishes a click-triggered result on the next tick. Closing the anvil inside the click event + * left the client holding its predicted cursor item (a copy of the input), which creative-mode + * players could then keep. Waiting one tick lets the server resync the cancelled click first. + */ + private void completeAfterClick(ScreenResult result) { + try { + ScheduledTask task = + player.getScheduler().run(plugin, ignored -> complete(result, true), null); + if (task != null) { + return; + } + } catch (Throwable ignored) { + // Fall through and finish now. + } + complete(result, true); + } + private void complete(ScreenResult result, boolean closePlayer) { AnvilGui gui; AnvilInventoryImpl impl; @@ -393,6 +413,7 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); + player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory From 1bceb354811abbea485353f67a11fa3ea15f4095 Mon Sep 17 00:00:00 2001 From: cyr1en Date: Mon, 9 Nov 2026 08:09:38 -0700 Subject: [PATCH 2/2] fix(anvil): remove unreachable inventory update The screen has already switched back to the player's inventory menu by the time this guard runs, so the call never executes. --- .../java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java | 1 - .../java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java | 1 - .../java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java | 1 - 3 files changed, 3 deletions(-) diff --git a/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java b/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java index a8fbfd7..f2458e3 100644 --- a/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java +++ b/prompt-ui-26.1/src/main/java/dev/cyr1en/promptui/v26_1/FrameworkAnvilScreen.java @@ -419,7 +419,6 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); - player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory diff --git a/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java b/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java index 10ee6d8..7446a9c 100644 --- a/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java +++ b/prompt-ui-26.2/src/main/java/dev/cyr1en/promptui/v26_2/FrameworkAnvilScreen.java @@ -413,7 +413,6 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); - player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory diff --git a/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java b/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java index 73ec110..5a627da 100644 --- a/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java +++ b/prompt-ui-26.3/src/main/java/dev/cyr1en/promptui/v26_3/FrameworkAnvilScreen.java @@ -413,7 +413,6 @@ private void cleanupScreen(AnvilGui gui, AnvilInventoryImpl impl, boolean closeP try { if (player.getOpenInventory().getTopInventory().equals(gui.getInventory())) { player.closeInventory(); - player.updateInventory(); } } catch (RuntimeException ignored) { // Retired/disconnected players may reject inventory