My datastore is giving players random stats, and there is lots of data loss

I need my data store to stop giving random stats and ensure data loss does not happen. I recently released a game and a lot of the players complained about Dataloss and them being given random stats

local x = require(game.ReplicatedStorage.EternityNum)
local Data = game:GetService("DataStoreService"):GetDataStore("ButtonSim2")
game.Players.PlayerAdded:Connect(function(plr)
	
	local s = Instance.new("Folder", plr)
	s.Name = "Stats"
	local ls = Instance.new("Folder", plr)
	ls.Name = "leaderstats"
	
	local money = Instance.new("StringValue", s)
	money.Name = "Money"
	money.Value = 0
	local multi = Instance.new("StringValue", s)
	multi.Name = "Multi"
	multi.Value = 1
	local reb = Instance.new("StringValue", s)
	reb.Name= "Rebirth"
	reb.Value = 0
	
	local lvl = Instance.new("StringValue",s)
	lvl.Name = "Level"
	lvl.Value = "0"
	
	local sup = Instance.new("StringValue", s)
	sup.Name= "SuperRebirth"
	sup.Value = 0
	
	--RUNES
	
	local com = Instance.new("StringValue", s)
	com.Name= "Common"
	com.Value = 0
	local ucom = Instance.new("StringValue", s)
	ucom.Name= "Uncommon"
	ucom.Value = 0
	local rare = Instance.new("StringValue", s)
	rare.Name= "Rare"
	rare.Value = 0
	local epic = Instance.new("StringValue", s)
	epic.Name= "Epic"
	epic.Value = 0
	local leg = Instance.new("StringValue", s)
	leg.Name= "Legendary"
	leg.Value = 0
	
	--Geodes
	
	local dirt = Instance.new("StringValue", s)
	dirt.Name= "Dirt"
	dirt.Value = 0
	local stone = Instance.new("StringValue", s)
	stone.Name= "Stone"
	stone.Value = 0
	local diamond = Instance.new("StringValue", s)
	diamond.Name= "Diamond"
	diamond.Value = 0
	
	--Reset Layers
	
	local ascension = Instance.new("StringValue", s)
	ascension.Name= "Ascension"
	ascension.Value = 0
	
	--Passes
	
	local statmast = Instance.new("StringValue", s)
	statmast.Name= "StatMaster"
	statmast.Value = 0
	
	local vip = Instance.new("StringValue", s)
	vip.Name= "VIP"
	vip.Value = 0
	
	local mvp = Instance.new("StringValue", s)
	mvp.Name= "MVP"
	mvp.Value = 0
	
	local runemast = Instance.new("StringValue", s)
	runemast.Name= "RuneMaster"
	runemast.Value = 0
	
	local geodemast = Instance.new("StringValue", s)
	geodemast.Name= "GeodeMaster"
	geodemast.Value = 0
	
	--Leaderboard Stats
	
	local runesopened = Instance.new("StringValue", s)
	runesopened.Name= "RunesOpened"
	runesopened.Value = 0
	local geodesopened = Instance.new("StringValue", s)
	geodesopened.Name= "GeodesOpened"
	geodesopened.Value = 0
	
	local dataload = Data:GetAsync(tostring(plr.UserId))
	if dataload then
		money.Value = dataload[1] or 0
		multi.Value = dataload[2] or 1
		reb.Value = dataload[3] or 0
		com.Value = dataload[4] or 0
		ucom.Value = dataload[5] or 0
		rare.Value = dataload[6] or 0
		epic.Value = dataload[7] or 0
		leg.Value = dataload[8] or 0
		dirt.Value = dataload[9] or 0
		stone.Value = dataload[10] or 0
		diamond.Value = dataload[11] or 0
		ascension.Value = dataload[12] or 0
		statmast.Value = dataload[13] or 0
		lvl.Value = dataload[14] or 0
		sup.Value = dataload[15] or 0
		vip.Value = dataload[16] or 0
		mvp.Value = dataload[17] or 0
		runemast.Value = dataload[18] or 0
		geodemast.Value = dataload[19] or 0
		runesopened.Value = dataload[20] or 0
		geodesopened.Value = dataload[21] or 0
	end
	
	local MoneyBoosts = {
		{Value = "Rebirth", Function = "Multiply", Number1 = {1,0}},
		{Value = "Common", Function = "Multiply", Number1 = {0.001,0}},
		{Value = "Uncommon", Function = "Multiply", Number1 = {0.005,0}},
		{Value = "Rare", Function = "Multiply", Number1 = {0.01,0}},
		{Value = "Epic", Function = "Multiply", Number1 = {0.05,0}},
		{Value = "Legendary", Function = "Multiply", Number1 = {0.5,0}},
		{Value = "VIP", Function = "Multiply", Number1 = {1.5,0}},
		{Value = "MVP", Function = "Multiply", Number1 = {2,0}},
		{Value = "StatMaster", Function = "Multiply", Number1 = {3,0}},
	}
	
	local function CalculateMultiplier(Element)
		local array = MoneyBoosts[Element]
		
		if array.Function == "Multiply" then
			return x.add(x.mul(x.convert(s[array.Value].Value), array.Number1), {1,0})
		elseif array.Function == "Power" then
			return x.add(x.pow(x.convert(s[array.Value].Value), array.Number1), {1,0})
		end
	end
	
	local function CalculateMoney()
		local val = x.convert(0.5)
		val = x.mul(val, x.convert(multi.Value))
		
		for i = 1, #MoneyBoosts do
			val = x.mul(val, CalculateMultiplier(i))
		end
		
		local svb = x.convert(money.Value)
		svb = x.add(svb,val)
		money.Value = x.bnumtostr(svb)
		
	end
	
	local function CalculateLevel(TableOfStats, Outcome)
		
		for i, v in pairs(TableOfStats) do
			Outcome = x.add(Outcome, x.mul(x.log10(x.add(v.Stat, {1, 0})), v.MultiplicationValue))
		end
		return Outcome
		
	end
	while wait() do
		CalculateMoney()
		lvl.Value = x.bnumtostr(CalculateLevel({
			{Stat = x.convert(money.Value), MultiplicationValue = {2,0}},
			{Stat = x.convert(multi.Value), MultiplicationValue = {4,0}},
			{Stat = x.convert(reb.Value), MultiplicationValue = {8,0}},
		}, 0))
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	Data:SetAsync(tostring(plr.UserId),{
		plr.Stats.Money.Value,
		plr.Stats.Multi.Value,
		plr.Stats.Rebirth.Value,
		plr.Stats.Common.Value,
		plr.Stats.Uncommon.Value,
		plr.Stats.Rare.Value,
		plr.Stats.Epic.Value,
		plr.Stats.Legendary.Value,
		plr.Stats.Dirt.Value,
		plr.Stats.Stone.Value,
		plr.Stats.Diamond.Value,
		plr.Stats.Ascension.Value,
		plr.Stats.StatMaster.Value,
		plr.Stats.SuperRebirth.Value,
		plr.Stats.VIP.Value,
		plr.Stats.MVP.Value,
		plr.Stats.RuneMaster.Value,
		plr.Stats.GeodeMaster.Value,
		plr.Stats.RunesOpened.Value,
		plr.Stats.GeodesOpened.Value,
		plr.Stats.Level.Value,})
end)

