Player data keeps getting reverted / lost

I have no idea why this keeps happening. Players keep complaining about their data being reverted but i see no issues in the script. I thought that maybe that there were too many datastore request. But even after i increased the wait time it still happens.

Here’s the script:

local DataStoreService = game:GetService("DataStoreService")
local playerDataStore = DataStoreService:GetDataStore("BlockDataFR")

local statslist = {
	playerstats = {
		{ Name = "Xp", Class = "NumberValue" },
		{ Name = "Level", Class = "NumberValue" },
		{ Name = "HighestLevel", Class = "NumberValue" },
		{ Name = "Perks", Class = "NumberValue" },
		{ Name = "Blocks", Class = "NumberValue" },
		{ Name = "PBlocks", Class = "NumberValue" },
		{ Name = "Gems", Class = "NumberValue" },
		{ Name = "Steel", Class = "NumberValue" },
		{ Name = "Clicks", Class = "NumberValue" },
		{ Name = "ClicksTier", Class = "NumberValue" },
		{ Name = "ClicksT", Class = "NumberValue" },
		{ Name = "Crystalizes", Class = "NumberValue" },
		{ Name = "Playtime", Class = "NumberValue" },
		{ Name = "TotalCollected", Class = "IntValue" },
		{ Name = "HighestTier", Class = "IntValue" },
		{ Name = "Robux", Class = "IntValue" }
	},
	shopstats = {
		block_shop = {
			{ Name = "BlockShopUPGR1", Class = "IntValue" },
			{ Name = "BlockShopUPGR2", Class = "IntValue" },
			{ Name = "BlockShopUPGR3", Class = "IntValue" }
		},
		gem_shop = {
			{ Name = "GemShopUPGR1", Class = "IntValue" },
			{ Name = "GemShopUPGR2", Class = "IntValue" },
			{ Name = "GemShopUPGR3", Class = "IntValue" },
			{ Name = "GemShopUPGR4", Class = "IntValue" },
			{ Name = "GemShopUPGR5", Class = "IntValue" },
			{ Name = "GemShopUPGR6", Class = "IntValue" },
			{ Name = "GemShopUPGR7", Class = "IntValue" },
		},
		steel_shop = {
			{ Name = "SteelShopUPGR1", Class = "IntValue" },
			{ Name = "SteelShopUPGR2", Class = "IntValue" },
			{ Name = "SteelShopUPGR3", Class = "IntValue" }
		},
		prestige_shop = {
			{ Name = "PrestigeShopUPGR1", Class = "IntValue" },
			{ Name = "PrestigeShopUPGR2", Class = "IntValue" },
			{ Name = "PrestigeShopUPGR3", Class = "IntValue" },
			{ Name = "PrestigeShopUPGR4", Class = "IntValue" },
			{ Name = "PrestigeShopUPGR5", Class = "IntValue" },
		},
		click_shop = {
			{ Name = "ClickShopUPGR1", Class = "IntValue" },
			{ Name = "ClickShopUPGR2", Class = "IntValue" },
			{ Name = "ClickShopUPGR3", Class = "IntValue" },
			{ Name = "ClickShopUPGR4", Class = "IntValue" },
		},
	},
	unlockstats = {
		{ Name = "steel_unlock", Class = "BoolValue" },
		{ Name = "prestige_unlock", Class = "BoolValue" },
		{ Name = "gem_unlock", Class = "BoolValue" }, 
		{ Name = "achievement_unlock", Class = "BoolValue" },
		{ Name = "click_unlock", Class = "BoolValue" }, 
		{ Name = "crystalize_unlock", Class = "BoolValue" }, 
	},
	achievements = {
		{ Name = "Achievement1", Class = "IntValue" },
		{ Name = "Achievement2", Class = "IntValue" },
		{ Name = "Achievement3", Class = "IntValue" },
	},
	leaderboard = {
		{ Name = "Placement", Class = "IntValue" }
	},
	gamepassesstats = {
		{ Name = "Gamepass1", Class = "BoolValue" },
		{ Name = "Gamepass2", Class = "BoolValue" },
		{ Name = "Gamepass3", Class = "BoolValue" },
		{ Name = "Gamepass4", Class = "BoolValue" },
		{ Name = "Premium", Class = "BoolValue" }
	}
}

local playersPendingSave = {}

-- Helper function to save player data
local function savePlayerData(plr)
	playersPendingSave[plr.UserId] = true -- Add player to pending save list

	local playerstats = plr:FindFirstChild('playerstats')
	local shopstats = playerstats:FindFirstChild('shopstats')
	local unlockstats = playerstats:FindFirstChild('unlockstats')
	local gamepassesstats = playerstats:FindFirstChild('gamepassesstats')
	local achievements = playerstats:FindFirstChild('achievements')
	local leaderboard = playerstats:FindFirstChild('leaderboard')

	-- Helper function to save values
	local function saveValues(statsTable, parent)
		local savedData = {}
		for _, stat in ipairs(statsTable) do
			savedData[stat.Name] = parent[stat.Name].Value
		end
		return savedData
	end

	local dataToSave = {}
	-- Save player stats
	for key, value in pairs(saveValues(statslist.playerstats, playerstats)) do
		dataToSave[key] = value
	end

	-- Save shop stats
	for shopName, shopValues in pairs(statslist.shopstats) do
		dataToSave[shopName] = saveValues(shopValues, shopstats[shopName])
		for key, value in pairs(dataToSave[shopName]) do
			dataToSave[key] = value
		end
	end

	-- Save unlock stats
	for key, value in pairs(saveValues(statslist.unlockstats, unlockstats)) do
		dataToSave[key] = value
	end

	for key, value in pairs(saveValues(statslist.leaderboard, leaderboard)) do
		dataToSave[key] = value
	end

	-- Save gamepasses stats
	for key, value in pairs(saveValues(statslist.gamepassesstats, gamepassesstats)) do
		dataToSave[key] = value
	end

	-- Save achievements stats
	for key, value in pairs(saveValues(statslist.achievements, achievements)) do
		dataToSave[key] = value
	end

	local success, errorMessage = pcall(function()
		playerDataStore:SetAsync(plr.UserId, dataToSave)
	end)

	if not success then
		warn("Failed to save data: " .. errorMessage)
	else
		playersPendingSave[plr.UserId] = nil -- Remove player from pending save list
	end
