Issue removing hats from table

I’m working on a Raycasting gun for my game, and im using FindPartOnRayWithIgnoreList to add a blacklist to accessories, however, when I attempt to remove the hats from the array when the character dies, none of the hats end up getting removed

local function findInTable(tbl, element)
	for _, v in ipairs(tbl) do
		if (rawequal(v, element)) then
			return true
		end
	end
	return false
end

local function removeValuesFromArray(array, value)
	for i = #array, 1, -1 do
		if array[i] == value then
			table.remove(array, i)
		end
	end
end

local function gethats(p)
	for na,ni in pairs(p:GetChildren()) do
		if (ni:IsA("Accoutrement") or ni:IsA("Tool") or ni:IsA("Accessory")) and ni:FindFirstChild('Handle') then
			table.insert(hatlist, ni.Handle)
		end
		gethats(ni)
	end
end

local function removehats(p)
	for na,ni in pairs(p:GetChildren()) do
		if (ni:IsA("Accoutrement") or ni:IsA("Tool") or ni:IsA("Accessory")) and ni:FindFirstChild('Handle') then
			if findInTable(hatlist, ni.Handle) then
				removeValuesFromArray(hatlist, ni.Handle)
			end
		end
		removehats(ni)
	end
end

local function PlayerAdded(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:FindFirstChild("Humanoid")
		gethats(player)
	end)
	player.CharacterRemoving:Connect(function(character)
		removehats(player)
	end)
end

game.Players.PlayerAdded:Connect(PlayerAdded)

gethats(workspace)

for i,v in next,game.Players:GetPlayers() do
	PlayerAdded(v)
end

The script is a ServerScript parented to a tool, If anyone could possible help me figure out the issue, that would help me a lot

Ok so I read through the script and cannot see anything obviously wrong.
Can you do some prints to let us know where the failure is occurring?

I honestly have no clue,

local function removehats(p)
	for na,ni in pairs(p:GetChildren()) do
		if (ni:IsA("Accoutrement") or ni:IsA("Accessory")) and ni:FindFirstChild('Handle') then
			if findInTable(hatlist, ni.Handle) then
				print("FOUND HATS BRO WOOP")
				removeValuesFromArray(hatlist, ni.Handle)
			end
		end
		removehats(ni)
	end
end

“FOUND HATS BRO WOOP” prints when the character respawns, but doesn’t actually remove anything from the array.

Is there a particular reason you’re trying to remove hats when the player dies?

Is there a particular reason you’re trying to remove hats when the player dies?

If I don’t remove hats from the table when the player dies, the next time they respawn it will add hats aswell, causing a memory leak.

Then why don’t you call removehats when the character is added as well? That way you can ensure it is 100% removed.

local function PlayerAdded(player)
	player.CharacterAdded:Connect(function(character)
		local humanoid = character:FindFirstChild("Humanoid")
        removehats(player)
		gethats(player)
	end)
	player.CharacterRemoving:Connect(function(character)
		removehats(player)
	end)
end

Or better yet just remove the CharacterRemoving event entirely.

When I do this, the first time the character gets added the table is empty, when I reset again, the table has my hats, the next time I reset, the table has doubled. Im not sure what the issue even is.

You call both gethats and removehats with the player object, not the players character. The hats are a child of the players character, not the player.

1 Like

Changing player to character makes no change whatsoever with the output.

My bad, there is a difference, but it still doesnt delete from the table, which is my issue

When you were using player all you were adding to the table where the tools in the players backpack, not the hats in the players character. removeValuesFromArray seems like a very simple function that looks like it should work, how do you know that the problem doesn’t lie elsewhere? Have you tried printing the length of the array at the start and end of the function?

After printing in multiple places to see where the function stopped

local function removehats(p)
	for na,ni in pairs(p:GetChildren()) do
		if (ni:IsA("Accoutrement") or ni:IsA("Accessory") or ni:IsA("Tool")) and ni:FindFirstChild('Handle') then
			if findInTable(hatlist, ni.Handle) then -- It seems to stop here, and not run anything after it
				print("FOUND HATS BRO WOOP")
				removeValuesFromArray(hatlist, ni.Handle)
			end
		end
		removehats(ni)
	end
end
local function removehats(p)
	for na,ni in pairs(p:GetChildren()) do
		if (ni:IsA("Accoutrement") or ni:IsA("Accessory")) and ni:FindFirstChild('Handle') then
			if findInTable(hatlist, ni.Handle) then
				--removeValuesFromArray(hatlist, ni.Handle)
				for i = #hatlist, 1, -1 do
					if hatlist[i] == ni.Handle then
						table.remove(hatlist, i)
						print("Should have worked..?")
					end
				end
			end
		end
		removehats(ni)
	end
