Trouble saving and loading accessories with serialization

I’m making a customization system in roblox with data saving, but im having a hard time saving the data of the accessories.

here’s my code:

local datastores = game:GetService("DataStoreService")
local datastore = datastores:GetDataStore("DataStore")
local players = game:GetService("Players")
local HttpService = game:GetService("HttpService")

local properties = {"HeadColor", "LeftArmColor", "LeftLegColor", "RightArmColor", "RightLegColor", "TorsoColor"}
local playersData = {}
local lastsavedChar = {}


local function serializeData(plr)
	local data = playersData[plr] or {} -- check if player has data or create empty table
	local character = lastsavedChar[plr] -- we reference the last saved character
	local bodyColors = character["Body Colors"]
	for _, property in ipairs(properties) do
		data[property] = bodyColors[property].Name
	end

	local shirt = character:FindFirstChild("Shirt")
	if shirt then
		data["Shirt"] = shirt.ShirtTemplate
	end

	local pants = character:FindFirstChild("Pants")
	if pants then
		data["Pants"] = pants.PantsTemplate
	end

	local shirtGraphic = character:FindFirstChild("ShirtGraphic")
	if shirtGraphic then
		data["ShirtGraphic"] = shirtGraphic.Graphic
	end

	local torsoMesh = character:FindFirstChild("TorsoMesh")
	if torsoMesh then
		data["TorsoMesh"] = torsoMesh.MeshId
	else
		data["TorsoMesh"] = "" -- No TorsoMesh equipped
	end	
	
	local accessories = {}
	for _, accessory in ipairs(character:GetChildren()) do
		if accessory:IsA("Accessory") then
			local accData = {
				["Handle"] = accessory.Handle
			}
			table.insert(accessories, accData)
		end
	end
	data["Accessories"] = HttpService:JSONEncode(accessories)
	
	playersData[plr] = data -- set player's data as modified data table


	return data
end

local function deserializeData(player, data)
	local character = lastsavedChar[player] -- we reference the last saved character
	local bodyColors = character["Body Colors"]
	for _, property in ipairs(properties) do
		bodyColors[property] = BrickColor.new(data[property])
	end

	if data["Shirt"] then
		local shirt = character:FindFirstChild("Shirt")
		if shirt then
			shirt.ShirtTemplate = data["Shirt"]
		else
			shirt = Instance.new("Shirt")
			shirt.Name = "Shirt"
			shirt.Parent = character
			shirt.ShirtTemplate = data["Shirt"]
		end
	end

	if data["Pants"] then
		local pants = character:FindFirstChild("Pants")
		if pants then
			pants.PantsTemplate = data["Pants"]
		else
			pants = Instance.new("Pants")
			pants.Name = "Pants"
			pants.Parent = character
			pants.PantsTemplate = data["Pants"]
		end
	end

	if data["ShirtGraphic"] then
		local shirtGraphic = character:FindFirstChild("ShirtGraphic")
		if shirtGraphic then
			shirtGraphic.Graphic = data["ShirtGraphic"]
		else
			shirtGraphic = Instance.new("ShirtGraphic")
			shirtGraphic.Name = "ShirtGraphic"
			shirtGraphic.Parent = character
			shirtGraphic.Graphic = data["ShirtGraphic"]
		end
	end

	if data["TorsoMesh"] then
		local torsoMesh = character:FindFirstChild("TorsoMesh")
		if torsoMesh then
			torsoMesh.MeshId = data["TorsoMesh"]
		else
			if data["TorsoMesh"] ~= "" then
				game.ServerStorage.TorsoMesh.Parent = character
				print("Created new TorsoMesh with MeshId:", data["TorsoMesh"]) -- Debug print
			else
				print("No TorsoMesh data found.") -- Debug print
			end
		end
	end
	
	local decodedAccessories = HttpService:JSONDecode(data["Accessories"])
	for _, accData in ipairs(decodedAccessories) do
		local accessory = Instance.new("Accessory")
		accessory.Name = "Accessory"
		accessory.Handle = accData["Handle"]
		accessory.Parent = character
	end


	serializeData(player)