end

game.Players.PlayerAdded:Connect(function(plr)
	local leaderstats = Instance.new('Folder') 
	leaderstats.Name = 'leaderstats' 
	leaderstats.Parent = plr

	local playerstats = Instance.new('Folder') 
	playerstats.Name = 'playerstats' 
	playerstats.Parent = plr

	local shopstats = Instance.new('Folder') 
	shopstats.Name = 'shopstats' 
	shopstats.Parent = playerstats

	local unlockstats = Instance.new('Folder') 
	unlockstats.Name = 'unlockstats' 
	unlockstats.Parent = playerstats

	local leaderboard = Instance.new('Folder') 
	leaderboard.Name = 'leaderboard' 
	leaderboard.Parent = playerstats

	local gamepassesstats = Instance.new('Folder') 
	gamepassesstats.Name = 'gamepassesstats' 
	gamepassesstats.Parent = playerstats

	local achievements = Instance.new('Folder') 
	achievements.Name = 'achievements' 
	achievements.Parent = playerstats

	-- Define all stats in tables

	-- Helper function to create values
	local function createValues(parent, values)
		for _, value in ipairs(values) do
			local newValue = Instance.new(value.Class)
			newValue.Name = value.Name
			newValue.Parent = parent
		end
	end

	-- Create player stats
	createValues(playerstats, statslist.playerstats)

	-- Create shop stats
	for shopName, shopValues in pairs(statslist.shopstats) do
		local shopFolder = Instance.new('Folder')
		shopFolder.Name = shopName
		shopFolder.Parent = shopstats
		createValues(shopFolder, shopValues)
	end

	-- Create unlock stats
	createValues(unlockstats, statslist.unlockstats)
	createValues(leaderboard, statslist.leaderboard)

	-- Create gamepasses stats
	createValues(gamepassesstats, statslist.gamepassesstats)

	createValues(achievements, statslist.achievements)
	-- Load the player data
	local success, data = pcall(function()
		return playerDataStore:GetAsync(plr.UserId)
	end)

	if success and data then
		for _, stat in ipairs(statslist.playerstats) do
			playerstats[stat.Name].Value = data[stat.Name] or '0'
		end

		for shopName, shopValues in pairs(statslist.shopstats) do
			for _, shopValue in ipairs(shopValues) do
				shopstats[shopName][shopValue.Name].Value = data[shopValue.Name] or 0
			end
		end

		for _, stat in ipairs(statslist.unlockstats) do
			unlockstats[stat.Name].Value = data[stat.Name] or false
		end

		for _, stat in ipairs(statslist.gamepassesstats) do
			gamepassesstats[stat.Name].Value = data[stat.Name] or false
		end

		for _, stat in ipairs(statslist.achievements) do
			achievements[stat.Name].Value = data[stat.Name] or 0
		end
	end

	-- Increment playtime every second
	coroutine.wrap(function()
		while wait(1) do
			playerstats.Playtime.Value += 1
		end
	end)()

	-- Automatic saving every 300 seconds
	coroutine.wrap(function()
		while wait(400) do
			savePlayerData(plr)
		end
	end)()
end)

game.Players.PlayerRemoving:Connect(function(plr)
	savePlayerData(plr)
end)

game:BindToClose(function()
	-- Wait for all players' data to be saved
	for _, plr in pairs(game.Players:GetPlayers()) do
		if playersPendingSave[plr.UserId] then
			repeat wait() until not playersPendingSave[plr.UserId]
		end
	end

	-- Save all remaining players' data (in case they didn't trigger PlayerRemoving)
	for _, plr in pairs(game.Players:GetPlayers()) do
		savePlayerData(plr)
	end
end)
1 Like

It COULD be because of too many request because you are saving quite a few stats. You seem to be using pcalls which is good, i would say add print functions everywhere and join a running server and just check the output and see if certain functions arent running or something.

In the end, i really dont know because pcalls are supposed to handle this stuff

1 Like

You have a ton of values and keys which is quite the toll on your data store. Consider ways to reduce amount of data you need in your game or perhaps consult a second one incase you need all of them

1 Like

You’re using SetAsync which has no regard for previous data.

Imagine a scenario:

  1. Player joins server A
  2. Server A loads player’s data
  3. Player’s data changes in that server
  4. Player leaves Server A, triggering a save
  5. Server A starts saving Player’s data, but it is taking some time
  6. Before Server A finishes saving the Player’s data, Player joins a new server: Server B
  7. Server B loads player’s data which is the not-yet-updated copy of it
  8. Server A finally finishes saving, updating the data but it’s too late because Server B already loaded outdated data
  9. Player leaves server B because they see they lost data, triggering a save
  10. Server B saves their oudated copy of the data, overwriting the newer data that Server A had saved with outdated data

The remedy to this is using Session locking. I found a community tutorial for it here: Session locking explained (Datastore)

But other community modules like ProfileService handle this out of the box if you want a ready-made solution.

1 Like

Added a 3 second delay before i load in the player data, this seems to have fixed it. I’ll look into session locking later. Thank you

2 Likes

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.