Data duplication problem when saving tool information? ProfileService

I have a problem that I think should not occur, and that is that the data is duplicated when you exit the game. I know I made a post about the same thing, but this one is different as here I am trying to save the data that I put into the tool (tool stacks and tool name). From what I have noticed, the tools are duplicated when exiting the game (This happens when you have a tool and exit and it saves, then when entering, they will have duplicated)

The Data handled by the ProfileService
local Players = game:GetService("Players") 
local ProfileService = require(script.ProfileService)
local saveStructure = {
	Tools = {ToolsWithStacks = {}, ToolsWithoutStacks = {}};
	Money = 0;
	LogInTimes = 0;
	LogInGiift = 0;
	
	Shadows = "Medio";
	Water = "Medio";
	Tree = "Enabled";
	
	FPS = "UnEnabled";
	Ping = "UnEnabledPing";
	BrilloVol = 0.7;
	PlayerChose = "ForLocalPlayer";
	Language = "English";
	SlotsInventory = 90;
	
	Crouch = "Keep";
	Sprint = "Keep";
}

local PlayerProfileStore = ProfileService.GetProfileStore("test32", saveStructure)

local cachedProfiles = {}

local function PlayerAdded(player)
	local profile = PlayerProfileStore:LoadProfileAsync("Player_".. player.UserId, "ForceLoad")
	if profile ~= nil then
        profile:Reconcile()
		profile:ListenToRelease(function()
			cachedProfiles[player] = nil
			player:Kick("Tus datos no han sido cargados. Por favor Ăşnase nuevamente")
		end)
		if player:IsDescendantOf(Players) then
			cachedProfiles[player] = profile
			DoSomethingWithALoadedProfile(player, profile)
		else
			profile:Release()
		end
	else
		player:Kick("No se pueden cargar tus datos. Por favor Ăşnase nuevamente")
	end
end

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

local function PlayerRemoving(player)
	local profile = cachedProfiles[player]

	for _,tool in pairs (player.Backpack:GetChildren()) do 
		if (table.find(profile.Data.Tools,tool.Name)) then
			continue
		end
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			table.insert(profile.Data.Tools.ToolsWithStacks, {ToolName = tool.Name, Stacks = tool.Stack.Value})
		else
			table.insert(profile.Data.Tools.ToolsWithoutStacks, {ToolName = tool.Name})
		end
	end
	if profile ~= nil then
		profile:Release()
	end
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)

function cachedProfiles:Get(player, yield)
	local profile = cachedProfiles[player]
	if yield and not profile then
		repeat task.wait(0.1)
			profile = cachedProfiles[player]
		until profile or (not player.Parent)
	end
	if profile then
		return profile
	end
end

return cachedProfiles
When loading the tools:
Players.PlayerAdded:Connect(function(player)
	local Profile = DataManager:Get(player, true)
	if not Profile then
		repeat task.wait(0.1)
			Profile = DataManager:Get(player, true)
		until Profile or (not player.Parent)
	end
	if Profile then
		--Obtener las herramientas
		for _, ToolsWithStacks in pairs(Profile.Data.Tools.ToolsWithStacks) do
			if not toolsFolder:FindFirstChild(ToolsWithStacks.ToolName) then continue end
			local Tool = toolsFolder[ToolsWithStacks.ToolName]:Clone()
			Tool:WaitForChild("Stack").Value = ToolsWithStacks.Stacks
			Tool.Parent = player:WaitForChild("Backpack")
			local ToolGear = toolsFolder[ToolsWithStacks.ToolName]:Clone()
			ToolGear:WaitForChild("Stack").Value = ToolsWithStacks.Stacks
			ToolGear.Parent = player:WaitForChild("StarterGear")
		end
		for _, ToolsWithoutStacks in pairs(Profile.Data.Tools.ToolsWithoutStacks) do
			if not toolsFolder:FindFirstChild(ToolsWithoutStacks.ToolName) then continue end
			local Tool = toolsFolder[ToolsWithoutStacks.ToolName]:Clone()
			Tool.Parent = player:WaitForChild("Backpack")
			local ToolGear = toolsFolder[ToolsWithoutStacks.ToolName]:Clone()
			ToolGear.Parent = player:WaitForChild("StarterGear")
		end
	end
end)

