Leaderstat DataSaves Not Working Properly

Hello, I am trying to make a points and wins system, but whenever I try and save it using the script it breaks. It sets them at the same number, and the cash per second doesn’t work anymore. If anyone could help me and look into it I would be greatly thankful.

I just started getting serious into scripting lately, and sorry if the script looks like free model scripts and random stuff I learned because it is lol.

game.Players.PlayerAdded:Connect(function(p)
	local datastore = game:GetService("DataStoreService")--Gets the service
	local ds1 = datastore:GetDataStore("DataSaver")
	
	local leaderstats = Instance.new("IntValue", p)
	leaderstats.Name = "leaderstats"

	local wins = Instance.new("IntValue", leaderstats)
	wins.Name = "Wins"

	local Points = Instance.new("IntValue", leaderstats)
	Points.Name = "Points"
	local x = wins.Value
	Points.Value = 0
	
	Points.Value = ds1:GetAsync(p.UserId) or 0--Loads the money
	ds1:SetAsync(p.UserId, Points.Value)
	

	wins.Value = ds1:GetAsync(p.UserId) or 0--Loads the money
	ds1:SetAsync(p.UserId, wins.Value)
	
	Points.Changed:connect(function()--Saves the money
		ds1:SetAsync(p.UserId, Points.Value)	
	end)
		
		
		wins.Changed:connect(function()--Saves the money
			ds1:SetAsync(p.UserId, wins.Value)	
			
	while true do
		Points.Value += 1 + wins.Value
		task.wait(1)
	end
end)

for i,v in pairs(game.Workspace.Doors:GetChildren()) do

	v.Touched:Connect(function(hit)
	if hit.Parent and hit.Parent:FindFirstChild("HumanoidRootPart") then
		local value = v.Value
		local player = game.Players:GetPlayerFromCharacter(hit.Parent)
		local leaderstats = player.leaderstats
		local points = leaderstats.Points
		if points.Value - value.Value >= 5 and v.Open.Value == true then

			points.Value -= value.Value
			game.ReplicatedStorage.PointSubtraction:FireClient(player,v)
			v.Open.Value = false
			wait(1)
			v.Open.Value = true
		end
	end
	end)
	end
end)

Hi Collin, do not listen to Sea_KidTwelve as most of his responses are either random or automatically generated by ChatGPT.

As for your issue, you are saving the money right after loading them:

wins.Value = ds1:GetAsync(p.UserId) or 0 --Loads the money
ds1:SetAsync(p.UserId, wins.Value) -- Saves the money?

You should save the stat under game.Players.PlayerRemoved:Connect(function(p) end) instead.

I think I can go ahead and instruct you on how Data Stores should work, there is an actual Documentation for it as well if you’d like to research about it but I’ll go ahead & explain the issues with your code here if that’s fine :L

The Whole Explanation & Process

Within your first lines here, you keep obtaining the DataStoreService whenever a Player joins the game which isn’t exactly good practice, where instead you should define your datastore & ds1 variables outside the function so that we can easily obtain it within our script:

local datastore = game:GetService("DataStoreService")--Gets the service
local ds1 = datastore:GetDataStore("DataSaver")
local AutoSaveInterval = 60

Now I went ahead and added in a new variable here, but I’ll explain that later when I get to it

Next up, please do not put the Instance.new(“Object”, Parent) within your script as it actually takes up more performance then what you’d originally expect, but do make sure to go ahead and set the .Parent property last after changing & setting all your Properties:

local leaderstats = Instance.new("IntValue")
leaderstats.Name = "leaderstats"
leaderstats.Parent = p

local wins = Instance.new("IntValue")
wins.Name = "Wins"
wins.Parent = leaderstats

local Points = Instance.new("IntValue")
Points.Name = "Points"
Points.Value = 0
Points.Parent = leaderstats

Next up on the list, are your Data Store functions. It’s highly recommended to encase a function that may return back nil in something that’s called a pcall() function (Or protective calls), as these ensure that your script doesn’t fully error so you can prepare for situations like lost data & such

What you’re trying to do, is assume that ds1:GetAsync/ds1:SetAsync is fine when calling it, but it’s better to be prepared rather than not, so we’ll go ahead and add 2 functions that I included which is capable of handling DataStore functions:

local function SaveData(Plr, Data)
	local success, whoops = pcall(function()
		ds1:SetAsync(Plr.UserId, Data)
	end)
	
	return success
end

local function LoadData(Plr)
	local Data = nil
    local success, whoops = pcall(function()
	    Data = ds1:GetAsync(Plr.UserId)
	end)
	
	if success then
		print("Loaded data successfully!")
	else
		warn("There was an error retrieving the Player's Data!", whoops)
	    return false
	end
	
	return Data
end

In our scenario here, we want to encase our data stores within pcall functions so that they don’t throw back an error in our Console, and we can add a response whether or not if the data has been loaded or not

It isn’t necessary to have these functions exactly, but it’s definitely more organized & you can easily reference it this way, and both of these functions will return something back as a result

In case you don’t know what that is, return is simply just returning a value back from where we originally called our function from (With our provided parameters), it’s kinda like a Yard Sale if you think about it here:

You want to pay for something within the Yard, and you get the specified item back in return

  • Paying: $50.00 (SaveData(PlrObject, DataTable))
  • The Result of Paying: Teddy Bear (return DataResult)

Next up in our PlayerAdded Event, we want to call our LoadData() function that we just created, and reference it with a Player object so that we know what object we’re specifying for exactly:

local leaderstats = Instance.new("IntValue")
leaderstats.Name = "leaderstats"
leaderstats.Parent = p 
	
local wins = Instance.new("IntValue")
wins.Name = "Wins"
wins.Parent = leaderstats

