Data not saving

Hello! My data is not saving, (or loading?) even though it prints MyName's data was saved successfully.

Bit of context: I want to save a PlayerGui displaying the number of coins they have, so whenever the local script detects when the player’s leaderstat is being changed, it adds the coins and sends a remote event signal to the server to pick up.

Then, the server script picks it up and changes the IntValue that it is being parented in into the number of coins that they earned. Then there is another script, called "CoinSaving" that the IntValue is being parented in.

The CoinSaving script is directly from a tutorial but with some modifications so it only saves the Coins IntValue.

There are no errors, it prints everything, but it just doesn’t seem to save (or load).

If anyone can figure this out, I would really appreciate it!

--Coin saving script [LONG].

local playerService = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")

-- You can replace the "Data" in the brackets with anything you'd like.
-- It is the name of the data store
local gameDataStore = dataStoreService:GetDataStore("CoinsDataStore")

local function serialiseData(player)
	-- Collects a player's data into a table

	-- Find the player's leaderstats/playerstats
	local playerstats = player:FindFirstChild("playerstats")
	if not (playerstats) then
		warn("No leaderstats/playerstats found for " .. player.Name)
		return
	end

	-- Create a table for the data to go in
	local data = {

		Coins = playerstats.Coins.Value;
		-- Because 'SpawnPoint' is a vector3 value which can't be saved,
		-- we need to convert it to a dictionary

		DataId = playerstats.DataId.Value;
	}

	return data -- This data can now be saved to a DatastoreService
end

local function deserialiseData(data)

end

local function saveData(player)

	-- First we need to find their leaderstats / playerstats
	local playerstats = player:FindFirstChild("playerstats")
	if not (playerstats) then
		-- Warn the script
		return false, "No playerstats found for " .. player.Name
	end

	-- Next, serialise the data into a saveable format
	local data = serialiseData(player)

	-- Get their userId
	local userId = player.UserId

	-- Now we can attempt to save to Roblox
	-- We retry up to 10 times if something goes wrong
	local attemptsLeft = 10
	local success, errorMessage

	repeat wait()
		pcall(function()
			-- We should use UpdateAsync here instead of
			-- SetAsync() as we can compare to previous data
			success, errorMessage = gameDataStore:UpdateAsync(userId, function(old)
				if not (old and old.DataId) then
					-- If no old data was found, we should overwrite the data
					return data
				elseif (old.DataId > data.DataId) then
					-- The data didn't load correctly! Do not overwrite
					error("Data did not load correctly!")
				else
					-- The data is safe to overwrite
					return data
				end
			end)
		end)
		attemptsLeft -= 1 -- Decrease the number of attempts left
	until (attemptsLeft == 0) or (success)

	-- Check that the data was saved
	if success then
		print(player.Name .. "'s data was saved successfully!")
		-- We should increment their data Id so that their data
		-- can be saved again in this server
		playerstats.DataId.Value += 1
	else
		-- Display the error message
		warn(player.Name .. "'s data wasn't saved! :", errorMessage)
	end

	-- Return the result
	return success, errorMessage
end

local function loadData(player)

	-- Get their user Id
	local userId = player.UserId

	local data = nil
	local attemptsLeft = 10
	-- Repeat 10 times, or until the script has communicated successfully
	local success, errorMessage
	repeat task.wait()
		success, errorMessage = pcall(function()
			data = gameDataStore:GetAsync(userId)
		end)
		attemptsLeft -= 1 -- Reduce the number of attempts left	
	until (attemptsLeft == 0) or success

	-- Check if there was a problem with the Roblox servers
	if (not success) then
		warn("Error Loading player data:", errorMessage)
		return
	end

	-- Check whether there is any data or they're a new player
	-- Also, we should get their leaderstats and playerstats folders
	local playerstats = player:WaitForChild("playerstats")

	if data then
		-- Returning player
		-- Here we can load in the values
		playerstats.Coins.Value = data.Coins or 0

		-- Set their data Id to what it previously was
		playerstats.DataId.Value = data.DataId or 0
	else
		-- New player!
		-- Here we can set default values e.g cash = 50
		playerstats.Coins.Value = 0	
	end

	-- Finally we want to increment their dataId, to indicate that the data loaded
	playerstats.DataId.Value += 1

	return true -- Success!
