Dialogues are a feature that can be used from API or from datapacks to play conversations in a GUI. They display entities, view pages of text, and handle various types of input.
A dialogue that has been registered through a datapack can be opened using the /opendialogue command.
Every case of a MoLang expression can be either a single text property or can be an array of text to support running multiple expressions to produce a result.
Dialogue
A Dialogue JSON file consists of the following main elements:
- escapeAction
: A string or array that defines the action to be taken when the dialogue is escaped. This can be a MoLang expression or a sequence of MoLang expressions.
- speakers
: An object that defines the speakers in the dialogue. Each speaker is represented by a key-value pair, where the key is the speaker's identifier and the value is an object that describes what the speaker looks like and their name.
- pages
: An array of objects, each representing a page in the dialogue.
Speakers
Each speaker is represented by an object with the following properties:
- name
: A string or object that defines the speaker's name. This is usually just text. If it's an object, it must have a type
property set to "expression"
and an expression
property that contains a MoLang expression. You would use an expression if you wanted the name to be dynamic.
- face
: An object or MoLang expression that defines the speaker's face. This will be rendered in a frame of the GUI. For objects, it must have a type
property set to the type of face. The only object type support currently is "standard". The others are done using MoLang expressions rather than an object.
Faces
The speaker faces can be defined in multiple ways. If it is an object it is going to be a "standard" face type.
"standard"
The standard face type is for when the face displayed is not connected to a specific entity in the world but does follow Cobblemon's format of identifier and aspect list. The object must have the following properties:
- modelType
: One of 'pokemon', 'npc'
- identifier
: The identifier of the Pokémon or NPC, such as cobblemon:bulbasaur
.
- aspects
: An array of aspects that will be applied to the model.
For example:
"face": { "type": "artificial", "modelType": "pokemon", "identifier": "cobblemon:pikachu", "aspects": [ "shiny" ] }
player
By using q.player.face()
you can make the player's model render for this face.
For example:
"face": "q.player.face();"
npc
By using q.npc.face()
you can show a direct reference to the NPC you're having the dialogue with, if this was dialogue started with an NPC entity.
For example:
"face": "q.npc.face();"
Pages
Each page is represented by an object with the following properties:
- id
: A string that uniquely identifies the page. This is needed for instances where you might want to jump to a specific page. Page numbers could be used, but you should use page IDs.
- speaker
: A string that specifies the speaker's identifier. This depends upon the speaker by this identifier existing in the speakers list.
- lines
: An array of strings or objects, each representing a line of text. If it's an object, it must have a type
property set to "expression"
and an expression
property that contains a MoLang expression that evaluates to a string. You would use an expression if you want to include variables to make dynamic text.
- input
: An object or string that defines the input prompt. If it's an object, it must have a type
property set to "option"
, "text"
, "auto-continue"
, or "none"
, and other properties depending on the type. By default, the input value will be none
.
- escapeAction
: A string or array that defines the action to be taken when the page is escaped. This can be a MoLang expression or a sequence of MoLang expressions.
Input Types
The input
property in a page object can have different structures depending on the type
.
For all inputs that specify an action
property which is set to a MoLang expression, you can access the time it took for them to make that input using v.seconds_taken_to_input
.
"option"
The input is a set of options. The object must have an options
property that is an array of objects, each representing an option. It can also specify whether the options should be displayed vertically or horizontally by whether or not the vertical
property is true or false.
Each option object can have the following properties:
- value
: The unique value of the dialogue option. This is not displayed to the player and is instead used to reference this option from scripts.
- text
The display text for the option. This can be a simple string or can be an object
- action
: The MoLang expression to run when the user chooses the option.
- isVisible
: A MoLang expression which must return either true or false. If false, the option will not be visible for the player. Defaults to true.
- isSelectable
: A MoLang expression which must return either true or false. If false, the option will be greyed out (but only if it is visible). Defaults to true.
"text"
The input is a text field. The action
property is a MoLang expression that will run when the player hits enter with text typed in. As specified earlier, you can check the text that they input using v.selected_option
inside of the MoLang expression.
"auto-continue"
The dialogue will automatically progress after some period of time. There are several optional properties for this.
- delay
: The delay in seconds until it progresses. By default this is 5 seconds.
- allowSkip
: Whether the user is able to skip the delay by clicking. By default this is true. If false, the player will be forced to wait for the delay.
- showTimer
: Whether a bar will be displayed to indicate how much more time the player has until it auto-continues. Defaults to false.
- action
: A MoLang expression that runs when either the player clicks (only when allowSkip
is true) or the delay finishes. By default this moves to the next page.
"none"
There is no input except that it waits until the user clicks to continue. The object can have an action
property that defines the MoLang expression to run when the player clicks. By default, it moves to the next page.
Timeouts
All input types except for "none"
and "autocontinue"
can have a 'timeout' object which allows you to apply time constraints to the player viewing the page.
The timeout object can have 3 properties:
- duration
: The time, in seconds, that the player will have to complete the input. By default this is 10 seconds.
- showTimer
: Whether or not a bar will be shown to show the player how much time they have left. By default this is true.
- action
: A MoLang expression to run if they do not enter the input in time. By default this will close the dialogue.
Example
The following example is built-in to the mod, labelled "example".
{ "escapeAction": "q.dialogue.set_page('quitter')", "speakers": { "pikachu": { "name": "The Voice of Hiro", "face": { "type": "artificial", "modelType": "pokemon", "identifier": "cobblemon:pikachu", "aspects": [ "shiny" ] } }, "player": { "face": "q.player.face();", "name": { "type": "expression", "expression": "q.player.username" } } }, "pages": [ { "id": "player-chat", "speaker": "player", "lines": [ { "type": "expression", "expression": "'Hello, I\\'m ' + q.player.username + '!'" } ] }, { "speaker": "pikachu", "lines": [ { "type": "expression", "expression": "'Nice to meet you, ' + q.player.username + '! Welcome to the world of dialogues!'" } ] }, { "id": "intro", "speaker": "pikachu", "lines": [ "Do you want to learn more?"], "input": { "type": "option", "vertical": false, "options": [ { "text": "Yes", "value": "yes", "action": [ "v.player_response = 'That sounds great!';", "v.next_page = 'page1';", "q.dialogue.set_page('player-surrogate');" ] }, { "text": "No", "value": "no", "action": [ "v.player_response = 'No, I really don\\'t care.';", "v.next_page = 'quitter';", "q.dialogue.set_page('player-surrogate');" ] }, { "text": "Sword!", "value": "sword", "isVisible": "q.player.main_held_item.is_of('minecraft:iron_sword')", "isSelectable": "q.player.data.scared_npc != true;", "action": [ "v.player_response = 'How about you die! Muahahaha!';", "v.next_page = 'sword';", "q.dialogue.set_page('player-surrogate');" ] }, { "text": "Sword again!", "value": "sword2", "isVisible": "q.player.main_held_item.is_of('minecraft:iron_sword')", "isSelectable": "q.player.data.scared_npc == true;", "action": [ "v.player_response = 'How about you die... again! Muahahaha!';", "v.next_page = 'sword-again';", "q.dialogue.set_page('player-surrogate');" ] } ] } }, { "id": "player-surrogate", "speaker": "player", "lines": [ { "type": "expression", "expression": "v.player_response;" } ], "input": "q.dialogue.set_page(v.next_page);" }, { "id": "page1", "speaker": "pikachu", "lines": [ "Well you see, this is a dialogue.", "You can have multiple pages, and each page can have multiple lines." ] }, { "id": "page2", "speaker": "pikachu", "lines": ["On an unrelated note, did you know that I'm deeply afraid of iron swords? Anyway, cya!"], "input": { "type": "auto-continue", "delay": 5, "allowSkip": true, "action": "q.dialogue.close();" } }, { "id": "quitter", "speaker": "pikachu", "lines": ["Fine. Be that way."], "input": "q.dialogue.close();", "escapeAction": "q.dialogue.close();" }, { "id": "sword", "speaker": "pikachu", "lines": ["Oh god no, please don't hurt me!"], "escapeAction": "q.dialogue.input();", "input": [ "t.data = q.player.data();", "t.data.scared_npc = true;", "q.player.save_data();", "q.dialogue.close();" ] }, { "id": "sword-again", "speaker": "pikachu", "lines": ["I'm ready for you this time. YOU will die today!"], "escapeAction": "0", "input": { "type": "option", "vertical": true, "timeout": { "delay": 6, "action": "q.dialogue.input('stand');" }, "options": [ { "text": "Parry his attack.", "value": "parry", "action": [ "v.reaction = 'parry';", "q.dialogue.set_page('sword-again-decided');" ] }, { "text": "Stand perfectly still.", "value": "stand", "action": [ "v.reaction = 'stand still';", "q.dialogue.set_page('sword-again-decided');" ] }, { "text": "Drop your sword.", "value": "drop", "action": [ "v.reaction = 'drop your sword';", "q.dialogue.set_page('sword-again-decided');" ] } ] } }, { "id": "sword-again-decided", "lines": [ { "type": "expression", "expression": "v.reaction == 'parry' ? 'You parry his attack and kill him.' : (v.reaction == 'stand still' ? 'He kills you.' : 'You drop your sword and he kills you.')" } ], "input": { "type": "auto-continue", "delay": 3, "allowSkip": false, "showTimer": false, "action": "q.dialogue.set_page('sword-again-reaction');" } }, { "id": "sword-again-reaction", "speaker": "pikachu", "lines": [ { "type": "expression", "expression": "'Nah, I\\'m just kidding. Interesting that you chose to ' + v.reaction + ' though.'" } ] }, { "id": "name-question", "speaker": "pikachu", "lines": [ "Here's a puzzle though... What is my name? Lowercase only."], "input": { "type": "text", "timeout": { "delay": 10, "action": "q.dialogue.set_page('too-slow');" }, "action": [ "q.dialogue.set_page('name-speak');", "t.data = q.player.data();" ] } }, { "id": "name-speak", "speaker": "player", "lines": [ { "type": "expression", "expression": "'Your name is ' + v.selected_option + ', right?'" } ], "input": "q.dialogue.set_page('name-guess');" }, { "id": "name-guess", "speaker": "pikachu", "lines": [ { "type": "expression", "expression": "v.selected_option == 'hiroku' ? 'Correct! You know things that you shouldn\\'t...' : ('Wrong! I\\'m not \"' + v.selected_option + '\". Whoever that is.')" } ], "input": { "type": "auto-continue", "delay": 5, "allowSkip": true, "action": "q.dialogue.set_page('farewell');" } }, { "id": "farewell", "speaker": "pikachu", "lines": ["Anyway, I'm done with this dialogue. Bye!"], "input": "q.dialogue.close();" }, { "id": "too-slow", "speaker": "pikachu", "lines": ["Don't know? Sad. Well I don't know your name either, so I guess we're even."], "input": "q.dialogue.close()" } ] }