Optimization/ data saving and deleting accessorys

hey guys i am trying to make a gui where you can manage ur accessorys and paste in ids to put them on, i made a script but, it isnt well optimized and some things dont work for example the client deleted the accessory but on the server its still there, i tried to fix this but got nothing to work yet
ill just provide the scripts since i am running low on time.

Client:

local textBox = script.Parent.TextBox
local viewWearingFrame = script.Parent.Frame
local player = game.Players.LocalPlayer
local character = player.Character or player.CharacterAdded:Wait()
local debounce = false

textBox:GetPropertyChangedSignal("Text"):Connect(function()
	print(textBox.Text)
end)

script.Parent.TextButton.MouseButton1Up:Connect(function()
	local assetId = textBox.Text
	game.ReplicatedStorage.Getaccesory:FireServer(assetId)
end)
local function setAssetIdForAccessories(character)
	-- Iterate through the character's children to find accessories
	for _, child in ipairs(character:GetChildren()) do
		if child:IsA("Accessory") then
			local accessoryName = child.Name
			local assetId = accessoryName:match("(%d+)$") -- Extract asset ID from the accessory name
			if assetId then
				-- Do something with the asset ID, such as setting attributes or creating buttons
				-- For now, let's just print it
				print("Asset ID:", assetId)
			end
		end
	end
end
-- Function to update the viewport frames
local function updateViewports()
	if debounce then
		return
	end
	debounce = true

	-- Clear existing viewports
	for _, viewport in ipairs(viewWearingFrame:GetChildren()) do
		if viewport:IsA("UIGridLayout") then
			print( " NOW NOT DO")
		else
				viewport:Destroy()
		end
	
	end

	-- Create viewport frames for each accessory
	for _, accessory in ipairs(character:GetChildren()) do
		if accessory:IsA("Shirt") then
			local viewport = Instance.new("ImageButton")
			viewport.Size = UDim2.new(0.2, 0, 0.2, 0)
			viewport.Image = "rbxassetid://4973965649"
			viewport.ImageColor3 = Color3.fromRGB(0, 0, 0)
			viewport.BackgroundTransparency = 1
			viewport.BorderSizePixel = 0
			viewport.Parent = viewWearingFrame
			viewport.MouseButton1Down:Connect(function()
				accessory:Destroy()
				updateViewports() -- Update viewports after removing an item
			end)
		end
		if accessory:IsA("Pants") then
			local viewport = Instance.new("ImageButton")
			viewport.Size = UDim2.new(0.2, 0, 0.2, 0)
			viewport.Image = "rbxassetid://10098755331"
			viewport.ImageColor3 = Color3.fromRGB(0, 0, 0)
			viewport.BackgroundTransparency = 1
			viewport.BorderSizePixel = 0
			viewport.Parent = viewWearingFrame
			viewport.MouseButton1Down:Connect(function()
				accessory:Destroy()
				updateViewports() -- Update viewports after removing an item
			end)
		end
		if accessory:IsA("ShirtGraphic") then
			local viewport = Instance.new("ImageButton")
			viewport.Size = UDim2.new(0.2, 0, 0.2, 0)
			viewport.Image = "rbxassetid://6478597824"
			viewport.ImageColor3 = Color3.fromRGB(0, 0, 0)
			viewport.BackgroundTransparency = 1
			viewport.BorderSizePixel = 0
			viewport.Parent = viewWearingFrame
			viewport.MouseButton1Down:Connect(function()
				accessory:Destroy()
				updateViewports() -- Update viewports after removing an item
			end)
		end
		if accessory:IsA("Accessory") then
			local viewport = Instance.new("ImageButton")
			viewport.Size = UDim2.new(0.2, 0, 0.2, 0)
			viewport.BackgroundTransparency = 1
			viewport.BorderSizePixel = 0
			viewport.Parent = viewWearingFrame

			local textureId = "" -- Set texture ID based on item type
			local specialMesh = accessory:FindFirstChild("Handle") and accessory.Handle:FindFirstChild("SpecialMesh")
			if specialMesh then
				textureId = specialMesh.TextureId or ""
			end
			print("Texture ID:", textureId)
			print("Accessory:", accessory)
			-- Set thumbnail image
			local assetId = accessory:GetAttribute("AssetId")

			if assetId then
				-- Construct the thumbnail URL using the retrieved asset ID
				local thumbnailUrl = "https://www.roblox.com/asset-thumbnail/image?assetId=" .. tostring(assetId) .. "&width=420&height=420&format=png"

				-- Set thumbnail image
				viewport.Image = thumbnailUrl

				-- Add click event to remove item
				viewport.MouseButton1Down:Connect(function()
					accessory:Destroy()
					updateViewports() -- Update viewports after removing an item
					game.ReplicatedStorage.DeleteAccessory:FireServer(assetId, accessory.Name)
				end)
			else
				viewport:Destroy()
				local TextButton = Instance.new("TextButton")
				TextButton.Size = UDim2.new(0.2, 0, 0.2, 0)
				TextButton.BackgroundTransparency = 1
				TextButton.BorderSizePixel = 0
				TextButton.Parent = viewWearingFrame
				TextButton.Text = accessory.Name
				TextButton.TextScaled = true
				TextButton.MouseButton1Down:Connect(function()
					accessory:Destroy()
					updateViewports() -- Update viewports after removing an item
					game.ReplicatedStorage.DeleteAccessory:FireServer(assetId, accessory.Name)
				end)
			end
		end
	end

	debounce = false
