My obby datastore sometimes breaks and I don't know why

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)
1 Like

Not sure what’s the reason, but you might be interested in using datastore2.

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).

3 Likes

I’d try to do that, but I don’t understand how to make that work into the script.

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)
1 Like