4 Likes
  1. Do you experience these issues yourself?
  2. What type of random stats were being given? Is there a specific one?
  3. How often does it happen to a single player?
  4. Did you update the game with new stat values? Did updating the game cause more reports in data loss?
1 Like
  1. Yes
  2. No specific stats, but mostly between 15-19 are the most common ones
  3. It happens very often, and I get a lot of complaints about it
  4. I have not updated anything, it was like this on release
1 Like

Are you sure that the corrupted data is random and not just being rolled back?

For example, you join the game and have 100 money, you grind and get 500 money, then leave the game. You rejoin the game and see that you’re back at 100 money. Your data has been rolled back because of the game not saving properly.

I am positive about that since the players are also gaining gamepass stats, despite the players not even owning any gamepasses

I’m not sure then, your code looks almost fine. There’s a couple of bad practices in the code but I don’t see any potential holes that could be causing the random number behavior.

Verify in-game using print statements or some kind of tool right now and make sure that your 22 values inside of Stats are holding the correct values. It might not even be related to the datastore. Other things could explain the numbers, such as exploiters, inaccurate calculations/coding, or other scripts interacting with the values strangely.

Just for good measure, fix the bad implementations.

  1. Wrap your Data:GetAsync in a pcall(), datastore methods have a small chance of erroring.
	local dataload, errorMessage = pcall(function()
		return Data:GetAsync(tostring(plr.UserId))
	end)

	if not dataload and errorMessage then
		error(`GetAsync Failed! {errorMessage}`)
	end
  1. Your data needs to guarantee to save when you leave the game. In your current method of saving, what happens if the player leaves the game and the SaveAsync errors? Save data is lost. Look into BindToClose and implement loops to guarantee that the data truly saves. You can also alternatively use Datastore2 to handle the heavy lifting.

