diff --git a/pom.xml b/pom.xml
index ab56300..c3bf7fe 100644
--- a/pom.xml
+++ b/pom.xml
@@ -69,7 +69,7 @@
-LOCAL
- 2.28.0
+ 2.28.1
BentoBoxWorld_Level
bentobox-world
https://sonarcloud.io
diff --git a/src/main/java/world/bentobox/level/util/ConversationUtils.java b/src/main/java/world/bentobox/level/util/ConversationUtils.java
index 968f19e..6a13dc1 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 0000000..ac6ff4f
--- /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());
+ }
+}