Before doing a save do you check if the data is already there?

1 Like

Are the tools in StarterPack??

1 Like

No, they are not in the starterpack. I go to the server and put the tools in my backpack

Well, when looking for the tools, it is omitted because there are the tools (ready to save)

Analyzing your code, seems like you are trying to search a tool inside the Tools table. The issue is that table.find() won’t search for values inside ToolsWithStacks or ToolsWithoutStacks, and will always return nil because of that.

Instead, you should try searching inside each table:

if (table.find(profile.Data.Tools.ToolsWithStacks,tool.Name) or table.find(profile.Data.Tools.ToolsWithoutStacks,tool.Name)) then
	continue
end
1 Like

I certainly hadn’t noticed that, thank you. Unfortunately, it keeps doubling the tools

Did some testing at studio and I found the error. It was pretty similar, the tools are saved inside tables and once again table.find() wasn’t looking inside each tool table. I added a for loop to iterate over all the tools and it solved the issue for me.

Try this:

local toolFound = false
for _, toolTypeTable in pairs(profile.Data.Tools) do -- iterate over ToolsWithStacks and ToolsWithoutStacks
	for _, toolInfoTable in pairs(toolTypeTable) do -- iterate over each tool table
		if type(toolInfoTable) == "table" then -- just so it doesn't error if you accidentaly add something that's not a table here
			if toolInfoTable.ToolName == tool.Name then -- compare tool names
				toolFound = true
			end
		end
	end
end
if toolFound == true then
	continue
end

The whole function should look like this:

local function PlayerRemoving(player)
	local profile = cachedProfiles[player]

	for _,tool in pairs (player.Backpack:GetChildren()) do 
		local toolFound = false
		for _, toolTypeTable in pairs(profile.Data.Tools) do
			for _, toolInfoTable in pairs(toolTypeTable) do
				if type(toolInfoTable) == "table" then
					if toolInfoTable.ToolName == tool.Name then
						toolFound = true
					end
				end
			end
		end
		if toolFound == true then
			continue
		end
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			table.insert(profile.Data.Tools.ToolsWithStacks, {ToolName = tool.Name, Stacks = tool.Stack.Value})
		else
			table.insert(profile.Data.Tools.ToolsWithoutStacks, {ToolName = tool.Name})
		end
	end
	if profile ~= nil then
		profile:Release()
	end
end
1 Like

Certainly no longer duplicates, thank you! Now there is another problem, the added tools are not saved and the tools that are removed

Sorry, I didn’t understand what exactly you tried to say.

Did you mean that removed tools won’t be removed from the player’s data when leaving? If that’s the case, your script doesn’t handle removing tools, only adding them. You would have to compare the saved tools with the player’s backpack and remove the ones that are not found inside it.

1 Like

mmmmmm, I’ll explain: ProfileService saves the data, but duplicates the tools. What I want to save is the added tools, and if a tool was removed, I want that tool to cease to exist in the player’s ProfileService / Backpack player.
It is difficult for me to explain, but, I want the data of the tools or the tools to be saved if there are changes (a tool was added, a tool was eliminated (I don’t want the profileService to save tools that cease to exist), the stak of a tool).

Pepito has 3 tools, he leaves his house (he leaves the game and save the tools) and when he enters again he grabs his tools (he enters the game and loads 3 tools). Later, Pepito loses a tool, leaves his house (he leaves the game and the tools are saved) and when he enters again, he grabs his tools (he enters the game and loads his two tools).

Camila has a toy pistol with 25 bullets, she drops her pistol (I left the game and saves the data following the example of Pepito, but with the difference that I want the bullets to be saved), and grabs her pistol (loads the tool with the tool data (bullets according to this example)). camila spends 5 bullets, drops her gun (saves the data), and grabs it (loads the data, which is now the gun with 20 bullets).
sorry this is my best way to explain

oh right, camila spends all her bullets and then magically explodes the weapon (following this example (bullets are stacks), if the stack reaches 0, the ProfileService tool is deleted)

This script is a normal DataStore (but with the things I want the ProfileService to do)

local dss = game:GetService("DataStoreService")
local Players = game:GetService("Players")
local toolsDS = dss:GetDataStore("ToolDataPlayer")

local ServerStorage = game:GetService("ServerStorage")

local toolsFolder = ServerStorage.ToolsFolder