end

-- This will run when a player joins the game.
local function onPlayerAdded(player)

	-- Clone a playerstats folder
	local playerstats = script.playerstats:Clone()
	playerstats.Parent = player

	-- **Load their data**
	loadData(player)
end

-- This will run when a player leaves the game
local function onPlayerRemove(player)
	-- Save their data
	saveData(player)
end

-- This function will run when the script loads
local function initialise()
   --[[ Here, we want to iterate through all players that
        may have joined before this script loaded.
   ]]--

	for _, player in ipairs(playerService:GetPlayers()) do
		onPlayerAdded(player)
	end
end

-- Connect events for when players leave / join
playerService.PlayerAdded:Connect(onPlayerAdded)
playerService.PlayerRemoving:Connect(onPlayerRemove)

game:BindToClose(function() -- Runs when a server shuts down
	-- Iterate through every player in the server and save their data
	for _, p in ipairs(playerService:GetPlayers()) do
		saveData(p)
	end
end)
2 Likes

Alright, so one error in your code is that you’re looking for players that’s, but it’s in brackets, it might be erroring

Also, I’ll share my version of saving things:

Saving

local DatastoreServoce = game:GetService("DataStoreService")
local CoinsDataStore = DatastoreService:GetDataStore("CoinsDataStore")
local tries = 3
local loaded = false

