Can't implement callback functions into dialogue system

Heya, so I made a dialogue system using a tutorial and here’s the thing - I can’t figure out how to integrate callback functions.

MODULE // CODE
local Info = {}
local head = script.Parent.Head

Info.LeaveMessage = "..'Cya 'round, I guess."
Info.LeaveButtonText = "I must go."

local Callbacks = {
	killSomeone = function()
		print("Starting assassination quest.")
	end,
	birthdayWish = function()
		print("Happy Birthday event triggered!")
	end,
	luckyOne = function()
		print("Who is to die?")
	end
}

Info.Dialog = {
	[1] = {
		Text = "'Sup, need someone popped?",
		Choices = {
			[1] = {
				Text = "Err.. popped?",
				Next = 2
			},
			[2] = {
				Text = "Happy birthday.",
				Next = 3,
				Follow = false,
				Callback = Callbacks.birthdayWish
				
			},
		},
	};
	
	[2] = {
		Text = "Popped, killed, terminated, whatever you wanna call it. Whaddya need?",
		Choices = {
			[1] = {
			Text = "Kill someone.",
			Next = 4 ,
			Follow = false,
			},
			
		},
	};
	
	[3] = {
		Text = "..These words will have you end up in a grave some unfortunate day.",
		Choices = {},
	};
	
	[4] = {
		Text = "Great! Now, who's the lucky fella?",
		Choices = {},
	};
}

Info.CameraSpeed = 2
Info.CameraCFrame = CFrame.new(Vector3.new(-104.361, 75.317, 133.111), head.Position)

return Info

The above code is what stores the information (it’s located inside of the NPCs)

CLIENTSIDE HANDLER // CODE
repeat wait() until game:IsLoaded()

-------------------------
-- VARIABLES / SERVICES
--------------------------
local repStorage = game:GetService("ReplicatedStorage")
local players = game:GetService("Players")
local tweenService = game:GetService("TweenService")

-----------------------
-- VARIABLES / GUI
-----------------------
local gui = script.Parent


local main = gui.Main
local background = main.BG
local choiceFrame = main.ChoicesFrame
local nameFrame = main.NameFrame
local dialogFrame = main.DialogFrame

local nameLabel = nameFrame.Label
local dialogLabel = dialogFrame.Label
local choiceTemplate = script.ChoiceTemplate


--------------------------
-- VARIABLES / CONSTANTS
--------------------------
local event = repStorage.Events:FindFirstChild("TalkEvent")
local camera = workspace.CurrentCamera
local plr = players.LocalPlayer
local char = plr.Character or plr.CharacterAdded:Wait()

local currentNPC = nil
local storedChoices = {}

--------
-- CODE
--------



char:WaitForChild('Humanoid').Died:Connect(function()
	task.wait(0.5)
	if currentNPC then
		currentNPC.HumanoidRootPart.ProximityPrompt.Enabled = true
	end
	camera.CameraType = Enum.CameraType.Custom
	camera.CameraSubject = game.Players.LocalPlayer.Character			

	main:TweenPosition(UDim2.new(0,0,1,0))
	task.wait(1)
	main.Visible = false
	nameLabel.Text = ""
	dialogLabel.Text = ""
	storedChoices = {}

	currentNPC = nil
end)

-- // TYPEWRITER EFFECT

local function typeWrite(text, label, TypeDelay)
	label.Text = ""
	
	for _, letter in text:split("") do
		label.Text = label.Text..letter
		task.wait(TypeDelay)
	end
end

-- // END DIALOG

local function endDialog(info)
	for _, button in pairs(choiceFrame:GetChildren()) do
		if button:IsA("TextButton") then
			button:Destroy()
		end
	end
	
	typeWrite(info.LeaveMessage, dialogLabel, 0.05)
	
	task.wait(0.5)
	
	currentNPC.HumanoidRootPart.TalkPrompt.Enabled = true
	camera.CameraType = Enum.CameraType.Custom
	camera.CameraSubject = char
	
	main:TweenPosition(UDim2.new(0, 0,1.221, 0))
	task.wait(1)
	main.Visible = false
	nameLabel.Text = ""
	dialogLabel.Text = ""
	storedChoices = {}
	currentNPC = nil
end

-- // MOVE TO NEXT DIALOG

