Needs a good Saving system, errors in data

I have made this script for Gear Shop system, I have worked on doing Equip and UnEquip, but I see that sometimes there are some errors and some item I bought could not be saved.

sometimes some of Item I did buy, it reset like I need to buy the item again.

local DataStoreService = game:GetService("DataStoreService")
local ItemsDataStore = DataStoreService:GetDataStore("ArabV2")
local event = game.ReplicatedStorage.Events.Shop
local allitems = game.ReplicatedStorage["Shop System"]
local Players = game:GetService("Players")
local NotificationEvent = game.ReplicatedStorage.Events:WaitForChild("NotificationEvent")
local UpdateInventoryEvent = game.ReplicatedStorage.Events:WaitForChild("UpdateInventoryEvent")

local saveCooldown = {}  -- A table to track player cooldowns
local saveAttempts = {}  -- Track failed save attempts to try again later

local function canSave(player)
	local currentTime = tick()
	if not saveCooldown[player.UserId] or (currentTime - saveCooldown[player.UserId]) > 10 then
		saveCooldown[player.UserId] = currentTime
		return true
	else
		return false
	end
end

-- Function to send a notification
local function sendNotification(player, message, isError)
	NotificationEvent:FireClient(player, message, isError)
end

-- Save item data with activation status
local function saveItemsData(player)
	if not canSave(player) then return end

	if player:FindFirstChild("ItemInventory") then
		local ItemInventory = {}
		for _, v in pairs(player.ItemInventory:GetChildren()) do
			if typeof(v.Name) == "string" and typeof(v.Value) == "string" then
				table.insert(ItemInventory, { Name = v.Name, Status = v.Value })
			else
				warn("Invalid item data detected: ", v.Name, v.Value)
			end
		end

		local success, err = pcall(function()
			ItemsDataStore:SetAsync("player: " .. tostring(player.UserId), ItemInventory)
		end)

		if not success then
			warn("Failed to save item data:", err)
			saveAttempts[player.UserId] = saveAttempts[player.UserId] or 0
			saveAttempts[player.UserId] = saveAttempts[player.UserId] + 1

			-- Retry saving if failed more than 3 times
			if saveAttempts[player.UserId] <= 3 then
				delay(5, function()
					saveItemsData(player)
				end)
			else
				sendNotification(player, "Failed to save your data. Please try again later.", true)
				warn("Failed to save your data. Please try again later")
			end
		end
	end
end

local function removeEquippedShopItems(player)
	-- Clear from Backpack
	if player.Backpack then
		for _, item in ipairs(player.Backpack:GetChildren()) do
			if allitems:FindFirstChild(item.Name) then -- Only shop items
				item:Destroy()
			end
		end
	end

	-- Clear from StarterGear
	if player.StarterGear then
		for _, item in ipairs(player.StarterGear:GetChildren()) do
			if allitems:FindFirstChild(item.Name) then -- Only shop items
				item:Destroy()
			end
		end
	end

	-- Clear from Character model (if equipped)
	if player.Character then
		for _, item in ipairs(player.Character:GetChildren()) do
			if item:IsA("Tool") and allitems:FindFirstChild(item.Name) then -- Only shop items
				item:Destroy()
			end
		end
	end
end

-- Load and equip items
local function loadAndEquipItems(player)
	local itemInventory = player:FindFirstChild("ItemInventory")
	if itemInventory then
		itemInventory:ClearAllChildren()
	else
		itemInventory = Instance.new("Folder")
		itemInventory.Name = "ItemInventory"
		itemInventory.Parent = player
	end

	-- Clear only shop items, not other items
	removeEquippedShopItems(player)

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

	if success and data then
		for _, itemData in pairs(data) do
			if typeof(itemData) == "table" and itemData.Name then
				local stringValue = Instance.new("StringValue")
				stringValue.Name = itemData.Name
				stringValue.Value = itemData.Status or "Unequip"
				stringValue.Parent = itemInventory

				if itemData.Status == "Equip" then
					local itemTemplate = allitems:FindFirstChild(itemData.Name)
					if itemTemplate then
						local itemClone = itemTemplate:Clone()
						itemClone.Parent = player.Backpack
						itemClone:Clone().Parent = player.StarterGear
					end
				end
			end
		end
	end
end

-- Player added event
Players.PlayerAdded:Connect(function(player)
	-- Load and equip items when player joins
	loadAndEquipItems(player)

	-- Create ItemInventory if it doesn't exist
	local itemInventory = player:FindFirstChild("ItemInventory") or Instance.new("Folder")
	itemInventory.Name = "ItemInventory"
	itemInventory.Parent = player

	-- Save data when ItemInventory is modified
	itemInventory.ChildAdded:Connect(function()
		saveItemsData(player)
	end)

	itemInventory.ChildRemoved:Connect(function()
		saveItemsData(player)
	end)

	-- Manage restoration
	player.CharacterAdded:Connect(function()
		loadAndEquipItems(player)
	end)
end)