local function save(player)
    local key = player.UserId
    local counter = 0
    local playerstats= player:FindFirstChild("playerstats")

    local data = {}

    for i, value in pairs(playerstats:GetChildren()) do
        local Table = {
            Name = value.Name,
            Value = value.Value
        }
        table.Insert(data, Table)
    end

    local success, errormessage 
    If loaded then
        repeat
            success, errormessage = pcall(function()
                CoinsDataStore:SetAsync(key,data)
            end
            counter = counter + 1
        until success or counter >= tries

        if not success then
            print(errormessage)
        end
    else
        warn("don't save data if it's not loaded")
    end
end

This is loading:

local function load(player)
    local key = player.UserId
    local counter = 0
    local playerstats = -- create playerstats

    local data

    local success, errormessage
        success, errormessage = pcall(function()
            data = CoinsDataStore:GetAsync(key)
        end
        counter = counter + 1
    repeat 

    until success or counter > tries

    If not success then
       --do thing you would do if data not loaded
    else
        loaded = true
        for i, thing in pairs(data) do
            local Value = Instance.new(Set Value Type Here, like int or bool)
            Value.Name = thing.Name
            Value.Value = thing.Value
            Value.Parent = playerstats
        end
    end
end

sorry if I missed anything, I’m writing this on my phone, formatting was a pain

reach out if you have any questions, I’ll answer them asap

Hope I helped!

1 Like

Also, all the variable names are changeable, and don’t forget to add a

game.Players.PlayerAdded:Connect(load)
game.Players.PlayerRemoving:Connect(save)
game:BindToClose(function()
    for i, plr in pairs(game.Players: GetChildren()) do
        save(plr)
    end
end)

Also, watch out for copying and pasting, I used spaces instead of indenting, and capitalization is wrong is some places

1 Like

i made some code for you, this time in studio, so feel free to copy it! if you have any questions about how it works, ill happily help!

local DataStoreService = game:GetService("DataStoreService")
local CoinsStore = DataStoreService:GetDataStore("CoinsStore")

local tries = 3
local dataloaded = nil

local function save(player)
	if dataloaded then
		local key = player.UserId
		local count = 0

		local data = {}
		
		for i, Value in pairs(player.playerstats) do
			local Thing = {
				["Name"] = Value.Name,
				["Value"] = Value.Value
			}
			table.insert(data, Thing)
		end
		
		local success, err

		repeat
			success, err = pcall(function()
				CoinsStore:SetAsync(key, data)
			end)

			count = count + 1
		until count >= tries or success

		if not success then
			warn("Data could not be set." .. tostring(err))

			return
		end
	else
		warn("Data has not been loaded. Do not attempt to set data when it has not been loaded.")

		return
	end
end

local function load(player)
	
	local playerstats = Instance.new("Folder")
	playerstats.Name = "playerstats"
	playerstats.Parent = player
	

	local key = player.UserId
	local count = 0

	local data

	local success, err

	-- Loading Magic

	repeat
		success, err = pcall(function()
			data = CoinsStore:GetAsync(key)
		end)

		count = count + 1
	until count >= tries or success

	-- Did we load?

	if not success then
		warn("Failed to read data." .. tostring(err))

		player:Kick("Failed to read data. Please rejoin the game.")

		return
	end

	-- We Loaded. Hooray

	if data then
		for i, table in pairs(data) do
			local Value = Instance.new("IntValue") -- can be changed to bool value, string value, etc.
			Value.Name = table.Name
			Value.Value = table.Value
			Value.Parent = playerstats
		end
		dataloaded = true
	else
		local coins = Instance.new("IntValue")
		coins.Name = "Coins"
		coins.Parent = playerstats
		
		local dataId = Instance.new("IntValue")
		dataId.Name = "DataId"
		dataId.Value = --set value here

		dataloaded = true
	end

end

game.Players.PlayerAdded:Connect(load)
game.Players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for i, plr in ipairs(game.Players:GetPlayers()) do
		save(plr)
	end
end)
1 Like

Thank you for writing this code for me. I really appreciate it, but it does not seem to work for me. No errors in the output or anything.

If the code doesn’t work, first of all, try changing some of the variables I put in

Secondly, make sure you have studio access to API services enabled. Without it enabled, datastores won’t work (might be the cause of your problem)

If that doesn’t work, try putting that script into a different place, and see if it works (make sure studio access to API services is enabled, can be found in game settings → security)

1 Like

You arent allowed to write entire scripts for people, just letting you know.

1 Like
  1. The Rules:
    About the Scripting Support category

  2. Doesnt mean you give out entire code.

1 Like

There is a reason nobody does this, saying “oh, because it doesnt say this” just simplt isnt true, even if they say or dont say they want code for stuff, you still dont provide the entire thing.

If you are recommending a Source, Provide the Source.

talk to me over Messages btw, not replies to a topic, we would be flooding the topic with off topic stuff.

1 Like

Alright, I enabled API Access to studio but now its saying:
Warn Statement

whenever I leave. Should I test this system in-game or in studio as I am currently doing right now?

In this section, make sure that both statements have dataloaded = true. I edited the code a bit after, perhaps you copied the code before I edited it

It print can’t save, because data loaded hasn’t been set to true, and it only does so in the above statements. Try it, and let me know if it works!

1 Like

I did it, and there is an error in the for loop:

if dataloaded then
		local key = player.UserId
		local count = 0

		local data = {}

		for i, Value in pairs(player.playerstats) do --error here
			local Thing = {
				["Name"] = Value.Name,
				["Value"] = Value.Value
			}
			table.insert(data, Thing)
		end

It says, invalid argument #1 to 'pairs' (table expected, got Instance)

Also, I have a question, in

does the else statement mean that there is no data and it is a new player? And for this:

Do I have to put a number there because it is a new player? Or is it intentional like that?

Also I’m very sorry for the late response lol, busy with school and had to do other things.

Thanks so much.

In the for loop, I forgot to put a get children

This part:

for i, Value in pairs(player.playerstats) do

Should be replaced with:

for i, Value in pairs(player.playerstats:GetChildren()) do

Yes, the else that you showed is for when a player has joined game for the first time, and doesn’t have any saves

You have to set the value you want for the dataId, because I don’t know what that variable is. Set it as 0, 25, or whatever! or change “IntValue” in Instance.new() to anything you would like and set value accordingly

Let me know if you have any other issues!

1 Like

Thank you, it works much better now. However, I want the # of coins to be in an IntValue inside of playerstats. I can’t seem to make it change, and there are no errors in the script.

When I print the IntValue storing the coins when the game saves, it keeps saying it’s 0 even though I add the value at the beginning of the load function.

I am also viewing the change on the client side and the server side in case it changes on one of them.

Here is the script starting from the load function, idk what’s happening.

local function load(player)

	player:WaitForChild("leaderstats"):WaitForChild("Stage"):GetPropertyChangedSignal("Value"):Connect(function()
		print('player leaderstats changed')
		local leaderstats = player:WaitForChild("leaderstats"):WaitForChild("Stage").Value
		local coinValue = tonumber(script.playerstats.Coins.Value)
		if leaderstats < 20 and leaderstats > 1 then --in easy
			coinValue += 2
		elseif leaderstats < 40 and leaderstats > 20 then --if in casual
			coinValue += 4
		elseif leaderstats < 51 and leaderstats > 40 then --if in medium
			coinValue += 5
		end
	end)

	local playerstats = Instance.new("Folder")
	playerstats.Name = "playerstats"
	playerstats.Parent = player


	local key = player.UserId
	local count = 0

	local data

	local success, err

	-- Loading Magic

	repeat
		success, err = pcall(function()
			data = CoinsStore:GetAsync(key)
		end)

		count = count + 1
	until count >= tries or success

	-- Did we load?

	if not success then
		warn("Failed to read data." .. tostring(err))

		player:Kick("Failed to read data. Please rejoin the game.")

		return
	end

	-- We Loaded. Hooray

	if data then
		for i, table in pairs(data) do
			local Value = Instance.new("IntValue") -- can be changed to bool value, string value, etc.
			Value.Name = table.Name
			Value.Value = table.Value
			Value.Parent = playerstats
		end
		dataloaded = true
	else
		local coins = Instance.new("IntValue")
		coins.Name = "Coins"
		coins.Parent = playerstats

		local dataId = Instance.new("IntValue")
		dataId.Name = "DataId"
		dataId.Value = 0

		dataloaded = true
	end

end

game.Players.PlayerAdded:Connect(load)
game.Players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for i, plr in ipairs(game.Players:GetPlayers()) do
		save(plr)
		print('fired save function')
	end
end)

