Recently, I encountered a very strange bug that seems to duplicating my items whenever I join the game.
So I have those 2 tables named savedItems and SESSION_TABLE.
The main differences between them is that savedItems is saved to the datastore, while SESSION_TABLE is not. The purpose of SESSION_TABLE is to basically have savedItems table assigned to it and whenever an item is picked up by the player, the SESSION_TABLE is going to write down the data about an item that has to be saved in savedItems e.g. its position in player’s inventory, display name.
When player leaves the game, right before the data is saved, savedItems table will get all the data about the items from the SESSION_TABLE and assign it for itself.
(For your information, I’m using ProfileService to save data)
My problem is that the items somehow duplicate itself multiple times when the player owns one. (I’m using HttpService:GenerateGUID() to generate unique identifiers for items so they dont override eachother when player has multiple items of the same kind)
I’ll give an example:
I start the game with no items. During my progression, I find a Sword. When I leave the game, my Sword is saved to savedItems table. Here’s how it looks like:
But when I join back, my Sword duplicates itself for horrendous amounts for unknown reason:
Here are fragments of 2 scripts:
local SS = game:GetService("ServerStorage")
local RS = game:GetService("ReplicatedStorage")
local ProfileService = require(script.Parent.ProfileService)
local InventoryService = require(script.Parent.InventoryService)
local ProfileTemplate = {
savedItems = {},
money = 0
}
local ProfileStore = ProfileService.GetProfileStore("PlayerData",ProfileTemplate)
local players = {}
local PlayerService = {}
local Profiles = {}
function onPlayerAdded(plr)
local profile = ProfileStore:LoadProfileAsync("Player_" .. plr.UserId)
if profile ~= nil then
profile:AddUserId(plr.UserId)
profile:Reconcile()
profile:ListenToRelease(function()
Profiles[plr] = nil
plr:Kick()
end)
if plr:IsDescendantOf(game.Players) then
Profiles[plr] = profile
else
profile:Release()
end
else
plr:Kick()
end
end
function PlayerService.start()
for i,v in game.Players:GetPlayers() do
task.spawn(onPlayerAdded, v)
end
game.Players.PlayerAdded:Connect(function(plr)
onPlayerAdded(plr)
players[plr.UserId] = {}
local PlayerInfo = players[plr.UserId]
local PlayerData = PlayerService.GetData(plr)
local PlayerInventory = InventoryService.GetPlayerInventory(plr)
PlayerInfo.SESSION_TABLE = PlayerData.savedItems
for i,v in pairs(PlayerInfo.savedItems) do
PlayerInventory:AddTool(v.Name)
end
end)
game.Players.PlayerRemoving:Connect(function(plr)
local PlayerData = PlayerService.GetData(plr)
PlayerData.savedItems = players[plr.UserId].SESSION_TABLE
players[plr.UserId] = nil
local profile = Profiles[plr]
if profile ~= nil then
profile:Release()
end
end)
end
function PlayerService.GetList()
return players
end
function PlayerService.GetPlayer(plr)
return players[plr.UserId]
end
function PlayerService.GetData(plr)
local profile = Profiles[plr]
if profile ~= nil then
return profile.Data
end
end
function PlayerService.GetProfile(plr)
local profile = Profiles[plr]
if profile ~= nil then
return profile
end
end
return PlayerService
local MaxHotbarTools = 5
local HotbarKeys = {
[1] = "One",
[2] = "Two",
[3] = "Three",
[4] = "Four",
[5] = "Five",
}
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local ServerStorage = game:GetService("ServerStorage")
local HttpService = game:GetService("HttpService")
local Networking = ReplicatedStorage.Inventory.Networking
local Util = ReplicatedStorage.Inventory.Util
local InventorySystem = script.Parent.Parent
local ToolSystem = InventorySystem.ToolSystem
local Loader = require(Util.Loader)
local ToolClasses = Loader.LoadDirectory(ToolSystem.Tools, true)
local PlayerService = require(script.Parent.Parent.Parent.PlayerService)
local InventoryBase = {}
InventoryBase.__index = InventoryBase
function InventoryBase.new(player)
local self = setmetatable({
Tools = {
Hotbar = {},
Backpack = {},
EquippedTool = nil
},
Player = player,
NumberOfTools = 0,
ToolsInHotbar = 0,
LastId = 0
}, InventoryBase)
return self
end
function GenerateGUID()
return HttpService:GenerateGUID(false)
end
function InventoryBase:AddTool(toolName)
if (not toolName or type(toolName) ~= "string") then return end
if (not ToolClasses[toolName]) then return end
if (self.ToolsInHotbar < MaxHotbarTools) then
self.NumberOfTools += 1
self.ToolsInHotbar += 1
self.LastId += 1
-- // This is going to be added to player's inventory with all the tool's functions and other stuff
local toolKey = HotbarKeys[self.NumberOfTools]
self.Tools.Hotbar[toolKey] = ToolClasses[toolName].new()
local newTool = self.Tools.Hotbar[toolKey]
newTool.EquipKey = toolKey
newTool.ID = self.LastId
newTool.Name = toolName
-- // This is going to hold the most important data about the tool such as its name, position in inventory etc.
local identifier = GenerateGUID()
local PlayerInfo = PlayerService.GetPlayer(self.Player)
PlayerInfo.SESSION_TABLE[identifier] = {}
local data = PlayerInfo.SESSION_TABLE[identifier]
data.Name = toolName
data.positionId = self.LastId
-- // Then the client does the rest of the code (The data and the session table isn't affected by client in any means)
Networking.RE.ToolAdded:FireClient(self.Player, {Name = toolName, Key = toolKey, ID = newTool.ID})
end
end
return InventoryBase
What I find really strange is how this fragment iterates itself multiple times even if the savedItems table clearly has 1 item saved:
for i,v in pairs(PlayerInfo.savedItems) do
PlayerInventory:AddTool(v.Name)
end
I’m deeply sorry if I explained my issue poorly