Plot saving and loading system

  1. What do you want to achieve?
    Hi, I’ve been trying for days to get an area and save everything in there or save all the newly placed models in there. So to get to the point: I want to make a placemetn system with which you can set things and you can edit them with the tool f3x and this plot is then saved as loaded.

  2. What is the issue?
    Here are some pictures:
    image
    image

  3. What solutions have you tried so far?
    As I said, I’ve been looking for it for days and haven’t found anything. Only once was a plot system that only saved what was set and not the edited one with the Tool.

1 Like

You can use workspace:GetPartsInPart() or workspace:GetPartBoundsInBox() to get all the parts inside of a player’s plot.

local playersParts = {} --Define a holding table for all players' parts that can be referenced between functions.

local function fetchParts(Player)
	local playersZone = Player.Zone.Value --In this example, Zone would be an ObjectValue inside the player set to their zone area.
	if not playersZone then return nil end --Return nil if they don't have a zone. (This way you can detect when the function returns nil so you don't accidently overwrite their save with an empty table if they haven't gotten a zone yet.)

	if not playersParts[Player.Name] then playersParts[Player.Name] = {} end --Create a table for the specific player if one does not exist.
	
	local params = OverlapParams.new() --Create a filter for the GetPartsInPart function.
	local characterFilter = {} --Define a table to hold every character model.
	for i,v in ipairs(game:GetService("Players"):GetChildren()) do
		table.insert(characterFilter, v.Character) --Insert every character model into that table.
	end
	params.FilterType = Enum.RaycastFilterType.Blacklist --We will use a blacklist since we want to filter parts that are NOT character parts.
	params.FilterDescendantsInstances = characterFilter --Set the filter to that character table.
	
	local partsInZone = workspace:GetPartsInPart(playersZone, params) --This will give us a table/list of all parts inside the zone bounds.
	
	for i,v in ipairs(partsInZone) do
		if not table.find(playersParts[Player.Name], v) then --If the part isn't in the master table,
			table.insert(playersParts[Player.Name], v) --Insert it into the table.
		end
	end

	for i,v in pairs(playersParts[Player.Name]) do --Need to make sure this is run in pairs instead of ipairs since playersParts[Player.Name] will have nil values.
		if not table.find(partsInZone, v) then --If there is a part in the table that is not in the zone, 
			playersParts[Player.Name][i] = nil --Remove it from the table.
		end
	end
	return playersParts[Player.Name]
end

Then you will have to create a DataStore system to save/load the player’s part table using serialization. To do this you will have to gather properties from every part in the table. Since you need to use DataStores for it, you need to format those values into acceptable types such as tables, numbers, strings, and booleans.

For example,

print(Part.Color) --> Color3.new(1,0.5,0)

needs to be formatted (encoded) into its own table:

--> {R = 1, G = 0.5, B = 0}

You will only want to save essential properties such as the ClassName, CFrame, Size, Material, and Color because of save/load speed, server stress, and DataStore size limits. When it’s time to load, you will need to create new parts and change their properties accordingly after decoding the save data into their proper formats. (Color3.new, CFrame.new, Enum.Material, etc.)

Here is a serialization module that should help simplify the entire serialization process. You can simply call require(9768577563):Convert(Part) which will return a fully serialized table that’s ready for DataStores. You can also call that same function on the save data to automatically convert it back into parts.

Here’s an example of using that module with the fetchParts() function:

local encodedParts = {} --Define a temporary holding table that we can put all of the encoded parts into.
local rawParts = fetchParts(PLAYER) --Gather the parts we need to save for a specific player using the function above.
for i,v in pairs(rawParts) do --Do for every part that was detected in the zone.
	local encodedTable = require(9768577563):Convert(v) --Require the module then call :Convert() (In this case, using the default conversion settings. See the documentation for overridable settings.)
	if encodedTable then table.insert(encodedParts,encodedTable) end --If the conversion was successful, put the data into the table.
end
--Now the encodedParts table is ready to be saved to a DataStore.

Then you will have to setup a way to reposition the parts relative to each zone when you load the data.
It’s easiest to group the parts into a model, give it a PrimaryPart in the center of the zone, and pivot the whole model to the zone position (otherwise you would have to save their relative position and manually calculate a new position every time you load the player’s part data).

In the serialization module’s documentation, there is a video demo showing a plot building/saving system similar to what you’re trying to achieve and the link to the demo place is uncopylocked so you can edit the place for yourself to see how all those scripts work together. Though, the scripts there aren’t optimized for other use cases and were only meant to cater that specific demonstration.

Functions:
:GetPartsInPart()
:GetPartBoundsInBox()
:PivotTo()

Module Links:
Converter Module: Instance ↔ Table [Marketplace]
Converter Module: Instance ↔ Table [Documentation]
Furniture Customization Video Demo

Recommended Tutorials:
DataStore (Saving & Loading Player Data) - Roblox Scripting Tutorial
How to save parts, and the idea of Serialization

3 Likes