Unsure of how it works,as you add the coinvalue variable before the coin instance is loaded in?

Also, an IntValue has the default value always set to 0, so if you aren’t changing the value while in game (do it from server, client edits don’t save) then it will always be 0

Also, I assume you want to add coins based on difficulty?

Problem: let’s say that when you get to stage 21 you have 50 coins. When you reach stage 22, it asks to add 4 coins. Now the CoinsValue variable is set to 54. But the player coins is still 50. You changed the value of the variable, not the IntValue in the player.playerstats.

Instead, set CoinsValue as just the path to the IntValue
~

CoinsValue = player.playerstats.Coins

CoinsValue.Value += 4

This will add 4 to the player coins

Also, and I can’t stress this enough, I really recommend only having the saving / loading functions as part of the datastores script, and put everything else (like the stat hanged event) into a separate script.

Also, don’t forget to actually create stuff… I don’t see you creating a leader stats folder in this script, but it’s good, again, if it works, it works, just keep this script working the way it’s intended: saving + loading

Let me know if you have any other trouble!

1 Like

Hi, I changed some stuff in the script. So basically, this part:

Is in another script (see what I wrote below this) and that basically handles the coin value adding.


The coin instance is already loaded in:
Hierarchy
The script inside of the Coins IntValue is changing the coin value that it’s parented in.