end

game.ReplicatedStorage.Getaccesory.OnClientEvent:Connect(function(success, Text)
	if success then
		updateViewports()
		textBox.Text = ""
		script.Parent.TextButton.Text = "SUCCESSFULLY LOADED ".. Text
		wait(1)
		script.Parent.TextButton.Text = "Put on"
	else
		if Text == "MAX_HATS_REACHED" then
			-- Display a message indicating that the player has reached the maximum hat count
			print("You have reached the maximum number of hats.")
		else
			textBox.Text = ""
			script.Parent.TextButton.Text = "PUT IN A WORKING ID"
			wait(1)
			script.Parent.TextButton.Text = "Put on"
		end
	end
end)

-- Initial update of viewports
updateViewports()

-- Connect character added event to update viewports when the character changes
player.CharacterAdded:Connect(function(char)
	character = char
	setAssetIdForAccessories(character)
	updateViewports()
end)


for _, player in ipairs(game.Players:GetPlayers()) do
	if player.Character then
		setAssetIdForAccessories(player.Character)
	end
end
-- Connect character descendant added event to update viewports when an item is added
character.DescendantAdded:Connect(updateViewports)

-- Connect character descendant removing event to update viewports when an item is removed
character.DescendantRemoving:Connect(updateViewports)

Server:

-- Server Script
local MAX_HATS_COUNT = 100 -- Maximum number of hats allowed
local Players = game:GetService("Players")
local DataStore = game:GetService("DataStoreService"):GetDataStore("PlayerHatData")

local function loadPlayerHatData(player)
	local success, data = pcall(DataStore.GetAsync, DataStore, player.UserId)
	if success then
		return data or {}
	else
		warn("Failed to load hat data for player", player.Name, data)
		return {}
	end
end

local function savePlayerHatData(player, hatData)
	local success, error = pcall(DataStore.SetAsync, DataStore, player.UserId, hatData)
	if not success then
		warn("Failed to save hat data for player", player.Name, error)
	end
end

local function equipHat(player, assetId)
	local character = player.Character
	if character then
		local existingAccessories = {}
		for _, accessory in ipairs(character:GetChildren()) do
			if accessory:IsA("Accessory") then
				if accessory.Name == assetId then
					accessory:Destroy()
				else
					table.insert(existingAccessories, accessory.Name)
				end
			end
		end

		local hatCount = #existingAccessories
		if hatCount < MAX_HATS_COUNT then
			local success, asset = pcall(game:GetService("InsertService").LoadAsset, game:GetService("InsertService"), assetId)
			if success then
				for _, child in ipairs(asset:GetChildren()) do
					if child:IsA("Accessory") then
						-- Instead of directly adding, use a coroutine to add with a delay
						coroutine.wrap(function()
							child.Parent = character
							child:SetAttribute("AssetId", assetId)
							local objectvalue = Instance.new("StringValue")
							objectvalue.Value = assetId
							objectvalue.Parent = player:FindFirstChild("ASSETS")
						end)()
						wait(0.1)  -- Adjust this delay time as needed
					end
				end
				return true
			end
		end
	end
	return false
end

Players.PlayerAdded:Connect(function(player)
	local leaderstats = Instance.new("Folder", player)
	leaderstats.Name = "ASSETS"

	player.CharacterAdded:Connect(function(character)
		local hatData = loadPlayerHatData(player)
		for _, assetId in ipairs(hatData) do
			if not equipHat(player, assetId) then
				warn("Failed to equip hat for player", player.Name, assetId)
			end
		end
	end)
end)

local function onPlayerRemoving(player)
	local hatData = loadPlayerHatData(player)
	savePlayerHatData(player, hatData)
end

Players.PlayerRemoving:Connect(onPlayerRemoving)
Players.PlayerDied:Connect(onPlayerRemoving)

game.ReplicatedStorage.Getaccesory.OnServerEvent:Connect(function(player, assetId)
	if player then
		if equipHat(player, assetId) then
			game.ReplicatedStorage.Getaccesory:FireClient(player, true, assetId)
		else
			game.ReplicatedStorage.Getaccesory:FireClient(player, false, assetId)
		end
	end
end)


game.ReplicatedStorage.DeleteAccessory.OnServerEvent:Connect(function(player, assetId, Name)
	if player then
		local assetsFolder = player:FindFirstChild("ASSETS")
		if player.Character then
			if player.Character:FindFirstChild(Name) then
				player.Character:FindFirstChild(Name):Destroy()
			end
		end
		if assetsFolder then
			local children = assetsFolder:GetChildren()
			for _, child in ipairs(children) do
				if child.Value == assetId then
					child:Destroy()
				end
			end
		end
	end
end)

also i want to make it so the table does -1 when an accessory is deleted.
i got so for to write this script but now i am starting to get stuck.

please help me with this i am so thankful if you do!

I am not sure why it’s lagging for you, but I think there is too much loops, maybe you can store every accessory inside a table so you can loop through that table instead of looping through every object inside the character.

Make sure that the accessory is deleted on the server side.

1 Like

this is a good idea i will look into this, thank you!