local function nextDialog(info, lastChoice)
	for _, button in pairs(choiceFrame:GetChildren()) do
		if button:IsA("TextButton") then
			button:Destroy()
		end
	end
	
	local dialog
	
	if lastChoice then
		if lastChoice.Next then
			dialog = info.Dialog[lastChoice.Next]
			if lastChoice.Callback then
				lastChoice.Callback()
			end
		else
			
		end
	else
		dialog = info.Dialog[1]
	end
	
	if not dialog then return end
	
	typeWrite(dialog.Text, dialogLabel, 0.05)
	
	if dialog.Choices and (#dialog.Choices > 0 or #storedChoices > 0) then
		for i, choice in pairs(storedChoices) do
			local clone = choiceTemplate:Clone()
			clone.Text = choice.Text
			clone.Name = choice.Text
			clone.Parent = choiceFrame
			
			clone.MouseButton1Click:Connect(function()
				storedChoices[i] = nil
				nextDialog(info, choice)
			end)
		end
		
		local leaveButton = choiceTemplate:Clone()
		leaveButton.Text = info.LeaveButtonText
		leaveButton.Name = "Leave"
		leaveButton.UIStroke.Color = Color3.fromRGB(136, 0, 0)
		leaveButton.BackgroundColor3 = Color3.fromRGB(83, 0, 0)
		leaveButton.TextColor3 = Color3.fromRGB(255, 0, 0)
		leaveButton.Parent = choiceFrame

		leaveButton.MouseButton1Click:Connect(function()
			endDialog(info)
		end)
		
		for i, choice in pairs(dialog.Choices) do
			local clone = choiceTemplate:Clone()
			clone.Text = choice.Text
			clone.Name = choice.Text
			clone.Parent = choiceFrame
			
			if choice.Follow == true then
				storedChoices[i] = choice
			end

			clone.MouseButton1Click:Connect(function()
				storedChoices[i] = nil
				if choice.Callback then
					print("Calling callback for choice:", choice.Text)  -- Debugging: Confirm callback exists
					choice.Callback()  -- Call the callback
				else
					print("No callback found for choice:", choice.Text)  -- Debugging: If no callback, print this
					print(choice.Next)
					print(choice.Follow)
				end
				nextDialog(info, choice)
			end)
			
			if dialog.DialogCallback then
				dialog.DialogCallback()
			end
		end
	else
		task.wait(1)
		
		if dialog.Next then
			nextDialog(info,dialog)
			
		else
			endDialog(info)
		end
	end
end

-- // MAIN CODE

event.OnClientEvent:Connect(function(npc, info, useCameraParams)
	if currentNPC == nil and npc then
		currentNPC = npc
		
		nameLabel.Text = npc.Name
		
		-- // Reset dialogue
		dialogLabel.Text = ""
		storedChoices = {}
		
		-- // Reset choices
		for _, button in pairs(choiceFrame:GetChildren()) do
			if button:IsA("TextButton") then
				button:Destroy()
			end
		end
		
	end
	
	npc.HumanoidRootPart.TalkPrompt.Enabled = false
	main.Visible = true
	
	if useCameraParams == true then
		camera.CameraType = Enum.CameraType.Scriptable
		
		local Tinfo = TweenInfo.new(info.CameraSpeed, Enum.EasingStyle.Cubic, Enum.EasingDirection.In, 0, false)
		local goal = {}
		goal.CFrame = info.CameraCFrame
		
		tweenService:Create(camera, Tinfo, goal):Play()
	end
	
	main.Position = UDim2.new(0, 0,1.221, 0)
	main:TweenPosition(UDim2.new(0, 0,0.561, 0), Enum.EasingDirection.In, Enum.EasingStyle.Sine, 1, true)
	task.wait(1)
	nextDialog(info, nil)
	
end)

That is the clientside code. I can’t seem to figure out how to implement the callbacks, as it keeps printing No callback found for choice. It’s been getting on my nerves quite a bit.

You can’t pass a function from server to client as a callback in this way. The ‘callback’ being sent via the remote event in the info table is a reference to a function which is on the server, so the client can’t actually see it let alone run it.

To get around this you could either:

If you want the callback to run on client.
Put the callbacks in a module in replicated storage, have the client require this module then use a string name (instead of the function reference) to find the correct call back and call it.

If you want it to run on server.
Fire a remote event (or remote function if you want a return value) to the server with the selection reference to find and run the right callback.

2 Likes
	[2] = {
		Text = "Ooh! A client. What is it that you need?",
		Choices = {
			[1] = {
			Text = "Kill someone.",
			Next = 4 ,
			Follow = true,
			Callback = function()
				print("Peak")
			end
			},
			
		},
	};

It won’t detect the callback though.

callback.OnServerInvoke = function(plr, info, choice)
	print("huh")
	print(choice.Text)
	if choice.Callback then
		choice.Callback()
	end
end

Server ^

Client:

clone.MouseButton1Click:Connect(function()
	storedChoices[i] = nil
	callbackEv:InvokeServer(info, choice)
	nextDialog(info, choice)
end)

You can’t pass functions through the client-server boundary

Then how am I meant to do this?

Why are you creating a remote function just to call the function? It seems redundant you can just call the function on the client

It does not even detect the callback function despite it being there.

As @Wigglyaa said, define the callback in a module script in a location accessible by both the client and server (ReplicatedStorage perhaps), and then pass the name of the callback to the client so that it can call the function in the module with that name.

If the callback function HAS to run on the server (for example, if its setting a leaderstat), create a remoteEvent which will activate said function when called, and pass in the name of the remoteEvent as the callback function instead. Then, using this name, find the remoteEvent and fire it when you want to call the callback function.

1 Like

So… require the callback module in the client handler script or something along the lines?

How do I link said callback function to the choices then??

yes

I believe module[name](arguments) should work

1 Like

Holy crap, I got it to work.
I added an additional value in the dialogue module called ‘Id’ (Names contain invalid characters) then checked it like this:

if callbacks[choice.Id] then
   callbacks[choice.Id]()
end

Thank you!

(i would make both replies as answers but i cant do that so i’ll just like them)

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.