Datastores not loading? And not kicking the player properly!

I have a really bad problem with datastores failing to load and I had to private my game over it!

local dt = game:GetService("DataStoreService")

local storage = dt:GetDataStore('PlrCosmetics')

function sendRequestToSave(plr)
	coroutine.wrap(function()
		local list = plr:WaitForChild("PlayerCosmetics")

		local itemList = {}

		for _,i in pairs(list:GetChildren()) do
			if i:IsA("BoolValue") or i:IsA("StringValue") or i:IsA("NumberValue") then
				local newTable = {}
				table.insert(itemList, newTable)
				table.insert(newTable, 1, i.Name)
				table.insert(newTable, 2, i.Value)
			end
		end

		storage:SetAsync(tostring(plr.UserId) .. 'Cosmetics', itemList)
	end)()
end

game:GetService("Players").PlayerAdded:Connect(function(plr)
	coroutine.wrap(function()
		local list = script:WaitForChild("CosmeticList"):Clone()
		list.Parent = plr
		list.Name = 'PlayerCosmetics'

		local loaded = Instance.new("BoolValue", plr)
		loaded.Name = 'Loaded'
		loaded.Value = false

		local datastore = storage:GetAsync(tostring(plr.UserId) .. 'Cosmetics')
		local datastore = storage.GetAsync(storage, tostring(plr.UserId) .. 'Cosmetics')

		local work, data = pcall(storage.GetAsync, storage, tostring(plr.UserId) .. 'Cosmetics')

		if work == false then 
			plr:Kick('Data could not be loaded! Please rejoin.') 
			return end
		if work == true then
			for _,value in pairs(data) do
				if list:FindFirstChild(value[1]) ~= nil then
					list:FindFirstChild(value[1]).Value = value[2]
				end
			end
		end

		loaded.Value = true
		
		while wait(120) do
			if plr ~= nil then
				sendRequestToSave(plr)
			end
		end
	end)()
end)

game:GetService("Players").PlayerRemoving:Connect(function(plr)
	sendRequestToSave(plr)
end)

script:WaitForChild("ForceSave").Event:Connect(function(plr)
	sendRequestToSave(plr)
end)

This should work in theory but sometimes it loads nothing and doesn’t kick the player which this is bad because I plan to make paid cosmetics at some point.

If I were you, I would use profile service. Now it may not work in your situation, but if you hook up old data to the new service, it would allow this kick system to be allowed, and a lot better, because the profile service is made by developers who made this service really well and know what they are doing . But again, this has nothing to do with your script, but rather an idea if you haven’t looked at other options! Here is a link a video if you want explaining it well (Made by MonzterDEV) Profile Service Video

But I wish you luck in finding an answer!

The issue with your code could be the way you’re checking if the data is loaded correctly. Your code is using pcall function to wrap the GetAsync function call, but it only checks if the data was successfully retrieved by checking if work == true .

This is not a reliable way of checking if the data was retrieved correctly as a failed data retrieval can be caused by many reasons (such as the player being banned, the player’s data being corrupted, etc.). You should also check the data retrieved to make sure it’s not empty, or if it’s the correct data format you expect.

Here’s an updated version of the code that should resolve the issue:

local dt = game:GetService("DataStoreService")
local storage = dt:GetDataStore('PlrCosmetics')

function sendRequestToSave(plr)
	coroutine.wrap(function()
		local list = plr:WaitForChild("PlayerCosmetics")

		local itemList = {}

		for _,i in pairs(list:GetChildren()) do
			if i:IsA("BoolValue") or i:IsA("StringValue") or i:IsA("NumberValue") then
				local newTable = {}
				table.insert(itemList, newTable)
				table.insert(newTable, 1, i.Name)
				table.insert(newTable, 2, i.Value)
			end
		end

		storage:SetAsync(tostring(plr.UserId) .. 'Cosmetics', itemList)
	end)()
end

game:GetService("Players").PlayerAdded:Connect(function(plr)
	coroutine.wrap(function()
		local list = script:WaitForChild("CosmeticList"):Clone()
		list.Parent = plr
		list.Name = 'PlayerCosmetics'

		local loaded = Instance.new("BoolValue", plr)
		loaded.Name = 'Loaded'
		loaded.Value = false

		local success, data = pcall(storage.GetAsync, storage, tostring(plr.UserId) .. 'Cosmetics')

		if not success then 
			plr:Kick('Data could not be loaded! Please rejoin.') 
			return 
		end
		
		if not data or #data == 0 then
			plr:Kick('Data could not be loaded! Please rejoin.')
			return
		end
		
		for _,value in pairs(data) do
			if list:FindFirstChild(value[1]) ~= nil then
				list:FindFirstChild(value[1]).Value = value[2]
			end
		end

		loaded.Value = true
		
		while wait(120) do
			if plr ~= nil then
				sendRequestToSave(plr)
			end
		end
	end)()
end)

game:GetService("Players").PlayerRemoving:Connect(function(plr)
	sendRequestToSave(plr)
end)

