NPC Dialogue
Give mobs interactive, branching dialogue with Lua — dialogue pages, choice buttons, and the response table format.
Mobs can present players with an interactive dialogue window: a portrait, a page of text, and up to eight choice buttons. Two Lua hooks drive it — one that opens the conversation, and one that handles each button the player clicks. The branching structure of the conversation lives entirely in your Lua.
Declaring the Hooks
Both hooks are declared inside the mob’s entry in mob-config.md:
mobs:
- name: village_elder
# ...
scripts:
server-on-dialogue-open: /mobs/elder/dialogue_open.lua
server-on-dialogue-choice: /mobs/elder/dialogue_choice.lua
Each script file returns its function (see mob-config.md#scripts):
-- /mobs/elder/dialogue_open.lua
return function(player, npc)
-- ...
end
| Hook | When Called | Signature |
|---|---|---|
server-on-dialogue-open | Player interacts with the mob | function(player, npc) |
server-on-dialogue-choice | Player clicks a dialogue button | function(player, npc, choice_id) |
player and npc are entity objects (see lua-server-entity.md); choice_id is the id string of the button the player clicked.
The Response Table
Both hooks must return a table telling the engine what to show next.
Show a dialogue page:
return {
type = "page",
portrait = "Creation:elder_portrait", -- icon texture to show
text = "Welcome, traveller.",
buttons = {
{ id = "quest", label = "Any work for me?" },
{ id = "bye", label = "Goodbye." },
},
}
Close the dialogue window:
return { type = "close" }
Rules:
- Up to 8 buttons are shown; extras are dropped.
- A button needs a non-empty
id(what your choice hook receives) and alabel(what the player sees). Buttons without anidare dropped. portrait,text, andbuttonsare all optional — missing fields show as empty. Text is capped at 2048 bytes.- Any malformed return (or
typeother than"page") closes the dialogue rather than erroring.
Full Example — Branching Conversation
-- /mobs/elder/dialogue_open.lua
return function(player, npc)
return {
type = "page",
portrait = "Creation:elder_portrait",
text = "Welcome, traveller. The mines have gone quiet lately...",
buttons = {
{ id = "ask_mines", label = "What happened in the mines?" },
{ id = "heal", label = "Can you patch me up?" },
{ id = "bye", label = "Farewell." },
},
}
end
-- /mobs/elder/dialogue_choice.lua
return function(player, npc, choice_id)
if choice_id == "ask_mines" then
return {
type = "page",
portrait = "Creation:elder_portrait",
text = "Something old woke beneath level thirty. Bring me proof and I'll reward you.",
buttons = {
{ id = "bye", label = "I'll look into it." },
},
}
elseif choice_id == "heal" then
player:heal(50)
return {
type = "page",
portrait = "Creation:elder_portrait",
text = "There. Do be more careful.",
buttons = {
{ id = "bye", label = "Thank you." },
},
}
end
return { type = "close" }
end
Use the npc’s lua_data (or the player’s) to remember conversation state between pages — for example a quest flag set in one branch and checked when the dialogue is next opened.
See Also
- lua-server-entity.md — everything you can do with
playerandnpc - mob-config.md — mob YAML reference