Help with datastore error?

You mean that, when server shutdown happens PlayerRemoving is not firing?

Yup. Everyone leaves without firing The player removing function

I’m more use to SetAsync, So lets try it.

	local	success, err = pcall(function()
			playerDataStore:SetAsync(key, data)
		end)

if not success then
		warn("Failed to save "..player.Name.."'s data! (" ..tostring(player.UserId).. ")")
else 
print("Saved Data!")
	end
	print(data)	
end

forget to remove the extra end, you should be good now

I just made a test in a real server, using PlayerRemoving event, and from the website, shutdown all server, PlayerRemoving fired saving the DSS

Tried it, unfortunately still not working. I tested it without the BindToClose function and it still didn’t save, which is leading me to believe that it is not the source of the problem. Maybe its the data im trying to store? I am storing multiple tables inside tables, idk if that makes a difference or not.

try to use the datastore2, How to use DataStore2 - Data Store caching and data loss prevention it is very simple and easy to use and you dont need to code all datastore systems, all is done just use module functions from the datastore2

2 Likes

I agree with POG about using SetAsync, Im more used to it.
I was playing with your code a little, this code probably is not friendly with other systems you could have, I deleted the leader board and stuff that was not needed for the test, I was playing… so I added a repeat function instead of repeat return etc. And modified keys in your tables.

Its working fine for me, when I place a model rig into the player folder, and leave/close studio. It saves normally, when joining again its printing what its saved.

Obviously it has an issue trying to save twice when in a real server, it shutdown. Cause apparently PlayerRemoving and BindToClose can fire both when a real server shutdown. I just added a InStudio check to not fire it from studio.

Give it a try, dont forget to add your leaderstats and stuff.

local plrs = game:GetService("Players")

local DSS = game:GetService("DataStoreService")
local playerDataStore = DSS:GetDataStore("PlayerData")

local RS = game:GetService("RunService")
local cs = game:GetService("CollectionService")

local function retry(func, attempt)
	if not attempt then
		attempt = 0
	end
	local s, m = pcall(function()
		return func()
	end)

	if not s then
		attempt = attempt + 1
		--print(attempt)
		--warn(m, "Attempt:", attempt)
		task.wait(2)
		return retry(func, attempt)
	end
	
	if attempt == 5 and not s then
		warn('Error' .. tostring(m))
		return false
	end
	
	return s, m
end

local function loadData(player:Player)
	local plrNPCs = Instance.new("Folder")
	plrNPCs.Parent = player
	plrNPCs.Name = "PlayerNPCFolder"

	local deceasedPlrNPCs = Instance.new("Folder")
	deceasedPlrNPCs.Parent = plrNPCs
	deceasedPlrNPCs.Name = ("DeceasedNPCs")
	
	local s, m = retry(function()
		return playerDataStore:GetAsync(player.UserId)
	end)

	if s then
		warn("DSS connection success")
		if m then
			warn("data found for:", player)
			print(m)
			for _, items in pairs(m) do
				--print(items)
			end
		else
			warn("player has not data")
		end
	else
		warn("DSS connection failed after retries", player)
	end
end