local Points = Instance.new("IntValue")
Points.Name = "Points"
Points.Parent = leaderstats
		
local Data = LoadData(p)
	
if Data then
	Points.Value = Data.Points
	wins.Value = Data.Wins
else
	Points.Value = 0
	wins.Value = 0
end

And we’ll go ahead to focus on each part specifically on how our LoadData function will work:

local function LoadData(Plr) -- This is our Player that we tranferred over to ( LoadData(p) )
	local Data = nil -- We create a variable for our Data, so that we can check if it's equal to `nil` or not
    local success, whoops = pcall(function() -- We encase this in a pcall function here
	    Data = ds1:GetAsync(Plr.UserId) -- Call GetAsync()
	end)
	
	if success then -- "success" will return back true or false if the `GetAsync()` function properly worked, or if it returned back an error
		print("Loaded data successfully!")
	else
		warn("There was an error retrieving the Player's Data!", whoops)
	    return false -- An error occured, whoops! We want to return this value back as "false" and this will actually stop this function so that "return Data" will not run!
	end
	
	return Data -- We found data, now we can return this back!
end

Printing is 1 of the most important debugging features to have, make sure to use it we whenever you can! :wink:

Moving on though, I highly suggest not to do this, especially since you have a loop going on that increases the Point.Value by +1 per second, because this will result in DataStore requests queueing up your ds1, which’ll start to throttle your Data Stores and you should do them not that frequently, & most effective when possible

And now comes the next section here, we want to create another loop (After your v.Touched loop) that’ll auto-save every Player’s individual data depending how often we want it to save

Remember that AutoSaveInterval variable we defined earlier ago? Well, it’s time to use that which’ll estimate our Auto Save rate:

-- Now here, we want to implement a loop that'll autosave our Data every 60 seconds:
while true do
	task.wait(AutoSaveInterval)

	for _, Plr in pairs(game.Players:GetPlayers()) do 
		local leaderstats = Plr:WaitForChild("leaderstats")
		local PlrWins = leaderstats:WaitForChild("Wins")
		local PlrPoints = leaderstats:WaitForChild("Points")
		
		local DataTable = {
			Wins = PlrWins.Value,
			Points = PlrPoints.Value,
		}
		
		local SaveResult = SaveData(Plr, DataTable)
		
		if SaveResult == true then
		    print(Plr.Name.."'s Data has been saved successfully!")
		end
	end

end

Now what we’re doing here, is obtaining all our Players using a function called GetPlayers() through another loop, converting our Data into a table which possesses the ability to store multiple values into 1 variable here, and since we define local DataTable inside our secondary loop (Not our first one, which is while true do!), we call our SaveData() function and create it into a variable (SaveResult), so we have to specify our 2 parameters which are:

  • The Player Object
  • The Data Table

Now SaveResult can return back as a bool value (Or true/false), and we can check if SaveResult == true to respond back with a success print if it saved!

Overall, our script should look like this in its full entirety!

local datastore = game:GetService("DataStoreService")
local ds1 = datastore:GetDataStore("DataSaver")
local AutoSaveInterval = 60 -- This is in seconds, so change this to whatever you want

local function SaveData(Plr, Data)
	local success, whoops = pcall(function()
		ds1:SetAsync(Plr.UserId, Data)
	end)
	
	return success
end

local function LoadData(Plr)
	local Data = nil
    local success, whoops = pcall(function()
	    Data = ds1:GetAsync(Plr.UserId)
	end)
	
	if success then
		print("Loaded data successfully!")
	else
		warn("There was an error retrieving the Player's Data!", whoops)
	    return false
	end
	
	return Data
end

game.Players.PlayerAdded:Connect(function(p)
	local leaderstats = Instance.new("IntValue")
	leaderstats.Name = "leaderstats"
	leaderstats.Parent = p 
	
	local wins = Instance.new("IntValue")
	wins.Name = "Wins"
	wins.Parent = leaderstats

	local Points = Instance.new("IntValue")
	Points.Name = "Points"
	Points.Parent = leaderstats
		
	local Data = LoadData(p)
	
	if Data then
	    Points.Value = Data.Points
		wins.Value = Data.Wins
	else
		Points.Value = 0
		wins.Value = 0
	end

	while true do
		Points.Value += 1 + wins.Value
		task.wait(1)
	end
end)

for i,v in pairs(game.Workspace.Doors:GetChildren()) do
	v.Touched:Connect(function(hit)
		if hit.Parent and hit.Parent:FindFirstChild("HumanoidRootPart") then
			local value = v.Value
			local player = game.Players:GetPlayerFromCharacter(hit.Parent)
			local leaderstats = player.leaderstats
			local points = leaderstats.Points
			
			if points.Value - value.Value >= 5 and v.Open.Value == true then

				points.Value -= value.Value
				game.ReplicatedStorage.PointSubtraction:FireClient(player,v)
				v.Open.Value = false
				wait(1)
				v.Open.Value = true
			end
		end
	end)
end

-- Now here, we want to implement a loop that'll autosave our Data every 60 seconds:
while true do
	task.wait(AutoSaveInterval)

	for _, Plr in pairs(game.Players:GetPlayers()) do 
		local leaderstats = Plr:WaitForChild("leaderstats")
		local PlrWins = leaderstats:WaitForChild("Wins")
		local PlrPoints = leaderstats:WaitForChild("Points")
		
		local DataTable = {
			Wins = PlrWins.Value,
			Points = PlrPoints.Value,
		}
		
		local SaveResult = SaveData(Plr, DataTable)
		
		if SaveResult then
		    print(Plr.Name.."'s Data has been saved successfully!")
		end
	end

end

I definitely hope though that you’ve learned something from all of this, and that you can take note on what the best practices are! :slight_smile:

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.