Help with plot saving/loading system

Hey again. Im trying to make a game that you can build things on your plot, and I wrote a script that (mostly) works, except for rotation. Normally without rotation, the loading system works just fine:


If I try to add rotation into the script below, however, this happens:

I know there is nothing wrong with the saving/loading, if thats what your thinking, because it works completely fine and worked before I added toObjectSpace, but I figured out I needed to use Object space because If im gonna have multiple plots I need a way to translate the location specific to your plot. I’ve pasted the script below that handles the placement and saving/loading. If anyone could help, that would be nice.

local ProfileService = require(game.ServerScriptService.ProfileService)
local PlayerData = require(game.ReplicatedStorage.Modules.PlayerData)
local RunService = game:GetService("RunService")

local ProfileTemplate = {
	Items = {
		["Furnace"] = {
			ObjectName = "Furnace",
			Count = 1
		},
		["Straight Conveyor"] = {
			ObjectName = "Straight Conveyor",
			Count = 3
		}
	},
	PlotData = {}
}

local Players = game:GetService("Players")

local ProfileStore = ProfileService.GetProfileStore(
	"StudioPlayerData17",
	ProfileTemplate
)

local Profiles = {}

local function ProfileLoaded(plr, profile, plot)
	--Player Status
	-- 1 = Idle
	-- 2 = Placing
	-- 3 = Destroying
	PlayerData:AddOrSetNumber(plr, 1, "PlayerStatus")
	PlayerData:AddOrSetBool(plr, false, "ObjectDebounce")
	for _, obj in pairs(profile.Data.Items) do
		local model = game.ReplicatedStorage.Models:FindFirstChild(obj.ObjectName)
		PlayerData:AddOrSetNumber(plr, obj.Count, obj.ObjectName)
		game.ReplicatedStorage.Remotes.AddClientObject:FireClient(plr, model, obj.Count)
	end
	print(profile.Data.PlotData)
	for index, plotObj in pairs(profile.Data.PlotData) do
		local model = game.ReplicatedStorage.Models:FindFirstChild(plotObj.Object):Clone()
		
		local locpos = CFrame.new(plotObj.Location.x, plotObj.Location.y, plotObj.Location.z) * CFrame.Angles(0, math.rad(plotObj.Location.r), 0):ToObjectSpace(workspace.Plots:FindFirstChild(plr.playerdata.Plot.Value).CFrame) -- The * CFrame.Angles() is what messes with the rotation but I dont know how to fix this
		model:SetPrimaryPartCFrame(locpos)
		model.Parent = plot.ItemHolder
		model:SetAttribute("Identifier", index)
	end
end

local function PlayerAdded(plr)
	local playerPlot = nil
	for _, plot in pairs(workspace.Plots:GetChildren()) do
		if plot:GetAttribute("Owner") == "Nobody" then
			PlayerData:AddOrSetString(plr, plot.Name, "Plot")
			plot:SetAttribute("Owner", plr.Name)
			playerPlot = plot
			break
		end
	end
	if playerPlot == nil then
		plr:Kick()
	end
	
	local profile = ProfileStore:LoadProfileAsync("Player_" .. plr.UserId)
	if profile ~= nil then
		profile:AddUserId(plr.UserId)
		profile:Reconcile()
		profile:ListenToRelease(function()
			Profiles[plr] = nil
			plr:Kick()
		end)
		if plr:IsDescendantOf(Players) == true then
			Profiles[plr] = profile
			ProfileLoaded(plr, profile, playerPlot)
		else
			profile:Release()
		end
	else
		plr:Kick() 
	end
end

for _, player in ipairs(Players:GetPlayers()) do
	coroutine.wrap(PlayerAdded)(player)
end

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
	local profile = Profiles[player]
	if profile ~= nil then
		profile:Release()
	end
end)

game.ReplicatedStorage.Remotes.PlaceObjectServer.OnServerEvent:Connect(function(player)
	local profile = Profiles[player]
	if profile ~= nil then
		profile.Data.PlotData = {}
		for index, p in pairs(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).ItemHolder:GetChildren()) do
			profile.Data.PlotData[#profile.Data.PlotData + 1] = {
				Object = p.Name,
				Location = {
					x = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).X,
					y = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Y,
					z = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Z,
					r = p.PrimaryPart.Orientation.Y
				}
			}
			p:SetAttribute("Identifier", index)
			if game.ReplicatedStorage.Models:FindFirstChild(p.Name) then
				profile.Data.Items[p.Name] = {
					ObjectName = p.Name,
					Count = player.playerdata:FindFirstChild(p.Name).Value
				}
			end
		end
		print(profile.Data)
	end