function saveData(player)
	local data = {}

	for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
		if item:IsA("Model") then
			local bodycolors = {}
			local gender
			local accessory = false
			
			for _, bodypart in pairs(item:GetChildren()) do
				if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then

					local colors = {
						R = math.floor(bodypart.Color.R*255), 
						G = math.floor(bodypart.Color.G*255), 
						B = math.floor(bodypart.Color.B*255)
					}

					bodycolors[bodypart.Name] = colors
					
				elseif bodypart:IsA("Accessory") then
					accessory = bodypart.Name
				end
			end

			for _, tag in pairs(cs:GetTags(item)) do
				if tag == "Male" or tag == "Female" then
					gender = tag
				end
			end
			
			table.insert(data, {
				NameNPC = item.Name,
				Accessory = accessory,
				BodyColors = bodycolors,
				Gender = gender
			})
		end
	end
	
	print(#data)
	if #data > 0 then
		warn("saving")
		local s, m = retry(function()
			return playerDataStore:SetAsync(player.UserId, data)
		end)

		if s then
			warn("data saved for:", player)
		else
			warn("data not saved for:", player)
		end
	end
end

plrs.PlayerAdded:Connect(loadData)
plrs.PlayerRemoving:Connect(saveData)

game:BindToClose(function()
	if (RS:IsStudio()) then
		warn("BindToClose in Studio should do nothing")
		--task.wait(1)
	else
		-- Shutdown in a real Server
		-- In a real server when it shutdown, its trying to save twice.
		-- One from PlayerRemoving event and one from this BindToClose
		for _, plr in pairs(plrs:GetPlayers()) do
			task.spawn(function()
				saveData(plr)
			end)
		end
	end
end)
2 Likes

Developer shut downs are only one. And often the most protected from data. Crashing, etc. Also PlayerRemoving doesn’t have the 30 seconds, rather player removing runs while the server is still alive. While It can save a couple in time , provided that it does fire, it won’t say a full server of people like how bind to close will. Also it doesn’t run most of the time. This is something that has been debated, and most developers like using it. I use it myself, and it improved a lot of my data lost, after updates

I’m going to try this out. I’ll let you know if it works.

1 Like

I agree, that PlayerRemoving is not consistent when a server shutdown, thats why would be important to use BindToClose to save the data of players when its about to shutdown.

I was just pointing out that it needs some debounces, cause it could try to save twice for some players.

Not really important, cause the data is surely saved once at least, trying to save twice it will only “clog” the datastore for that player.

1 Like

Yup. The only thing is those limits of Set/Updating.

I don’t make my data stores, rather I use profile service for it. But it was useful when making my own, and I didn’t have too many issues

Just tried this out - everything fires like it should but when I rejoin it says I have no data.

Sorry, as I said, I was playing with your code I changed many stuff from your original script, I changed the key of the player when saving into datastore too. Make sure to check it carefully. I will check it again and send a video, Im sure its working for me

2 Likes

Double checked and I realized there was an error being produced:

I have no idea what this means or how to fix it. Any ideas?

Edit - Here is my adapted version of your code, just for reference:

-- Services
local dss = game:GetService("DataStoreService")
local playerDataStore = dss:GetDataStore("PlayerData")
local plrs = game:GetService("Players")
local wrkspce = game:GetService("Workspace")
local cs = game:GetService("CollectionService")
local runservice = game:GetService("RunService")

-- Functions
local function FolderMaker(Name, Parent)
	local Folder = Instance.new("Folder", Parent)
	Folder.Name = Name
end

local function retry(func, attempt)
	if not attempt then
		attempt = 0
	end
	local success, err = pcall(function()
		return func()
	end)

	if not success then
		attempt = attempt + 1
		--print(attempt)
		warn(err, "Attempt:", attempt)
		task.wait(2)
		return retry(func, attempt)
	end

	if attempt == 5 and not success then
		warn('Error' .. tostring(err))
		return false
	end

	return success, err
end

local function saveData(player: Player)
	local key = ("Player: " ..tostring(player.UserId))
	local data = {}
	
	for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
		if item:IsA("Model") then
			local bodycolors = {}
			local gender
			local accessory = nil
			
			for _, bodypart in pairs(item:GetChildren()) do
				if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then

					local colors = {
						R = math.floor(bodypart.Color.R*255), 
						G = math.floor(bodypart.Color.G*255), 
						B = math.floor(bodypart.Color.B*255)
					}

					bodycolors[bodypart.Name] = colors
				elseif bodypart:IsA("Accessory") then
					accessory = bodypart.Name
				
				end
			end
			
			for _, tag in pairs(cs:GetTags(item)) do
				if tag == "Male" or tag == "Female" then
					gender = tag
				end
			end
			
			table.insert(data, {
				item.Name,
				item:FindFirstChildWhichIsA("Accessory"),
				bodycolors,
				gender
			})
		end
	end
	
	
	print(#data)
	if #data > 0 then
		print(data)
		warn("saving")
		local success, err = retry(function()
			return playerDataStore:SetAsync(player.UserId, data)
		end)

		if success then
			warn("data saved for: ", player)
		else
			warn("data not saved for: ", player)
		end
	end
end


local function loadData(player: Player)
	
	-- Game Folder
	FolderMaker(player.Name, wrkspce.CurrentNPCs)
	
	-- Player Folders
	FolderMaker("leaderstats", player)
	FolderMaker("PlayerNPCFolder", player)
	FolderMaker(player.Name.. "'s Deceased NPCs", player:WaitForChild("PlayerNPCFolder"))
	
	local success, err = retry(function()
		return playerDataStore:GetAsync(player.UserId)
	end)

	if success then
		warn("DSS connection success")
		if err then
			warn("data found for:", player)
			print(err)
			for _, items in pairs(err) do
				--print(items)
			end
		else
			warn("player has not data")
		end
	else
		warn("DSS connection failed after retries", player)
	end
end


-- Events
plrs.PlayerAdded:Connect(loadData)
plrs.PlayerRemoving:Connect(saveData)
game:BindToClose(function()
	if (runservice:IsStudio()) then
		warn("BindToClose in Studio should do nothing")
		--task.wait(1)
	else
		-- Shutdown in a real Server
		-- In a real server when it shutdown, its trying to save twice.
		-- One from PlayerRemoving event and one from this BindToClose
		for _, plr in pairs(plrs:GetPlayers()) do
			task.spawn(function()
				saveData(plr)
			end)
		end
	end
end)

You are trying my code? Cause that should not happen with what I sent. All values to be saved are safe

Because this line:
item:FindFirstChildWhichIsA("Accessory"),

I already supplied a workaround to save the Name of the Accessory that the NPC is using. Its not possible to save an accessory, you can just save the name for reference. Plus, you need to check if theres any before trying to save into table

The code I supplied does this:

			table.insert(data, {
				NameNPC = item.Name,
				Accessory = accessory,
				BodyColors = bodycolors,
				Gender = gender
			})

instead than this:

			table.insert(data, {
				item.Name,
				item:FindFirstChildWhichIsA("Accessory"),
				bodycolors,
				gender
			})

And talking about that. You dont want to save only one accessory as you are doing, create a subtable with all the accessories the NPC has

Save function should look like this:

Save data function:

function saveData(player)
	local key = ("Player: "..tostring(player.UserId))
	local data = {}

	for _, item in pairs(player:WaitForChild("PlayerNPCFolder"):GetChildren()) do
		if item:IsA("Model") then
			local bodycolors = {}
			local gender
			local accessory = false

			for _, bodypart in pairs(item:GetChildren()) do
				if bodypart.Name == "Head" or bodypart:IsA("MeshPart") then

					local colors = {
						R = math.floor(bodypart.Color.R*255), 
						G = math.floor(bodypart.Color.G*255), 
						B = math.floor(bodypart.Color.B*255)
					}

					bodycolors[bodypart.Name] = colors

				elseif bodypart:IsA("Accessory") then
					accessory = bodypart.Name
				end
			end

			for _, tag in pairs(cs:GetTags(item)) do
				if tag == "Male" or tag == "Female" then
					gender = tag
				end
			end

			table.insert(data, {
				NameNPC = item.Name,
				Accessory = accessory,
				BodyColors = bodycolors,
				Gender = gender
			})
		end
	end

	print(#data)
	if #data > 0 then
		warn("saving")
		local s, m = retry(function()
			return playerDataStore:SetAsync(key, data) -- You forgot to use youe key instead of mine, which is based only in UserId
		end)

		if s then
			warn("data saved for:", player)
		else
			warn("data not saved for:", player)
		end
	end
end

And again, same error on this line 109, you are not using your key, you are still using mine:
playerDataStore:GetAsync(player.UserId)
Load data function:

local function loadData(player: Player)
	local key = ("Player: "..tostring(player.UserId))
	
	-- Game Folder
	FolderMaker(player.Name, wrkspce.CurrentNPCs)

	-- Player Folders
	FolderMaker("leaderstats", player)
	FolderMaker("PlayerNPCFolder", player)
	FolderMaker(player.Name.. "'s Deceased NPCs", player:WaitForChild("PlayerNPCFolder"))

	local success, err = retry(function()
		return playerDataStore:GetAsync(key)
	end)

	if success then
		warn("DSS connection success")
		if err then
			warn("data found for:", player)
			print(err)
			for _, items in pairs(err) do
				--print(items)
			end
		else
			warn("player has not data")
		end
	else
		warn("DSS connection failed after retries", player)
	end
end
1 Like

Thanks for pointing that out - didn’t catch it when I was checking. Everything works fine now, I just had one more question. Everytime I spawn a new NPC and then leave, all NPCs from the previous session are erased from the DataStore. Is there a way to add new data to old data rather than overwriting old data with new data?

EDIT - I realized that this problem will be solved once I start cloning NPCs

1 Like

Thank you to everyone who replied. You were all a great help!

1 Like

I understand what you mean, but SetAsync is exactly as useful as UpdateAsync.

Lets see, if a player leaves, and it has lets say 3 NPC in folder. When player leaves, all data about them is saved.
When player join again, you should populate those NPC into the player folder. Using the data from DataStore, create all NPC again, and place them in player folder, when player leaves, old NPC plus new ones will be saved into the DataStore.

Right now, with your code, you are doing nothing when player joins, datastore readed, and then nothing, you should create the old NPC again, based on the player’s datastore. When player leaves, all NPC will be saved again, old ones and new ones, cause all are in the player’s folder

1 Like