From 355d7613426fbd7402789e49e0a8609d14200db4 Mon Sep 17 00:00:00 2001 From: tastybento Date: Wed, 29 Jul 2026 08:14:14 -0700 Subject: [PATCH] fix: don't queue duplicate search conversations in value GUI Bukkit queues conversations per player, so every click on the value panel's search button started another 90-second conversation. When the player finally typed in chat (or the timeouts expired), the queued conversations replayed one after another as alternating 'Conversation cancelled!' / 'Please enter a search value' spam, with the GUI re-opening each time. If the player is already conversing, repeat the pending question instead of beginning a new conversation. Fixes #451 Co-Authored-By: Claude Fable 5 Claude-Session: https://claude.ai/code/session_01NSvRYW3Rgh6Vgvx62QRYE1 --- .../level/util/ConversationUtils.java | 10 +++ .../level/util/ConversationUtilsTest.java | 62 +++++++++++++++++++ 2 files changed, 72 insertions(+) create mode 100644 src/test/java/world/bentobox/level/util/ConversationUtilsTest.java diff --git a/src/main/java/world/bentobox/level/util/ConversationUtils.java b/src/main/java/world/bentobox/level/util/ConversationUtils.java index 968f19ef..6a13dc14 100644 --- a/src/main/java/world/bentobox/level/util/ConversationUtils.java +++ b/src/main/java/world/bentobox/level/util/ConversationUtils.java @@ -43,6 +43,16 @@ public static void createStringInput(Consumer consumer, @NonNull String question, @Nullable String successMessage) { + if (user.getPlayer().isConversing()) + { + // Bukkit queues conversations per player, so starting another one here would + // stack prompts that later replay as cancel/prompt spam (#451). Repeat the + // question for the pending conversation instead. + user.closeInventory(); + user.getPlayer().sendRawMessage(user.getTranslation("level.conversations.prefix") + question); + return; + } + // Text input message. StringPrompt stringPrompt = new StringPrompt() { diff --git a/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java b/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java new file mode 100644 index 00000000..ac6ff4f8 --- /dev/null +++ b/src/test/java/world/bentobox/level/util/ConversationUtilsTest.java @@ -0,0 +1,62 @@ +package world.bentobox.level.util; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.function.Consumer; + +import org.bukkit.conversations.Conversation; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import world.bentobox.bentobox.api.user.User; +import world.bentobox.level.CommonTestSetup; + +/** + * Tests for {@link ConversationUtils}. + */ +class ConversationUtilsTest extends CommonTestSetup { + + private User user; + + @Override + @BeforeEach + protected void setUp() throws Exception { + super.setUp(); + // The conversation timeout canceller starts a scheduler timer on build. + when(plugin.getServer()).thenReturn(server); + user = User.getInstance(p); + } + + /** + * A player with no active conversation should get a new one. + */ + @Test + void testCreateStringInputStartsConversation() { + when(p.isConversing()).thenReturn(false); + + Consumer consumer = value -> {}; + ConversationUtils.createStringInput(consumer, user, "question?", "done"); + + verify(p).beginConversation(any(Conversation.class)); + } + + /** + * Clicking the search button while a conversation is already pending must not + * queue a second conversation (#451) — it should just repeat the question. + */ + @Test + void testCreateStringInputDoesNotQueueSecondConversation() { + when(p.isConversing()).thenReturn(true); + + Consumer consumer = value -> {}; + ConversationUtils.createStringInput(consumer, user, "question?", "done"); + + verify(p, never()).beginConversation(any(Conversation.class)); + verify(p).closeInventory(); + verify(p).sendRawMessage(anyString()); + } +}