Randomly Resetting Inventory

Hey everybody-

I’ve been coding an inventory system for my game. It uses a table-to string- to table method, as I found this method to be the easiest for me.

Unfortunately, I often experience random inventory resets. However, no error or warning is printed to the console.

Side note: Sometimes, the “EquippedItem” saves fine, but the inventory is erased. I do not know what the issue is here, and I’m hoping one of you can point it out for me.

local ds = game:GetService("DataStoreService")

local invSave = ds:GetDataStore("InveOkOkOkOkOkntoryDataNewOkYayStore22234")

local equippeditemsave = ds:GetDataStore("FirstOfManySave219")

game.Players.PlayerAdded:Connect(function(plr)

	--player = plr

	local statsFolder = Instance.new("Folder")

	statsFolder.Name = "Inventory"
	statsFolder.Parent = plr

	local inventory = Instance.new("StringValue")

	inventory.Parent = statsFolder
	inventory.Name = "ItemsStringValue"
	
	local equippedItem = Instance.new("StringValue")
	
	equippedItem.Parent = statsFolder
	equippedItem.Name = "EquippedItem"
	
	
	
	equippedItem.Changed:Connect(function()
		local yay, nay = pcall(function()
			warn("The Equipped item has been changed.")
			plr.StarterGear:ClearAllChildren()
			plr.Backpack:ClearAllChildren()
			for i,v in pairs(plr.Backpack:GetChildren()) do
				v:Destroy()
			end
			for i,v in pairs(plr.Character:GetChildren()) do
				if v:IsA("Tool") then
					v:Destroy()
				end

			end
			
				local item1 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
				local item2 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
				item1.Parent = plr.Backpack
				item2.Parent = plr.StarterGear
		


			warn("Equipped item given to player.")
		end)
		if nay then
			while task.wait(0.1) do
				warn(nay)
			end
		end
	end)
	
	local blockAutoSave = false
	
	task.wait(5)
	
	local yay, nay = pcall(function()
		local saveData = invSave:GetAsync(plr.UserId)
		local saveData2 = equippeditemsave:GetAsync(plr.UserId)
		if saveData ~= nil then
			inventory.Value = saveData
		else
			inventory.Value = ""
		end
		if saveData2 ~= nil and saveData2 ~= "Nothing" then
			equippedItem.Value = saveData2
		else
			warn("data not found")
			equippedItem.Value = "Nothing"
		end



		plr.StarterGear:ClearAllChildren()
		plr.Backpack:ClearAllChildren()
		

		if equippedItem.Value ~= "Nothing" and game.ReplicatedStorage.Items:FindFirstChild(equippedItem.Value) then
			local item1 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
			local item2 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
			item1.Parent = plr.Backpack
			item2.Parent = plr.StarterGear
			
			
		end
		equippedItem.Value = equippedItem.Value
	end)

	

	if nay then
		warn("Error while loading data.".. nay)
		--inventory.Value = ""
		--equippedItem.Value = "Nothing"
		blockAutoSave = true
		plr:Kick("Something went wrong while trying to load your data. Please rejoin.")
		return
	end
	
	if yay then
		warn("Data loaded.")
	end
	
	while task.wait(120) do
		local yay, nay = pcall(function()
			if blockAutoSave then
				plr:Kick("Critical data failure occured. Please rejoin.")
			else
				invSave:SetAsync(plr.UserId, inventory.Value)
			end
			

		end)
		if nay then
			warn("Data save failed.")
			--plr:Kick("Data saving failed. Please rejoin or try again later.")
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	local yay, nay = pcall(function()
		invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value)
		equippeditemsave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value)
	end)
	if nay then
		warn("Data save failed.".. nay)
		if nay then
			warn("Attempt 2.")
			invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value)
			equippeditemsave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value)
		end
	end
	if yay then
		warn("Data saved.")
	end
end)

Unfortunately, this issue is extremely hard to replicate, as it happens at random, I’d say it’s about a 1 in 20 chance this happens on join.

Thank you all!

3 Likes

Try this code:

local ds = game:GetService("DataStoreService")

local invSave = ds:GetDataStore("InveOkOkOkOkOkntoryDataNewOkYayStore22234")
local equippeditemsave = ds:GetDataStore("FirstOfManySave219")
local sessionLock = ds:GetDataStore("SessionLock")