-- Helper function to handle item cloning and equipping
local function equipItem(player, itemTemplate)
	-- Check if item is already equipped
	if not player.Backpack:FindFirstChild(itemTemplate.Name) then
		itemTemplate:Clone().Parent = player.Backpack
	end

	if not player.StarterGear:FindFirstChild(itemTemplate.Name) then
		itemTemplate:Clone().Parent = player.StarterGear
	end
end

-- Manage item equip/unequip
event.OnServerEvent:Connect(function(player, itemName, action)
	if player and allitems:FindFirstChild(itemName) then
		local itemTemplate = allitems:FindFirstChild(itemName)
		local itemInInventory = player.ItemInventory:FindFirstChild(itemName)

		-- Function to remove item from both Backpack and StarterGear
		local function removeItemFromGear()
			-- Remove from Backpack
			if player.Backpack:FindFirstChild(itemName) then
				player.Backpack[itemName]:Destroy()
			end
			-- Remove from StarterGear (even if held)
			if player.StarterGear:FindFirstChild(itemName) then
				player.StarterGear[itemName]:Destroy()
			end

			-- Remove from player character if it exists
			if player.Character and player.Character:FindFirstChild(itemName) then
				player.Character[itemName]:Destroy()
			end
		end

		-- Equip item
		if action == "Equip" and itemInInventory then
			-- Remove the item if already equipped, even if in StarterGear (held)
			removeItemFromGear()

			-- Now equip the item
			equipItem(player, itemTemplate)

			-- Update item status to "Equip"
			itemInInventory.Value = "Equip"

			-- Unequip item
		elseif action == "Unequip" and itemInInventory then
			-- Remove the item if already equipped, even if in StarterGear (held)
			removeItemFromGear()

			-- Update inventory status to "Unequip"
			itemInInventory.Value = "Unequip"
		end

		-- Save changes in ItemsDataStore
		saveItemsData(player)
	end
end)

-- Manage buying and activating items
event.OnServerEvent:Connect(function(player, itemName, action)
	local item = allitems:FindFirstChild(itemName)
	if not item then
		sendNotification(player, "Item not found: " .. itemName, true)
		return
	end

	local itemInInventory = player.ItemInventory:FindFirstChild(itemName)

	-- Helper: Remove items from Backpack, StarterGear, and Character
	local function removeItemFromGear()
		for _, location in ipairs({player.Backpack, player.StarterGear, player.Character}) do
			if location and location:FindFirstChild(itemName) then
				location[itemName]:Destroy()
			end
		end
	end

	-- **1️⃣ Manage equip**
	if action == "Equip" and itemInInventory then
		removeItemFromGear()  -- Remove duplicate items before adding new
		item:Clone().Parent = player.Backpack
		item:Clone().Parent = player.StarterGear
		itemInInventory.Value = "Equip"
		sendNotification(player, "Item equipped: " .. itemName, false)

		-- **2️⃣ Manage unequip**
	elseif action == "Unequip" and itemInInventory then
		removeItemFromGear()
		itemInInventory.Value = "Unequip"
		sendNotification(player, "Item unequipped: " .. itemName, false)

		-- **3️⃣ Manage purchase**
	elseif not action then
		-- Check the price
		local price = (item:FindFirstChild("Handle") and item.Handle:FindFirstChild("PermanentPrice")) and item.Handle.PermanentPrice.Value
			or (item:FindFirstChild("PermanentPrice") and item.PermanentPrice.Value)

		if not price then
			sendNotification(player, "Price not found for item: " .. itemName, true)
			return
		end

		-- Check if player already owns the item
		if player.ItemInventory:FindFirstChild(itemName) then
			sendNotification(player, "You already own the item: " .. itemName, true)
			return
		end

		-- Purchase logic
		if player.leaderstats.Cash.Value >= price then
			player.leaderstats.Cash.Value -= price

			local saveItem = Instance.new("StringValue")
			saveItem.Name = itemName
			saveItem.Value = "Equip"
			saveItem.Parent = player.ItemInventory

			item:Clone().Parent = player.Backpack
			item:Clone().Parent = player.StarterGear

			sendNotification(player, "Item purchased successfully: " .. itemName, false)

			UpdateInventoryEvent:FireClient(player)
		else
			sendNotification(player, "Not enough money to buy " .. itemName, true)
		end
	else
		sendNotification(player, "Invalid action: " .. tostring(action), true)
	end

	saveItemsData(player)
end)

Players.PlayerRemoving:Connect(function(player)
	saveItemsData(player)
end)

datastore has a limit of how many requests you can make for a period.
so you should only modify the table of item inventory for each player when they equip/unequip
then only save their inventory to datastore on player removing

you can use this Module, it do the above logic and other related process

1 Like

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.