Topic for help with my entire save script (DataStore2)

My topic died so here we go again.

I need to make a script that can 1: save data 2: convert saveable bunker array properties into parts and put them into my map 3: Convert models into datastoreable arrays 4: Offset bunkers to a free position.

Map format explanation: image

Each map is stored in game.Workspace.maps and the client makes sure that all maps but the map the player is in is parented to game.ReplicatedStorage.local_map_storage. It also clears any existing terrain and adds new terrain using :FillBlock() on parts in cubes and also in :FillBlock() enum.Material is set to the part’s name.

Function for loading maps (Locally stored module script)

ReplicatedStorage = game.ReplicatedStorage
loaded_map = script.Parent.loaded_map.Value
local_map_storage = ReplicatedStorage.local_map_storage
maps = game.Workspace.maps
maps_children = maps:GetChildren()
storage_children = local_map_storage:GetChildren()
Terrain = game.Workspace.Terrain


local functions = {
    load_map = function (area, spawn_area_X, spawn_area_Y, spawn_area_Z, angle_X, angle_Y, angle_Z, use_respawn, transition, transition_name, transition_description, transition_colour)
Terrain:Clear()
ReplicatedStorage.load_area:FireServer(area, spawn_area_X, spawn_area_Y, spawn_area_Z)
maps[loaded_map].Parent = local_map_storage
loaded_map = area
local_map_storage[area].Parent = maps
local map_terrain = maps[loaded_map].terrain
local cubes = map_terrain.cubes:GetChildren()
local spheres = map_terrain.spheres:GetChildren()

for i = 1, #cubes do 		
      local cube_child = cubes[i] 		 	
      if cube_child:IsA("Part") then
		game.Workspace.Terrain:FillBlock(cube_child.CFrame, cube_child.Size, Enum.Material[cube_child.Name])
      end
end
print (area.." loaded")

   if use_respawn == true then
	print ("Using respawn points.")
	
		else
local character = game.Players.LocalPlayer.Character
local pos = CFrame.new(spawn_area_X, spawn_area_Y, spawn_area_Z)
local rot = CFrame.Angles(math.rad(angle_X), math.rad(angle_Y), math.rad(angle_Z))
character.HumanoidRootPart.CFrame =  pos * rot
end

end

};


return functions;

It is important that I store the name, position, rotation, and size of blocks to be used with :FillBlock()

To load bunkers from datastore2 I need to have default values for each property and load those in if there is no saved data.

And of course I need to store all other data: Wins, Omega Coins, and Bunker Likes

Save script:

local DataStore2 = require(1936396537)
local ServerStorage = game.ServerStorage
local Workspace = game.Workspace 
local Players = game.Players
local default omega_coins = 0
local default_likes = 0
local default_wins = 0
local default_bunker_terrain_position = {CFrame.new(26.927, -33.383, -435.507), CFrame.new(26.927, 23.365, -572.788), CFrame.new(164.479, 23.365, -434.153), 
CFrame.new(-110.625, 23.365, -435.236), CFrame.new(25.844, 23.365, -297.955)}
local default_bunker_terrain_size = {Vector3.new(277.27, 8.143, 277.27), Vector3.new(277.27, 108.309, 2.708),Vector3.new(2.166, 108.309, 274.562),Vector3.new(2.166, 108.309, 272.396),Vector3.new(275.104, 108.309, 2.166)}
local default_bunker_terrain_rotation = {CFrame.Angles(0,0,0), CFrame.Angles(0,0,0), CFrame.Angles(0,0,0), 
CFrame.Angles(0,0,0), CFrame.Angles(0,0,0)}
local default_bunker_materials = {"Mud","Rock","Rock","Rock","Rock"}


DataStore2.Combine("main", "Wins", "Ωmega Coins", "Likes", "object_name", "object_position", "object_rotation", "terrain-position", "terrain_rotation", "terrain_size", "terrain_materials")

Players.PlayerAdded:Connect(function(plr)
local winsStore = DataStore2("Wins", plr)
local coinStore = DataStore2("Ωmega Coins", plr)
local likesStore = DataStore2("Likes", plr)
local objectnameStore = DataStore2("object_name", plr)
local objectpositionStore = DataStore2("object_position", plr)
local objectrotationStore = DataStore2("object_rotation", plr)
local terrainpositionStore = DataStore2("terrain-position", plr)
local terrainrotationStore = DataStore2("terrain_rotation", plr)
local terrainsizeStore = DataStore2("terrain_size", plr)
local terrainmaterialsStore = DataStore2("terrain_materials", plr)
 	
	
	local leaderstats = Instance.new("Folder",plr) ---leaderstats folder
	leaderstats.Name = "leaderstats"
	
	
	local wins = Instance.new("IntValue",leaderstats)  --leaderstats
	wins.Name = "Wins"
	
	local omega_coins = Instance.new("IntValue",leaderstats)
	omega_coins.Name = "Ωmega $"
	
	local likes = Instance.new("IntValue",leaderstats)
	likes.Name = "Likes"
end)



function save_to_model(folder_name, target_location, CFrameoffset)
	local newMap = ServerStorage.empty_map:Clone()
	newMap.Name = folder_name
	newMap.Parent = Workspace
	local terrain = newMap.terrain:WaitForChild("cubes")
	
for i, v in pairs(default_bunker_materials) do
	local part = Instance.new("Part")
	part.Name = v

	-- new block:
	part.CFrame = default_bunker_terrain_position*default_bunker_terrain_position[i]
	part.Size = default_bunker_terrain_size[i]

	part.Parent = terrain
end
end

save_to_model("MrGuyROBLOX's Dungeon", game.Workspace.maps,0,0,0)

I am having trouble with saving data and adding a default value as you can see in my script I am not using the actual datastore for the bunker loading function, I’m using the default values and I need to fix that.

1 Like

As you probably already know, you can’t save data directly when involving objects like parts, models, Vector3s, and CFrames; you can only save values as primitive values and tables. Doing this is known as serialization. Datastore2 provides a convenient function, datastore:BeforeSave(). It takes a function as a parameter that is passed the object you are saving. That function you passed returns the final, serialized object that is saved.

Serialization:

To save items such as whatever objects might be in there, you need to create a dictionary so all the objects can be represented as values. When they’re deserialized, you can connect each value to an object. Each object also has a CFrame: position and orientation. Position is just made up of 3 numbers: the x, y, and z values of the position. The orientation is also made up of 3 numbers, the x, y, and z rotations which you can conveniently get through CFrame:FromOrientation(). Saving the object id, the three values representing position, and the 3 objects representing rotation, you have your serialized object! Have a list of these and you have your entire bunker.

Deserialization

Datastore2 provides a convenient function, datastore:BeforeInitialGet(), which is takes a function as a parameter that is passed the serialized version retrieved directly from the Roblox datastores. That function returns the deserialized, full object version that is what you see when you do datastore:Get(default). To deserialize, simply reverse the process; for each object in the bunker you’d want to connect its value with the object in your dictionary and create a new CFrame based on your position and orientation values saved to position and orient that deserialized object. Remember those x, y, and z rotations? Now you can neatly put them in CFrame.Angles(x, y, z) to create the orientation. Just don’t forget to multiply this with the CFrame that takes the position to rotate it to align it with the orientation!

object.CFrame = CFrame.new(xPos, yPos, zPos) * CFrame.Angles(xRot, yRot, zRot)

At this point, you successfully prepared it for saving and retrieving: input the full objects, output the full objects and can use them. :slight_smile:

More information and examples of serialization can be found on this page.

4 Likes

Oh I should include a list of what I got working.

I got the terrain array to model work but I need to use the actual save data and not the default value and assign the default value to the save data if it is nil which I dont know how to do. Could you show me how?

I didn’t do objects though yet.

Datastore2’s datastore:Get(default) already handles this for you. If you execute this function and the datastore contains a value, then it gets that value. Otherwise, it uses the default value you passed into that function. As for saving the data, as I said, you need serialization which I described extensively above. As for using the data that you saved, again, that’s deserialization and I also described it extensively above.

1 Like

I did DataStore2:Get(default_bunker_terrain_position) but it said attempt to call a nil value. the script says datastore:Get(default) is an unknown global

datastore:Get(default) is an example with placeholders. datastore represents your Datastore2 instance. For you, these would be things like winsStore, coinStore, likesStore, objectnameStore, objectpositionStore, etc.

Note that you could (and should) store all object related information together in a single datastore in a table so you can keep the data together and easily retrieve it. In fact, I’d recommend you should keep in a single datastore a list of objects represented by tables of names, positions, and rotations. You would not be able to relate an object with its position and rotation otherwise unless you do some weird things that involve storing more data than necessary.

As for default, that represents the parameter of Get(), the default for each datastore. For you, these would be things such as default_likes, default_wins, etc. that you already defined.

Some random things to note

  • Remember that default omega_coins is not a valid variable name because variable names cannot contain spaces
  • Something that will make life easier for you in the future: you can access game.Workspace simply through a global called workspace (all lowercase). This will save you time in the future.
  • Try to format your code with indents for every new code block; it makes it neater and people are more willing to help and read your code :+1:

I’m having trouble visualizing all of this. Could you please modify my save script to show what you mean?

A simple example for retrieving from the datastore. Using the Datastore2 instances you already defined, you can use

wins:Get(0)

to get the number of wins from the datastore. This will retrieve it from the Roblox datastores and if nothing is already saved, it will use the default value you provided (in this case, 0). The way Datastore2 is designed to work is that it already uses a cache from which you can grab things and when you set values, it saves to the cache. To change the number of wins, you can do

wins:Increment(1)

and this will change the number of wins both in the cache and in the actual datastore. You don’t need to manually save when the player leaves because Datastore2 does that for you.

Serialization

To serialize an object, you can use the function I mentioned earlier, BeforeSave().

I’d recommend having a dictionary of values (for serialization) to the actual object. An example:

local bunkerObjectNames = {
    [turretModel] = 1,
    [doorModel] = 2,
    -- etc, etc.
}
Datastore2.Combine("main", "BunkerObjects")

local bunkerObjects = DataStore2("BunkerObjects", plr) -- bunkerObjects:Get(default) will return a list of deserialized, usable objects

bunkerObjects:BeforeSave(function(deserialized)
    -- The fact is, I can't write your code for you because I'm neither willing nor knowledgeable about how you can get the following things from your deserialized objects; it depends on how you store them. This is just a demonstration that you will need to edit and change yourself
    -- This function will turn a list of objects into a list of serialized tables representing the object type, position, and orientation, which it will return
    -- This assumes you're saving all the bunkerObjects together in one datastore as a list of models and they have PrimaryParts whose CFrames are set to position the object
    local serializedList = {}
    for _, object in ipairs(deserialized) do
        local type = bunkerObjectNames[object] -- Number corresponding to the model
        local primaryPart = object.PrimaryPart
        local xPos, yPos, zPos = primaryPart.Position.X, primaryPart.Position.Y, primaryPart.Position.Z
        local xRot, yRot, zRot = primaryPart.CFrame:ToEulerAnglesYXZ() -- the other function, ToEulerAnglesXYZ is inaccurate on the y-axis rotation due to the range it limits the y-axis to
        local serializedObject = {type, xPos, yPos, zPos, xRot, yRot, zRot}
        table.insert(serializedList, serializedObject)
    end
    return serializedList
end)

bunkerObjects:BeforeInitialGet(function(serialized)
    -- Deserializes a serialized list of numbers into a list of usable objects
    local deserializedList = {}
    for _, object in ipairs(serialized) do -- iterates through the serialized objects
        local objModel -- the model of the object
        for model, value in pairs(bunkerObjectNames) do
            if value == object[1] then objModel = model:Clone() break end
        end
        objModel.PrimaryPart.CFrame = CFrame.new(object[2], object[3], object[4]) * CFrame.Angles(object[5], object[6], object[7])
        table.insert(deserializedList, objModel)
    end
    return deserializedList
end)

Each object in the bunker list in this demonstration is stored like this:

{numberThatRepresentsTheObjectType, xPos, yPos, zPos, xRot, yRot, zRot}

A list of these would comprise all the bunker objects.

Now, the list changes automatically every time you set it through bunkerObjects:Set() or bunkerObjects:Update() and you can retrieve them through bunkerObjects:Get(default).

Keep in mind that combined datastores will have issues in part to deserialization. The page states a warning on the very top.

Deserialization is known to cause issues with combined data stores. If you can reproduce these issues, please file an issue on GitHub!

Unfortunately, while saving models, you can only serialize and deserialize otherwise you don’t have any way to store them. If a bug comes up, please show them your code and how to reproduce it so it may help them fix any bugs. If you don’t want to serialize and deserialize using the Datastore2’s built-in methods, then you’re free to try with default Roblox Datastores using the same method of serialization/deserialization I told you :slight_smile:

1 Like

No im not having trouble with formatting the objects I just need my code to use the actual datastore values instead of the default values and assign a default value to it if its nil. Specifically

function save_to_model(folder_name, target_location, CFrameoffset)
	DataStore2:Get(default_bunker_terrain_position) 
	
	local newMap = ServerStorage.empty_map:Clone()
	newMap.Name = folder_name
	newMap.Parent = Workspace
	local terrain = newMap.terrain:WaitForChild("cubes")
	
for i, v in pairs(default_bunker_materials) do
	local part = Instance.new("Part")
	part.Name = v

	-- new block:
	part.CFrame = default_bunker_terrain_position[i] -- this
	part.Size = default_bunker_terrain_size[i] -- and this needs to be replaced with the datastore values.

	part.Parent = terrain
end

and I have no idea how to create combined datastores and of course assign a default value. I’l look at the script now and see what i can do.

Again, datastore:Get(default) will get it just fine. It will only use the default value you passed if it can’t find any saved values. You can use datastore:Set(), datastore:Increment(), and datastore:Update() and it will automatically save the values such that datastore:Get() would get the saved values across servers, joins, and quits.

did for i, v in pairs(terrainmaterialsStore:Get(default_bunker_materials)) do but it still sees the value as nil even though its supposed to get default bunker materials.

If anything it is probably something I did wrong at line 18

DataStore2.Combine("main", "Wins", "Ωmega Coins", "Likes", "object_name", "object_position", "object_rotation", "terrain-position", "terrain_rotation", "terrain_size", "terrain_materials")

Players.PlayerAdded:Connect(function(plr)
winsStore = DataStore2("Wins", plr)
coinStore = DataStore2("Ωmega Coins", plr)
likesStore = DataStore2("Likes", plr)
objectnameStore = DataStore2("object_name", plr)
objectpositionStore = DataStore2("object_position", plr)
objectrotationStore = DataStore2("object_rotation", plr)
terrainpositionStore = DataStore2("terrain-position", plr)
terrainrotationStore = DataStore2("terrain_rotation", plr)
terrainsizeStore = DataStore2("terrain_size", plr)
terrainmaterialsStore = DataStore2("terrain_materials", plr)

Sorry, but you weren’t descriptive enough in this error for me to help you. You said it sees a value in that line of code is nil. Which is nil? terrainmaterialsStore or default_bunker_materials? Your exact error message would also help.

Also note that on DevForum, unlike in Studio, the lines are not numbered so to make it easier on us, it would be great if you could put a comment on the line you’re talking about. It’s especially so if you didn’t show us your entire script and only a snippet (which would throw off the line numbers).