game.Players.PlayerAdded:Connect(function(plr)
	local sessionKey = "lock_" .. plr.UserId
	local alreadyPlaying = sessionLock:GetAsync(sessionKey)
	if alreadyPlaying then
		plr:Kick("You are already in the game from another device.")
		return
	end
	sessionLock:SetAsync(sessionKey, true)

	local statsFolder = Instance.new("Folder")
	statsFolder.Name = "Inventory"
	statsFolder.Parent = plr

	local inventory = Instance.new("StringValue")
	inventory.Name = "ItemsStringValue"
	inventory.Parent = statsFolder

	local equippedItem = Instance.new("StringValue")
	equippedItem.Name = "EquippedItem"
	equippedItem.Parent = statsFolder

	equippedItem.Changed:Connect(function()
		local yay, nay = pcall(function()
			warn("The Equipped item has been changed.")
			plr.StarterGear:ClearAllChildren()
			plr.Backpack:ClearAllChildren()

			for _, v in pairs(plr.Character:GetChildren()) do
				if v:IsA("Tool") then
					v:Destroy()
				end
			end

			if equippedItem.Value ~= "Nothing" and equippedItem.Value ~= "" then
				local item1 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
				local item2 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
				item1.Parent = plr.Backpack
				item2.Parent = plr.StarterGear
			end
		end)
		if not yay then
			while task.wait(0.1) do
				warn(nay)
			end
		end
	end)

	local blockAutoSave = false
	task.wait(5)

	local yay, nay = pcall(function()
		local saveData = invSave:GetAsync(plr.UserId)
		local saveData2 = equippeditemsave:GetAsync(plr.UserId)

		if type(saveData) == "string" then
			inventory.Value = saveData
		else
			inventory.Value = ""
		end

		if type(saveData2) == "string" and saveData2 ~= "Nothing" then
			equippedItem.Value = saveData2
		else
			warn("equipped item data not found")
			equippedItem.Value = "Nothing"
		end

		plr.StarterGear:ClearAllChildren()
		plr.Backpack:ClearAllChildren()

		if equippedItem.Value ~= "Nothing" and game.ReplicatedStorage.Items:FindFirstChild(equippedItem.Value) then
			local item1 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
			local item2 = game.ReplicatedStorage.Items:WaitForChild(equippedItem.Value):Clone()
			item1.Parent = plr.Backpack
			item2.Parent = plr.StarterGear
		end
	end)

	if not yay then
		warn("Error while loading data: " .. nay)
		blockAutoSave = true
		plr:Kick("Something went wrong while trying to load your data. Please rejoin.")
		sessionLock:RemoveAsync(sessionKey)
		return
	else
		warn("Data loaded.")
	end

	-- Auto-save loop
	while task.wait(120) do
		local yay, nay = pcall(function()
			if blockAutoSave then
				plr:Kick("Critical data failure occurred. Please rejoin.")
			else
				invSave:SetAsync(plr.UserId, inventory.Value or "")
				equippeditemsave:SetAsync(plr.UserId, equippedItem.Value or "Nothing")
			end
		end)
		if not yay then
			warn("Auto-save failed: " .. nay)
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	local sessionKey = "lock_" .. plr.UserId
	local yay, nay = pcall(function()
		invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value or "")
		equippeditemsave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value or "Nothing")
	end)

	if not yay then
		warn("PlayerRemoving save failed: " .. nay)
		pcall(function()
			invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value or "")
			equippeditemsave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value or "Nothing")
		end)
	else
		warn("Data saved on PlayerRemoving.")
	end

	-- Release session lock
	pcall(function()
		sessionLock:RemoveAsync(sessionKey)
	end)
end)

game:BindToClose(function()
	for _, plr in ipairs(game.Players:GetPlayers()) do
		pcall(function()
			invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value or "")
			equippeditemsave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value or "Nothing")
			sessionLock:RemoveAsync("lock_" .. plr.UserId)
		end)
	end
end)

The random inventory resets are likely caused by the ‘Changed’ event firing before data loads, missing auto saves for the equipped item and overwriting valid data with empty values. I’d use a flag to suppress early ‘Changed’ events, save both inventory and equipped item in the loop, and validate data before saving. Also I’d remove unnecessary delays before loading data and add debug prints to trace when things go wrong until you know your system is solid.

