Folia icon indicating copy to clipboard operation
Folia copied to clipboard

Conversations API results in chat breaking

Open sowelipililimute opened this issue 2 years ago • 1 comments

Expected behavior

Bukkit Conversations API can be used as normal

Observed/Actual behavior

Once a conversation is begin()ed with a player, every chat message they say results in this:

[00:04:52 ERROR]: Chain link failed, continuing to next one
java.lang.UnsupportedOperationException: null
        at net.minecraft.server.network.ServerGamePacketListenerImpl.broadcastChatMessage(ServerGamePacketListenerImpl.java:2550) ~[?:?]
        at net.minecraft.server.network.ServerGamePacketListenerImpl.lambda$handleChat$11(ServerGamePacketListenerImpl.java:2238) ~[?:?]
        at java.util.concurrent.CompletableFuture$UniAccept.tryFire(CompletableFuture.java:718) ~[?:?]
        at java.util.concurrent.CompletableFuture$Completion.run(CompletableFuture.java:482) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136) ~[?:?]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635) ~[?:?]
        at java.lang.Thread.run(Thread.java:833) ~[?:?]

Steps/models to reproduce

Scala snippet of the code I am using to invoke the conversations API:

private case class PromptState(
    promise: Promise[String],
    conversation: Conversation,
)

class Prompts(using plugin: Plugin):
    private val prompts = scala.collection.concurrent.TrieMap[UUID, PromptState]()

    def prompt(player: Player, prompt: String): Future[String] =
        if prompts.contains(player.getUniqueId()) then
            val state = prompts(player.getUniqueId())
            state.promise.failure(Exception("cancelled by another prompt"))
            state.conversation.abandon()
            prompts.remove(player.getUniqueId())

        val promise = Promise[String]()
        given ctx: ExecutionContext = EntityExecutionContext(player)

        val bukkitPrompt = new StringPrompt:
            override def getPromptText(context: ConversationContext): String =
                prompt
            override def acceptInput(context: ConversationContext, input: String): Prompt =
                prompts(player.getUniqueId()).promise.complete(Try(input))
                prompts.remove(player.getUniqueId())
                Prompt.END_OF_CONVERSATION

        val conversation = ConversationFactory(plugin)
            .withModality(false)
            .withLocalEcho(false)
            .withFirstPrompt(bukkitPrompt)
            .buildConversation(player)
            
        val state = PromptState(promise, conversation)
        Future { player.closeInventory() }
        conversation.begin()
        prompts(player.getUniqueId()) = state
        promise.future

Plugin and Datapack List

Only my own code (findable at https://github.com/BlueMoonVineyard/BallCore/tree/work/jblackquill/folia)

Folia version

> version
[00:09:49 INFO]: Checking version, please wait...
[00:09:49 INFO]: This server is running Folia version git-Folia-"ed7a5c5" (MC: 1.19.4) (Implementing API version 1.19.4-R0.1-SNAPSHOT) (Git: ed7a5c5)
You are 1 version(s) behind
Download the new version at: https://papermc.io/downloads

Other

No response

sowelipililimute avatar Apr 01 '23 00:04 sowelipililimute

No plans to resolve

Spottedleaf avatar Apr 01 '23 00:04 Spottedleaf

No plans to resolve

@Spottedleaf excuse me? This is unacceptable given the large amount of plugins using the Conversation API. It breaks by merely blocking the input.

I was able to workaround this by using ProtocolLib. Here is a pseudocode:

this.addReceivingListener(PacketType.Play.Client.CHAT, event -> {
	String message = event.getPacket().getStrings().read(0);
	Player player = event.getPlayer();

	if (player.isConversing()) {
		player.acceptConversationInput(message);

		event.setCancelled(true);
	}
});

kangarko avatar Aug 21 '23 06:08 kangarko

For anyone interested in reproducing this on Folia here is the full Java code that makes Folia break:

Player player = getPlayer();

player.beginConversation(new Conversation(SimplePlugin.getInstance(), player, new ValidatingPrompt() {

	@Override
	public String getPromptText(ConversationContext context) {
		context.getForWhom().sendRawMessage("Dopice");

		return "Type anything";
	}

	@Override
	protected boolean isInputValid(ConversationContext context, String input) {
		return false;
	}

	@Override
	protected Prompt acceptValidatedInput(ConversationContext context, String input) {
		return Prompt.END_OF_CONVERSATION;
	}
}));

kangarko avatar Aug 21 '23 06:08 kangarko

No plans to resolve

@Spottedleaf excuse me? This is unacceptable given the large amount of plugins using the Conversation API. It breaks by merely blocking the input.

I was able to workaround this by using ProtocolLib. Here is a pseudocode:

this.addReceivingListener(PacketType.Play.Client.CHAT, event -> {
	String message = event.getPacket().getStrings().read(0);
	Player player = event.getPlayer();

	if (player.isConversing()) {
		player.acceptConversationInput(message);

		event.setCancelled(true);
	}
});

The conversation API will not be implemented at this time as I imagine there is higher priority stuff going on, and there's lots of API that doesn't work. If you know how to implement it, you are free to open a pull request.

Nacioszeczek avatar Aug 21 '23 07:08 Nacioszeczek

Thanks for clarification. I understand now. Sure, if I have time I'll try to make a pull request.

kangarko avatar Aug 21 '23 07:08 kangarko