There is also an error: attempt to index number with 'Name' in this part:
(I made it stand out)

if data then
		for i, table in pairs(data) do
			local Value = Instance.new("IntValue") -- can be changed to bool value, string value, etc.

			Value.Name = table.Name --this line

			Value.Value = table.Value
			Value.Parent = playerstats
		end
		dataloaded = true

Here is the entire script if you need it:

local DataStoreService = game:GetService("DataStoreService")
local CoinsStore = DataStoreService:GetDataStore("CoinStore")
local repStorage = game:GetService("ReplicatedStorage")

local tries = 3
local dataloaded = nil

local function save(player)

	if dataloaded then
		local key = player.UserId
		local count = 0

		local data = {}

		for i, Value in pairs(player.playerstats:GetChildren()) do
			local Thing = {
				["Name"] = Value.Name,
				["Value"] = Value.Value
			}
			table.insert(data, Thing)
		end

		print(data)

		local success, err

		repeat
			success, err = pcall(function()
				CoinsStore:SetAsync(key, data)
				print(script.playerstats.Coins.Value)
				print('Saved!')
			end)

			count = count + 1
		until count >= tries or success

		if not success then
			warn("Data could not be set." .. tostring(err))
			return
		end
	else
		warn("Data has not been loaded. Do not attempt to set data when it has not been loaded.")

		return
	end
end

local function load(player)

	local playerstats = Instance.new("Folder")
	playerstats.Name = "playerstats"
	playerstats.Parent = player
	
	print('playerstats created')


	local key = player.UserId
	local count = 0

	local data

	local success, err

	-- Loading Magic

	repeat
		success, err = pcall(function()
			data = CoinsStore:GetAsync(key)
			print('got data')
			print(data)
		end)

		count = count + 1
	until count >= tries or success

	-- Did we load?

	if not success then
		warn("Failed to read data." .. tostring(err))

		player:Kick("Failed to read data. Please rejoin the game.")

		return
	end

	-- We Loaded. Hooray

	if data then
		for i, table in pairs(data) do
			local Value = Instance.new("IntValue") -- can be changed to bool value, string value, etc.
			Value.Name = table.Name
			Value.Value = table.Value
			Value.Parent = playerstats
		end
		dataloaded = true
	else
		warn('Player has no data! Creating new data for them.')
		local coins = Instance.new("IntValue")
		coins.Name = "Coins"
		coins.Parent = playerstats

		local dataId = Instance.new("IntValue")
		dataId.Name = "DataId"
		dataId.Value = 0

		dataloaded = true
	end

end

game.Players.PlayerAdded:Connect(load)
game.Players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for i, plr in ipairs(game.Players:GetPlayers()) do
		save(plr)
		print('fired save function')
	end
end)

repStorage.RequestData.OnServerInvoke = function(player)
	return script.playerstats.Coins.Value
end

one:

it will not update, as you are changing a number value, not coins. converting it using tonumber() secures the inability to change it, but im here just to help with saving.

two: try changing the name from table to savedInstance

for i, savedInstance in pairs(data) do

if that doesnt work try saving the name in a tostring()

for i, Value in pairs(player.playerstats:GetChildren()) do
	local Thing = {
		["Name"] = tostring(Value.Name),
		["Value"] = Value.Value
	}
	table.insert(data, Thing)
end

Ohhhhh, I think I may have found the problem.

Testing the game while in studio, I go to players > myName > playerstats but in playerstats, nothing is there! The folder is empty.

Why is this happening?


I did all your steps and I think it would work if there was stuff in the playerstats since the for loops use the playerstats.


update: I just created a DataId value and a Coin value inside of player.playerstats but it still gives me the error: attempt to index number with 'Name'

i do not understand why things are ging wrong

created a place in roblox, copied the script you provided before

