I’m making something similar to a minigame where when the player finishes the obby, it adds 50 points and 1 win into the player’s leaderstats. I want to make it so that these stats save and load properly whenever a player leaves and rejoins the game.
The stats either don’t save/load properly. For example, before I leave the game, I would have 200 points and 4 wins, but when I rejoin, I would only have 4 points and 4 wins. I tried using a pcall function, and it says that the data saved successfully.
This is the leaderstats script:
local dataStore = game:GetService("DataStoreService"):GetDataStore("Data")
game.Players.PlayerAdded:Connect(function(plr)
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local val = Instance.new("IntValue")
val.Name = "Points"
val.Value = dataStore:GetAsync(plr.UserId) or 0
val.Parent = leaderstats
local val = Instance.new("IntValue")
val.Name = "Wins"
val.Value = dataStore:GetAsync(plr.UserId) or 0
val.Parent = leaderstats
end)
game.Players.PlayerRemoving:Connect(function(plr)
local success, errormessage = pcall(function()
dataStore:SetAsync(plr.UserId, plr.leaderstats.Points.Value)
dataStore:SetAsync(plr.UserId, plr.leaderstats.Wins.Value)
end)
if success then
print ("Player Data successfully saved!")
else
print ("There was an error when saving data.")
warn (errormessage)
end
end)
This is the script that increases the wins and points of the player who finished the obby first:
db = false
script.Parent.Touched:connect(function(hit)
if hit.Parent:FindFirstChild("Humanoid") then
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if db == false then
db = true
player.leaderstats.Wins.Value = player.leaderstats.Wins.Value +1
player.leaderstats.Points.Value = player.leaderstats.Points.Value +50
end
end
end)
I haven’t touched Datastores in a while. But I think I found your issue.
Try setting your values into a dictionary. Then, save the Dictionary with the SetAsync call. From what I can see, it seems you’re overriding the player data with a single value on these lines.
dataStore:SetAsync(plr.UserId, plr.leaderstats.Points.Value) – You’re setting plr.UserId with Point.Value
dataStore:SetAsync(plr.UserId, plr.leaderstats.Wins.Value) – You’re overriding the plr.UserId data with Wins.Value data.
Try this instead.
local data = {Wins = player.leaderstats.Wins.Value , Points = player.leaderstats.Points.Value};
dataStore:SetAsync(plr.UserId, data);
When you load the data, it would be:
local data = dataStore:GetAsync(plr.UserId);
local val = Instance.new("IntValue")
val.Name = "Points"
val.Value = data.Points
val.Parent = leaderstats
val = Instance.new("IntValue")
val.Name = "Wins"
val.Value = data.Wins
val.Parent = leaderstats
Yeah, if he’s overriding the variable, then he will only ever get 1 Value, because it gets overridden, as far as I know. At least to my knowledge, in this case he will have two intvalues with the same name and value, because he has chosen the same name. Unless him defining local Val stopped the script from using the previous val(which it could’ve I’m not too sure)
When he initially creates the first val object. He initializes it with a Instance.new(“IntValue”). In the computer memory, a new 4 byte memory block (Size of an integer in memory) has been reserved for “Points”. When he parents Points to leaderstats, leaderstats will start tracking the object.
Then, he initializes val to a new Instance.new(“IntValue”). So, a new 4 byte memory block is allocated in computer memory. Since leaderstats has the “Points” IntValue parented to it. It will not get picked up by the garbage collector and deleted. However, he does lose the reference to “Points”.
local val = Instance.new("IntValue") -- Creates new IntValue
val.Name = "Points"
val.Value = data.Points
val.Parent = leaderstats -- leaderstats now has this IntValue parented to it.
val = Instance.new("IntValue") -- Loses the reference to the "Points" IntValue and sets it to a new IntValue instance.
val.Name = "Wins"
val.Value = data.Wins
val.Parent = leaderstats -- Parents it to the leaderboard, but still has a reference to the "Wins" IntValue with the val variable
But if he didn’t parent “Points” to the leaderstats, then your argument is valid. “Points” would not have been used by anything and the garbage collector would delete it.
Since he’d be overriding it with “Wins” and would lose the reference to “Points”.
I just found out that I have another problem, the datastore only worked for myself since I already had data before, but when new players join, their leaderstats show up as - for points and - for wins instead of saying 0. I’m looking in the console and it says
ServerScriptService.leaderstats:13: attempt to index nil with ‘Points’
Stack Begin
Script ‘ServerScriptService.leaderstats’, Line 13
Stack End
and when they leave the game, it states, “Wins is not a valid member of Folder”
This is my current script, I might’ve did something wrong:
local dataStore = game:GetService("DataStoreService"):GetDataStore("Data")
game.Players.PlayerAdded:Connect(function(plr)
local data = dataStore:GetAsync(plr.UserId);
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local val = Instance.new("IntValue")
val.Name = "Points"
val.Value = data.Points
val.Parent = leaderstats
local val = Instance.new("IntValue")
val.Name = "Wins"
val.Value = data.Wins
val.Parent = leaderstats
end)
game.Players.PlayerRemoving:Connect(function(plr)
local success, errormessage = pcall(function()
local data = {Wins = plr.leaderstats.Wins.Value , Points = plr.leaderstats.Points.Value};dataStore:SetAsync(plr.UserId, data);
end)
if success then
print ("Player Data successfully saved!")
else
print ("There was an error when saving data.")
warn (errormessage)
end
end)
You still need to check if the joined player has data. If you don’t, the new player will have no data which results in a nil exception.
When a player joins, check if the data is nil by doing the following:
local data = dataStore:GetAsync(plr.UserId);
if (data == nil) then -- Check if the data exists.
data = {Wins = 0 , Points = 0}; -- Default new data values
dataStore:SetAsync(plr.UserId, data); -- Create their first save data, just in case you want to save data later in the game.
end
local leaderstats = Instance.new("Folder")
leaderstats.Name = "leaderstats"
leaderstats.Parent = plr
local val = Instance.new("IntValue")
val.Name = "Points"
val.Value = data.Points
val.Parent = leaderstats
local val = Instance.new("IntValue")
val.Name = "Wins"
val.Value = data.Wins
val.Parent = leaderstats
I recommend placing the data array table as a variable and not inside a function. That way, you can simply change the array and all variable references will point to the same table. Instead of having to change every table manually throughout the code. It’s prone for less error. e.g:
local GameData = {Wins = 0, Points = 0};
game.Players.PlayerAdded:Connect(function(plr)
if (data == nil) then -- Check if the data exists.
data = GameData; -- Default new data values
dataStore:SetAsync(plr.UserId, data); -- Create their first save data, just in case you want to save data later in the game.
end
end