"attempt to concatenate string with nil" Error after unknown change?

You can write your topic however you want, but you need to answer these questions:

  1. What do you want to achieve?
    Ability to chose towers in the GUI menu

  2. What is the issue?
    The GUI isn’t populated and the error attempt to concatenate string with nil appears in the console

  3. What solutions have you tried so far?
    Nothing so far, after I changed the name of “Gunslinger” to “GunSlinger” these errors appeared but after reverting the name in the tables, the error still appears.

ShopServer Script, In SSS, Handles DataSaving, Loading, and Shop purchasing and selecting

local DataStoreService = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))
local database = DataStoreService:GetDataStore("database")
local RunService = game:GetService("RunService")

local MAX_SELECTED_TOWERS = 5

local data = { 

}
local defaultData = {
	["Gems"] = 100,
	["SelectedTowers"] = {"Cutler"},
	["OwnedTowers"] = {"Cutler", "GunSlinger"}
}


local function LoadData(player)
	local success = nil
	local playerData = nil
	local attempt = 1

	while not success and attempt < 4 do
		success, playerData = pcall(function()
			return database:GetAsync(player.UserId)
		end)
		if not success then
			attempt += 1
			warn(playerData)
			task.wait()
		end
	end
	--[[			Repeat until is often unreliable
	repeat
	until
	]]
	if success then
		print("Data Loaded Successfully")  --Why connection established??
		if not playerData then
			print("New player, giving default data")
			playerData= {
				["Gems"] = 100,
				["SelectedTowers"] = {"Cutler"},
				["OwnedTowers"] = {"Cutler", "GunSlinger"}
			}
		end
		data[player.UserId] = playerData
	else
		warn("Unable to get data for player", player.UserId)
		player:Kick("There was a problem loading your data! Message the developer on Roblox if this problem continues!")
	end
end

Players.PlayerAdded:Connect(LoadData)

local function SaveData(player)
	if data[player.UserId] then
		local success = nil
		local playerData = nil
		local attempt = 1

		while not success and attempt < 4 do
			success, playerData = pcall(function()
				database:SetAsync(player.UserId, data[player.UserId])
				--[[			UpdateAsync overcomplicates things in my opinion
				return database:UpdateAsync(player.UserId, function()
					return data[player.UserId]
				end)
				]]
			end)

			if not success then
				attempt += 1
				warn(playerData)
				task.wait()
			end
		end
		--[[				Again repeat until is often unreliable
		repeat
		until success == true or attempt == 3
		]]
		if success then
			print("Data saved successfully")

		else
			warn("Unable to save data for", player.UserId)
		end
	else
		warn("No Data for", player.UserId)
	end




end


Players.PlayerRemoving:Connect(function(player)
	SaveData(player)
	data[player.UserId] = nil
end)
game:BindToClose(function()
	if not RunService:IsStudio() then
		for index, player in pairs(Players:GetPlayers()) do
			task.spawn(function()
				SaveData(player)
			end)

		end
	else
		print("Shutting down inside the studio")
	end
	
end)
-- I won't bother changing anything else

local function GetItemStatus(player, itemName)
	local playerData = data[player.UserId]
	if table.find(playerData.SelectedTowers, itemName) then
		return "Equipped"
	elseif table.find(playerData.OwnedTowers, itemName) then
		return "Owned"
	else
		return "For Sale"
	end
end

ReplicatedStorage.InteractItem.OnServerInvoke = function(player, itemName)
	local shopItem = towers[itemName]
	local playerData = data[player.UserId]
	if shopItem and playerData then
		local status = GetItemStatus(player, itemName)

		if status == "For Sale" and shopItem.Price <= playerData.Gems then
			-- Purchase
			playerData.Gems -= shopItem.Price
			table.insert(playerData, shopItem.Name)
		elseif status == "Owned" then
			--Equip Tower
			table.insert(playerData.SelectedTowers, shopItem.Name)
			if #playerData.SelectedTowers > MAX_SELECTED_TOWERS then
				table.remove(playerData.SelectedTowers, 1)
			end
		elseif status == "Equipped" then
			-- Unselect tower
			if #playerData.SelectedTowers > 1 then
				local towerToRemove = table.find(playerData.SelectedTowers, itemName)
				table.remove(playerData.SelectedTowers, towerToRemove)
			end
		end
		return playerData
	else
		warn("Tower/Player Data Does Not Exist!")
	end
	return false
end

ReplicatedStorage.GetData.OnServerInvoke = function(player)
	return data[player.UserId]
end

TowerShop module script in RepStorage which has all the tower data to populate the gui.

local TowerShop = {
	["Cutler"] = {
		["Name"] = "Cutler",
		["ImageAsset"] = "http://www.roblox.com/asset/?id=15289908015",
		["Price"] = 0,
		["Damage"] = 5,
		["Range"] = 5,
		["PlacementCash"] = 50
	},
	["GunSlinger"] = {
		["Name"] = "GunSlinger",
		["ImageAsset"] = "rbxassetid://",
		["Price"] = 0,
		["Damage"] = 420,
		["Range"] = 60,
		["PlacementCash"] = 70
	},
}

