Getting a function from a table in a module script

Heya!

I’m working on a simple dialogue system for a game.
Looks like this at the moment of writing this post:

RobloxStudioBeta_CHU732GAoV

In the code for the system, I made it so every NPC has a module script inside of them that has a table with all the dialogue options, messages and functions to execute.

The whole code is a bit complex so I’ll avoid going into too much detail to keep this question simple.

Here’s what one of the module scripts would look like:

local Info = {}

Info.LeaveMessage = "Alright. Goodbye then!"
Info.Dialogue = {
	[1] = {
		Text = "Hey, I need help collecting some more crops, could you do me a favor and get me 25 Wheat?";
		Choices = {
			[1] = { Text = "Sure!"; GoTo = 2 };
			[2] = { Text = "Sorry, I'm busy."; };
		};
	};
	
	[2] = {
		Text = "Thanks bud! Just collect some right here behind me!";
		Choices = {
			[1] = { Text = "Alright!"; GoTo = 3; Follow = true; };
			[2] = { Text = "Let's do this!"; GoTo = 4; Follow = true; };
		};
	};
	
	[3] = {
		Text = "Alright! Good luck!";
		Execute = function(Player)
			if Player then
				print(Player.Name.." is cool!")
			else
				print("Couldn't get Player, but they're still cool!")
			end
		end;
		Choices = {};
	};
	
	[4] = {
		Text = "I'll be here if you need me.";
		Execute = function(Player)
			if Player then
				print(Player.Name.." is cool!")
			else
				print("Couldn't get Player, but they're still cool!")
			end
		end;
		Choices = {};
	};
}

return Info

The “Execute” you can see inside some of the Messages of the Dialogue table is a function that would get executed when that Message is displayed.

To display the message, I use a LocalScript, here’s what the relevant part to the question looks like:

if Dialogue.Execute then -- Check if there's an Execute function inside the current Message
		Dialogue.Execute(Player) -- Execute the function
	end

For some reason, although there are functions in the Messages, the if statement never returns true.
I tried printing the Info.Dialogue list before executing the if statement, and for some reason, it looks like this:

{
                    [1] =   {
                       ["Choices"] =   {
                          [1] =   {
                             ["GoTo"] = 2,
                             ["Text"] = "Sure!"
                          },
                          [2] =   {
                             ["Text"] = "Sorry, I'm busy."
                          }
                       },
                       ["Text"] = "Hey, I need help collecting some more crops, could you do me a favor and get me 25 Wheat?"
                    },
                    [2] =   {
                       ["Choices"] =   {
                          [1] =   {
                             ["Follow"] = true,
                             ["GoTo"] = 3,
                             ["Text"] = "Alright!"
                          },
                          [2] =   {
                             ["Follow"] = true,
                             ["GoTo"] = 4,
                             ["Text"] = "Let's do this!"
                          }
                       },
                       ["Text"] = "Thanks bud! Just collect some right here behind me!"
                    },
                    [3] =   {
                       ["Choices"] = {},
                       ["Text"] = "Alright! Good luck!"
                    },
                    [4] =   {
                       ["Choices"] = {},
                       ["Text"] = "I'll be here if you need me."
                    }
                 }

The functions disappeared?

Weirdly enough, when I print the content of the same list but from inside the Module Script, it returns the list WITH the functions.

Also, although this weird error happens. [“Choices”] and [“Text”] are always returned correctly, so the Dialogue System still works (without executing the needed functions though…).

Is this the expected Luau behavior? If so what work-arounds should I use?
Otherwise, where did I mess up?
I’ve looked on the forums and documentations and got nowhere.

Any and all answers are very welcomed!
Thanks in advance Developers!

I believe the problem is occurring when you check for the Execute function. You’re running Dialogue.Execute(Player), however there is no Execute inside of Info.Dialogue. Execute is inside of each Dialogue ([1], [2], [3]…). So, you should be using the number when calling execute Dialogue[3].Execute(Player).

I don’t know the correct lingo to these terms but I hope this was helpful.

maybe try moving the execute functions to part of the module script? Could be that require() will only export functions directly under the module.

function Info.player_exiting_getter(player: Player)
	if Player then
		print(Player.Name.." is cool!")
	else
		print("Couldn't get Player, but they're still cool!")
	end