I’ve played around with your code a bit to help with the above recommendations

local ds = game:GetService("DataStoreService")
local invSave = ds:GetDataStore("InventoryData")
local equippedSave = ds:GetDataStore("EquippedItemData")

game.Players.PlayerAdded:Connect(function(plr)
	local stats = Instance.new("Folder")
	stats.Name = "Inventory"
	stats.Parent = plr

	local inventory = Instance.new("StringValue")
	inventory.Name = "ItemsStringValue"
	inventory.Parent = stats

	local equipped = Instance.new("StringValue")
	equipped.Name = "EquippedItem"
	equipped.Parent = stats

	local suppressChanged = true
	local blockAutoSave = false

	equipped.Changed:Connect(function()
		if suppressChanged then return end
		local success, err = pcall(function()
			plr.StarterGear:ClearAllChildren()
			plr.Backpack:ClearAllChildren()
			for _, tool in pairs(plr.Character:GetChildren()) do
				if tool:IsA("Tool") then tool:Destroy() end
			end
			if equipped.Value ~= "" and equipped.Value ~= "Nothing" then
				local item1 = game.ReplicatedStorage.Items:FindFirstChild(equipped.Value)
				if item1 then
					item1:Clone().Parent = plr.Backpack
					item1:Clone().Parent = plr.StarterGear
				end
			end
		end)
		if not success then warn(err) end
	end)

	local success, err = pcall(function()
		local invData = invSave:GetAsync(plr.UserId)
		local equipData = equippedSave:GetAsync(plr.UserId)

		inventory.Value = invData or ""
		equipped.Value = (equipData and equipData ~= "Nothing") and equipData or "Nothing"

		print("Loaded inventory:", inventory.Value)
		print("Loaded equipped:", equipped.Value)
	end)

	if not success then
		warn("Data load failed:", err)
		blockAutoSave = true
		plr:Kick("Data failed to load. Please rejoin.")
		return
	end

	suppressChanged = false

	while task.wait(120) do
		if blockAutoSave then
			plr:Kick("Critical data failure. Please rejoin.")
			return
		end
		pcall(function()
			if inventory.Value ~= "" then
				invSave:SetAsync(plr.UserId, inventory.Value)
			end
			if equipped.Value ~= "" and equipped.Value ~= "Nothing" then
				equippedSave:SetAsync(plr.UserId, equipped.Value)
			end
		end)
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	pcall(function()
		invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value)
		equippedSave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value)
	end)
end)

Thank you for the code, however I am seeking the issue, not the copy-and-paste solution. If you could explain what went wrong and why this one works, I would appreciate it.

I’ll try a few of these solutions out, and report back to you whether I experience this more often or not. Thank you for the solutions!

1 Like

Only save the inventory when it changes, keep the real data in a table instead of a StringValue, and make sure not to save if the data is empty or looks wrong.

If you use table-to-string serialization, make sure that logic is elsewhere in your system (e.g., when updating inventory.Value) and not causing nil or corrupted data. Also, keep your DataStore names consistent and try to test with real players using Publish to Roblox to ensure DataStore access

No pcall for equippeditemsave:SetAsync in the autosave loop.
If the inventory string is set to the same value, .Changed won’t fire.

This still happens to me, by the way.

I did a quick cleanup based on your script.
I hope you have a folder named plr.StarterGear and didn’t mean to use plr.StarterPack.
If that’s the case, it won’t work properly.

not tested
local ds = game:GetService("DataStoreService")
local invSave = ds:GetDataStore("InveOkOkOkOkOkntoryDataNewOkYayStore22234")
local equippedSave = ds:GetDataStore("FirstOfManySave219")

