Non-working DataStore. Not sure what's wrong?

Leaderboard comes up fine (that’s the most basic of things), however, I keep getting an error around line 25 telling me:

ServerScriptService.DataStore:25 attempt to index nil with ‘Wins’

I’m still fairly new at programming so bear with me.

-- Datastore Service (for player statistics)
local dataStoreService = game:GetService("DataStoreService");
local dataStore = dataStoreService:GetDataStore("GeneralSaveData", "Players");

function generateDataKey(player)
	local ret = "_uid" .. player.userId;
	return ret;
end

function generateDataTable(player)
	local dataTable = {
		Wins = player.leaderstats.Wins.Value;
		Points = player.leaderstats.Points.Value;
	}
	return dataTable;
end

function saveDataForPlayer(player)
	local key = generateDataKey(player);
	local data = generateDataTable(player);
	dataStore:SetAsync(key, data);
end

function inputDataToPlayer(player, data)
	player.leaderstats.Wins.Value = data.Wins;
	player.leaderstats.Points.Value = data.Points;
end

function loadDataForPlayer(player)
	local key = generateDataKey(player);
	local data = dataStore:GetAsync(key);
	inputDataToPlayer(player, data);
end

game.Players.PlayerAdded:connect(function(player)
	-- Opens up the leaderboard --
	local leaderstats = Instance.new("Folder");
	leaderstats.Name = "leaderstats";
	leaderstats.Parent = player;

	local wins = Instance.new("IntValue");
	wins.Name = "Wins";
	--wins.Value = 1;
	wins.Parent = leaderstats;
	
	local points = Instance.new("IntValue");
	points.Name = "Points";
	points.Parent = leaderstats;
	
	loadDataForPlayer(player); -- Load data the first thing when the player joins
	player.leaderstats.Wins.Changed:connect(function()
		saveDataForPlayer(player); -- Save the data when Wins change value
	end)
end)

game.Players.PlayerRemoving:connect(saveDataForPlayer) -- Saves the data when the player leaves the game

Make sure you have spelt everything correctly because once I put “ocal” instead of local and I was wondering why it wasn’t working!

Problem here is that data is nil thus “attempting to index nil with ‘Wins’”. If a player doesn’t have existing data then GetAsync will return nil. You will need to account for this by setting up some default data.

function inputDataToPlayer(player, data)
	player.leaderstats.Wins.Value = data and data.Wins or 0;
	player.leaderstats.Points.Value = data and data.Points or 0;
end

Your solution could be as simple as setting the values to 0 if no data was returned.

On a side note, you should be using pcall on datastore:GetAsync() because it may error.

It’s player.UserId, look at your generateDataKey function.

In loading data for players, you must also expect that the player is new; hence, the data retrieved from loadDataForPlayer function will be nil.

Try adding a conditional statement in your inputDataToPlayer function:

function inputDataToPlayer(player, data)
	if data ~= nil then -- If data is not equal to nil
		player.leaderstats.Wins.Value = data.Wins;
		player.leaderstats.Points.Value = data.Points;
	else
		player.leaderstats.Wins.Value = 0
		player.leaderstats.Points.Value = 0
end

Quick question. How would you utilize a pcall() there? I’m looking at examples online, but I’m not getting it.

You basically ‘wrap’ your code within a pcall function.

function saveDataForPlayer(player)
	local key = generateDataKey(player);
	local data = generateDataTable(player);

	local success, errormessage = pcall(function()
		dataStore:SetAsync(key, data)
	end)
end

function loadDataForPlayer(player)
	local key = generateDataKey(player);
	local data 
	
	local success, errormessage = pcall(function()
		data = dataStore:GetAsync(key)
	end)

	inputDataToPlayer(player, data);
end

I was confused too when I was a beginner, but to make it simple, the pcall is usually used whenever there is a possibility for a certain function to cause an error. When a pcall is used and an error occurs, the thread will still continue; however, if the pcall function is not used, the thread will stop at that error, causing the whole script to stop.

1 Like

Like Dundale said, anything which may or may not error should be wrapped up in a pcall. I think that anything marked Async on roblox may error (there are many other functions which could error too of course).

What’s important to know is that the first return value is a boolean which represents whether an error occured or not. The second return value represents an error message if an error occurred or otherwise the actual return value of the function you called.

local success, data = pcall(dataStore.GetAsync, dataStore, key)

if not success then
  -- GetAsync threw some kind of error, data represents this error and not your actual player data
else
  if not data then
    -- The user had no actual data saved, GetAsync did not error but returned nil
  else
    -- User had data saved and it was returned

With that being said, you may want to have a while loop retrying after a short interval if the datastore fetch fails. Although you may want different behavior depending on what kind of error the datastore throws. See the Data Store Errors and Limits article on what you could expect.

1 Like