end

-- ...

[4] = {
	Text = "I'll be here if you need me.";
	Execute = Info.player_exiting_getter;
	Choices = {}
};

Sorry, the variable names are repeated and that makes the code and question just the more confusing, even to me.
I forgot to add that, before executing the if statement in the Local Script, I set the Dialogue variable as Info.Dialogue[Index],
which makes the local Dialogue variable [1] for example.
So the Dialogue variable would look like this during the if statement:

{
                       ["Choices"] =   {
                          [1] =   {
                             ["GoTo"] = 2,
                             ["Text"] = "Sure!"
                          },
                          [2] =   {
                             ["Text"] = "Sorry, I'm busy."
                          }
                       },
                       ["Text"] = "Hey, I need help collecting some more crops, could you do me a favor and get me 25 Wheat?"
}

That sounded like a really good idea, but sadly, after testing the result was the same.
It still returns all Messages without ‘Execute’, and the function wasn’t executed :frowning:

Did you mistakenly change Dialogue.Execute in the script before calling it?

I can’t find any errors when I test it, wish I could help. :confused:

I took your original ModuleScript above and created a LocalScript with the following code it works correctly. Maybe you can notice how you set up your Dialogue variable and see if it is done the same.

local rs = game:GetService("ReplicatedStorage")
local Info = require(rs:WaitForChild("Info"))

for i, v in pairs(Info.Dialogue) do
	if Info.Dialogue[i].Execute then
		print(Info.Dialogue[i].Execute(game:GetService("Players").quakage))
	end
end

Would giving the whole Local Script help? Sorry for the spaghetti code btw. It’s pretty long, sorry about that, but here it is:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Event = ReplicatedStorage.RemoteEvents:WaitForChild("Dialogue")

local Player = game:GetService("Players").LocalPlayer
local DialogueGUI = Player.PlayerGui.Dialogue
local Camera = workspace.CurrentCamera
local Main = script.Parent.Main
local ChoicesFrame = Main.Choices
local NameLabel = Main.NameLabel.Label
local DialogueLabel = Main.Message
local ChoiceTemplate = script.Choice

local CurrentNPC = nil
local StoredChoices = {}


local function Typewrite(Text, Label)
	Label.Text = ""
	
	for i, v in Text:split("") do
		Label.Text = Label.Text..v
		task.wait(.01)
	end
	return true
end


local function End(Info, ShowLeaveMessage, Dialogue)
	for i, child in pairs(ChoicesFrame:GetChildren()) do
		if child:IsA("TextButton") then
			child:Destroy()
		end
	end
	-- Handle Leave Message
	if not ShowLeaveMessage and Dialogue then
		Typewrite(Dialogue.Text, DialogueLabel)
	else
		Typewrite(Info.LeaveMessage, DialogueLabel)
	end
	
	task.wait(.25)
	DialogueGUI.Enabled = false
	CurrentNPC.HumanoidRootPart.ProximityPrompt.Enabled = true
	CurrentNPC = nil
	Camera.CameraType = Enum.CameraType.Custom
end


