Unable to assign property Text. string expected, got nil

Hello, developers! I currently in the make of a dialogue system, but seem to have encountered any error every once in a while.

Unable to assign property Text. string expected, got nil - Line 82

I’ve checked through all of my code and can’t seem to find the problem with my code.

--//variables
local ReplicatedStorage = game:GetService("ReplicatedStorage")

local RemoteEvents = ReplicatedStorage:WaitForChild("RemoteEvents")

local Frame = script.Parent.Frame
local ChoicesFrame = script.Parent.choices.Frame

local ChatStarted = false
local canContinue = true

local currentChatIndex = 0 --//this is used for which number of messages we're at
local choiceIndex = 0 --//this is used for which index choice the player has chosen
local maxChoices = 0 --//this is used to determine how many choices the player is given (disregarding the "Goodbye!" choice)
local oldChoiceIndex = 0 --//this is used to get the old choice index value

local choiceSelected
local choices
local responses
local currentChoice

local typewriteMod = require(ReplicatedStorage:WaitForChild("typewrite"))

--//functions
local function getTableItems(array)
	local totalItems = 0 --//total items in array

	for _, element in ipairs(array) do
		if typeof(element) == "table" then
			totalItems += getTableItems(element)
		else
			totalItems += 1
		end
	end

	return totalItems
end

local function tweenChoicesFrame(open: boolean)
	if open == true then
		ChoicesFrame.Parent:TweenPosition(
			UDim2.new(
				0.125,
				0,
				0.725,
				0
			),
			Enum.EasingDirection.InOut,
			Enum.EasingStyle.Quad,
			0.5
		)
	else
		ChoicesFrame.Parent:TweenPosition(
			UDim2.new(
				0.125,
				0,
				1.25,
				0
			),
			Enum.EasingDirection.InOut,
			Enum.EasingStyle.Quad,
			0.5
		)
	end
end

local function getResponse()
	for _, response in ipairs(responses) do
		if ChatStarted == false then
			break
		end
		
		currentChatIndex += 1
		
		if typeof(responses[currentChatIndex]) == "string" then
			Frame.speech_.Text = responses[currentChatIndex]
		elseif typeof(responses[currentChatIndex]) == "table" then
			local multiResps = responses[currentChatIndex]
			
			print(multiResps[oldChoiceIndex])
			
			Frame.speech_.Text = multiResps[oldChoiceIndex]
		end

		typewriteMod.typewrite(Frame.speech_, 0.025)

		repeat wait()

		until canContinue == true
	end
end

local function getChoice()
	for index, choiceT in ipairs(choices) do		
		if ChatStarted == false then
			break
		end
		
		currentChoice = choices[currentChatIndex]
		
		local currentIndex = 0 --//used to be added to for the table loop below
		
		maxChoices = getTableItems(currentChoice)
		
		for _, choice in ipairs(currentChoice) do
			currentIndex += 1
			
			if maxChoices == 1 then
				ChoicesFrame["3"].Visible = false
				ChoicesFrame["2"].Text = "Goodbye!"
			elseif maxChoices == 2 then				
				ChoicesFrame["3"].Visible = true
			end
			
			ChoicesFrame[currentIndex].Text = choice
			
			oldChoiceIndex = table.find(currentChoice, choiceSelected)
			
			print("old choice index:", oldChoiceIndex)
		end
		
		repeat wait()
			
		until canContinue == true
		
		currentChoice = nil
	end
end

RemoteEvents.StartChat.OnClientEvent:Connect(function(chatTree, speakerName: string)
	local Lresponses = chatTree["resps"]
	local Lchoices = chatTree["choices"]
	
	ChatStarted = true
	
	responses = Lresponses
	choices = Lchoices
	
	Frame.name_.Text = speakerName .. ":"
	Frame.speech_.Text = "..."
	
	Frame:TweenPosition(
		UDim2.new(
			0.5,
			0,
			0.909,
			0
		),
		Enum.EasingDirection.InOut,
		Enum.EasingStyle.Quad,
		0.5
	)
	
	wait(1)
	
	while canContinue == true do
		canContinue = false
		
		local coroGetResps = coroutine.create(getResponse)
		local coroGetChoice = coroutine.create(getChoice)
		
		if choiceSelected == "Goodbye!" then
			ChatStarted = false

			Frame.speech_.Text = "Goodbye!"
			typewriteMod.typewrite(Frame.speech_, 0.025)

			break
		end
		
		coroutine.resume(coroGetResps)
		coroutine.resume(coroGetChoice)

		repeat wait()

		until typewriteMod.isFinished() == true
		
		if currentChatIndex == getTableItems(responses) - 1 then
			ChatStarted = false

			Frame.speech_.Text = "Goodbye!"
			typewriteMod.typewrite(Frame.speech_, 0.025)

			break
		end

		if typewriteMod.isFinished() == true then
			coroutine.wrap(tweenChoicesFrame)(true)
		end

		repeat wait()

		until canContinue == true
	end
	
	wait(2)
	
	RemoteEvents.ChatEnded:FireServer()
	
	Frame:TweenPosition(
		UDim2.new(
			0.5,
			0,
			1.25,
			0
		)
	)
	
	local dialogueScript = script:Clone()
	dialogueScript.Parent = script.Parent
	script:Destroy()
end)