script:WaitForChild("ForceSave").Event:Connect
2 Likes

It’s risky to try this out as I have data saving servers (It’s a building game) but I guess I could try it out but how would I simulate a fail?

1 Like
local dt = game:GetService("DataStoreService")

local storage = dt:GetDataStore('PrivateServerSaves')

local doNotSave = false

function saveBlocks()
	if game.VIPServerId ~= '' then		
		
		if doNotSave == false and game:GetService("ServerStorage"):WaitForChild("GameLoaded").Value == true then
			local saved= {}

			for _,i in pairs(game:GetService("Workspace"):GetChildren()) do
				if i:IsA("SpawnLocation") and i.Name == 'gamespawn__' then
					local spawnInfo = {}
					local t = {
						i.Position.X,
						i.Position.Y,
						i.Position.Z
					}
					local t2 = {
						i.Size.X,
						i.Size.Y,
						i.Size.Z
					}
					table.insert(spawnInfo, '(spawn)')
					table.insert(spawnInfo, t)
					table.insert(spawnInfo, t2)
					table.insert(saved, spawnInfo)
				end
			end
			for _,i in pairs(game:GetService("Workspace"):WaitForChild("Blocks__"):GetChildren()) do
				local blockInTable = {}
				table.insert(blockInTable, i:WaitForChild("ModelName").Value)
				table.insert(blockInTable, {i.Position.X, i.Position.Y, i.Position.Z})
				table.insert(blockInTable, i:WaitForChild("Owner").Value)
				table.insert(saved, blockInTable)
			end
			for _,i in pairs(game:GetService("Workspace"):WaitForChild("Entities__"):GetChildren()) do
				local blockInTable = {}
				if i:FindFirstChild("HumanoidRootPart") ~= nil and i:FindFirstChildWhichIsA("Humanoid") ~= nil then
					table.insert(blockInTable, '(entity)')
					table.insert(blockInTable, {i:WaitForChild("HumanoidRootPart").Position.X, i:WaitForChild("HumanoidRootPart").Position.Y, i:WaitForChild("HumanoidRootPart").Position.Z})
					table.insert(blockInTable, string.sub(i.Name, #'_Entity_' + 1, #i.Name))
					table.insert(blockInTable, i:FindFirstChildWhichIsA("Humanoid").Health)
					table.insert(saved, blockInTable)
				end
			end

			storage:SetAsync(tostring(game.VIPServerId), saved)
		end
	end
end

local blocksLoaded = 0
local maxLoadableBlocks = 30

if game.PrivateServerOwnerId == 0 and game.PrivateServerId ~= '' then
	coroutine.wrap(function()
		while wait(10) do
			local listOfPlayersCool = {}
			for _,i in pairs(game:GetService("Players"):GetPlayers()) do
				table.insert(listOfPlayersCool, i.Name)
			end
			game:GetService("MessagingService"):PublishAsync(game.PrivateServerId, game:GetService("HttpService"):JSONEncode(
			{
				Players = #game.Players:GetPlayers(),
				PlayerList = listOfPlayersCool,
				Ping = 0,
				Id = -1,
				ServerName = ''
			}))
		end
	end)()
end

if game.PrivateServerId ~= '' then
	coroutine.wrap(function()
		while wait(180) do
			saveBlocks()
		end
	end)()
end

function loadBlocks()
	if game.VIPServerId ~= '' then
		local datastore = storage:GetAsync(tostring(game.VIPServerId))
		local datastore = storage.GetAsync(storage, tostring(game.VIPServerId))

		local work, data = pcall(storage.GetAsync, storage, tostring(game.VIPServerId))

		if work == false then 
			for _,i in pairs(game:GetService("Players"):GetPlayers()) do
				i:Kick('Server failed to load!')
			end
			return end
		if work == true then
			if data == nil or data == {} then
				defaultMapLoad()
			else
				coroutine.wrap(function()
					local loadedFully = false
					for _,i in pairs(data) do
						if i[1] ~= '(spawn)' and i[1] ~= '(entity)' then
							if i[1] ~= 'WaterTest' then
								game:GetService("ServerStorage"):WaitForChild("CurrentlyLoading").Value = 'Blocks'
								if blocksLoaded > maxLoadableBlocks then
									blocksLoaded = 0
									wait()
								end
								blocksLoaded = blocksLoaded + 1
								local pro = nil
								if i[3] == 'game' then
									pro = 'game'
								end
								placeModel('At', Vector3.new(i[2][1], i[2][2], i[2][3]), i[1], false, false, pro, i[3])
							else
								coroutine.wrap(function()
									repeat wait() until loadedFully == true
									game:GetService("ServerStorage"):WaitForChild("CurrentlyLoading").Value = 'Blocks'
									if blocksLoaded > maxLoadableBlocks then
										blocksLoaded = 0
										wait()
									end
									blocksLoaded = blocksLoaded + 1
									local pro = nil
									if i[3] == 'game' then
										pro = 'game'
									end
									placeModel('At', Vector3.new(i[2][1], i[2][2], i[2][3]), i[1], false, false, pro, i[3])
								end)()
							end	
						else
							if i[1] == '(spawn)' then
								game:GetService("ServerStorage"):WaitForChild("CurrentlyLoading").Value = 'Spawnpoint'
								if blocksLoaded > maxLoadableBlocks then
									blocksLoaded = 0
									wait()
								end
								blocksLoaded = blocksLoaded + 1
								local newSpawn = Instance.new("SpawnLocation", game:GetService("Workspace"))
								newSpawn.Position = Vector3.new(i[2][1],i[2][2],i[2][3])
								newSpawn.Size = Vector3.new(i[3][1],i[3][2],i[3][3])
								newSpawn.Anchored = true
								newSpawn.Name = 'gamespawn__'
								newSpawn.CanCollide = false
								newSpawn:ClearAllChildren()
								newSpawn.CanTouch = true
								newSpawn.Locked = false
								newSpawn.Duration = 0
								newSpawn.CanQuery = false
								newSpawn.Transparency = 1
							end
							if i[1] == '(entity)' then
								game:GetService("ServerStorage"):WaitForChild("CurrentlyLoading").Value = 'Entities'
								if blocksLoaded > maxLoadableBlocks then
									blocksLoaded = 0
									wait()
								end
								blocksLoaded = blocksLoaded + 1
								local entity = game:GetService("ServerStorage"):WaitForChild("Entities"):FindFirstChild(i[3])
								if entity ~= nil then
									entity = entity:Clone()
									local root = entity:WaitForChild("HumanoidRootPart")
									local pos = Vector3.new(i[2][1],i[2][2],i[2][3])
									root.CFrame = CFrame.new(pos, pos + root.CFrame.LookVector)
									entity:FindFirstChildWhichIsA("Humanoid").Health = i[4]
									entity.Parent = game:GetService('Workspace'):WaitForChild("Entities__")
								end
							end
						end	
					end
					loadedFully = true
					game:GetService("ServerStorage"):WaitForChild("GameLoaded").Value = true
					if #game:GetService("Workspace"):WaitForChild("Blocks__"):GetChildren() <= 5 then
						doNotSave = true
						for _,i in pairs(game:GetService("Players"):GetPlayers()) do
							i:Kick('Server failed to load!')
						end
					end
				end)()
			end
		end
	end
end

function loadServer()
	if game.VIPServerId ~= '' then
		loadBlocks()
	else
		defaultMapLoad()
	end
end

game.Close:Connect(function()
	saveBlocks()
end)

Heres the code for the server saving.

2 Likes

To simulate a failure in data saving, you could try the following steps:

  1. Temporarily turn off the internet connection while the game is saving data.
  2. Turn off the computer or server that the game is running on.
  3. Manually corrupt or delete the data stored in the datastore.

Doing these steps can simulate a failure in data saving, allowing you to test how your code will handle such failures. However, be cautious and make sure to backup your data before attempting these steps.

1 Like

Instead of kicking the player why not handling retries? I wouldnt recomend kicking players due to datastore fails.

You could simulate a fail if you disable the API connection from studio to roblox

1 Like

Will this possibly reset all data if I turn off the api service?

1 Like

Handling retries is a good approach in case of data store failures as it avoids penalizing the player and provides a better user experience. You can implement retries by using a try-catch block or by using an error-handling library that can automatically retry failed requests a certain number of times.

Disabling the API connection from Studio to Roblox can indeed simulate a fail, but it is not recommended as it can interfere with the development process and may cause other unintended consequences. It is better to test your error handling logic in a controlled environment that does not affect the overall functionality of the game.

1 Like

This is just dangerous sense me and my friends just finished build a huge castle.

1 Like

I hate situations like this. Are you confident your system is likely to succeed?

1 Like

Disabling the API connection from Studio to Roblox can have unintended consequences and should not be taken lightly. It could result in loss of data and work, so it’s important to have a backup and a plan in place before making any changes to the API connection. It’s always best to avoid actions that could potentially cause harm to your game or creations.

I am not going to disconnect the api I am just asking if you’re confident that your revision of my code will work.

It does nothing… it just studio cant communicate with roblox api in this case datastoreService, it cant cause a lost of data cause its not even trying to save cause theres no connection

We have made too many big builds that I don’t feel safe risking it.

It may be possible, but I’m not completely sure. However, I think it will happen

I put your new code in for the player saving but what do you think I could do for server saving?

Correct, disconnecting the API connection between Studio and the Roblox API will only prevent the data from being saved. It will not cause data loss as the data is not being attempted to be saved in the first place.

Servers have a backup system where if they haven’t finish loading they can’t save. So they can’t easily corrupt I am going to add that onto the players.

yeah, sorry for saying this, why your replies sounds like a popular AI out there? Im not saying you are using it, but, Im not really sure about the approach you suggest as solution, and you are saying mixed stuff and correcting it into the next message