Player data always resets itself (Especially when they teleport into a subplace)

Hey devs,
I’m currently working on a game where you have to get different endings which will be saved in BoolValues. If they are completed, the value is set to true. A trophy will appear in the spawn room for each completed ending. (Like in get a snack at 4 am if you played it)
Once the player completed an ending, a GUI with a retry option appears. When the player clicks this button, they will be teleported into the same place (new server)
Here’s my problem. There are several endings in my game and one of them is a Christmas ending. Sometimes when I join, my data (except for the Christmas ending) is gone. All the values, except for Christmas, are false (donated is 0). I first assumed that something with SetAsync() went wrong but wouldn’t that just restore my last saved stats before it went wrong?
Here’s my datastore code. (I deleted some endings so you don’t have to go through all of them. Their code is the same.

local DataStoreService = game:GetService("DataStoreService")
local SaveData = DataStoreService:GetDataStore("SaveData")

local function onPlayerJoin(plr)
	local Endings = Instance.new("Folder")
	Endings.Name = "Endings"
	Endings.Parent = plr
	
	local GoodEnding = Instance.new("BoolValue")
	GoodEnding.Name = "GoodEnding"
	GoodEnding.Parent = Endings
	
	local Oof = Instance.new("BoolValue")
	Oof.Name = "Oof"
	Oof.Parent = Endings
	
	local donated = Instance.new("NumberValue")
	donated.Name = "Donated"
	donated.Parent = plr
	
	---Season endings
	local Christmas = Instance.new("BoolValue")
	Christmas.Name = "Christmas"
	Christmas.Parent = Endings
	---
	
	local playerUserId = "Player_"..plr.UserId
	local data 
	
	local success, errormessage = pcall(function()
		data = SaveData:GetAsync(playerUserId)
	end)
	
	if success then
		if data then
			Oof.Value = data["Oof"]
			if Oof.Value == true then
				game.ReplicatedStorage.Trophies:WaitForChild("TrophyOof"):Clone().Parent = game.Workspace
			end
			GoodEnding.Value = data["GoodEnding"]
			if GoodEnding.Value == true then
				game.ReplicatedStorage.Trophies:WaitForChild("TrohpyGoodEnding"):Clone().Parent = game.Workspace
			end
		
			---Season Endings
			Christmas.Value = data["Christmas"]
			if Christmas.Value == true then
				game.ReplicatedStorage.SeasonTrophies:WaitForChild("TrohpyChristmas"):Clone().Parent = game.Workspace
			end
			---
		end
	else
		Oof.Value = false
		GoodEnding.Value = false
		donated.Value = 0
		---Season Endings
		Christmas.Value = false
		---
	end
end

local function create_table(plr)
	local player_stats = {}
	for _, stat in pairs(plr:GetChildren()) do
		if stat.Name ==  "Donated"  then
			player_stats[stat.Name] = stat.Value
		elseif stat.Name == "Endings" then
			for i, v in pairs(stat:GetChildren()) do
				player_stats[v.Name] = v.Value
			end
		end
	end
	return player_stats
end

local function onPlayerRemoving(plr)
	local player_stats = create_table(plr)
	local success, err = pcall(function()
		local playerUserId = "Player_"..plr.UserId
		SaveData:SetAsync(playerUserId,player_stats)
	end)
	if not success then
		warn(err)
	end
end

game:BindToClose(function()
	for i, plr in pairs(game.Players:GetPlayers()) do
		local player_stats = create_table(plr)
		local success, err = pcall(function()
			local playerUserId = "Player_"..plr.UserId
			SaveData:SetAsync(playerUserId,player_stats)
		end)
		if not success then
			warn(err)
		end
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerRemoving)

while wait(60) do
	for i, plr in pairs(game.Players:GetPlayers()) do
		local player_stats = create_table(plr)
		local success, err = pcall(function()
			local playerUserId = "Player_"..plr.UserId
			SaveData:SetAsync(playerUserId,player_stats)
		end)
		if not success then
			warn(err)
		end
	end
end

Like i said, sometimes the data is just gone (except for christmas). However, I also have a subplace with the same objective but new endings. The Christmas ending and the “donated” value also exists in the subplace. Whenever I get teleported to the subplace, my subplace data is gone (except for christmas) and my data in the main place is gone aswell (also except for christmas). I also experienced this data loss before I added the subplace, just teleporting between them deletes the data aswell.
Here’s the datastore code of the subplace (again i deleted some endings so you dont have to read all of them. They all have the same code)

local DataStoreService = game:GetService("DataStoreService")
local SaveData = DataStoreService:GetDataStore("SaveData")
local BadgeService = game:GetService("BadgeService")

local function onPlayerJoin(plr)
	local Endings = Instance.new("Folder")
	Endings.Name = "Endings"
	Endings.Parent = plr
	
	local donated = Instance.new("NumberValue")
	donated.Name = "Donated"
	donated.Parent = plr

	local Reverse = Instance.new("BoolValue")
	Reverse.Name = "Reverse"
	Reverse.Parent = Endings
	
	---Season endings
	local Christmas = Instance.new("BoolValue")
	Christmas.Name = "Christmas"
	Christmas.Parent = Endings

	local playerUserId = "Player_"..plr.UserId
	local data 
	
	local success, errormessage = pcall(function()
		data = SaveData:GetAsync(playerUserId)
	end)
	
	if success then
		if data then
			---Season Endings
			Christmas.Value = data["Christmas"]
			if Christmas.Value == true then
				game.ReplicatedStorage.SeasonTrophies:WaitForChild("TrohpyChristmas"):Clone().Parent = game.Workspace
			end
			---
			Reverse.Value = data["Reverse"]
			if Reverse.Value == true then
				game.ReplicatedStorage.Trophies:WaitForChild("TrophyReverse"):Clone().Parent = game.Workspace
			end
			donated.Value = data["Donated"]
			Sued.Value = data["Sued"]
			if Sued.Value == true then
				game.ReplicatedStorage.Trophies:WaitForChild("TrophySued"):Clone().Parent = game.Workspace
			end
		end
	else
		donated.Value = 0
		Reverse.Value = false
		---Season Endings
		Christmas.Value = false
	end
	
end

local function create_table(plr)
	local player_stats = {}
	for _, stat in pairs(plr:GetChildren()) do
		if stat.Name == "Donated" then
			player_stats[stat.Name] = stat.Value
		elseif stat.Name == "Endings" then
			for i, v in pairs(stat:GetChildren()) do
				player_stats[v.Name] = v.Value
			end
		end
	end
	return player_stats
end

local function onPlayerRemoving(plr)
	local player_stats = create_table(plr)
	local success, err = pcall(function()
		local playerUserId = "Player_"..plr.UserId
		SaveData:SetAsync(playerUserId,player_stats)
	end)
	if not success then
		warn(err)
	end
end

game:BindToClose(function()
	for i, plr in pairs(game.Players:GetPlayers()) do
		local player_stats = create_table(plr)
		local success, err = pcall(function()
			local playerUserId = "Player_"..plr.UserId
			SaveData:SetAsync(playerUserId,player_stats)
		end)
		if not success then
			warn(err)
		end
	end
end)

game.Players.PlayerAdded:Connect(onPlayerJoin)
game.Players.PlayerRemoving:Connect(onPlayerRemoving)

while wait(60) do
	for i, plr in pairs(game.Players:GetPlayers()) do
		local player_stats = create_table(plr)
		local success, err = pcall(function()
			local playerUserId = "Player_"..plr.UserId
			SaveData:SetAsync(playerUserId,player_stats)
		end)
		if not success then
			warn(err)
		end
	end
end

*donated is a NumberValue, not a bool
Thank you so much for reading. How do I fix this?

The DataStoreService is not designed to be used instantaneously like this. You’re more than likely trying to fetch data before it’s been completely saved. If you wish to send data with a teleport, there are methods for doing that.

Take a look at the TeleportService API, especially the page for TeleportOptions | Roblox Creator Documentation

My recommendation would be to send the player’s data with the teleport and rely on that instead of trying to save/load it so fast.

1 Like

Thank you! But isn’t it very easy to exploit teleport data?

Given that it’s passed through the client, yes it would be possible to exploit that. If you’re concerned with security, you could look into session-locking a players data to make sure you get the most recent version through data stores. It will be slower, but would work.

You might consider using a module such as ProfileService to implement this, no need to reinvent the wheel.

A solution would probably be to use the DataStore2 module and rewrite the whole code. Instead of only saving the data when the player leaves and every 60 seconds I now save the data whenever the player gets an ending.