Players.PlayerAdded:Connect(function(plr)
	local OwnedTools = toolsDS:GetAsync(plr.UserId)

	for _, toolData in pairs(OwnedTools) do
		
		for _2, ToolValue in pairs(toolData.ValueTools) do
			if not toolsFolder:FindFirstChild(ToolValue.ToolName) then continue end
			local Tool = toolsFolder[ToolValue.ToolName]:Clone()
			Tool:WaitForChild("Stack").Value = ToolValue.Stacks
			Tool.Parent = plr.Backpack
			local ToolGear = toolsFolder[ToolValue.ToolName]:Clone()
			ToolGear:WaitForChild("Stack").Value = ToolValue.Stacks
			ToolGear.Parent = plr.StarterGear
		end
		
		for _3, NoValueTools in pairs(toolData.NotValueTools) do
			if not toolsFolder:FindFirstChild(NoValueTools.ToolName) then continue end
			local Tool = toolsFolder[NoValueTools.ToolName]:Clone()
			Tool.Parent = plr.Backpack
			local ToolGear = toolsFolder[NoValueTools.ToolName]:Clone()
			ToolGear.Parent = plr.StarterGear
		end
	end
	plr.CharacterRemoving:Connect(function(char)
		char.Humanoid:UnequipTools()
	end)
end)


Players.PlayerRemoving:Connect(function(plr)
	local OwnedTools = { }

	for i, tool in pairs(plr.Backpack:GetChildren()) do
		local Tools_Without_Value = {}
		local Tools_With_Value = {}
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			table.insert(Tools_With_Value, {ToolName = tool.Name, Stacks = tool.Stack.Value})
		else
			table.insert(Tools_Without_Value, {ToolName = tool.Name})
		end
		table.insert(OwnedTools, {NotValueTools = Tools_Without_Value, ValueTools = Tools_With_Value})
	end
	local success, errormsg = pcall(function()
		toolsDS:SetAsync(plr.UserId, OwnedTools)
	end)
	if errormsg then
		warn(errormsg) 
	end
end)

As you can see, it is quite simple, and it does the functions that I want it to do: It saves the tools without stacks, it saves the tools with stacks with their respective stacks; After loading the data (it does not delete it, I think it is updated) when exiting and there is no tool that you had before, that tool will no longer be in the DataStore (and that is what I want the ProfileService to do)

Oh now I understand. Your old code didn’t handle removing tools that cease to exist since you never used a table.remove() or something similar to detect whether the player lost a tool from his inventory. I rewrote the PlayerRemoving() function so it should work just fine now.