end)

local function delete(plr, obj)
	if obj ~= nil and obj.Parent ~= nil then
		if obj.Name == "Hitbox" then
			local profile = Profiles[plr]
			if profile ~= nil then
				local delete = obj.Parent
				local identifier = delete:GetAttribute("Identifier")
				local newCount = plr.playerdata:FindFirstChild(delete.Name).Value + 1
				profile.Data.PlotData[identifier] = nil
				plr.playerdata:FindFirstChild(delete.Name).Value = newCount
				obj.Parent:Destroy()
				profile.Data.Items[delete.Name] = {
					ObjectName = delete.Name,
					Count = newCount
				}
				game.ReplicatedStorage.Remotes.PlaceObjectClient:FireClient(plr, delete.Name, newCount)
				delete:Destroy()
			end
		end
	end
end

game.ReplicatedStorage.Remotes.DeleteItem.OnServerEvent:Connect(delete)

game.ReplicatedStorage.Remotes.SetPlayerStatus.OnServerEvent:Connect(function(plr, status)
	PlayerData:AddOrSetNumber(plr, status, "PlayerStatus")
end)
1 Like

Are all the objects in the wrong place or only the ones that are rotated?

In the second picture yes, because thats when I multiply the CFrame by CFrame.Angles and use the Y parameter of the CFrame.Angles as the rotation that was saved, its probably being messed up since the CFrame is translated to Object Space

It might be that multiplication comes after :ToObjectSpace() and the function is affecting the CFrame.Angles instead.

So should I try multiplying it after the ToObjectSpace?

No multiply before doing ToObjectSpace()

Doing this instead should work.

local locpos = CFrame.new(plotObj.Location.x, plotObj.Location.y, plotObj.Location.z) * CFrame.Angles(0, math.rad(plotObj.Location.r), 0)

locpos = locpos:ToObjectSpace(workspace.Plots:FindFirstChild(plr.playerdata.Plot.Value).CFrame)


The rotation for anything that is placed normally (the 2 conveyors and the furnace) works fine, but the one circled in red was placed at 90 degrees in the spot where the second red circle is on the plot (this is using the code you gave me)

Are you using ToWorldSpace in the script?

No I’m not sure how to use it, I tried looking at multiple devforums and I cant figure out how to use it, but when I use toObjectSpace in both the saving and loading it seemed to work on any plot no matter where it was apart from rotation

The cframe that is saved should be ItemCFrame:ToObjectSpace(PlotCFrame) and the cframe the items are set to after loading should be ItemCFrame:ToWorldSpace(PlotCFrame)

What about the orientation? Do I leave it as ItemHere.Orientation.Y when saving? Or should I use the VectorToWorldSpace function thingy

The orientation should also be a part of the cframe, if you don’t include it rotated plots will make items have the wrong orientation.

Now when I place things regardless of the orientation it just breaks
This is using

r = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Rotation.Y

Is this segment of code from the saving part?

Yeah, I can send the full script again if need be

1 Like

Yes just send the full script.

local ProfileService = require(game.ServerScriptService.ProfileService)
local PlayerData = require(game.ReplicatedStorage.Modules.PlayerData)
local RunService = game:GetService("RunService")

local ProfileTemplate = {
	Items = {
		["Furnace"] = {
			ObjectName = "Furnace",
			Count = 1
		},
		["StraightConveyor"] = {
			ObjectName = "StraightConveyor",
			Count = 3
		}
	},
	PlotData = {}
}

local Players = game:GetService("Players")

local ProfileStore = ProfileService.GetProfileStore(
	"StudioPlayerData22",
	ProfileTemplate
)

local Profiles = {}