local function NextDialogue(Info, LastChoice)
	for i, child in pairs(ChoicesFrame:GetChildren()) do
		if child:IsA("TextButton") then
			child:Destroy()
		end
	end
	
	local Dialogue
	local DontNext = false
	if LastChoice then
		if LastChoice.GoTo then
			Dialogue = Info.Dialogue[LastChoice.GoTo]
		else
			End(Info, false, Dialogue)
			return
		end
	else
		Dialogue = Info.Dialogue[1] -- Read index 1 if player just started talking to NPC
	end
	
	print(Dialogue)
	print(Info)
	if Dialogue.Execute then -- Handle Functions
		Dialogue.Execute(Player)
	end
	
	if not Dialogue then return end
	
	if Dialogue.Choices and (#Dialogue.Choices > 0 or #StoredChoices > 0) then
		
		local done = Typewrite(Dialogue.Text, DialogueLabel) -- Write Current Dialogue
		while not done do
			task.wait(.01)
		end -- Wait for Typewriting to be done to show buttons (avoids A TON of bugs)
		task.wait(.1)
		
		for i, Choice in pairs(StoredChoices) do
			local Clone = ChoiceTemplate:Clone()
			Clone.Parent = ChoicesFrame
			Clone.Name = Choice.Text
			Clone.Text = Choice.Text
			
			Clone.MouseButton1Click:Connect(function()
				StoredChoices[i] = nil
				NextDialogue(Info, Choice)
			end)
		end
		
		for i, Choice in pairs(Dialogue.Choices) do
			local Clone = ChoiceTemplate:Clone()
			Clone.Parent = ChoicesFrame
			Clone.Name = Choice.Text
			Clone.Text = Choice.Text
			
			if Choice.Follow then
				StoredChoices[i] = Choice
			end

			Clone.MouseButton1Click:Connect(function()
				StoredChoices[i] = nil
				NextDialogue(Info, Choice)
			end)
		end
		
		local LeaveButton = ChoiceTemplate:Clone()
		LeaveButton.Parent = ChoicesFrame
		LeaveButton.Name = "Leave"
		LeaveButton.Text = "Exit"
		LeaveButton.MouseButton1Click:Connect(function()
			End(Info, true)
		end)
		
	else
		task.wait(1)
		
		if Dialogue.GoTo then
			NextDialogue(Info, Dialogue)
		else
			End(Info, false, Dialogue)
		end
	end
end


Event.OnClientEvent:Connect(function(NPC, Info)
	if CurrentNPC == nil and NPC then
		CurrentNPC = NPC
		NameLabel.Text = NPC.Name
		DialogueLabel.Text = ""
		StoredChoices = {}
		

		for i, child in pairs(ChoicesFrame:GetChildren()) do
			if child:IsA("TextButton") then
				child:Destroy()
			end
		end
		
		Camera.CameraType = Enum.CameraType.Scriptable
		DialogueGUI.Enabled = true
		CurrentNPC.HumanoidRootPart.ProximityPrompt.Enabled = false
		NextDialogue(Info, nil)
	end
end)

Also, another dumb mistake from me, but I forgot to mention this script only executes, as you can see in

Event.OnClientEvent:Connect(function(NPC, Info)

when the server fires the client, here’s the server script that does that:

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Event = ReplicatedStorage.RemoteEvents:WaitForChild("Dialogue")

for i, v in pairs(workspace.NPCFolder:GetChildren()) do
	if v:IsA("Model") and v:FindFirstChild("Humanoid") then
		local HumanoidRootPart = v:WaitForChild("HumanoidRootPart")
		local Module = require(v.Dialogue)
		
		local Prompt = Instance.new("ProximityPrompt", HumanoidRootPart)
		Prompt.ActionText = "Talk"
		Prompt.ObjectText = v.Name
		Prompt.RequiresLineOfSight = false
		
		Prompt.Triggered:Connect(function(Player)
			print("Tried to fire client!")
			Event:FireClient(Player, v, Module)
		end)
	end
end

From what I can see, please correct me if I am wrong, dialogue variable only has 2 options:

local function NextDialogue(Info, LastChoice)

local Dialogue
-- Only 2 options:
-- Dialogue = Info.Dialogue[LastChoice.GoTo] (line 60)
-- or
-- Dialogue = Info.Dialogue[1] (line 66)

if Dialogue.Execute then -- Dialogue.Execute never will exsist unless the Dialogue variable is changed to one that includes it
		Dialogue.Execute(Player)
	end

Both of these options do not include the Execute function. When do you use a dialogue containing an Execute function?

For example, [3] has an Execute inside it:

[3] = {
		Text = "Alright! Good luck!";
		Execute = function(Player)
			if Player then
				print(Player.Name.." is cool!")
			else
				print("Couldn't get Player, but they're still cool!")
			end
		end;
		Choices = {};
	};

So yeah, just before the if statement runs, Dialogue is set to one of the Messages inside Info.Dialogue, as you can see here:

local Dialogue
	if LastChoice then
		if LastChoice.GoTo then
			Dialogue = Info.Dialogue[LastChoice.GoTo]
		else
			End(Info, false, Dialogue)
			return
		end
	else
		Dialogue = Info.Dialogue[1] -- Read index 1 if player just started talking to NPC
	end

In the case Dialogue is, for instance, Info.Dialogue[3], please correct me if I’m wrong, the if statement would return true, right?

Edit:
To answer your question, sometimes Dialogue can have only Text and Choices, but other times it has the Execute function.

LastChoice.GoTo is the index of the next Message to show after the player answered to the previous Message.

For example, if the player answers with “Alright!”, the script will show Info.Dialogue[LastChoice.Goto], in other words Info.Dialogue[3].

Choices = {
			[1] = { Text = "Alright!"; GoTo = 3; Follow = true; };
[...]

If the Dialogue variable is set to Info.Dialogue[3] then the if statement should execute.

local Dialogue
if LastChoice then
		if LastChoice.GoTo then
			Dialogue = Info.Dialogue[LastChoice.GoTo]
		else
			End(Info, false, Dialogue)
			return
		end
	else
		Dialogue = Info.Dialogue[3] -- Let's just say for this example you set it to Dialogue[3]
	end

if Dialogue.Execute then -- Because Dialogue is Info.Dialogue[3], it has Execute in it
		Dialogue.Execute(Player) -- This should then work
	end

Exactly!

It’s a mystery to me why it doesn’t. I’ve read through my code a few times already and it doesn’t seem to be me changing the list in any way.

My only guess is maybe when the server sends the Module to the client it removes the functions or something?

Event:FireClient(Player, v, Module)

Again though, I also looked in the forums and no one has talked about this before.
Either way, thank you very much for the help, I’ll keep trying to look for solutions :slight_smile:

My mistake! I didn’t realize that LastChoice.GoTo changes dialogue to the next number. The only problem that could be causing this is maybe the server cannot send a function to the client?

That seems to be the problem for me. I didn’t try sending the function from the server to the client until now. I get other results of the Dialogue list (returns “Alright! Good luck!”) like you, however, execute is nil on the client.

1 Like

A work around for this that I can think of is to just add a true/false variable to the list and send it back to the server.

module.Dialogue = {
	[1] = {};
	[2] = {};
	[3] = {
		Text = "Alright! Good luck!";
		CanExecute = true; -- Client checks if there is an execute
		Execute = function(Player)
			if Player then
				print(Player.Name.." is cool!")
			else
				print("Couldn't get Player, but they're still cool!")
			end
		end;
		Choices = {};
	};
}	

end

Alternatively you could have all the executions in a module handled by the client (under the localscript probably)

--- LocalScript
if (Dialogue.CanExecute) then -- Handle Functions
	-- Execute:FireServer(Dialogue, Player)
    -- or
    -- ExecuteModuleScript:Executes(GoTo, Player)
end



-- ModuleScript for Executions
function module:Executes(GoTo, Player)
module.Choices[GoTo].Execute(Player)
end

module.Choices = {
	[1] = {};
	[2] = {};
	[3] = {
		Execute = function(Player)
			if Player then
				print(Player.Name.." is cool!")
			else
				print("Couldn't get Player, but they're still cool!")
			end
		end;
	};
}	

But, I’m sure you want the server to handle it. Just send it back to the server.

2 Likes

Thank you so much! It finally works!
I implemented your solution like this:

[Server Side]

ExecuteFunctionFromTable.OnServerEvent:Connect(function(Player, NPC, Index)
	local mod = require(NPC.Dialogue)
	print("[Server] Calling function with ".. Index .. " value.")
	mod.Dialogue[Index].Function(Player)
end)

Edit: (Adding module changes as well)
[Module]

[...]
[3] = {
		Text = "Alright! Good luck!";
		Execute = true;
		Function = function(Player)
			if Player then
				print(Player.Name.." is SUPER COOL!")
			else
				print("Couldn't get Player, but they're still super cool!")
			end
		end;
		Choices = {};
	};
[...]

[Client Side]

if Dialogue.Execute == true then -- Send function request to server
		ExecuteFunctionFromTable:FireServer(CurrentNPC, table.find(Info.Dialogue, Dialogue))
	end

On another note, I read up a bit on the matter, and yeah, in fact it’s not possible to send functions from the server to the client. I’ll have to keep this in mind for the future.

Anyways, thanks to everyone for helping with this problem, and thanks for the solution! :slight_smile:

3 Likes

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