Help needed with saving every existing tool in the player's backpack

As I moved from Godot and Unreal Engine 5 to Roblox(beacuse it’s easier to make games and earn some money out of it) I am trying to make a game in it with the new scripting language and Studio. I am trying to achieve a datastore saving system that saves as the title says every existing tool in the player’s backpack like thegame called “Fisch” or the new game “Beaks” is doing. I have watched many tutorials but they didnt help me with this beacuse they were using the types of tools saving system or something like that, there was a folder in serverstorage that had tools that needed to save. I am making the Beaks like style game that need the “advanced” type of saving system so it will save like the weight mutations and rarity of the tool that i have.
Any help will be appreciated.

4 Likes

I’m going to be honest but it does sound somewhat hard to me and my explanations might not be the best but the method might not work for you because I believe those tutorials save tools based on matching names from a folder (static tools). But Beaks/Fisch style games generate unique instances with varying properties

From what I know so far, I believe you could store and recreate attributes, not just values in scripts, as Attributes are native to Roblox and easier to serialize and you can simply just use GetAttributes() and/or SetAttribute()

2 Likes

Thank you for your responce, I wanted to ask, how to do that beacuse I am using luau as a scripting lanugage for like 2 weeks from now and I dont know some thing.

1 Like

I believe you could loop through the tools in the player’s backpack and then store each tool’s name and attributes in a table AND then (sorry for repeating and then) save that table with DataStore:SetAsync(), as so, when the player joins, the GetAsync() gets on, recreating the tools with their saved attributes

I’m as honest as possible saying that in 2 years since I started scripting I barely know anything of this kind.

But still, here is a simple example:

local function ToolSave(player)
local toolsData = {}

for _, tool in ipairs(player.Backpack:GetChildren()) do
    if tool:IsA("Tool") then
        table.insert(toolsData, {
            Name = tool.Name,
            Rarity = tool:FindFirstChild("Rarity") and tool.Rarity.Value,
            Weight = tool:FindFirstChild("Weight") and tool.Weight.Value
        })
    end
end

return toolsData

end

2 Likes

Thank’s mate, I will do best out of this example(if its working of course) Oh and about scripting, I am 9 to 10 years now in it soo I have maybe bigger knowlege about it but definely not about luau. Maybe in this post there will write soemone more experieced in this scripting laguage. This will not only help me but any fellow developers that want to make a system like this. Anyways I will try to make something out of this, if it will work I will post it here so everyone can see it and make that kind of saving system.

2 Likes

You’re totally much more experienced and skilled than I, I hope you’ll have no trouble sorting this out!

2 Likes

This is something that is easily possible using my player save library DMOP.

Defining a structure like this:
image

Means that all Players have a directory called EquippedItems, which is an associative array.

Items in that array have an array called TagsArray, a number called NumberMutation, and a string called DisplayName.

With a system like this, you only have to worry about using/updating the data stored within the Player, and it will be persistent with DMOP.

My system also supports Data Types, which can make your DataModel look nicer (and easier to use). It also supports save structure conversion if you ever want to change the layout of your save structure without wiping player data.

It is very easy to work with, I’d suggest at least giving it a try (I’ll help you if you need)

3 Likes

Sorry for a late resonce but I was working in my garden. Can you tell me more about it and how to set it up to support this type of data saving? I actually made some big progress in 30 minutes that i was working on it, here is what i got now it is saving it but not perfectly so the modifed values, names and most of the things like the offsets isnt saving now:

local Players = game:GetService("Players")
local DataStoreService = game:GetService("DataStoreService")
local CollectionService = game:GetService("CollectionService")
local ToolStore = DataStoreService:GetDataStore("AdvancedDynamicToolSave")

local function vec3ToTable(v) return {v.X, v.Y, v.Z} end
local function tableToVec3(t) return Vector3.new(t[1], t[2], t[3]) end


local function cframeToTable(cf)
	local components = {cf:GetComponents()}
	return components
end

local function tableToCFrame(t)
	return CFrame.new(unpack(t))
end


local function serializeInstance(obj)
	local data = {
		ClassName = obj.ClassName,
		Name = obj.Name,
		Properties = {},
		Children = {}
	}


	if obj:IsA("BasePart") then
		data.Properties.Size = vec3ToTable(obj.Size)
		data.Properties.Color = {obj.Color.R, obj.Color.G, obj.Color.B}
		data.Properties.Material = obj.Material.Name
		data.Properties.CFrame = cframeToTable(obj.CFrame)
		data.Properties.Anchored = obj.Anchored
		data.Properties.Orientation = vec3ToTable(obj.Orientation)
	end


	local mesh = obj:FindFirstChildOfClass("SpecialMesh")
	if mesh then
		data.Properties.Mesh = {
			MeshType = mesh.MeshType.Name,
			MeshId = mesh.MeshId,
			TextureId = mesh.TextureId
		}
	end


	for _, child in ipairs(obj:GetChildren()) do
		if child:IsA("Instance") and not child:IsA("Script") then
			table.insert(data.Children, serializeInstance(child))
		end
	end

	return data
end


local function serializeTool(tool)
	local toolData = {
		Name = tool.Name,
		Attributes = tool:GetAttributes(),
		Values = {},
		Structure = {}
	}

	for _, child in ipairs(tool:GetDescendants()) do
		if child:IsA("ValueBase") then
			table.insert(toolData.Values, {
				ClassName = child.ClassName,
				Name = child.Name,
				Value = child.Value
			})
		end
	end


	for _, child in ipairs(tool:GetChildren()) do
		if child:IsA("Instance") and not child:IsA("Script") then
			table.insert(toolData.Structure, serializeInstance(child))
		end
	end

	return toolData
end


local function recreateInstance(data)
	local inst = Instance.new(data.ClassName)
	inst.Name = data.Name

	local props = data.Properties or {}

	if inst:IsA("BasePart") then
		if props.Size then inst.Size = tableToVec3(props.Size) end
		if props.Color then inst.Color = Color3.new(unpack(props.Color)) end
		if props.Material then inst.Material = Enum.Material[props.Material] end
		if props.CFrame then inst.CFrame = tableToCFrame(props.CFrame) end
		if props.Anchored ~= nil then inst.Anchored = props.Anchored end
		if props.Orientation then inst.Orientation = tableToVec3(props.Orientation) end

		if props.Mesh then
			local mesh = Instance.new("SpecialMesh")
			mesh.MeshType = Enum.MeshType[props.Mesh.MeshType]
			mesh.MeshId = props.Mesh.MeshId
			mesh.TextureId = props.Mesh.TextureId
			mesh.Parent = inst
		end
	end

	for _, childData in ipairs(data.Children or {}) do
		local child = recreateInstance(childData)
		child.Parent = inst
	end

	return inst
end


local function recreateTool(data)
	local tool = Instance.new("Tool")
	tool.Name = data.Name

	
	for key, value in pairs(data.Attributes or {}) do
		tool:SetAttribute(key, value)
	end

	
	for _, valData in ipairs(data.Values or {}) do
		local val = Instance.new(valData.ClassName)
		val.Name = valData.Name
		val.Value = valData.Value
		val.Parent = tool
	end


	for _, objData in ipairs(data.Structure or {}) do
		local part = recreateInstance(objData)
		part.Parent = tool
	end

	return tool
end


local function saveTools(player)
	local backpack = player:FindFirstChild("Backpack")
	if not backpack then return end

	local serialized = {}

	for _, tool in ipairs(backpack:GetChildren()) do
		if tool:IsA("Tool") and CollectionService:HasTag(tool, "Echo") then
			table.insert(serialized, serializeTool(tool))
		end
	end

	pcall(function()
		ToolStore:SetAsync(tostring(player.UserId), serialized)
	end)
end


local function loadTools(player)
	local backpack = player:WaitForChild("Backpack")

	local success, data = pcall(function()
		return ToolStore:GetAsync(tostring(player.UserId))
	end)

	if success and data then
		for _, toolData in ipairs(data) do
			local tool = recreateTool(toolData)
			tool.Parent = backpack
		end
	else
		warn("No tools loaded for", player.Name)
	end
end

Players.PlayerAdded:Connect(function(player)
	player.CharacterAdded:Connect(function()
		task.delay(1, function()
			loadTools(player)
		end)
	end)
end)

Players.PlayerRemoving:Connect(saveTools)

2 Likes

That looks good! I do have some confusion over why you are storing the actual instance properties of the parts. Typically you have a list of cloneable items, and when you load them, you can adjust their properties based on the modifiers the item has.

Setting up DMOP is very simple. You just place the script in ServerScriptSevice, and then create a Folder called DataModel in ServerStorage.

All Folders/BaseValues in the DataModel will correspond to directories within the Player. This is a little hard to wrap your head around when you write it out, but let me show you:

If you set up the DataModel like this (DaysPlayed is a NumberValue):
image

All Players will have this structure as well (they take the default values from DataModel):

image

To make it so that players can have many items with modifiers, you need an Array, not just a static directory. You specify that like this (using the name “key” in this case):

image

So now PlayerItems is an array within the Player. The player spawns with an empty array, which looks like this:
image

The server can just add Folders into the PlayerItems in the Player, and they will save:

image


Side Note


I want to differentiate between using the names "key" and "value" for the array types. Tables in lua have this structure:
local tbl = {
    
    key = value
}

When you give the folder the name “key”, that means the folder name will represent a key in the array. Likewise, the name “value” means that the folder name will represent a value in the array.

So we used “key” because we want the value of the element to be a table (that’s what Folders represent).

Side Note End


Now we just have to structure the data we want to save for each item in the DataModel. You can do this for whatever data you may need to save. I don’t suggest saving the instanced values (Such as CFrames), but DMOP does supprt the core ones (CFrame, Vector3, Color3).

Your structure might look something like this (i did not fully populate it, for brevity):

image


Now here is the meaty part that you were really asking about. How do you give Players these items?

It would be inconvenient to make these folders yourself, So DMOP provides data interfacing through the Interface module. Giving a Player one of these items would look something like this:

local Interface = require(game:GetService("ServerScriptService"):WaitForChild("DMOP"):WaitForChild("Interface"))

local PlayerItemDataModel = Interface.GetDataModelDescendant("PlayerItems").key



local ItemProperties = {

    Tags = { "Tag1", "Tag2", "Tag3" },

    InstancedData = {

        CFrame = CFrame.new()

        -- Leaving Color unset will give it an empty color (which is the default value for Color3)
    },

    DisplayName = "Some Display Name"
}


local InstancedItemData = Interface:TableToModel(ItemProperties, PlayerItemDataModel)

-- you can pass this as a third parameter to TableToModel
InstancedItemData.Name = "KeyInTheArray"

InstancedItemData.Parent = plr.PlayerItems

How the Player looks after running that code:
image

DMOP also provides the reverse function ModelToTable to turn one of these Directories into a Table with the same structure you’d give to TableToModel. With this, you have all of the information you need to save/load/modify these items.

The way you actually do it is extremely similar to your current code, just without interacting with the DataStore at all. You have access to the Player Data within the Player instance itself.

Consider using ModelToTable to obtain a table of all deserialized player items (Roblox instances are properly deserialized i.e. the CFrame folder becomes a CFrame instance).


How do I do initial loading?

If you need to verify that a player’s data has finished being loaded, either use the ClientLoadedEvent ObjectValue as a RemoteEvent or bind to the Loaded bindable.


How to make PlayerItem a DataType instead?

You might want to make the PlayerItem be a DataType, which makes the code and DataModel look nicer.

To do this:

  1. make a folder in ServerStorage called whatever you want, preferably DataTypes.

  2. Set the CustomDataTypeHolder ObjectValue equal to the folder you just made (the ObjectValue is a child of DMOP.Interface).

  3. Copy the DataTypeTemplate next to the ObjectValue, and put it in your DataTypes folder. Name it the data type you want in all lowercase.

  4. Now you just have to put all children of PlayerItems.key into that new template module, like this:
    image

Now that playeritem is a valid Data Type, you can refactor DataModel like this:
image

This is much nicer because now:

  1. PlayerItem is a re-usable data type.
  2. You won’t have to dig through DataModel to modify it
  3. it makes the instancing code nicer because you don’t need a reference to the DataModel:
local Interface = require(game:GetService("ServerScriptService"):WaitForChild("DMOP"):WaitForChild("Interface"))


local ItemProperties = {

    Tags = { "Tag1", "Tag2", "Tag3" },

    InstancedData = {

        CFrame = CFrame.new()

        -- Leaving Color unset will give it an empty color
    },

    DisplayName = "Some Display Name"
}


local InstancedItemData = Interface:TableToModel(ItemProperties, "PlayerItem")

-- you can pass this as a third parameter to TableToModel
InstancedItemData.Name = "KeyInTheArray"

InstancedItemData.Parent = plr.PlayerItems

Conclusion

I hope this mega-long post helps you, if you do decide to use DMOP. Im always open for questions, or even helping you get it set up if you decide to use it.

I use it literally every single time I need to save client data because I absolutely hate interacting with the DataStoreService (and I made it so I’m biased).

2 Likes

Thanks for the tutorial mate! I am confued about it to be honest. Like I tried to make the datamodel then the playeritems and the key, it isnt showing in the player instance. And i am cofused how it would save every proerty of every item in the backpack, can you please tell me how it is doing and mayeb give me a script that just makes it :slight_smile: so i can test it and maybe modiy it by myself?

1 Like

Yes. I will make a test place with this functionality and send it to you. Give me about an hour.

1 Like

It is strange that nothing is appearing in the Player. Make sure your structure looks like ServerStorage > DataModel > PlayerItems The name of DataModel is case-sensitive. And DMOP should be in ServerScriptService.

Also check the console to see if there is an error relating to restricted module, Someone else had that issue and I have not been able to find a good fix…

1 Like

Here is the place with basic functionality:

TestBackpackItemSaving.rbxl (84.9 KB)

Make sure you save the place and enable Studio Access to API Services option in Game Settings.

It has a custom backpack controller which uses the name of items in the Backpack as their slot ID. So to change slots you just rename the Instances in Backpack and Hotbar.

(Hotbar is for storing the information associated with items in the Backpack, because the Backpack holds tools)

When you join, you will be given a debug item in the first slot. You should stop the simulation after you recieve it and comment out the following line of code (TestGiveHotbarItem line 42): CreateDebugHotbarItem(plr).

Then you can test out moving the item between slots, rejoining and stuff.

To access information associated with a slot in the Backpack, you can access the Hotbar folder. That is how you can get custom images/text to appear with the custom Backpack UI

1 Like

Yea it works but i meant that it should save every tool and its properites from the backpack not from a folder like roblox’s Fisch or Beaks do. Anyways thank you.

Look here, I mean in that way like it is on my screenshot, when i leave the items and properties of it saves.

It is unlikely that any game will store the raw information of a tool directly in the tool itself. This is because when an item is stored in the Backpack, it is not guaranteed to be in the Backpack at all times.

You can use a structure like how I sent you, and still have those properties persist. It is all about the data that you save. I sent you a very rudimentary template to expand on, and it is not incompatible with what you have in mind.

For example, you should store the custom properties of your item in PlayerItems. When you instance these tools, you can make those modifications. I even commented a part of the TestGiveHotbarItem script to signal where you should do that at

Yea but I mean store the enitre tool or the binary code of it in soem kind of a database, i made something like that in my script that just needs to store more and better inforamtions about the tool, is it possible with your system?

You can store all of the information that you need. DMOP is made to be easily expandable. What in particular do you have in mind that seems like it might be incompatible?

Okay so look on my screenshot, i want to store all of the data that there is, the localscript in it changes the look, the name and the wieight in the name + mutation for the tool. So now how to use your system to store this data? Remember that if i shoot down a new this type of npc’s are called "echoes’ in my game are allways diffrent so it needs to store unique data for every of the new “echo”. Can you please show me how to do it?
Zrzut ekranu (581)

Take a look at this, I updated the place I sent with a new data type called TypeData. Look at the changes I made to the DataModel and DataTypes directories.

TestBackpackItemSaving.rbxl (86.0 KB)

I made it so that a PlayerItem also saves a child called TypeData. Look at how the data is instanced when the player joins the game.

I don’t suggest storing the data in the tool like you are currently doing, but i have provided an example in the place on how you would load that data into the tool with DMOP

and this is why i dont suggest it:

image

because then you have the information in multiple places where it’s really only needed in one place.