local function PlayerRemoving(player)
	local profile = cachedProfiles[player]
	
	local OwnedTools = {ToolsWithStacks = {}, ToolsWithoutStacks = {}} -- creates an empty table to add tools inside it (if the player lost all his tools, he won't have any tool upon rejoining)
	
	for _,tool in pairs (player.Backpack:GetChildren()) do 
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			if tool.Stack > 0 then --  if the stack is equal or lower than 0, don't save the tool
				OwnedTools.ToolsWithStacks[tool.Name] = {ToolName = tool.Name, Stacks = tool.Stack} -- tools with the same name will be saved under the same table, so it won't duplicate
			end
		else
			OwnedTools.ToolsWithoutStacks[tool.Name] = {ToolName = tool.Name} -- tools with the same name will be saved under the same table, so it won't duplicate
		end
	end
	profile.Data.Tools = OwnedTools -- overwrite player tools with the table we just created
	if profile ~= nil then
		profile:Release()
	end
end

I tested dropping a tool and leaving the game and it worked for me, I lost it. I also tested getting 2 of the same gear, only one was saved.

By the way, if you want to manually check if the player has a gear, you can do it easier using this, example:

print(profile.Data.Tools.ToolsWithoutStacks["OmegaRainbowSword"] ~= nil) -- will print true if the player has the tool
1 Like

Thanks! That’s what I meant … Although now there is a little problem, like this, little one (sorry for bothering). As you can see in the title, I wanted the items not to be duplicated (since the items were duplicated when saving, thanks to you, not anymore) but now, I don’t know, they save the items that are the same as others, I mean, if there are others tools that are the same, they are not saved (not duplicated, but tools that were added for something are not saved, for example: buy several tools and only one of that tool is saved, I want the tools that you obtained to be saved by yourself, not duplicates)

I get it. Did some new modifications to address your issue. This time I had to change load tools script too. Also no problems! Feel free to ask how many times you need to.

PlayerRemoving function:

local function PlayerRemoving(player)
	local profile = cachedProfiles[player]
	
	local OwnedTools = {ToolsWithStacks = {}, ToolsWithoutStacks = {}}
	
	for _,tool in pairs (player.Backpack:GetChildren()) do 
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			if tool.Stack.Value > 0 then
				if OwnedTools.ToolsWithStacks[tool.Name] == nil then
					OwnedTools.ToolsWithStacks[tool.Name] = {{ToolName = tool.Name, Stacks = tool.Stack.Value}}
				elseif OwnedTools.ToolsWithStacks[tool.Name] ~= nil then
					table.insert(OwnedTools.ToolsWithStacks[tool.Name], {ToolName = tool.Name, Stacks = tool.Stack.Value})
				end
			end
		else
			if OwnedTools.ToolsWithoutStacks[tool.Name] == nil then
				OwnedTools.ToolsWithoutStacks[tool.Name] = {ToolName = tool.Name, Amount = 1}
			elseif OwnedTools.ToolsWithoutStacks[tool.Name] ~= nil then
				OwnedTools.ToolsWithoutStacks[tool.Name].Amount += 1
			end
		end
	end
	profile.Data.Tools = OwnedTools
	if profile ~= nil then
		profile:Release()
	end
end

LoadTools script:

local Players = game:GetService("Players")
local DataManager = require(script.Parent:WaitForChild("ProfileCacher"))
local ServerStorage = game:GetService("ServerStorage")
local toolsFolder = ServerStorage:WaitForChild("toolsFolder")

Players.PlayerAdded:Connect(function(player)
	local Profile = DataManager:Get(player, true)
	if not Profile then
		repeat task.wait(0.1)
			Profile = DataManager:Get(player, true)
		until Profile or (not player.Parent)
	end
	if Profile then
		--Obtener las herramientas
		for _, ToolsWithStacks in pairs(Profile.Data.Tools.ToolsWithStacks) do
			if not toolsFolder:FindFirstChild(ToolsWithStacks[1].ToolName) then continue end
			for i = 1, #ToolsWithStacks do
				local Tool = toolsFolder[ToolsWithStacks[1].ToolName]:Clone()
				Tool:WaitForChild("Stack").Value = ToolsWithStacks[i].Stacks
				Tool.Parent = player:WaitForChild("Backpack")
				local ToolGear = toolsFolder[ToolsWithStacks[1].ToolName]:Clone()
				ToolGear:WaitForChild("Stack").Value = ToolsWithStacks[i].Stacks
				ToolGear.Parent = player:WaitForChild("StarterGear")			
			end
		end
		for _, ToolsWithoutStacks in pairs(Profile.Data.Tools.ToolsWithoutStacks) do
			if not toolsFolder:FindFirstChild(ToolsWithoutStacks.ToolName) then continue end
			for i = 1, ToolsWithoutStacks.Amount do
				local Tool = toolsFolder[ToolsWithoutStacks.ToolName]:Clone()
				Tool.Parent = player:WaitForChild("Backpack")
				local ToolGear = toolsFolder[ToolsWithoutStacks.ToolName]:Clone()
				ToolGear.Parent = player:WaitForChild("StarterGear")
			end
		end
	end
end)

I tested it both with and without stacks and worked fine with me. See if it solves the issue!

Now if the tools are put away! Well, just keep the tools without stacks are saved.

Dinero2 - Roblox Studio 25_10_2021 09_18_37 p. m. (2)

As you can see, the print does not show any tools in the stacks part (there should be tools with their respective stacks but it does not show them because it does not save them)

I don't know if I hurt something
local Players = game:GetService("Players") 
local ProfileService = require(script.ProfileService)
local saveStructure = {
	Tools = {ToolsWithStacks = {}, ToolsWithoutStacks = {}};
	Money = 0;
	LogInTimes = 0;
	LogInGiift = 0;
	
	Shadows = "Medio";
	Water = "Medio";
	Tree = "Enabled";
	
	FPS = "UnEnabled";
	Ping = "UnEnabledPing";
	BrilloVol = 0.7;
	PlayerChose = "ForLocalPlayer";
	Language = "English";
	SlotsInventory = 90;
	
	Crouch = "Keep";
	Sprint = "Keep";
}

local PlayerProfileStore = ProfileService.GetProfileStore("test37", saveStructure)

local cachedProfiles = {}

local function DoSomethingWithALoadedProfile(player, profile)
	local GiftMoney = math.random(100, 270)
	profile.Data.LogInTimes = profile.Data.LogInTimes + 1
	profile.Data.LogInGiift = profile.Data.LogInGiift + 1
	print(player.Name, " has logged in " .. tostring(profile.Data.LogInTimes)..	" time" .. ((profile.Data.LogInTimes > 1) and "s" or ""))
	if profile.Data.LogInGiift >= 120 then
		profile.Data.Money = profile.Data.Money + GiftMoney
		print(player.Name, "has been given a gift of $".. GiftMoney.. ".", "Actualmente tiene $".. profile.Data.Money.. ".")
		profile.Data.LogInGiift = 0
	else
		print(player.Name .. " owns " .. tostring(profile.Data.Money) .. " now!")
	end
end

local function PlayerAdded(player)
	local profile = PlayerProfileStore:LoadProfileAsync("Player_".. player.UserId, "ForceLoad")
	if profile ~= nil then
        profile:Reconcile()
		profile:ListenToRelease(function()
			cachedProfiles[player] = nil
			player:Kick("Tus datos no han sido cargados. Por favor Ăşnase nuevamente")
		end)
		if player:IsDescendantOf(Players) then
			cachedProfiles[player] = profile
			DoSomethingWithALoadedProfile(player, profile)
		else
			profile:Release()
		end
	else
		player:Kick("No se pueden cargar tus datos. Por favor Ăşnase nuevamente")
	end
end

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

local function PlayerRemoving(player)
	local profile = cachedProfiles[player]

	local OwnedTools = {ToolsWithStacks = {}, ToolsWithoutStacks = {}}

	for _,tool in pairs (player.Backpack:GetChildren()) do 
		if not tool:IsA("Tool") then continue end
		if tool:FindFirstChild("Stack") then
			if tool.Stack.Value > 0 then
				if OwnedTools.ToolsWithStacks[tool.Name] == nil then
					OwnedTools.ToolsWithStacks[tool.Name] = {{ToolName = tool.Name, Stacks = tool.Stack.Value}}
				elseif OwnedTools.ToolsWithStacks[tool.Name] ~= nil then
					table.insert(OwnedTools.ToolsWithStacks[tool.Name], {ToolName = tool.Name, Stacks = tool.Stack.Value})
				end
			end
		else
			if OwnedTools.ToolsWithoutStacks[tool.Name] == nil then
				OwnedTools.ToolsWithoutStacks[tool.Name] = {ToolName = tool.Name, Amount = 1}
			elseif OwnedTools.ToolsWithoutStacks[tool.Name] ~= nil then
				OwnedTools.ToolsWithoutStacks[tool.Name].Amount += 1
			end
		end
	end
	profile.Data.Tools = OwnedTools
	if profile ~= nil then
		profile:Release()
	end
	print(profile)
end

Players.PlayerAdded:Connect(PlayerAdded)
Players.PlayerRemoving:Connect(PlayerRemoving)

function cachedProfiles:Get(player, yield)
	local profile = cachedProfiles[player]
	if yield and not profile then
		repeat task.wait(0.1)
			profile = cachedProfiles[player]
		until profile or (not player.Parent)
	end
	if profile then
		return profile
	end
end

return cachedProfiles

Tools with stacks saved just fine with me. Did you change LoadTools script to the one I sent? Is “Stack” a NumberValue?

If both these answers are yes, I don’t know what could be causing the issue. One alternative would be saving only one tool with stack and, instead of loading separate tools of that type, load a single one with both stacks added together.

2 Likes

the stacks are NumberValues. the script that loads the data I think is fine, it just doesn’t save the tools with stacks (I check it with print)

A few days ago I had found a possible solution, it was to delete the old data when exiting the game, later it was to save the tools from your backpack

ah I understand, I think it does not save the data of the tools with stacks because it set the value of the NumberValue to zero