Resource Spigot Conversation Guide

Discussion in 'Spigot Plugin Development' started by wytrem, Apr 30, 2020.


  1. [​IMG]

    Hello everyone!

    Few days ago, I came across the conversation API (located in org.bukkit.conversation), and I looked for a recent document about it. I didn't find any, so I read deeper the sources and docs and decided to write this thread! I hope it will be useful to some people. :)
    Do not hesitate to report me any inaccuracies you might notice or leave a comment if you liked it (or not :D)!

    Note: this is a work in progress tutorial, I'll continue writing as soon as possible. :)


    I) What is a conversation?
    A conversation is a spoken or written dialog between two people: they answer or react to what the other has just said. The Spigot Conversation API will allow us to engage a conversation between a player and our plugin, and that's great because the (almost) only way to retrieve text input from the client is to use chat messages.

    But, first, let's look at this example from a famous British playwright:

    BERNARDO
    Who's there?
    FRANCISCO
    Nay, answer me: stand, and unfold yourself.
    BERNARDO
    Long live the king!
    FRANCISCO
    Bernardo?
    BERNARDO
    He.
    FRANCISCO
    You come most carefully upon your hour.

    Each message leads the conversation partner to answer and this flow keeps going until the end of the conversation. It will be the same with the ConvAPI: while a player converses with the server, every message he sends will be answered by the server. A message sent by the player is called input, and the server answer is called a prompt.

    Now, imagine Bernardo is the nickname of our plugin and Francisco is a player. A very useful way to think about conversation is to draw a diagram. In the following, the rounded rectangles represent Bernardo's lines and the arrows represent Francisco answers (more technically, the player inputs):

    [​IMG]


    Note here that the first "message" is sent by Bernardo, our lovely plugin. This will always be the case using ConvAPI: the server sends the first prompt, and then the user sends some input in response. The first prompt is the entry point in the conversation.

    As you surely know, the mathematical object behind this diagram is called a graph, which happens to be oriented in that case. This graph concept is very useful in computing science, and for the ConvAPI, it will allow complex conversation trees to be implemented quite easily.

    Of course in a real life plugin, we don't want our server to answer fixed texts like "He." all the time ; we want it to parse the input, checks if it's valid, perform actions... such as this diagram (still a simple one though):

    [​IMG]
    See section IV for this diagram.

    This can be easily done with the ConvAPI, so let's do it!

    II) Building a Conversation using ConversationFactory
    Implementing a conversation is done by creating its graph. In other words, we have to create all the prompts our server will be sending and connect them so the player can be guided through the conversation.

    Let's start by implementing the very first diagram I showed you above: the conversation between Bernardo (our plugin) and Francisco.

    To create a prompt, we need to implement the org.bukkit.conversations.Prompt interface. You can do this by either creating a dedicated class or an anonymous class, I'll choose the second option for this tutorial.

    For this first part, we'll use the StringPrompt class (more on that later). It inherits two abstract methods from Prompt we need to implement:
    • String getPromptText(): Used to retrieve the text to send when this prompt is first presented (i.e. when the players reaches its node in the graph).
    • Prompt acceptInput(ConversationContext, String): this gets called when the player sends an answer to this prompt text. (more on the context later)
    Since we have to link a prompt with the next in the graph, we'd better start with the last one! Here we go:
    Code (Java):
    Prompt he = new StringPrompt() {
        @Override
        public String getPromptText(ConversationContext context) {
            return "He.";
        }

        @Override
        public Prompt acceptInput(ConversationContext context, String input) {
            return Prompt.END_OF_CONVERSATION;
        }
    };
    Here, we do nothing with the given input string, we just show Prompt.END_ON_CONVERSATION which indicates... the end of conversation. :eek:

    Now we can build the previous prompt and so on:
    Code (Java):
    Prompt longLiveTheKing = new StringPrompt() {
        @Override
        public String getPromptText(ConversationContext context) {
            return "Long live the king!";
        }

        @Override
        public Prompt acceptInput(ConversationContext context, String input) {
            // Here we connect the next prompt
            return he;
        }
    };

    Prompt whosThere = new StringPrompt() {
        @Override
        public String getPromptText(ConversationContext context) {
            return "Who's there?";
        }

        @Override
        public Prompt acceptInput(ConversationContext context, String input) {
            return longLiveTheKing;
        }
    };
     
    Now we created all the prompts, we can use the ConversationFactory to create our conversation:
    Code (Java):
    ConversationFactory factory = new ConversationFactory(plugin); // We need our plugin reference here
    factory.withFirstPrompt(whosThere);
    And let the conversation begin!
    Code (Java):
    Conversation conversation = factory.buildConversation(player);
    conversation.begin();
    Notes:
    • If you want to prevent the player inputs to appear in his chat, use factory.withLocalEcho(false)
    • If you want to prevent the player to receive any message from outside the conversation use factory.withModality(true).
    • A player can still use commands when conversing.
    • If a conversation is begun whilst another has not yet ended, it gets queued and actually begins only when the previous one terminates.

    III) Using ValidatingPrompt
    We've so far created a basic conversation using StringPrompt, but we did nothing with the player's input. In most use cases, the player input has to match a certain pattern and therefore must be somehow validated before it gets parsed. That's the purpose of ValidatingPrompt, which will keep replaying the prompt text until the user enters a valid response (I recommend you have a look at the code to understand how it works, it's very easy to read).

    IV) Using the ConversationContext to store and retrieve parsed inputs
    WIP

    V) Conclusion
    WIP
     
    #1 wytrem, Apr 30, 2020
    Last edited: May 3, 2020
    • Informative Informative x 7
    • Like Like x 2
    • Useful Useful x 1
  2. Seems like it will be useful, once you finish writing it.
     
    • Friendly Friendly x 1
  3. Will do my best to finish it soon ;)
     
  4. I've been using Spigot, Bukkit more than 7 year. And I have no idea what it is.
    Thank you for sharing :)
     
  5. The ConversationAPI is an amazing Prompt->GetInput addition to Bukkit that people never used. It has been under the radar for so long. I have used it numerous times and I am always amazed that people don't use this more. I am current writing a Zork like game using it and it makes it super easy to get input and the like.

    I hope your guide takes off. I would also recommend taking a look at the markup the person who developed it wrote to explain how to use it. Documentation Located on Google Drive i always reference this when im using the Coversation API
     
    • Like Like x 1