If you try all of these things and find nothing, you’ll probably have to provide a lot more information.

Do I just add that underneath the first dataload?

local x = require(game.ReplicatedStorage.EternityNum)
local Data = game:GetService("DataStoreService"):GetDataStore("ButtonSim3")
game.Players.PlayerAdded:Connect(function(plr)
	
	local s = Instance.new("Folder", plr)
	s.Name = "Stats"
	local ls = Instance.new("Folder", plr)
	ls.Name = "leaderstats"
	
	local money = Instance.new("StringValue", s)
	money.Name = "Money"
	money.Value = 0
	local multi = Instance.new("StringValue", s)
	multi.Name = "Multi"
	multi.Value = 1
	local reb = Instance.new("StringValue", s)
	reb.Name= "Rebirth"
	reb.Value = 0
	
	local lvl = Instance.new("StringValue",s)
	lvl.Name = "Level"
	lvl.Value = "0"
	
	local sup = Instance.new("StringValue", s)
	sup.Name= "SuperRebirth"
	sup.Value = 0
	
	--RUNES
	
	local com = Instance.new("StringValue", s)
	com.Name= "Common"
	com.Value = 0
	local ucom = Instance.new("StringValue", s)
	ucom.Name= "Uncommon"
	ucom.Value = 0
	local rare = Instance.new("StringValue", s)
	rare.Name= "Rare"
	rare.Value = 0
	local epic = Instance.new("StringValue", s)
	epic.Name= "Epic"
	epic.Value = 0
	local leg = Instance.new("StringValue", s)
	leg.Name= "Legendary"
	leg.Value = 0
	
	--Geodes
	
	local dirt = Instance.new("StringValue", s)
	dirt.Name= "Dirt"
	dirt.Value = 0
	local stone = Instance.new("StringValue", s)
	stone.Name= "Stone"
	stone.Value = 0
	local diamond = Instance.new("StringValue", s)
	diamond.Name= "Diamond"
	diamond.Value = 0
	
	--Reset Layers
	
	local ascension = Instance.new("StringValue", s)
	ascension.Name= "Ascension"
	ascension.Value = 0
	
	--Passes
	
	local statmast = Instance.new("StringValue", s)
	statmast.Name= "StatMaster"
	statmast.Value = 0
	
	local vip = Instance.new("StringValue", s)
	vip.Name= "VIP"
	vip.Value = 0
	
	local mvp = Instance.new("StringValue", s)
	mvp.Name= "MVP"
	mvp.Value = 0
	
	local runemast = Instance.new("StringValue", s)
	runemast.Name= "RuneMaster"
	runemast.Value = 0
	
	local geodemast = Instance.new("StringValue", s)
	geodemast.Name= "GeodeMaster"
	geodemast.Value = 0
	
	--Leaderboard Stats
	
	local runesopened = Instance.new("StringValue", s)
	runesopened.Name= "RunesOpened"
	runesopened.Value = 0
	local geodesopened = Instance.new("StringValue", s)
	geodesopened.Name= "GeodesOpened"
	geodesopened.Value = 0
	
	local Success, Returned = pcall(function()
		return Data:GetAsync(plr.UserId) -- Returns a table with all the saved data, key excluded
	end)
	
	local dataload = Data:GetAsync(tostring(plr.UserId))
	if dataload then
		money.Value = dataload[1] or 0
		multi.Value = dataload[2] or 1
		reb.Value = dataload[3] or 0
		com.Value = dataload[4] or 0
		ucom.Value = dataload[5] or 0
		rare.Value = dataload[6] or 0
		epic.Value = dataload[7] or 0
		leg.Value = dataload[8] or 0
		dirt.Value = dataload[9] or 0
		stone.Value = dataload[10] or 0
		diamond.Value = dataload[11] or 0
		ascension.Value = dataload[12] or 0
		statmast.Value = dataload[13] or 0
		lvl.Value = dataload[14] or 0
		sup.Value = dataload[15] or 0
		vip.Value = dataload[16] or 0
		mvp.Value = dataload[17] or 0
		runemast.Value = dataload[18] or 0
		geodemast.Value = dataload[19] or 0
		runesopened.Value = dataload[20] or 0
		geodesopened.Value = dataload[21] or 0
	end
	
	local dataload, errorMessage = pcall(function()
		return Data:GetAsync(tostring(plr.UserId))
	end)

	if not dataload and errorMessage then
		error(`GetAsync Failed! {errorMessage}`)
	end
	
	local MoneyBoosts = {
		{Value = "Rebirth", Function = "Multiply", Number1 = {1,0}},
		{Value = "Common", Function = "Multiply", Number1 = {0.001,0}},
		{Value = "Uncommon", Function = "Multiply", Number1 = {0.005,0}},
		{Value = "Rare", Function = "Multiply", Number1 = {0.01,0}},
		{Value = "Epic", Function = "Multiply", Number1 = {0.05,0}},
		{Value = "Legendary", Function = "Multiply", Number1 = {0.5,0}},
		{Value = "VIP", Function = "Multiply", Number1 = {1.5,0}},
		{Value = "MVP", Function = "Multiply", Number1 = {2,0}},
		{Value = "StatMaster", Function = "Multiply", Number1 = {3,0}},
	}
	
	local function CalculateMultiplier(Element)
		local array = MoneyBoosts[Element]
		
		if array.Function == "Multiply" then
			return x.add(x.mul(x.convert(s[array.Value].Value), array.Number1), {1,0})
		elseif array.Function == "Power" then
			return x.add(x.pow(x.convert(s[array.Value].Value), array.Number1), {1,0})
		end
	end
	
	local function CalculateMoney()
		local val = x.convert(0.5)
		val = x.mul(val, x.convert(multi.Value))
		
		for i = 1, #MoneyBoosts do
			val = x.mul(val, CalculateMultiplier(i))
		end
		
		local svb = x.convert(money.Value)
		svb = x.add(svb,val)
		money.Value = x.bnumtostr(svb)
		
	end
	
	local function CalculateLevel(TableOfStats, Outcome)
		
		for i, v in pairs(TableOfStats) do
			Outcome = x.add(Outcome, x.mul(x.log10(x.add(v.Stat, {1, 0})), v.MultiplicationValue))
		end
		return Outcome
		
	end
	while wait() do
		CalculateMoney()
		lvl.Value = x.bnumtostr(CalculateLevel({
			{Stat = x.convert(money.Value), MultiplicationValue = {2,0}},
			{Stat = x.convert(multi.Value), MultiplicationValue = {4,0}},
			{Stat = x.convert(reb.Value), MultiplicationValue = {8,0}},
		}, 0))
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	Data:SetAsync(tostring(plr.UserId),{
		plr.Stats.Money.Value,
		plr.Stats.Multi.Value,
		plr.Stats.Rebirth.Value,
		plr.Stats.Common.Value,
		plr.Stats.Uncommon.Value,
		plr.Stats.Rare.Value,
		plr.Stats.Epic.Value,
		plr.Stats.Legendary.Value,
		plr.Stats.Dirt.Value,
		plr.Stats.Stone.Value,
		plr.Stats.Diamond.Value,
		plr.Stats.Ascension.Value,
		plr.Stats.StatMaster.Value,
		plr.Stats.SuperRebirth.Value,
		plr.Stats.VIP.Value,
		plr.Stats.MVP.Value,
		plr.Stats.RuneMaster.Value,
		plr.Stats.GeodeMaster.Value,
		plr.Stats.RunesOpened.Value,
		plr.Stats.GeodesOpened.Value,
		plr.Stats.Level.Value,})
end)

game:BindToClose(function()
	wait(3)
end)

local Success, Returned = pcall(function(plr)
	return Data:GetAsync(plr.UserId) -- Returns a table with all the saved data, key excluded
end)

SetAsync looks fine. I would recommend to look at how datastore is used with BindToClose. Also please check your game for any other potential places that could be causing the datastore values to change. I highly doubt implementing these suggestions will fix the main issue.

maybe the players are receiving randoms stats because you is using dataload[1], i think sometimes the data dont save in the way that you organized the data, to try to fix this issue use

Data:SetAsync(tostring(plr.UserId), {
   Money = plr.Stats.Money.Value
})

and to set the data use

money.Value = dataload["Money"] --or dataload.Money

I have a feeling its something to do with that but this is not a valid solution as I need the number for something else in my game