Hello! I’m currently working on an obby game with a stage datastore. The datastore below randomly works, and sometimes players lose their progress, what solutions could there be? It makes me feel bad when people tell me that they lose their progress. Important: There’s already data on it, so I need it to still contain data that users already have.
local STAT_NAME = "Stage"
local PREVENT_SKIPPING = true
local dataStore = game:GetService('DataStoreService'):GetDataStore('PlaceHolderName')
local checkpoints = {}
local i = 1
while true do
local checkpoint = game.Workspace:FindFirstChild("Checkpoint " .. i, true)
if not checkpoint then print("Last Checkpoint : " .. i-1) break end
table.insert(checkpoints, checkpoint)
i = i + 1
end
game.Players.PlayerAdded:connect(function(player)
local leaderstats = player:FindFirstChild("leaderstats") or Instance.new("Folder", player)
leaderstats.Name = "leaderstats"
local checkpointStat = Instance.new("IntValue", leaderstats)
checkpointStat.Name = STAT_NAME
checkpointStat.Value = 1
local pointsKey = "player"..player.userId
local savedStuff = dataStore:GetAsync(pointsKey)
if savedStuff then
checkpointStat.Value = savedStuff
else
wait(5)
if savedStuff then
checkpointStat.Value = savedStuff
end
end
player.CharacterAdded:connect(function(character)
local goto = checkpoints[leaderstats.Stage.Value]
if goto then
repeat wait() until character.Parent
character:MoveTo(goto.Position)
else
warn("Checkpoint " .. checkpointStat.Value .. " not found")
end
end)
while true do
wait(30)
dataStore:SetAsync(pointsKey, leaderstats.Stage.Value)
end
end)
for index, checkpoint in ipairs(checkpoints) do
checkpoint.Touched:connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if not player then return end
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then return end
if (PREVENT_SKIPPING and leaderstats.Stage.Value + 1 == index) or (not PREVENT_SKIPPING and leaderstats.Stage.Value < index) then
leaderstats.Stage.Value = index
end
end)
end
game.Players.PlayerRemoving:Connect(function(player)
local pointsKey = "player"..player.userId
local valuesToSave = player.leaderstats.Stage.Value
wait(5)
if not valuesToSave then return end
dataStore:SetAsync(pointsKey, valuesToSave)
end)
Sometimes datastore breaks. Your script is not prepared to handle a datastore failure. So if it dosen’t work once, the script stops working. Here is how to fix it: GetAsync and SetAsync must be allowed failure without erroring the script.
local success,errorcode = pcall(function()
local savedstuff = datastore:GetAsync(pointskey)
end)
if not success then warn(errorcode) end
It will issue a warning if the datastore couldn’t be reached, but others will still get their data.
The problem is that you’re waiting 5 seconds after they’ve left to save. This means the last player to leave the server might not have time to save as the server will shut down. You probably want a BindToClose function that gives your datastores enough time to save, even if it’s something as simple as wait(30).
I recently just answered another topic about how while loops yield the thread meaning code past that wont run. To solve this use spawn or the RunService.Heartbeat event. You could’ve replaced the while loop with a for loop since that’s what you were trying to use it as. Also there’s nothing wrong with waiting 5 seconds before saving however you do need to add a game:BindToClose function. I like to kick all the players in BindToClose as that triggers the PlayerRemoving event. You should add pcalls when saving and loading data. Also, there’s some typos like “userId” which should be “UserId”. Pretty sure it’s case sensitive. I implemented most of the fixes to make sure it works in the code below.
local STAT_NAME = "Stage"
local PREVENT_SKIPPING = true
local dataStore = game:GetService('DataStoreService'):GetDataStore('PlaceHolderName')
local checkpoints = {}
local i = 1
spawn(function()
while true do
local checkpoint = game.Workspace:FindFirstChild("Checkpoint " .. i, true)
if not checkpoint then print("Last Checkpoint : " .. i-1) break end
table.insert(checkpoints, checkpoint)
i = i + 1
end
end)
game.Players.PlayerAdded:connect(function(player)
local leaderstats = player:FindFirstChild("leaderstats") or Instance.new("Folder", player)
leaderstats.Name = "leaderstats"
local checkpointStat = Instance.new("IntValue", leaderstats)
checkpointStat.Name = STAT_NAME
checkpointStat.Value = 1
local pointsKey = "player"..player.UserId
local savedStuff = dataStore:GetAsync(pointsKey)
if savedStuff then
checkpointStat.Value = savedStuff
end
player.CharacterAdded:connect(function(character)
local goto = checkpoints[checkpointStat.Value]
if goto then
repeat wait() until character.Parent
character:MoveTo(goto.Position)
else
warn("Checkpoint " .. checkpointStat.Value .. " not found")
end
end)
while true do
wait(30)
dataStore:SetAsync(pointsKey, leaderstats.Stage.Value)
end
end)
for index, checkpoint in ipairs(checkpoints) do
checkpoint.Touched:connect(function(hit)
local player = game.Players:GetPlayerFromCharacter(hit.Parent)
if not player then return end
local humanoid = hit.Parent:FindFirstChild("Humanoid")
if not humanoid or humanoid.Health <= 0 then return end
local leaderstats = player:FindFirstChild("leaderstats")
if not leaderstats then return end
if (PREVENT_SKIPPING and leaderstats.Stage.Value + 1 == index) or (not PREVENT_SKIPPING and leaderstats.Stage.Value < index) then
leaderstats.Stage.Value = index
end
end)
end
game.Players.PlayerRemoving:Connect(function(player)
local pointsKey = "player"..player.UserId
local valuesToSave = player.leaderstats.Stage.Value
wait(5)
if not valuesToSave then return end
dataStore:SetAsync(pointsKey, valuesToSave)
end)
game:BindToClose(function()
for _, player in pairs(game.Players:GetPlayers()) do
player:Kick("Game shutdown. Your data has been saved!")
end
end)