Why is this returning nil?

The full script is 163 lines long and requires a ton of context within the workspace to understand, but okay.

the script
local cradle = game.Workspace.Cradle.Balls
local ogPos = game.Workspace.Cradle.BallsPos
local sSound = game:GetService("SoundService").Song
local gp
local tempo
local pos
local gPos
local direction
local aDirection
local tim = 0
local tweenInfo
local evenOdd
local ogPos2 = {}
local inputAllowed = false
local contin = 0
local rContin = 0
local keyCodeTable = {Enum.KeyCode.One, Enum.KeyCode.Two, Enum.KeyCode.Three, Enum.KeyCode.Four, Enum.KeyCode.Five}

--Find the original CFrame of a ball in a table
local function findOg(part, model) return ogPos2[table.find(ogPos2, part.Name .. model.Name)+1] end

--Find how many buttons need to be pressed
local function findAm(pattern)
	rContin = 0
	for _, val in pairs(pattern:GetChildren()) do if val.Value then rContin += 1 end end return rContin
end

--Play the game
local function play(song)
	--Set up
	sSound.SoundId = "rbxassetid://" .. song.Value
	gp = song.Gameplay
	tempo = 60 / song.Tempo.Value
	pos = 1
	repeat game:GetService("RunService").Stepped:Wait() until sSound.IsLoaded
	--Play
	sSound:Play()
	repeat
		--Move to starting place
		for _, model in pairs(cradle:GetChildren()) do model:SetPrimaryPartCFrame(ogPos:FindFirstChild(model.Name .. "sP").CFrame) end
		--Write values
		tim = 0
		gPos = gp:FindFirstChild(pos)
		pos += 1
		--Move to proper place
		for _, model in pairs(cradle:GetChildren()) do
			if gPos.Used:FindFirstChild(3).Value then
				model:SetPrimaryPartCFrame(model.PosA.CFrame)
				evenOdd = "Odd"
			else
				model:SetPrimaryPartCFrame(model.PosB.CFrame)
				evenOdd = "Even"
			end
		end
		--Allow things to be seen
		for uPos = 1, 5 do
			if gPos.Used:FindFirstChild(uPos).Value then
				for _, part in pairs(cradle:FindFirstChild(uPos):GetChildren()) do
					if string.split(part.Name, "_")[1] == "Part" then
						game:GetService("TweenService"):Create(part, TweenInfo.new(tempo/2, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, 0, false, 0), {Transparency = 0}):Play()
					end
				end
			end
		end
		contin = findAm(gPos.Pattern)
		--Start moving
		direction = gPos.Direction.Value
		repeat
			--Reset table
			ogPos2 = {}
			--Direction
			for _, part in pairs(gPos.Pattern:GetChildren()) do
				if part.Value and direction then
					aDirection = part.Direction.Value
				elseif part.Value then
					if part.Direction.Value == "R" then aDirection = "L" else aDirection = "R" end
				end
			end
			--Tween Info
			tweenInfo = TweenInfo.new(gPos.Switch.Value*tempo/2, Enum.EasingStyle.Quart, Enum.EasingDirection.Out, 0, false, 0)
			--Move up
			for _, model in pairs(cradle:GetChildren()) do
				if gPos.Pattern:FindFirstChild(model.Name).Value then
					for _, part in pairs(model:GetChildren()) do
						if string.split(part.Name, "_")[1] == "Part" then
							--Save CFrame
							table.insert(ogPos2, part.Name .. model.Name)
							table.insert(ogPos2, part.CFrame)
							--Tween
							game:GetService("TweenService"):Create(part, tweenInfo,
								{CFrame = ogPos:FindFirstChild(evenOdd):FindFirstChild(aDirection):FindFirstChild(model.Name .. "eP"):FindFirstChild(part.Name).CFrame}):Play()
						end
					end
				end
			end
			wait(song.Window.Value*tempo/2)
			if contin ~= findAm(gPos.Pattern) then
				print ("fail " .. contin)
			else
				print "yay"
			end
			contin = 0
			inputAllowed = false
			wait(tempo*gPos.Switch.Value/2 - song.Window.Value*tempo/2)
			--Tween Info
			tweenInfo = TweenInfo.new(gPos.Switch.Value*tempo/2, Enum.EasingStyle.Quart, Enum.EasingDirection.In, 0, false, 0)
			--Move down
			for _, model in pairs(cradle:GetChildren()) do
				if gPos.Pattern:FindFirstChild(model.Name).Value then
					for _, part in pairs(model:GetChildren()) do
						if string.split(part.Name, "_")[1] == "Part" then
							--Tween
							game:GetService("TweenService"):Create(part, tweenInfo, {CFrame = findOg(part, model)}):Play()
						end
					end
				end
			end
			wait(tempo*gPos.Switch.Value/2 - song.Window.Value*tempo/2)
			--Change values
			inputAllowed = true
			tim += gPos.Switch.Value
			direction = not direction
			--Change which balls are swinging
			for _, val in pairs(gPos.Pattern:GetChildren()) do
				if val.Value and not val.Keep.Value then
					val.Value = false
					val = gPos.Pattern:FindFirstChild(6 - val.Name)
					val.Keep.Value = true
					val.Value = true
					--Input
					local inputFunc
					inputFunc = game:GetService("UserInputService").InputBegan:Connect(function(input)
						if input.KeyCode == keyCodeTable[val.Name] and inputAllowed then contin += 1 end
						inputFunc:Disconnect()
					end)
				end
			end
			--Make sure every instance of keep is false
			for _, val in pairs(gPos.Pattern:GetChildren()) do val.Keep.Value = false end
			--Wait
			wait(song.Window.Value*tempo/2)
		until tim == gPos.Length.Value
	until pos > #gp:GetChildren()