end

“Should have worked…?” prints, but nothing gets removed from the table.

It seems to be an issue with removing the value from the table itself, ive been trying for over 6 hours straight and cannot find a reason why this shouldn’t work

local function PlayerAdded(player)
	player.CharacterAdded:Connect(function(character)
		wait()
		gethats(character)
	end)
	player.CharacterRemoving:Connect(function(character)
		for i, value in pairs(hatlist) do
			if value:IsDescendantOf(character) then
				table.remove(hatlist, i)  -- issue is right here
			end
		end
	end)
end

I’m not sure why nothing is being removed from the table.

There is a gross amount of overcomplication and unnecessary work being performed by this code. Use the function Humanoid.GetAccessories instead. This will return all the accessories a player is currently wearing in a nice workable array format.

GetAccessories will return instances of both the Hat and Accessory class, so you don’t have to worry about legacy hats that haven’t been converted to Accessories or checking for the Accoutrement class to get hats or whatever.

With the array returned from this function, you can push items into an ignore list or use it as the ignore list itself and as such, any hats worn by the player will be ignored during the raycast. If you aren’t looking for that, well, you still have a nice table to iterate over.

There is a gross amount of overcomplication and unnecessary work being performed by this code. Use the function Humanoid.GetAccessories instead. This will return all the accessories a player is currently wearing in a nice workable array format.
I will try to better my code using this method, but this still doesn’t solve my issue, Im able to get the hats into the table, but not remove them, and thats what my issue is.

For the sake of clarification, could you explain what exactly you are trying to do - what is this code being used for? Why do you need to remove hats from a table after collecting them into a table?

I am scripting a gun using FindPartOnRayWithIgnoreList and I want to add everybodys hats to the ignore list, I do this by using a CharacterAdded function to get when a character is conected, and I add all the hats from the character to this table, once the character is removed (If someone changes their outfit) the hats they had equipped will be removed from the table. Another reason the hats are removed from the table is that when theres around 25 players in the server, and are constantly dying, the table will just keep filling up, causing a memory leak.

I’ve got an easier fix that involves still less work with high efficiency and doesn’t need to be parented to any tool. Instead, only one script should be responsible for identifying and collecting hats. This way, all your tools can just reference this collection instead of doing any heavy lifting themselves, up to managing hats and stressing the server. You don’t have to rely on any weird code or tricks either.

Wait… did I say collection? Yes I did! CollectionService!

Have only one server script be responsible for collecting the hats a player is wearing using CollectionService. We will fetch the hats a player is using still with GetAccessories and tag them all with a Hat tag. This will create a collection with player hats.

local Players = game:GetService("Players")
local CollectionService = game:GetService("CollectionService")

local function playerAdded(player)
    -- CharacterAppearanceLoaded may be better in this scenario, provided it was
    -- fixed to fire for blank appearances as well.
    player.CharacterAdded:Connect(function (character)
        local humanoid = character:WaitForChild("Humanoid")
        for _, hat in ipairs(humanoid:GetAccessories()) do
            CollectionService:AddTag(hat, "Hat")
        end
    end)
end

Players.PlayerAdded:Connect(playerAdded)
for _, player in ipairs(Players:GetPlayers()) do
    playerAdded(player)
end

With this, you now have a single server script that is determining what hats a player is using and creating a collection of every single player’s hats. You now no longer have to worry about fixing any complex code or having every single tool server-side do this. Throw that code right out!

Worried about memory leaks? Worry no more: tags are serialised and attached to instances, so garbage collection and memory management is all taken care of and done by the backend. When a player dies and their character is being removed, hat instances are destroyed. Destroyed instances having a tag will have that tag removed from the instance and your collection automatically filters out those instances, so it will always only have the hats of spawned character models.

Now, for your tool server side, there is just one thing you need to do: fetch that collection to use for your ignore list. Two simple lines or less and you are good to go.

local CollectionService = game:GetService("CollectionService")
local HatList = CollectionService:GetTagged("Hat")

And like that, you’ve got yourself this entire problem solved in a few lines. The engine takes care of all the heavy lifting and worries for you, so you just have to worry about using the API right. No more stress over weird code or trying to apply ugly tricks to have something work.

:smile:

6 Likes

Wow this actually worked, thank you!