search/
Start typing to search packages!
package_2
roblox-conversation
By @hurreman
Roblox
Mirrored from Wallyroblox-conversation
Data-driven, server-authoritative NPC dialogue: Slime Nebula's conversation framework, with Magic Neighborhood's tag binder and global text placeholders, as objects a game builds instead of module singletons.
# wally.toml
[dependencies]
Conversation = "hurreman/roblox-conversation@0.1.0"
How it fits together
- Definitions are plain data. Nodes hold Text, a list of random variants or
{Var}placeholders. Choices goNextto another node,Close, or run an opaqueAction, gated by an opaqueCondition/HideCondition, withHideorDisablevisibility and highlights. - The game registers what condition and action names mean. Conditions fail closed and actions are no-ops when missing; handler errors are caught.
- The server is the trust boundary. Clients only send
(conversationId, nodeId, choiceIndex), and the views they get never carry an Action or Condition. Every request is re-checked: an active session, the node they were shown, the choice exists, its gates still hold, and no request is already in progress. - The client controller never builds UI. It drives any Presenter (
Init,Open,ShowNode,SetChoices,Close).
-- server
local Conversation = require(ReplicatedStorage.Packages.Conversation)
local service = Conversation.Service.new({
send = function(player, action, payload) net:SendTo(player, "Conversation", action, payload) end,
globals = { EventEgg = function() return Seasons.eggName("Event") end }, -- {EventEgg} in any text
onStarted = function(player, id, source) progress:Fire("TalkTo", player, id) end,
})
service.Conditions:Register("HasCompletedQuest", function(player, args) return quests:Done(player, args.QuestId), "Finish the quest first" end)
service.Actions:Register("OpenVendor", function(player, args, ctx) vendors:Open(player, args.VendorId); ctx.Close("Vendor") end)
service:Listen({ choice = net:Function("ConversationChoice"), close = net:Event("ConversationClose") })
Conversation.Binder.new(service):Start(script.Definitions) -- id = module name; tag models "ConversationSource"
-- client
local client = Conversation.Client.new({
presenter = DialoguePresenter,
event = net:Event("Conversation"), choice = net:Function("ConversationChoice"), close = net:Event("ConversationClose"),
})
client:Start()
client:OnOpened(function(source) ... end) -- fires once per transition, like OnClosed
Binder attributes, set on the prompt or the model (the prompt's value wins):
ConversationId(required)ConversationDisplayNameConversationSourceKindConversationPortraitIdConversationMaxDistance
Moving the games over
- Slime Nebula:
ConversationService, its Registry, Session and both resolvers become aService.ConditionResolver.Register(...)calls keep working onservice.Conditions, with a dot or a colon.ConversationInteractionBinderbecomesBinder, or stays as the game's own binder callingservice:Start.ConversationClientControllerbecomesClient.Opened/ClosedbecomeOnOpened/OnClosed.- The
Integrations/handlers register on the service and are unchanged.
- Magic Neighborhood:
server/Conversation/*becomeServiceplusBinder. ItsGlobalVarsbecomeglobals, andDefinitions/passes toBinder:Start.- The client controller becomes
Client, with its ownDialoguePresenter.
Two things found in Slime Nebula's code
Both are fixed in Slime Nebula since it moved onto this package (2026-10-26).
- TalkTo progress never fires.
ConversationService:Startfires TalkTo progress throughServerScriptService.Progress.ProgressManager, but the file never declaresServerScriptService. The call errors inside itspcalland is silently swallowed. (Glorpo's prompt fires its own TalkTo, so that one still works.) The package replaces this with theonStartedhook. - Its Conversation spec fails on its own code. Commit
bf750bac(2026-08-28) added two highlight choices to the test definition without updating the visible-choice counts. Nothing runs the spec:RunAllTestshasENABLE_TESTS_IN_STUDIO = false, and the spec isn't in the Lune list.tests/specs/SlimeNebula.spec.luauruns the spec against the package with those counts corrected, and the choices looked up by Index. Every other assertion is as written.
Developing
lune run tests/run # specs on hurreman/luau-spec (DevPackages, else ../luau-spec)
The Lune specs cover:
- Slime Nebula's spec, corrected;
- view contents, placeholders and globals, onStarted, the Busy guard, ctx.Close, fail-closed conditions, registry validation, Listen and leaving;
- lifecycle closes: source destroyed or removed, character died, a replaced session;
- the client: presenter calls, choice round trips, one Closed per transition, closing past MaxDistance, rejected choices.
The binder hasn't been run: a ProximityPrompt only fires on real player input in Play mode.
Package Details
Install command (Click to copy)
Version
0.1.0
License
MIT
Safe for commercial use
Automated license review — not legal advice.