local function ProfileLoaded(plr, profile, plot)
	--Player Status
	-- 1 = Idle
	-- 2 = Placing
	-- 3 = Destroying
	PlayerData:AddOrSetNumber(plr, 1, "PlayerStatus")
	PlayerData:AddOrSetBool(plr, false, "ObjectDebounce")
	PlayerData:AddOrSetString(plr, "None", "ObjectBeingPlaced")
	for _, obj in pairs(profile.Data.Items) do
		local model = game.ReplicatedStorage.Models:FindFirstChild(obj.ObjectName)
		PlayerData:AddOrSetNumber(plr, obj.Count, obj.ObjectName)
		game.ReplicatedStorage.Remotes.AddClientObject:FireClient(plr, model, obj.Count)
	end
	print(profile.Data.PlotData)
	for index, plotObj in pairs(profile.Data.PlotData) do
		local model = game.ReplicatedStorage.Models:FindFirstChild(plotObj.Object):Clone()
		
		local locpos = CFrame.new(plotObj.Location.x, plotObj.Location.y, plotObj.Location.z) * CFrame.Angles(0, math.rad(plotObj.Location.r), 0)
		locpos = locpos:ToWorldSpace(workspace.Plots:FindFirstChild(plr.playerdata.Plot.Value).CFrame)
		model:SetPrimaryPartCFrame(locpos)
		model.Parent = plot.ItemHolder
		model:SetAttribute("Identifier", index)
	end
end

local function PlayerAdded(plr)
	local playerPlot = nil
	for _, plot in pairs(workspace.Plots:GetChildren()) do
		if plot:GetAttribute("Owner") == "Nobody" then
			PlayerData:AddOrSetString(plr, plot.Name, "Plot")
			plot:SetAttribute("Owner", plr.Name)
			playerPlot = plot
			break
		end
	end
	if playerPlot == nil then
		plr:Kick()
	end
	
	local profile = ProfileStore:LoadProfileAsync("Player_" .. plr.UserId)
	if profile ~= nil then
		profile:AddUserId(plr.UserId)
		profile:Reconcile()
		profile:ListenToRelease(function()
			Profiles[plr] = nil
			plr:Kick()
		end)
		if plr:IsDescendantOf(Players) == true then
			Profiles[plr] = profile
			ProfileLoaded(plr, profile, playerPlot)
		else
			profile:Release()
		end
	else
		plr:Kick() 
	end
end

for _, player in ipairs(Players:GetPlayers()) do
	coroutine.wrap(PlayerAdded)(player)
end

Players.PlayerAdded:Connect(PlayerAdded)

Players.PlayerRemoving:Connect(function(player)
	local profile = Profiles[player]
	if profile ~= nil then
		profile:Release()
	end
end)

game.ReplicatedStorage.Remotes.PlaceObjectServer.OnServerEvent:Connect(function(player)
	local profile = Profiles[player]
	if profile ~= nil then
		profile.Data.PlotData = {}
		for index, p in pairs(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).ItemHolder:GetChildren()) do
			profile.Data.PlotData[#profile.Data.PlotData + 1] = {
				Object = p.Name,
				Location = {
					x = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).X,
					y = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Y,
					z = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Z,
					r = p.PrimaryPart.CFrame:ToObjectSpace(workspace.Plots:FindFirstChild(player.playerdata.Plot.Value).CFrame).Rotation.Y
				}
			}
			p:SetAttribute("Identifier", index)
			if game.ReplicatedStorage.Models:FindFirstChild(p.Name) then
				profile.Data.Items[p.Name] = {
					ObjectName = p.Name,
					Count = player.playerdata:FindFirstChild(p.Name).Value
				}
			end
		end
		print(profile.Data)
	end
end)

local function delete(plr, obj)
	if obj ~= nil and obj.Parent ~= nil then
		if obj.Name == "Hitbox" then
			local profile = Profiles[plr]
			if profile ~= nil then
				local delete = obj.Parent
				local identifier = delete:GetAttribute("Identifier")
				local newCount = plr.playerdata:FindFirstChild(delete.Name).Value + 1
				profile.Data.PlotData[identifier] = nil
				plr.playerdata:FindFirstChild(delete.Name).Value = newCount
				obj.Parent:Destroy()
				profile.Data.Items[delete.Name] = {
					ObjectName = delete.Name,
					Count = newCount
				}
				game.ReplicatedStorage.Remotes.PlaceObjectClient:FireClient(plr, delete.Name, newCount)
				delete:Destroy()
			end
		end
	end
end

game.ReplicatedStorage.Remotes.DeleteItem.OnServerEvent:Connect(delete)

game.ReplicatedStorage.Remotes.SetPlayerStatus.OnServerEvent:Connect(function(plr, status, ...)
	local args = {...}
	PlayerData:AddOrSetNumber(plr, status, "PlayerStatus")
	if args[1] ~= nil then
		plr.playerdata.ObjectBeingPlaced.Value = args[1]
	end
end)

Thats what I currently have

math.rad shouldn’t be used here as it is already stored in radians. If this still doesn’t fix the issue I can just show a solution that i’m using for my own game that uses saving and loading.

Still didnt fix the issue, so I may need that solution