Branching conversation¶
This example combines speakers, a conditional choice, a branch, an action, and a session result.
Register game handlers¶
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local MrDialogue = require(ReplicatedStorage.Packages.MrDialogue.Server)
local playerState = {}
MrDialogue.RegisterCondition("hasCoins", function(context, amount)
local state = playerState[context.player]
return state ~= nil and state.coins >= amount
end)
MrDialogue.RegisterCondition("alreadyBoughtMap", function(context)
local state = playerState[context.player]
return state ~= nil and state.hasMap
end)
MrDialogue.RegisterAction("buyMap", function(context, price)
local state = playerState[context.player]
if not state or state.coins < price then
return false, "not enough coins"
end
state.coins -= price
state.hasMap = true
return true
end)
Production games should connect these handlers to their authoritative profile or inventory system. The table above keeps the example focused on dialogue flow.
Define the merchant¶
local merchantDialogue = MrDialogue.new({
format = 1,
id = "merchant_map",
entry = "check_purchase",
speakers = {
merchant = {
name = "Map Merchant",
icon = "rbxassetid://123456789",
emotions = {
happy = "rbxassetid://234567891",
},
},
},
defaultSpeaker = "merchant",
freezeCharacter = true,
nodes = {
check_purchase = {
type = "branch",
condition = {
name = "alreadyBoughtMap",
},
onTrue = "already_owned",
onFalse = "offer",
},
already_owned = {
type = "line",
text = "Take good care of that map.",
next = "leave",
},
offer = {
type = "choice",
text = "A map of the valley costs 25 coins.",
options = {
{
id = "buy",
text = "Buy the map.",
next = "purchase",
condition = {
name = "hasCoins",
arguments = { 25 },
},
},
{
id = "ask",
text = "What is on the map?",
next = "explain",
},
{
id = "leave",
text = "Maybe later.",
next = "leave",
},
},
},
explain = {
type = "line",
text = "Every road, ruin, and shortcut I could find.",
next = "offer",
},
purchase = {
type = "action",
action = {
name = "buyMap",
arguments = { 25 },
},
next = "purchased",
},
purchased = {
type = "line",
text = "A fine purchase. May it keep you on the path.",
emotion = "happy",
next = "bought",
},
bought = {
type = "end",
result = "map_bought",
},
leave = {
type = "end",
result = "left",
},
},
})
The buy option is absent when the player has fewer than 25 coins. The ask path
loops back only after presenting a visible line, so it is not a silent resolution
cycle.
Start and inspect¶
script.Parent.Triggered:Connect(function(player)
playerState[player] = playerState[player] or {
coins = 50,
hasMap = false,
}
local session, startError = merchantDialogue:Start(player, {
npc = script.Parent:FindFirstAncestorOfClass("Model"),
})
if not session then
warn(startError)
return
end
local handled = false
local function handleOutcome(outcome)
if handled then
return
end
handled = true
if outcome and outcome.status == "completed" then
print(player.Name, outcome.result)
end
end
local connection = session.Ended:Connect(handleOutcome)
local immediateOutcome = session:GetOutcome()
if immediateOutcome then
connection:Disconnect()
handleOutcome(immediateOutcome)
end
end)
Connecting before reading GetOutcome() handles both normal and immediately terminal
sessions without polling or missing a race.