Conversations
A Conversation is a scripted exchange played to one player: lines in the chat, pauses, choices and any command, in the order you write them. The progress is stored on each player, so ten players talk to the same NPC at the same time without sharing a timer, a line or a choice.
The steps are compiled into functions when the conversation(name) { } block returns, so they only count when declared inside it. A name is unique in the pack: declaring greet twice throws Conversation 'greet' is already declared in this pack.
- Objectives:
<pack>.conversation.<name>(dummy, the step of each player),<pack>.conversation.<name>.wait(dummy, only withwaitorwaitUntil) and<pack>.conversation.<name>.choice(trigger, only with achoice). - Storage: none.
- Entities: none.
- Forceloaded chunks: none.
- Every tick: nothing while nobody is waiting, the tick function is not in
#minecraft:tickbut the reward of the advancement, so a player waiting on a timer or a choice costs about 4 commands per tick. A conversation made of lines andwaitForonly has no tick at all. - Load and files:
conversation_<name>_loadcreates the objectives. One advancementconversation/<name>firesconversation_<name>_tickfor the player, the other functions are one per segment,_dispatch(only with a timed pause),_show_<n>and_reenable(only with achoice),_resumeand_advance(only whenresume()oradvance()is called).
Steps
| Step | What it does |
|---|---|
line(text) |
Says text in the chat, as <Bob> text when a speaker is set |
show(dialog) |
Shows a dialog to the player and goes on right away |
step { } |
Runs any commands as the player |
wait(duration) |
Pauses for a duration, at least one tick |
waitFor() |
Pauses until advance() or resume() runs for the player |
waitUntil { condition } |
Pauses until an execute condition holds, checked every tick |
choice(title) { } |
Pauses until the player picks an option, in a dialog or in the chat |
line(text, color) colors the text, a ChatComponents works too. speaker applies to the lines declared after it.
Inside step { } and waitUntil { } the conversation builder isn't reachable, step { line("x") } doesn't compile: the block only holds commands, and the conversation steps go beside it.
Pauses
wait(duration) truncates to whole ticks and refuses less than one: wait(0.5.seconds) stores 10, wait(0.ticks) throws.
The countdown loses one point per tick of the player, and the next step runs on its last point: wait(2.seconds) runs it 40 ticks after the previous step, or 39 when the advancement already fires again in the tick that set the countdown, depending on where the previous step ran in the game tick. It is never later than 40. waitUntil runs its condition every tick, the condition is the first line of the steps after it, the miner conversation above gives:
waitFor() costs nothing while waiting, there is no advancement and no tick: the conversation goes on when a function calls advance() or resume(), typically from the click on an NPC.
Choices
choice(title) { } pauses on options, option(label) { } declares what runs when one is picked. After the steps of the picked option, the conversation goes on with the steps after the choice. Options can hold any step, even another choice.
A choice has two styles, both setting the same trigger objective, trigger <pack>.conversation.<name>.choice set <n>, which any player can run, so the rest of the mechanism is shared. The options are numbered in order across the whole conversation, each choice's options together: two choices of two options give 1 and 2 to the first and 3 and 4 to the second, and a choice nested in an option comes after the choice holding it. So an old chat option scrolled up can't pick an option of a later choice. The game disables a trigger after each use, so when such a stale option is clicked, the tick function re-enables the trigger and sets it back to 0 (conversation_<name>_reenable), and the options of the pending choice keep working.
| Style | Shows |
|---|---|
ChoiceStyle.DIALOG (default) |
A dialog with one button per option |
ChoiceStyle.CHAT |
Clickable options in the chat, [Yes] [No] |
choiceStyle sets the style of the choices declared after it, and a choice gives its own as second argument:
Chat choices
The options are one tellraw line with a click event, after the title said by the speaker. hover is the tooltip of an option:
In game: the chat choice of the help conversation, hover Accept to see its tooltip
ChatChoiceFormat customizes the look. It is set for the conversation with chatFormat, and overridden for one choice with chat { }:
| Field | Default | Effect |
|---|---|---|
decorate |
the label in brackets of its color | Wraps the label, the result is what is clickable and carries the hover text |
prompt |
true |
Says the title of the choice first, as a line from the speaker |
separator |
two spaces | Written between options on a line, null writes one option per line |
option(label: ChatComponents, hover: ChatComponents?) takes full components when the label needs more than a color.
Dialog choices
The options are the buttons of a multi_action dialog, generated at data/<pack>/dialog/conversation_<name>_choice_<n>.json. hover is the tooltip of a button. dialog { } tweaks the generated MultiAction once its title and buttons are set: body, columns, canCloseWithEscape, the width of each actions...
In game: the dialog choice of the help conversation, the buttons wear their option's color
Closing the dialog with Escape leaves the choice pending, and advance() or resume() shows it again. With canCloseWithEscape = false the player has to pick.
Start, advance, cancel and resume
These run as the player the conversation is played to, @s.
| Call | Does |
|---|---|
start() |
Starts the conversation unless it is already running |
advance() |
Starts it when idle, continues a waitFor, shows a pending choice again, nothing during a timed pause |
resume() |
Continues a waitFor or shows a pending choice again, nothing in any other state |
cancel() |
Resets the scores, the conversation is idle again |
advance() is made for clicks: a player clicking an NPC twice during a timed pause doesn't skip it, and a player who closed a choice gets it back. cancel() leaves an open choice dialog on screen, its buttons no longer do anything.
start(), advance() and resume() can't be called from the steps of the conversation itself, a step { } runs while the conversation compiles and they throw Conversation 'greet' is used before its declaration block returned.. Call them from a function or a click callback declared after the conversation.
Players and state
Running a conversation as a non-player isn't possible because its timers run on the advancements of the player. Each player owns their state in the scores of the three objectives above, nothing else is stored, and none of the generated functions uses a selector other than @s.
A player who logs out during a pause stops its countdown, since only the tick of the player advances it, and the conversation goes on when they join again. An advancement fires for a player who just joined once, then the tick function ends with return fail and the advancement stays granted, costing nothing.
A Quest Giver
Without a helper, a quest giver is a mannequin, an interaction, a click advancement, a score per player for the quest, a timer per player for the lines, a trigger objective for the choice and a function per branch. With Kore it is one declaration: the first click plays three timed lines then a choice in the chat, accepting gives a pickaxe and remembers it for that player, and the next click says something else.
Played as the game runs it, the first click writes the two lines two seconds apart and asks the choice, accepting gives the pickaxe, and every later click gets the reminder line instead.
The conversation is declared inside the npc { } block to share the quest objective of the NPC. The choice of that conversation compiles to:
The state of each player lives in two places: the step of the conversation in my_pack.conversation.quest_intro and the quest in my_pack.npc.quest_giver.quest, both dummy scores held by the player. A click while the lines are playing does nothing, a click while the choice is pending writes it again. While nobody talks, nothing runs every tick.
The same choice as a dialog is the same declaration without choiceStyle, with the buttons and an optional tweak:
