Saving issue! Help

Current Situation:

Right now, I have a data-saving system that stores tools by saving the names. When the player rejoins, those tools are simply restored by inserting them back into their inventory from a folder in ServerStorage. This works well for predefined tools.

The Problem:

Some tools in my game are generated dynamically during gameplay. For example, a fish the player catches from a pond. These fish tools have random properties like weight or size that are set when they’re created. Since these are not predefined tools and their attributes vary, I don’t know how to save and restore them properly when the player rejoins.

What I Need:

I’m looking for a way to:

  • Save dynamically generated tools (like a fish with custom weight) and their attributes.
  • Restore them accurately when the player rejoins, with the same properties they had before.

Here is the ServerScript I am currently using:

local ds = game:GetService(“DataStoreService”):GetDataStore(“ToolSave”)
game.Players.PlayerAdded:connect(function(plr)
local key = “id-”…plr.userId
pcall(function()
local tools = ds:GetAsync(key)
if tools then
for i,v in pairs(tools) do
local tool = game.ServerStorage.Tools:FindFirstChild(v)
if tool then
tool:Clone().Parent = plr:WaitForChild(“Backpack”)
tool:Clone().Parent = plr:WaitForChild(“StarterGear”)
end
end
end
end)
end)
game.Players.PlayerRemoving:connect(function(plr)
local key = “id-”…plr.userId
pcall(function()
local toolsToSave = {}
for i,v in pairs(plr.Backpack:GetChildren()) do
if v then
table.insert(toolsToSave,v.Name)
end
end
ds:SetAsync(key,toolsToSave)
end)
end)

you can store arrays with the datastoreservice, just store their attributes as well, and set them when you re-give the tools. Do you get what I mean?

table.insert(toolsToSave, { v.Name, v:GetAttributes() })

I should warn you that PlayerRemoving doesn’t actually put the held tool back into the backpack, and also the character is gone by then :face_with_spiral_eyes: I honestly never looked for the solution to this problem, but it did annoy me when I wanted to do something similar. I ended up making a separate folder in the player which was like a fake backpack that just had all of the tool information in it…

I think there is a way to change the behavior of the leave button? So maybe you can add some code to just unequip the tool before leaving

2 Likes

I may be misunderstanding, but what’s stopping you from saving the data you use to GENERATE them, rather than the tools themselves? Then, you can just regenerate them when you need them again.

if you read the code, that is exactly what is done. Only the name is saved, then they are re-generated

1 Like

Yeah, I have trouble reading code that isn’t formatted.

But, given their problem, I still am a bit confused. If they’re only saving the name, but they need more than just the name…why don’t they save the other data too?

1 Like

I mean, that’s what my suggestion was and they gave it a like. They might just not have thought of it, it’s not obvious if you’ve never done it :sweat_smile:

1 Like

Here’s more on what we mean.

Save each tool as an object. I can’t test this myself right now, but I’d store it as a sum type (in Roblox, that’d be represented via tagged union)

type SavedTool
    = { tag: "Predefined", name: string } -- A predefined tool, you only need it's name.
    | { tag: "Dynamic", name: string, ... } -- Dynamic at runtime, more data (e.g. rarity)

So, first of all, from what I see there’s no actual piece of code that’d create the non-predefined tools. Second of all, instead of saving just a tool name, I’d save it like this:
local dataToInsert = { ['toolname'] = v.Name, ['customproperties'] = { -- here You'd insert custom properties } } table.insert(toolsToSave, dataToInsert)
You could then later check if tool under the v.toolname exist, if not then create it and apply custom properties from v.customproperties
Also, I’d recommend You also saving data to data store every given amount of time alongside when the player is leaving. What if the player’s client crashed and PlayerRemoving would fire too late, when there’s no Player instance?

1 Like

after drinking my coffee i have come to realize ADTs might be a tad bit confusing if you don’t have prior knowledge of them.

for reference, here’s the SavedTool type again:

type SavedTool
    = { tag: "Predefined", name: string } -- A predefined tool, you only need it's name.
    | { tag: "Dynamic", name: string, ... } -- Dynamic at runtime, more data (e.g. rarity)

So, this represents something that can be EITHER a Predefined tool or a Dynamic tool. A Predefined tool uses only a name field, but Dynamic has more. Both Predefined and Dynamic are SavedTools, but you can differentiate them by seeing what tag is set to.

The non-predefined tools are created in another ServerScript which is the one that handles fishes spawning:

local function spawnFishFromEgg(player, eggName, spawnCFrame, playerFarm)
	local fishTemplate = LiveFishes:FindFirstChild(eggName)
	if not fishTemplate then
		warn("❌ No fish found for:", eggName)
		return
	end



	local newFish = fishTemplate:Clone()
	local weight = math.random(10, 500) / 10
	local finalScale = math.clamp(weight / 12.5, 0.1, 4)


	local growthValue = newFish:FindFirstChild("FishGrowth")
	local growthName = growthValue and growthValue.Value or eggName
	local growthTime = getGrowthTime(growthName)


	newFish:SetAttribute("WeightKg", weight)
	newFish:SetAttribute("VirtualScale", finalScale)
	newFish:SetAttribute("GrowthStartTime", os.time())
	newFish:SetAttribute("GrowthDuration", growthTime)
	newFish:SetAttribute("FishID", HttpService:GenerateGUID(false))

This is how I create my fish when it’s still in the POND.
Then, when the player goes on to pick them up:

	-- Apply all original attributes
	for attrName, attrValue in pairs(attributes) do
		pcall(function()
			tool:SetAttribute(attrName, attrValue)
		end)
	end
	--  Base value
	local baseValue = tool:GetAttribute("BaseValue") or 0

	--  Rarity multipliers
	local multiplier = 1
	if tool:GetAttribute("IsGolden") then multiplier *= 10 end
	if tool:GetAttribute("IsRainbow") then multiplier *= 20 end

	--  Trait multipliers
	if tool:GetAttribute("Trait_Zombified") then multiplier *= 2 end
	if tool:GetAttribute("Trait_Vampiric") then multiplier *= 4 end
	if tool:GetAttribute("Trait_Neon") then multiplier *= 6 end
	if tool:GetAttribute("Trait_Fungal") then multiplier *= 8 end
	if tool:GetAttribute("Trait_Slippery") then multiplier *= 10 end

	--  Weight-based multiplier (up to ×50 at 50kg)
	local weight = tool:GetAttribute("WeightKg") or 1
	local weightMultiplier = math.clamp(weight / 1, 1, 50)

	--  Final value calculation
	local finalValue = math.floor(baseValue * multiplier * weightMultiplier)
	tool:SetAttribute("FinalValue", finalValue)

Then pretty much the rest of my script that based on the attributes, adds the visuals:

if isRainbow then
		local scriptSource = ReplicatedStorage:FindFirstChild("FishToolScripts") and ReplicatedStorage.FishToolScripts:FindFirstChild("RainbowEffectScript")
		if scriptSource then
			scriptSource:Clone().Parent = tool
		else
			warn(" Missing RainbowEffectScript in ReplicatedStorage.FishToolScripts")
		end
	end

    -- some more code and then

if backpack then
		tool.Parent = backpack
		
	else
		warn("❌ Could not find Backpack for player:", player.Name)
	end

At that point the tool is in my inventory, with a custom name like for example: [Rainbow] Salmon (35kg)

I’m trying to upload a clip but it’s not letting me, lol.

I think the approach we said before will work well. Make sure you have a function to create the tool from the data; that should be decoupled from wherever it is used if it isn’t already.

1 Like

Thanks guys!!! I did it :smiley:

No problem, make sure you set the reply that helped the most to the solution. That way other people reading this can know how you solved it!

Edit: I notice you did that, Roblox decided not to show me. Sorry if that was confusing lol

Also, I’m curious: Did you end up going with the tagged union I showed you? If not, I’d recommend still trying them out; it’s a great learning experience if you aren’t familiar with them.

nope, but i’ll still make sure to try them out. thank you!

1 Like

You’re welcome! ADTs can represent any type. OOP is actually quite limited compared to ADTs—it can only create what ADTs refer to as “product types.” What I showed you is not possible with pure OOP, as it is a sum type, not product. Technically, OOP can support sum types, but not as well as no-OOP or impure OOP. ADTs are nice to learn because they just open up a new way of thinking about structuring your data.

1 Like

ADT is short for Algebraic Data Type. The Wikipedia page is here.

I know there’s a lot of people who will consider ADTs to be “impractical theory” and “ivory tower,” but that only stems from a misunderstanding of what ADTs are. Chances are they’ve written ADTs and didn’t realize it!