"Data stores can only accept valid UTF-8 characters."

Please read this quoted post. That’s why I’m saying this, correct me if I’m wrong.

You can save tables in a datastore but to you can also specifically convert something that can’t be saved in a datastore into JSONEncode().

1 Like

To save:

local success, err
	repeat
		success, err = pcall(function()
			autoSavingStore:SetAsync(key, function()
				return game:GetService("HttpService"):JSONEncode(data)
			end)
		end)
		
		task.wait()
	until success

To load:

local function load(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local success, err
	local data
	
	repeat
		success,err = pcall(function()
			data = autoSavingStore:GetAsync(key)
		end)
	until success or not players:FindFirstChild(player.Name)
	
	if not data then return end
	if success then
		data = game:GetService("HttpService"):JSONDecode(data)

Hmm. I didn’t know that. Thanks

2 Likes

is that correct?

local players = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")
local autoSavingStore = dataStoreService:GetDataStore("F3XAutoSave")
local keyPrefix = "Player: "

local function save(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local data = {}

	for i, obj: BasePart in ipairs(workspace:FindFirstChild("Builds"):WaitForChild(player.Name):GetDescendants()) do
		if obj:IsA("BasePart") then
			table.insert(data, {
				-- PART PROPERTIES
				obj.Name,
				obj.CanCollide,
				obj.Position.X,
				obj.Position.Y,
				obj.Position.Z,
				obj.Orientation.X,
				obj.Orientation.Y,
				obj.Orientation.Z,
				obj.Anchored,
				obj.Size.X,
				obj.Size.Y,
				obj.Size.Z,
				obj.Color.R,
				obj.Color.G,
				obj.Color.B,
				(string.match(tostring(workspace.Baseplate.TopSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.BottomSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.LeftSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.RightSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.BackSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.FrontSurface), "%.(%w+)$")),
				obj.Transparency,
				obj.Reflectance,
				string.match(tostring(obj.Material), "%.(%w+)$")
			})

			local success, err
			repeat
				success, err = pcall(function()
					autoSavingStore:SetAsync(key, function()
						return game:GetService("HttpService"):JSONEncode(data)
					end)
				end)

				task.wait()
			until success
		end
    end
end

local function load(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local success, err
	local data

	repeat
		success,err = pcall(function()
			data = autoSavingStore:GetAsync(key)
		end)
	until success or not players:FindFirstChild(player.Name)

	if not data then return end
	if success then
		data = game:GetService("HttpService"):JSONDecode(data)
		for i, obj in ipairs(data) do
			local partInstance = Instance.new("Part")
			partInstance.Name=obj[1]
			partInstance.CanCollide=obj[2]
			partInstance.Position = Vector3.new(obj[3], obj[4], obj[5])
			partInstance.CFrame *= CFrame.Angles(math.rad(obj[6]),math.rad(obj[7]),math.rad(obj[8]))
			partInstance.Anchored=obj[9]
			partInstance.Size = Vector3.new(obj[10], obj[11], obj[12])
			partInstance.Color = Color3.new(obj[13],obj[14],obj[15])
			partInstance.TopSurface = Enum.SurfaceType[obj[16]]
			partInstance.BottomSurface = Enum.SurfaceType[obj[17]]
			partInstance.LeftSurface = Enum.SurfaceType[obj[18]]
			partInstance.RightSurface = Enum.SurfaceType[obj[19]]
			partInstance.BackSurface = Enum.SurfaceType[obj[20]]
			partInstance.FrontSurface = Enum.SurfaceType[obj[21]]
			partInstance.Transparency = obj[22]
			partInstance.Reflectance = obj[23]
			partInstance.Material = Enum.Material[obj[24]]
		end
	else
		warn(tostring(err))
	end
end

players.PlayerAdded:Connect(load)
players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for i, plr: Player in ipairs(players:GetPlayers()) do
		save(plr)
	end
end)

Yes, but this is probably not the cause for the error you’re facing.

i still get the error :confused: , it is not working

hold on a minute, i forgot to add the httpservice, i will add that rn mb

i added the httpservice variable and applied it but it still doesn’t work… :frowning:

try changing the colon to an underscore in the keyPrefix

the key is customizable, also when i tried to change it , didnt work.

Hello,
I checked your script and the issue seems to occur because you’re using a function in SetAsync()


(which is what you should use for UpdateAsync
image

Here is the updated script:

local players = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")
local autoSavingStore = dataStoreService:GetDataStore("FX3AutoSave")
local keyPrefix = "Player_"

local function save(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local data = {}

	for i, obj: BasePart in ipairs(workspace:FindFirstChild("Builds"):WaitForChild(player.Name):GetDescendants()) do
		if obj:IsA("BasePart") then
			table.insert(data, {
				-- PART PROPERTIES
				obj.Name,
				obj.CanCollide,
				obj.CFrame.X,
				obj.CFrame.Y,
				obj.CFrame.Z,
				obj.Orientation.X,
				obj.Orientation.Y,
				obj.Orientation.Z,
				obj.Anchored,
				obj.Size.X,
				obj.Size.Y,
				obj.Size.Z,
				obj.Color.R,
				obj.Color.G,
				obj.Color.B,
				(string.match(tostring(workspace.Baseplate.TopSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.BottomSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.LeftSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.RightSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.BackSurface), "%.(%w+)$")),
				(string.match(tostring(workspace.Baseplate.FrontSurface), "%.(%w+)$")),
				obj.Transparency,
				obj.Reflectance,
				string.match(tostring(obj.Material), "%.(%w+)$")
			})


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

				task.wait()
			until success

			if not success then
				warn(tostring(err))
			end
		end
	end
end
local function load(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local success, err
	local data

	repeat
		success,err = pcall(function()
			data = autoSavingStore:GetAsync(key)
		end)
	until success or not players:FindFirstChild(player.Name)

	if not data then return end
	if success then
		for i, obj in ipairs(data) do
			local partInstance = Instance.new("Part")
			partInstance.Name=obj[1]
			partInstance.CanCollide=obj[2]
			partInstance.CFrame = CFrame.new(obj[3], obj[4], obj[5])
			partInstance.CFrame *= CFrame.Angles(math.rad(obj[6]),math.rad(obj[7]),math.rad(obj[8]))
			partInstance.Anchored=obj[9]
			partInstance.Size = Vector3.new(obj[10], obj[11], obj[12])
			partInstance.Color = Color3.new(obj[13],obj[14],obj[15])
			partInstance.TopSurface = Enum.SurfaceType[obj[16]]
			partInstance.BottomSurface = Enum.SurfaceType[obj[17]]
			partInstance.LeftSurface = Enum.SurfaceType[obj[18]]
			partInstance.RightSurface = Enum.SurfaceType[obj[19]]
			partInstance.BackSurface = Enum.SurfaceType[obj[20]]
			partInstance.FrontSurface = Enum.SurfaceType[obj[21]]
			partInstance.Transparency = obj[22]
			partInstance.Reflectance = obj[23]
			partInstance.Material = Enum.Material[obj[24]]
		end
	else
		warn(tostring(err))
	end
end

players.PlayerAdded:Connect(load)
players.PlayerRemoving:Connect(save)
game:BindToClose(function()
	for i, plr: Player in ipairs(players:GetPlayers()) do
		save(plr)
	end
end)

Some people mentioned data serialization with JSONEncode & JSONDecode which might be a good practise but imo it makes data harder to manipulate and may fail.
Hope that helps! :pray:t2:

1 Like

thank you so muchhhh, this is worked just like what i wanted!

1 Like

one question tho, how do i detect if the part have a decal so i add its options too

You could do:

if part:FindFirstChildWhichIsA("Decal") then
    local decal = part:FindFirstChildWhichIsA("Decal")
    -- save decal --
end

I’d advice you to create a recursive function that serializes the properties into a table outside of the SaveData func, so basically, you’re executing that serialize func on an instance which runs itself on all of the instance’s children and so on until you reach the end.

1 Like

alright thank you so much for your help!

2 Likes

i dont know where to put it, can you tell me where to put it and if it detected a decal it will make the decal with the options that are saved?

1 Like

I’ve made several changes in your script, here’s the updated version that should support both BasePart & Decals tho you can add more later!

please note there are already some serialization tools out there (RBLXSerialize - a easy to use and really cool all-in-one Roblox Serializer) for example; tho it was pretty fun working on this one.
local players = game:GetService("Players")
local dataStoreService = game:GetService("DataStoreService")
local autoSavingStore = dataStoreService:GetDataStore("FX3AutoSave")
local keyPrefix = "Player_"

local function serialize(obj: Instance | any)
	local _, properties = pcall(function()
		local r = {}
		r.Name = obj.Name
		r.ClassName = obj.ClassName
		r.Children = {}
		if obj:IsA("BasePart") then
			r.CanCollide = obj.CanCollide
			r.CFrameX = obj.CFrame.X
			r.CFrameY = obj.CFrame.Y
			r.CFrameZ = obj.CFrame.Z
			r.OrientationX = obj.Orientation.X
			r.OrientationY = obj.Orientation.Y
			r.OrientationZ = obj.Orientation.Z
			r.Anchored = obj.Anchored
			r.SizeX = obj.Size.X
			r.SizeY = obj.Size.Y
			r.SizeZ = obj.Size.Z
			r.ColorR = obj.Color.R
			r.ColorG = obj.Color.G
			r.ColorB = obj.Color.B
			r.TopSurface = (string.match(tostring(obj.TopSurface), "%.(%w+)$"))
			r.BottomSurface = (string.match(tostring(obj.BottomSurface), "%.(%w+)$"))
			r.LeftSurface = (string.match(tostring(obj.LeftSurface), "%.(%w+)$"))
			r.RightSurface = (string.match(tostring(obj.RightSurface), "%.(%w+)$"))
			r.BackSurface = (string.match(tostring(obj.BackSurface), "%.(%w+)$"))
			r.FrontSurface = (string.match(tostring(obj.FrontSurface), "%.(%w+)$"))
			r.Transparency = obj.Transparency
			r.Reflectance = obj.Reflectance
			r.Material = string.match(tostring(obj.Material), "%.(%w+)$")
		elseif obj:IsA("Decal") then
			-- Handle Decal properties
			r.Texture = obj.Texture
			r.Face = tostring(obj.Face)
			r.Transparency = obj.Transparency
			-- Add other Decal properties as needed
		else
			-- Handle other types of objects if needed
		end

		return r
	end)

	for i, v in pairs(obj:GetChildren()) do
		table.insert(properties.Children, serialize(v))
	end

	return properties
end

local function save(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local data = {}

	for i, obj: BasePart in ipairs(workspace:FindFirstChild("Builds"):WaitForChild(player.Name):GetChildren()) do
		if obj:IsA("BasePart") then
			local s, serializedTable = pcall(function()
				return serialize(obj)
			end)

			if typeof(serializedTable) == "string" then
				warn(serializedTable)
				continue
			end

			table.insert(data, serializedTable)
		end
	end

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

	if not success then
		warn(tostring(err))
	end
end

local function load(player: Player)
	local key = keyPrefix .. tostring(player.UserId)
	local success, err
	local data

	repeat
		success, err = pcall(function()
			data = autoSavingStore:GetAsync(key)
		end)
	until success or not players:FindFirstChild(player.Name)
	if not data then return end
	if success then
		for i, objData in pairs(data) do
			local function createInstance(objData)
				local instance
				if objData.Name and objData.ClassName then
					instance = Instance.new(objData.ClassName)
					instance.Name = objData.Name

					for property, value in pairs(objData) do
						if property ~= "Name" and property ~= "ClassName" and property ~= "Children" then
							if property == "CFrameX" or property == "CFrameY" or property == "CFrameZ" then
								instance.CFrame = CFrame.new(objData.CFrameX, objData.CFrameY, objData.CFrameZ)
							elseif property == "OrientationX" or property == "OrientationY" or property == "OrientationZ" then
								instance.Orientation = Vector3.new(objData.OrientationX, objData.OrientationY, objData.OrientationZ)
							elseif property == "ColorR" or property == "ColorG" or property == "ColorB" then
								instance.Color = Color3.new(objData.ColorR, objData.ColorG, objData.ColorB)
							elseif property == "SizeX" or property == "SizeY" or property == "SizeZ" then
								instance.Size = Vector3.new(objData.SizeX, objData.SizeY, objData.SizeZ)
							else
								instance[property] = value
							end
						end
					end

					if objData.Children then
						for _, childData in ipairs(objData.Children) do
							local childInstance = createInstance(childData)
							childInstance.Parent = instance
						end
					end
				end
				
				return instance
			end

			local partInstance = createInstance(objData)
			if partInstance then
				partInstance.Parent = workspace.Builds:WaitForChild(player.Name) -- Adjust the hierarchy based on your structure
			end
		end
	else
		warn(tostring(err))
	end
end


players.PlayerAdded:Connect(load)
players.PlayerRemoving:Connect(save)

game:BindToClose(function()
	for i, plr: Player in ipairs(players:GetPlayers()) do
		save(plr)
	end
end)

It might not be very optimized to save everything when a player leaves, i’d say you better to save when there’s a change in the game instead, imagine what may happen if the player tries to save a really big structure and the server closes before? That’d be an issue. But anyway, have fun!

works just like i expected,
about what you said about when a player leaves:
i made it saves every 3 minutes you build something

thank you so much

1 Like

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