local DataStoreService = game:GetService("DataStoreService")
local CoinsStore = DataStoreService:GetDataStore("CoinStore")
local repStorage = game:GetService("ReplicatedStorage")

local tries = 3
local dataloaded = nil

local function save(player)
	--make sure that data has actually loaded in (to not clear by accident)
	if dataloaded then
		local key = player.UserId
		local count = 0

		local data = {}
		
		--adds the name and value of every IntValue in playerstats to the "data" table
		for i, Value in pairs(player.playerstats:GetChildren()) do
			local Thing = {
				["Name"] = Value.Name,
				["Value"] = Value.Value
			}
			table.insert(data, Thing)
		end

		print(data)

		local success, err
		
		---keeps trying to set async until it either succeeds or goes over the allowed tries to save
		repeat
			success, err = pcall(function()
				CoinsStore:SetAsync(key, data)
				print(player.playerstats.Coins.Value)
				print('Saved!')
			end)

			count = count + 1
		until count >= tries or success
		
		-- warns if data could not be set
		if not success then
			warn("Data could not be set: " .. tostring(err))
			return
		end
	else
		-- warns that data has not loaded.
		warn("Data has not been loaded. Do not attempt to set data when it has not been loaded.")

		return
	end
end

local function load(player)
	
	-- creates playerstats
	local playerstats = Instance.new("Folder")
	playerstats.Name = "playerstats"
	playerstats.Parent = player

	print('playerstats created')


	local key = player.UserId
	local count = 0

	local data

	local success, err
	
	-- tries to fetch data from datastore until either succeeds or takes too many attempts
	repeat
		success, err = pcall(function()
			data = CoinsStore:GetAsync(key)
			print('got data')
			print(data)
		end)

		count = count + 1
	until count >= tries or success
	
	-- if didnt fetch data
	if not success then
		warn("Failed to read data." .. tostring(err))
		
		--kicks to force player to try again
		player:Kick("Failed to read data. Please rejoin the game.")

		return
	end
	
	-- if data exists (player left the game, and then rejoined some time later)
	if data then
		-- for data creates values, puts them in playerstats
		
		--[[ saved table looks like this at the point of writing:
		data = {
			[1] = {
				["Name"] = "Coins",
				["Value "] = 3
			}
			[2] = {
				["Name"] = "DataId",
				["Value"] = 10
			}
		}
		keep in mind its random values, yours will be different, cuz you set them yourself, i picked 2 random ones
		]]
		for i, table in pairs(data) do
			local Value = Instance.new("IntValue") -- can be changed to bool value, string value, etc.
			Value.Name = table.Name
			Value.Value = table.Value
			Value.Parent = playerstats
		end
		dataloaded = true
	else
		-- does not exist (player joined first time) so we create instance, set value, and parent
		warn('Player has no data! Creating new data for them.')
		local coins = Instance.new("IntValue")
		coins.Name = "Coins"
		coins.Parent = playerstats

		local dataId = Instance.new("IntValue")
		dataId.Name = "DataId"
		dataId.Value = 0
		dataId.Parent = playerstats 

		dataloaded = true
	end

end

--load when player joins
game.Players.PlayerAdded:Connect(load)

--save when player leaves
game.Players.PlayerRemoving:Connect(save)

--save when game gets shut down
game:BindToClose(function()
	for i, plr in ipairs(game.Players:GetPlayers()) do
		save(plr)
		print('fired save function')
	end
end)

i basically edited the location of playerstats.coins (in the print function)
deleted and wrote in table.insert(data, Thing), and added a dataId.Parent = playerstats that i forgot before. thats it. the saving and loading functions work correctly, create a new topic if your having trouble with other stuff, this isnt so much about saving anymore

also added text hints about what code does

hope i helped!!!

1 Like

tested your script, turned on API services, and everything works fine! it appears that the only thing you were missing was

image

then

image
image

followed by

image

1 Like