My leaderstats aren't saving and i've tried everything so I need some help
local datastore = game:GetService("DataStoreService") local ds1 = datastore:GetDataStore("SaveCashSystem") local ds2 = datastore:GetDataStore("SaveCoffeeSystem") game.Players.PlayerAdded:Connect(function(player) local leaderstats = Instance.new("Folder", player) leaderstats.Name = "leaderstats" local money = Instance.new("IntValue", leaderstats) money.Name = "Money" local totalcoffee = Instance.new("IntValue", leaderstats) totalcoffee.Name = "Total Coffee" money.Value = ds1:GetAsync(player.UserId) or 0 ds1:SetAsync(player.UserId, money.Value) totalcoffee.Value = ds2:GetAsync(player.UserId) or 0 ds2:SetAsync(player.UserId, totalcoffee.Value) totalcoffee.Changed:Connect(function(changed) ds2:SetAsync(player.UserId, totalcoffee.Value) print("Saved " .. totalcoffee.Value .. " coffees!") end) money.Changed:Connect(function(changed) ds1:SetAsync(player.UserId, money.Value) end) end) game.Players.PlayerRemoving:Connect(function(player) end)
There's much to go through and explain in this DataStore script that needs to be said since I've seen many individuals have been loading data this way. Thankfully my explanation follows a lot of Roblox's own article on the RobloxDev site.
I see a problem when needing more than one DataStore. DataStores give you many options like saving to different scopes but your case could be done an easier way.
DataStores become even easier to use when you understand that they can save tables. Even subtables within the table for some data organization. This is useful to know because you use Folders and IntValues to show the player the data they have. All we need to do is convert them to a table.
--Defined services with reasonable names local Players = game:GetService("Players") local DSS = game:GetService("DataStoreService") --Our DataStore we save everything to local LeaderboardStore = DSS:GetDataStore("Leaderboards") Players.PlayerAdded:Connect(function(player) local playerData --Will be set to any data player has as a table local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local money = Instance.new("IntValue") money.Name = "Money" money.Parent = leaderstats local totalcoffee = Instance.new("IntValue") totalcoffee.Name = "Total Coffee" totalcoffee.Parent = leaderstats local key = player.UserId.."_data" --We need a personal key for the joining player. --You should know what this is for already. --This part is explained some below local success, _error = pcall(function() LeaderboardStore:UpdateAsync(key, function(data) playerData = data end) end) if success then if playerData then money.Value = --set to the values in 'playerData' table totalcoffee.Value = --set to the values in 'playerData' table else --If no data was found then we set default values. money.Value = 0 totalcoffee.Value = 0 end else --DataStores requests fail sometimes so we could warn the player here warn("Data for player could not be loaded.") end end)
After creating the custom key for the player, UpdateAsync is used here to set our playerData
variable to any previous data the player may have. This is also wrapped in a pcall()
function to check if calling for the data fails for some reason.
Below that we use the first result of the pcall, success variable of type boolean, to check if the DataStore call had worked. If it didn't, then we could handle the player differently. If success was true then it means the DataStore did work but it doesn't really mean that the player had any data to start.
Your original code does this by saving (By using :SetAsync()
) everytime the IntValues change through the .Changed()
event. This is NEVER the way you want to go about saving because you will exceed data store limits. You could read on that here.
Instead you should only save at important moments or use some interval. A point you should always save that I will touch on is when the player leaves through PlayerRemoving
First we need to convert our 'leaderstats' folder to a table that will serve as the data to be saved. We can do this with a new function with a generic for-loop:
--This function will be passed the player as an argument local function requestPlayerData(player) local data = {} local folder = player.leaderstats for _, object in pairs(folder:GetChildren()) do if object:IsA("IntValue") then data[object.Name] = object.Value end end return data end
We define a table that we will write our IntValues to using the Name as the key and Value as the value. We then return that table once it's done.
We then can continue with the PlayerRemoving:
Players.PlayerRemoving:Connect(function(player) local key = player.UserId.."_data" --We call our function and pass the player to get player stats in table form local dataToSave = requestPlayerData(player) --pcall again to catch errors. local success, _error = pcall(function() LeaderboardStore:SetAsync(key, dataToSave) end) end)
When we call the requestPlayerData()
function, we are returned with a proper table that the DataStore can use to save. Almost that simple.
We then bring all the code together to get a more secure DataStore system:
local Players = game:GetService("Players") local DSS = game:GetService("DataStoreService") local LeaderboardStore = DSS:GetDataStore("Leaderboards") local function requestPlayerData(player) local data = {} local folder = player.leaderstats for _, object in pairs(folder:GetChildren()) do if object:IsA("IntValue") then data[object.Name] = object.Value end end return data end Players.PlayerAdded:Connect(function(player) local playerData; local leaderstats = Instance.new("Folder") leaderstats.Name = "leaderstats" leaderstats.Parent = player local money = Instance.new("IntValue") money.Name = "Money" money.Parent = leaderstats local totalcoffee = Instance.new("IntValue", leaderstats) totalcoffee.Name = "Total Coffee" totalcoffee.Parent = leaderstats local key = player.UserId.."_Data" local success, _error = pcall(function() LeaderboardStore:UpdateAsync(key, function(data) playerData = data end) end) if success then if playerData then money.Value = playerData[money.Name] totalcoffee.Value = playerData[totalcoffee.Name] else money.Value = 0 totalcoffee.Value = 0 end else warn("Data could not be loaded for "..player.Name) end end) Players.PlayerRemoving:Connect(function(player) local key = player.UserId.."_data" local dataToSave = requestPlayerData(player) local success, _error = pcall(function() LeaderboardStore:SetAsync(key, dataToSave) end) end)
The last thing I had added to the above code are lines 44 and 45. If any data retrieved is found for the player then the playerData
table can be indexed using the Names of our IntValues just like we had it set up in our requestPlayerData()
function!
That wraps up my explanation in using tables to save and when not to save for DataStores. This might be pretty lengthy so do make sure to read the articles I had linked.
Thank you!