wait() waits for 1/30 (0.03 seconds) which is sometimes not enough for data to save, you have 2 solutions here, set bigger number to wait in BindToClose function, to 2 for example or put another SetAsync into the function as sometimes not even wait(2) is enough:
local Players = game:GetService('Players')
...
game:BindToClose(function()
for _, player in ipairs(Players:GetPlayers()) do
data:SetAsync(player.UserId, player.Stats.Wins.Value)
end
end)
That way you will save your data twice if you leave and the game closes, you might get a warning that key has been queued, you don't need to worry about it but if you don't like it, you can create table of players who's data has been saved and in BindToClose check if player is not in the table, example like
local datastoreservice = game:GetService("DataStoreService")
local data = datastoreservice:GetDataStore("Stats")
local Saved = {}
game.Players.PlayerRemoving:Connect(function(player)
data:SetAsync(player.UserId, player.Stats.Wins.Value)
Saved[player] = true
end)
game:BindToClose(function()
for _, player in ipairs(Players:GetPlayers()) do
if not (Saved[player]) then
data:SetAsync(player.UserId, player.Stats.Wins.Value)
end
end
end)
Hover that does not have to always work, here is where you can (But don't have to) use your wait, it will make sure that player has been added to the table in most cases
...
game:BindToClose(function()
wait(0.05)
for _, player in ipairs(Players:GetPlayers()) do
if not (Saved[player]) then
data:SetAsync(player.UserId, player.Stats.Wins.Value)
end
end
end)
That should already work but, your data WILL be lost sometimes and it will happen a lot, trust me, in my game before i used better function i will show now, i used SetAsync and from 1000 joins i lost my data 7 times, sometimes even twice in a day, so this is why you switch SetAsync on UpdateAsync, it is not much different from SetAsync but it returns old data so you can check if new data is not corrupted, here is example of it:
game.Players.PlayerRemoving:Connect(function(player)
local NewData = player.Stats.Wins.Value
data:UpdateAsync(player.UserId, function(OldData)
if (OldData) and (tonumber(OldData)) then
if (OldData > NewData) then
return nil
end
end
return NewData
end)
Saved[player] = true
end)
It takes function as a second argument and whatever the function returns, will be saved, if it returns nil, nothing will be saved and it won't override previous data, in this example, you probably never want Wins to be lowered so it checks if previous data saved Wins is not higher than the data you are about to save and if it is then something is wrong with the new data so it will return nil. At the end it will return NewData because if nothing else returned, then it's all good. It also checks if OldData is a number to prevent errors in some cases.
I also see you saving data without pcalls, it's not that important in your case but it's better to have it, pcalls run given function and if the function errors, it returns the error and stops the function instead of stopping the whole script/scope from running. UpdateAsync and all other data store functions can fail, so you would wrap it into a pcall like this:
game.Players.PlayerRemoving:Connect(function(player)
local NewData = player.Stats.Wins.Value
local Success, Response = pcall(data.UpdateAsync, data, player.UserId, function(OldData)
if (OldData) and (tonumber(OldData)) then
if (OldData > NewData) then
return nil
end
end
return NewData
end)
print(Success, Response)
if (Success) then
Saved[player] = true
end
end)
pcall returns #1 Success (Whether the function did not error) and #2 Error? (Returns error if the function did error). In this case, you see i used data.UpdateAsync, data as 2 arguments, that's because data:UpdateAsync is called with : which means it requires self as it's first argument, since we called it with ., you need to put self argument manually, in this case just put data into next argument. The whole script at the end might look somewhat like this:
local datastoreservice = game:GetService("DataStoreService")
local data = datastoreservice:GetDataStore("Stats")
local Saved = {}
local function Save(player)
local NewData = player.Stats.Wins.Value
local Success, Response = pcall(data.UpdateAsync, data, player.UserId, function(OldData)
if (OldData) and (tonumber(OldData)) then
if (OldData > NewData) then
return nil
end
end
return NewData
end)
print(Success, Response)
if (Success) then
Saved[player] = true
end
end
game.Players.PlayerRemoving:Connect(function(player)
Save(player)
end)
game:BindToClose(function()
wait(0.05)
for _, player in ipairs(Players:GetPlayers()) do
if not (Saved[player]) then
Save(player)
end
end
end)
To not rewrite the saving function, i put it into Save local function. I tried to not change your script much so if you will try, you can achieve better function but this should work too.
Here are all the sources:
###Pcalls
###UpdateAsync
###pairs
1 comment
Thanks again!