return TowerShop

ShopClient local script, in the GUI itself, controls updating the GUI

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local towers = require(ReplicatedStorage:WaitForChild("TowerShop"))

local getDataFunc = ReplicatedStorage:WaitForChild("GetData")
local InteractItemFunc = ReplicatedStorage:WaitForChild("InteractItem")

local gui = script.Parent
local exit = gui.Container.Exit
local gems = gui.Container.Gems
local limit = gui.Container.Limit
local itemsFrame = gui.Container.ScrollingFrame
local openButton = gui.Open["main Button"]

local playerData = {}

local function GetItemStatus(itemName)
	if table.find(playerData.SelectedTowers, itemName) then
		return "Equipped"
	elseif table.find(playerData.OwnedTowers, itemName) then
		return "Owned"
	else
		return "For Sale"
	end
end

local function InteractItem(itemName)
	local data = InteractItemFunc:InvokeServer(itemName)
	if data then
		playerData = data
		UpdateItems()
	end
end


function UpdateItems()
	gems.Text = "💎 " .. playerData.Gems
	limit.Text = #playerData.SelectedTowers .. "/5"
	
	for i, tower in pairs(towers) do
		local oldButton = itemsFrame:FindFirstChild(tower.Name)
		if oldButton then
			oldButton:Destroy()
		end
		
		
		local newButton = itemsFrame.TemplateButton:Clone()
		newButton.Name = tower.Name
		newButton.TowerName.Text = tower.Name
		newButton.Image = tower.ImageAsset
		newButton.Visible = true
		newButton.LayoutOrder = tower.Price
		newButton.Parent = itemsFrame
		
		local status = GetItemStatus(tower.Name)
		if status == "For Sale" then
			newButton.Status.Text = "💎 " .. tower.Price
		elseif status == "Equipped" then
			newButton.Status.Text = "Equipped"
		else
			newButton.Status.Text = ""
		end
		
		newButton.Activated:Connect(function()
			InteractItem(tower.Name)
		end)
	end
end


local function ToggleShop()
	gui.Container.Visible = not gui.Container.Visible
	if gui.Container.Visible then
		playerData = getDataFunc:InvokeServer()
		UpdateItems()
	end
end

local function SetupShop()
	openButton.Activated:Connect(ToggleShop)
	
	exit.Activated:Connect(ToggleShop)
end
SetupShop()

I have no clue whats going on here and I cannot find a fix. This project is a tutorial I am following and going back and rewriting the code didn’t work and Im VERY confused.

Here is the GUI heiarchy if needed.
image

The error is at this line

gems.Text = "💎 " .. playerData.Gems
limit.Text = #playerData.SelectedTowers .. "/5"

Which line does the error appear on?

The first 2 lines in UpdateItems in the ShopClient.

gems.Text = "💎 " .. playerData.Gems
limit.Text = #playerData.SelectedTowers .. "/5"

Hopefully someone help - bump :heart:

Verify that you are actually getting a value returned for playerData.Gems when you invoke the RemoteFunction. Right now, the code suggests that you don’t have any.

Relatedly, this is why it’s always helpful to first verify values before using them in game:

Ex:

local Gems = playerData.Gems

if not Gems then
    Gems = "Data not found"
end

gems.Text = "💎 " .. Gems --// This will now either display the player's gem amount or a clean '💎 Data not found', giving you visual indication of an error with the player data without giving an error and potentially breaking follow-on code.

No gems have been found, but I think there is more to the issue than the gems data not being found. If i comment out
image
both of those then I get an issue that the table playerData cannot be found at all but I did add an if statement before the gem data check that it doesnt exist but it does exist but it also doesnt?

gems.Text = "💎 " .. (playerData.Gems or "0")

That made the error disapear but… if you look in the serverscriptservice, the default data is set to 100 and i should have 100 gems but I dont and when I do what you did I get this error Players.EncryptedBloxy.PlayerGui.ShopGUI.ShopClient:44: attempt to get length of a nil value which appears when I also comment out the line of code that the original problem occured

Somehow when it is getting to those two lines, playerData is nil
How its getting there, I’m not sure. You need to either use breakpoints or print statements along the flow of your code and see where its getting set to nil, or even make sure its getting the default value to begin with.

When using global variables (outside of a function) as you are doing with playerData, make sure you do not make a local variable with the same name, that can cause problems.

I think its the table itself becuase when those lines are removed, i get an error above that function says the playerData table = nil/doesnt exist. if you’d like to place file i can give you it if you think that could help but i appreciate everything you’ve tried so far.

Sorry its 2 am where I am, and I am in no condition to really look through code.
However, I see you have ‘data[player.UserId]’ to hold the player’s data.

put a print at each location you are returning data[player.UserId]
just before the ‘return’
also put a print before you return any ‘playerData’ in any function.

You just have to narrow it down.

I see, yea its for the best you get some rest, thank you so much for your contribution and I will definitly try that !! Anyways, gn!!

1 Like