How can I save this model's data to a DataStore?

I want to save the data of all the models in a specific location in the workspace. For this, I use a table which saves the name of the model and the CFrame of the PrimaryPart.
However, when I try this out, I get the following error:

18:57:32.884 - 104: Cannot store Array in data store. Data stores can only accept valid UTF-8 characters.

Is there any way to save this information, or in what other way can I get the information that determines the right model and its location in the model inside the workspace?
This is the script I use, a Script in a TextButton:

local clicked = false

script.Parent.MouseButton1Click:Connect(function()
	if clicked == false then
		clicked = true
		local ds = game:GetService("DataStoreService"):GetDataStore("ItemsStorage")
		local player = game.Players:FindFirstChild(script.Parent.Parent.Parent.Parent.Parent.Name)
		local data = {}
		local items = workspace.Base.ItemHolder:GetChildren()
		local progressBar = script.Parent.Parent.ProgressBar
		local button = script.Parent
		
		progressBar.Visible = true
		
		for i = 1, #items do
			local item = items[i]
			local mainPart = item.PrimaryPart
			
			table.insert(data, item.Name)
			wait()
			table.insert(data, mainPart.CFrame)
			wait()
			progressBar.ProgressFrame.Size = UDim2.new(((1 / #items) * i), 0, 1, 0)
		end
		
		progressBar.ProgressFrame.Size = UDim2.new(1, 0, 1, 0)
		ds:SetAsync(player.UserId, data)
		
		progressBar.Message.Text = "Saved successfully!"
		wait(2)
		progressBar.Message.Text = ""
		progressBar.Visible = false
		
		for i = 30, 0, -1 do -- add a wait time to only be able to save every 30 seconds
			button.Text = i
			wait(1)
		end
		button.Text = "Save"
		clicked = false
	end
end)
1 Like

CFrames are not saveable, you’ll need to serialize it.
I’d preferably save it’s Position and LookVector in individual arrays in a dictionary.

For example:

local MyCFrame = CFrame.new(Vector3.new(1,2,3), Vector3.new(3,2,1))
local MyPosition = MyCFrame.Position
local MyLookVector = MyCFrame.LookVector

local CFrameData = {
    Position = {MyPosition.X, MyPosition.Y, MyPosition.Z};
    LookVector = {MyLookVector.X, MyLookVector.Y, MyLookVector.Z};
}

--// Now you can save it
DataStore:SetAsync("key", CFrameData)
--// And get it
local Data = DataStore:GetAsync("key")
--// Yet also reconstruct it
local MyCFrame = CFrame.new(Vector3.new(unpack(Data.Position)), Vector3.new(unpack(Data.LookVector)))

FYI: I’d recommend that you use a LocalScript for listening on MouseButton1Click and integrate a RemoteEvent with a debounce - otherwise if a player spam clicked, they could continously cause the server to save their data.

8 Likes

Okay so… I think I’ve been able to follow your steps, and I’ve got 2 buttons with scripts now, one to save and one to load. I’ll show them right here.

Save:

local clicked = false

script.Parent.MouseButton1Click:Connect(function()
	if clicked == false then
		clicked = true
		local ds = game:GetService("DataStoreService"):GetDataStore("ItemsStorage")
		local player = game.Players:FindFirstChild(script.Parent.Parent.Parent.Parent.Parent.Name)
		local data = {}
		local items = workspace.Base.ItemHolder:GetChildren()
		local progressBar = script.Parent.Parent.ProgressBar
		local button = script.Parent
		
		progressBar.Visible = true
		
		for i = 1, #items do
			local item = items[i]
			local mainPart = item.PrimaryPart
			local mainCFrame = mainPart.CFrame
			local mainPos = mainCFrame.Position
			local mainLookVector = mainCFrame.LookVector
			local CFrameData = {
				Position = {mainPos.X, mainPos.Y, mainPos.Z};
				LookVector = {mainLookVector.X, mainLookVector.Y, mainLookVector.Z};
			}
			
			table.insert(data, item.Name)
			wait()
			table.insert(data, CFrameData)
			wait()
			progressBar.ProgressFrame.Size = UDim2.new(((1 / #items) * i), 0, 1, 0)
		end
		
		progressBar.ProgressFrame.Size = UDim2.new(1, 0, 1, 0)
		ds:SetAsync(player.UserId, data)
		
		progressBar.Message.Text = "Saved successfully!"
		wait(2)
		progressBar.Message.Text = ""
		progressBar.Visible = false
		
		for i = 30, 0, -1 do
			button.Text = i
			wait(1)
		end
		button.Text = "Save"
		clicked = false
	end
end)

Load script:

local clicked = false

script.Parent.MouseButton1Click:Connect(function()
	if clicked == false then
		clicked = true
		local ds = game:GetService("DataStoreService"):GetDataStore("ItemsStorage")
		local player = game.Players:FindFirstChild(script.Parent.Parent.Parent.Parent.Parent.Name)
		local data = {}
		local progressBar = script.Parent.Parent.ProgressBar
		local button = script.Parent
		
		progressBar.Visible = true
		
		data = ds:GetAsync(player.UserId)
		
		for i = 1, #data, 2 do
			wait(1)
			local item = data[i]
			local model =  game.ReplicatedStorage.Items:FindFirstChild(item):Clone() -- I forgot the = here
			model.Parent = workspace
			
			local mainCFrame = CFrame.new(Vector3.new(unpack(data[i + 1].Position)), Vector3.new(unpack(data[i + 1].LookVector)))
			
			model.PrimaryPart.CFrame = mainCFrame
		end
		
		clicked = false
	end
end)

The save script doesn’t give me any errors, so I suppose that it works. The load scrip however gives me the following error:
19:34:24.113 - Players.Dylan011444.PlayerGui.SaveLoad.Frame.LoadPlot1.LoadScript1:20: attempt to index local ‘model’ (a nil value)

Edit:
I’ve noticed my embarrassing mistake. It works, why thank you!

2 Likes

One other thing I’ve noticed is that the lookvector doesn’t quite work. When I try to load the game again, this is the result

Is there any way you can think of that fixes this? The mainpart in each model rotates as well when you rotate the model, so I wouldn’t know how it could be rotated like this.

I’m really sorry to bother you again with this.

2 Likes