end

--Find the correct song
local function findSong(num)
	if num.Name - 10 < 0 then num = "0" .. num.Name end
	for _, song in pairs(game:GetService("ReplicatedStorage").Songs:GetChildren()) do if string.split(song.Name, " ")[1] == num then return song end end
end

--Level activation
for _, level in pairs(script.Parent:GetChildren()) do if level:IsA("TextButton") then level.MouseButton1Click:Connect(function()
			play(findSong(level))
			script.Parent.Visible = false
			for _, lev in pairs(script.Parent:GetChildren()) do
				if lev:IsA("TextButton") then
					lev.Active = false
				end
			end
		end)
	end
end

Only one button to be able to be pressed. The game is basically a rhythm game revolving around a Newton’s Cradle, and so you have to press the right buttons at the right times in order to keep it going. Not at all how a Newton’s Cradle actually works, but whatever.

Only the third value, what number does it say?

local inputFunc
inputFunc = game:GetService("UserInputService").InputBegan:Connect(function(input)
	local N = tonumber(val.name)
	print(#val.Name)
	if N and input.KeyCode == keyCodeTable[N] and inputAllowed then contin += 1 end
	inputFunc:Disconnect()
end)

If it gives more than 1 it means that val.Name has spaces, in that case you have to remove them

local inputFunc
inputFunc = game:GetService("UserInputService").InputBegan:Connect(function(input)
	local N = tonumber(val.name:gsub("%s", ""))
	print(#val.Name, #val.Name:gsub("%s", ""))
	if N and input.KeyCode == keyCodeTable[N] and inputAllowed then contin += 1 end
	inputFunc:Disconnect()
end)

Source: String Patterns, string.gsub

1 Like

I am out of ideas so I will stop here. Sorry.

1 Like

First off, I can check if val.Name has spaces by looking in the workspace and renaming them. Second, it doesn’t.

And you’ve tried this already, right?

input.KeyCode == keyCodeTable[tonumber(val.Name)]

I’m asking this because you pasted your script and it didn’t have that in there.

1 Like

All good, it is a really weird problem regardless.

Yes, I have. As stated in the original post:

1 Like

I know, but it was in the script you pasted, so that’s why I asked.
I’m stumped. Either it’s a bug or we’re all overlooking something.

1 Like

Yeah, makes sense. I have a friend who’s a professional software engineer, and even they couldn’t figure it out, so there’s something going on here that we aren’t aware of.

1 Like

Can you make it print out “val”'s name?

print('"'..val.Name..'"')

This is returning nil because the table does not have val's name as a key, which is because the name couldn’t be parsed as an integer.

1 Like

I have printed out val.Name, as stated here:

Probably should’ve clarified that I printed it to find that out.

Could you please delete all of the irrelevant parts of this script until you are left with a simple example that we can all put into studio, execute, and see the issue?

I mean literally tell us, “put these number values in studio, put this script in workspace, and run it and see what I mean.”

Apparently your problem is pretty hard to reproduce, so you’ll need to make it easier for us to help you out by showing a minimal example that demonstrates the problem. Because you’re right: there is no reason the initial code you posted wouldn’t work—therefore the problem must be elsewhere.

Also consider putting a breakpoint in your code and inspect the variables as the code runs to make sure they’re what you expect.


edit: one thing that looks suspicious while you work on that:

	for _, val in pairs(gPos.Pattern:GetChildren()) do
		if val.Value and not val.Keep.Value then
			val.Value = false
			-- 🚨 suspicious:
			val = gPos.Pattern:FindFirstChild(6 - val.Name)
			-- ...
		end
	end

you’re reassigning val inside the loop, which is confusing. When you tell us you keep printing out val.Name and all that, are you certain you’re printing out the val after you reassign it?

Also another thing that is suspicious: You are looping through the children of gPos.Pattern to find the numbered BoolValues. However, earlier in the script you also seem to be using gPos.Pattern as a folder for non-number items.

Like line 72:

for _, part in pairs(gPos.Pattern:GetChildren()) do

You’re also looping through gPos.Pattern, but this time… you’re assuming the children are parts—but later you assume they’re all BoolValues.

That could be contributing to your errors, but I’m surprised you’re not getting more.

1 Like

(edit: @Lielmaster points out that FindFirstChild will convert to a string automatically, so apparently that’s not the issue. I’ll just fall back to my previous post, then.)

Actually, yeah, the line that looks suspicious is, I think, the culprit.

Imagine val.Name = "1"

  • You do 6 - "1".
  • Lua converts the "1" (string) to 1 (number)
  • You end up calling FindFirstChild(5)

Try turning it back into a string:

val = gPos.Pattern:FindFirstChild(tostring(6 - val.Name))

You might want to use a different variable name than your loop variable, so that this sort of issue is easier to debug in the future!

(Also: Don’t forgot to add the tonumber to keyCodeTable[...], that’s still necessary)

1 Like

When indexing a member of an instance using a number, the number turns it into a string.
image

Even FindFirstChild

2 Likes

Can you please print it out again and post the exact value, please? Its clearly not the correct number.

1 Like

So, in the game, during the first section, it switches between 2 and 4 each loop, then switches to 1 and 2 then 4 and 5 each loop, both of which are intended.

This actually has to do with the fact that:

and there are 5 balls, so when I want to alternate which ball is swinging, I do 6 minus the current one. So, 4 would turn into 2, which is intended. Also, yes, I print out val.Name after I do that. I always print it out during the Input function, both before and after the if statement.

Also, for

Put a local script into studio somewhere, and in ReplicatedStorage, get a folder, name it Pattern, and add 5 BoolValue Instances. (They’re not actual values, you have to call their value specifically to get their value, otherwise they’re still just instances. That’s why that part isn’t erroring) Name each of them 1-5 and give them each another BoolValue child called Keep, and set everything except for the BoolValue named 4 to false. Then, in the local script define gPos as ReplicatedStorage, add the keyCodeTable variable, and add the for loop containing the variable. It should replicate the problem. (Can’t do it myself right now, I have to go in like 10 minutes and still haven’t gotten ready very much, but I’ll try it later.)

(Also, I forgot to add this at first, but remove the inputAllowed variable. That’s not important to the problem and works perfectly fine. Also, set contin to 0 or just remove it and have the if statement do nothing.)

As I’ve said multiple times,

Unless you’re saying it’s necessary regardless, in which case I’d be willing to put it in there just in case.

Okay. Can you show the exact output that gets printed out?

1 Like

So, during the first section:
3
(The fail 0 is a check to make sure something else in the script isn’t failing.)
And during the second section:
2
This is exactly how it is supposed to print.