for _, choiceBtn in ipairs(ChoicesFrame:GetChildren()) do
	if choiceBtn:IsA("TextButton") then
		local UIStroke = ChoicesFrame:WaitForChild("UIStroke")
		
		choiceBtn.MouseButton1Click:Connect(function()
			if canContinue == false then
				canContinue = true
				
				choiceSelected = choiceBtn.Text
				
				coroutine.wrap(tweenChoicesFrame)(false)
			end
		end)
		
		choiceBtn.MouseEnter:Connect(function()
			UIStroke.Parent = choiceBtn
			UIStroke.Enabled = true
		end)
		
		choiceBtn.MouseLeave:Connect(function()
			UIStroke.Parent = ChoicesFrame
			UIStroke.Enabled = false
		end)
	end
end

I can’t seem if I should put this in code review or scripting support…

Hey, this is the correct category.

Line 82: Frame.speech_.Text = multiResps[oldChoiceIndex]
It seems the value oldChoiceIndex in the table is nil?

If this is intentional, you could do something like:
Frame.speech_.Text = multiResps[oldChoiceIndex] or "Nothing"

This means if the value doesn’t exist, the text will be set to “Nothing”

I was able to fix the error, but now I’m dealing with skipping (looping multiple times at once) in my getResponse loop.

local function getResponse()
	for _, response in ipairs(responses) do
		if ChatStarted == false then
			break
		end
		
		currentChatIndex += 1
		
		if typeof(responses[currentChatIndex]) == "string" then
			Frame.speech_.Text = responses[currentChatIndex]
		elseif typeof(responses[currentChatIndex]) == "table" then
			local multiResps = responses[currentChatIndex]
			
			if choiceSelected ~= GoodbyeTxt then
				Frame.speech_.Text = multiResps[choiceSelected]
			else
				break
			end
		end

		typewriteMod.typewrite(Frame.speech_, 0.025)
		
		print("current chat index:", currentChatIndex)

		repeat wait()

		until canContinue == true --> this prevents the loop from running more than once so the dialogue doesn't skip ahead
	end
end

for _, choiceBtn in ipairs(ChoicesFrame:GetChildren()) do
	if choiceBtn:IsA("TextButton") then
		local UIStroke = ChoicesFrame:WaitForChild("UIStroke")
		
		choiceBtn.MouseButton1Click:Connect(function()
			if canContinue == false then
				canContinue = true
				
				choiceSelected = table.find(currentChoice, choiceBtn.Text)
				
				if choiceSelected == nil and choiceBtn.Text == GoodbyeTxt then
					choiceSelected = GoodbyeTxt
				end
				
				coroutine.wrap(tweenChoicesFrame)(false)
			end
		end)
		
		choiceBtn.MouseEnter:Connect(function()
			UIStroke.Parent = choiceBtn
			UIStroke.Enabled = true
		end)
		
		choiceBtn.MouseLeave:Connect(function()
			UIStroke.Parent = ChoicesFrame
			UIStroke.Enabled = false
		end)
	end
end

But what I get instead is:

current chat index: 1 --> happened a bit before the next print below
current chat index: 3 (x2)  --> happened a bit after the print above

If is looping multiple times do a debounce

The debounce I’m using is the canContinue variable

The output shows that the variable is false, yet it still prints all at once

Anyone have anything? I still have this issue.

So what are you trying to achieve with the loop?

I want to make a loop that goes through all of the dialogue the NPC would speak, but to prevent skipping to the very last piece of dialogue, I make a repeat wait until canContinue == true, which canContinue is default to false, but once the player selects a choice, canContinue is true, meaning the NPC’s dialogue can continue.

But why does it need to loop through the dialogue?

I have a table that contains both the responses (NPC dialogue) and the choices (player’s choices), which are both tables. It needs to loop through the dialogue to get each dialogue for the NPC to speak