end

local function onPlayerAdded(player)
	local function onCharacterLoaded(character)
		if (lastsavedChar[player]) then serializeData(player) end -- we check if this is the player's 2nd+ time respawning and if so, serialize the data from the old character for the new character to reference
		local data = playersData[player]

		while (data == nil and player:IsDescendantOf(players)) do
			data = playersData[player] -- we know that it will either be the player's data or an empty table
			task.wait()
		end

		lastsavedChar[player] = character -- set the new character
		if (next(data) ~= nil) then deserializeData(player, data) end -- we check if the dictionary is not empty and if it is not, then we deserialize that data
	end

	player.CharacterAdded:Connect(onCharacterLoaded)

	local success, result = pcall(function()
		return datastore:GetAsync("BodyColors_"..player.UserId)
	end)

	if success then
		playersData[player] = result or {}
	else
		warn(result)
	end
end

local function saveData(plr, attempt)
	--saves the player's data and attempts 3 times if it errors
	if (attempt ~= nil and attempt > 3) then return end -- reached 3 attempts: return to avoid continuing

	local plrData = serializeData(plr)

	if (plrData ~= nil) then
		--<< Player's data exists: save the data >>--
		local success, error = pcall(function()
			return datastore:UpdateAsync("BodyColors_" .. plr.UserId, function(o_data) -- o_data is the previous version of the data
				local prev_data = o_data or {} -- in case o_data is nil, set as default

				if (plrData.Version == prev_data.Version) then -- we check if the versions of the data are the same
					-- update this data to the current
					plrData.Version = plrData.Version and plrData.Version + 1 or 1
					return plrData
				else
					-- do not update data, versions do not match
					return nil -- returning nil prevents it from sending a write request
				end
			end)
		end)

		if (success) then
			-- player's data has been saved!
			print("data key: BodyColors_" .. plr.UserId .. " has been saved!")
			playersData[plr] = nil
		else
			-- error occured, attempt to retry
			warn(error)
			saveData(plr, (attempt and attempt + 1 or 2)) -- add +1 to the current attempt or set as 2 if nil
		end
	end

end

players.PlayerAdded:Connect(onPlayerAdded)
players.PlayerRemoving:Connect(saveData)



game:BindToClose(function()
	-- Check if the game is running and if it's in Studio
	if game:GetService("RunService"):IsRunning() and game:GetService("RunService"):IsStudio() then
		local to_Save = 0
		-- Iterate through players to save their data
		for _, plr in pairs(players:GetPlayers()) do
			if playersData[plr] then
				to_Save += 1 -- Increment to_Save for each player with stored data
				task.spawn(function()
					saveData(plr)
					to_Save -= 1 -- Decrement to_Save when data is finished
				end)
			end
		end

		-- Yield until all data has been saved
		while to_Save > 0 do
			task.wait()
		end
	end

	-- Shut the server down as all data has been saved
	return
end)


i dont understand the issue, i’ve looked on the devforum, YT, etc. nothing seems to help.

1 Like

Hi! I am going to make a couple assumptions, one is that the data is not saving at all, which would indicate there seems to be some error in terms of updating the data and two bind to close does not work, which reinforces the case that there is an issue with saving. I would make the following suggestions:

  1. JSON Encoding: I do apologize in advance, I am not too well versed in JSON Encode and what it entails however from a post I read, I believe you should only use JSON Encode when sending data externally outside of Roblox (third parties), so run some prints and check is the data updating is the decoded information before using :UpdateAsynch.

  2. Print Debug your UpdateAsych logic. Honestly, my advice here would be instead of an if statement here, simply return plrData (assuming this is your new data), I don’t think checking the version is necessary here.

General tips of course would be to check to make sure APIs are enabled as well as Http Services. I hope this helps!

1 Like

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