game.Players.PlayerAdded:Connect(function(plr)
	local stats = Instance.new("Folder")
	stats.Name = "Inventory"
	stats.Parent = plr

	local inventory = Instance.new("StringValue")
	inventory.Name = "ItemsStringValue"
	inventory.Parent = stats

	local equippedItem = Instance.new("StringValue")
	equippedItem.Name = "EquippedItem"
	equippedItem.Parent = stats

	local loading = true

	local function clearGear()
		plr.StarterGear:ClearAllChildren()
		plr.Backpack:ClearAllChildren()
		if plr.Character then
			for _, v in pairs(plr.Character:GetChildren()) do
				if v:IsA("Tool") then
					v:Destroy()
				end
			end
		end
	end

	local function giveGear(name)
		local item = game.ReplicatedStorage.Items:FindFirstChild(name)
		if item then
			clearGear()
			item:Clone().Parent = plr.Backpack
			item:Clone().Parent = plr.StarterGear
		end
	end

	equippedItem.Changed:Connect(function()
		if loading then return end
		pcall(function()
			giveGear(equippedItem.Value)
		end)
	end)

	task.wait(5)
	local success, err = pcall(function()
		local saved = invSave:GetAsync(plr.UserId)
		local equip = equippedSave:GetAsync(plr.UserId)
		inventory.Value = saved or ""
		equippedItem.Value = (equip and equip ~= "Nothing") and equip or "Nothing"
	end)

	if not success then
		plr:Kick("Failed to load data. Please rejoin.")
		return
	end

	loading = false
	giveGear(equippedItem.Value)

	while task.wait(120) do
		local ok = pcall(function()
			if inventory.Value and inventory.Value ~= "" then
				invSave:SetAsync(plr.UserId, inventory.Value)
			end
			if equippedItem.Value and equippedItem.Value ~= "" then
				equippedSave:SetAsync(plr.UserId, equippedItem.Value)
			end
		end)
		if not ok then
			plr:Kick("Data save failure. Please rejoin.")
			break
		end
	end
end)

game.Players.PlayerRemoving:Connect(function(plr)
	pcall(function()
		if plr.Inventory then
			invSave:SetAsync(plr.UserId, plr.Inventory.ItemsStringValue.Value)
			equippedSave:SetAsync(plr.UserId, plr.Inventory.EquippedItem.Value)
		end
	end)
end)

This seems like a very odd name, InveOkOkOkOkOkntoryDataNewOkYayStore22234.
Idk about all that…

I appreciate it, but what did you change exactly?

The code is very helpful; but an explanation would really help, too

I just took what you were doing and tried to standardize it. I’m going to have to really look here to tell you all that was changed. I just ran down the script at warp speed. Let’s see here…

Renamed variables (equippeditemsave → equippedSave, statsFolder → stats).
Added loading flag to block .Changed during data load.
Created clearGear() and giveGear(name) functions for gear handling.
Removed duplicated gear clearing code.
Removed forced re-assignment of equippedItem.Value.
Simplified data loading with or fallback.
Removed verbose warnings and separate success/error vars.
Auto-save only saves if values are non-empty.
On auto-save failure, kicks player and stops loop.
Simplified PlayerRemoving saving, removed retry logic.

Hope this worked out for you. I had problems like this when I started with databases.
I did remove the retry logic. I just don’t believe in it and think it causes more problems than it helps.

This may not be for you but … I made one of these a while back here are a few key points.
It’s more about pointing out the concept rather than the script.

I started with this and made sure everything else worked with it. It uses ‘slots.’ My gear also shows on the player when not equipped. That’s why it’s labeled this way. This is the default starting table.

local database = ht:JSONEncode({
	side = {}, face = {}, back = {}, pack = {},
	helm = {}, garb = {}, suit = { "Suit02" },
	coms = {}, belt = {}, clip = {}
})

This is just a snippet

--player data
if playerData:FindFirstChild(playerId) then print("reading data")
	gear = ht:JSONDecode(playerData[playerId].Value)
else
	local temp = Instance.new("StringValue")
	temp.Name, temp.Parent = playerId, playerData

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

	if success and data then print("loading data")
		player.leaderstats.Cash.Value = data.Cash
		playerData[playerId].Value = data.Gear
		gear = ht:JSONDecode(data.Gear)
	else print("creating data")
		player.leaderstats.Cash.Value = 25000
		playerData[playerId].Value = database
		gear = ht:JSONDecode(playerData[playerId].Value)
	end
end

With this, I have a folder in ServerStorage named PlayerData. What I’m doing here is creating a StringValue named after the player’s ID, which stores the gear string. Everything then refers to and updates the StringValue in the PlayerData folder. It is removed when they exit the game.

This totally avoids repeated reading and writing to the actual database and cuts all data reading and writing down to just two times per player. Anything else is handled by the string, like if you went to a store in the game, that would all be handled by that string.

1 Like