Hat dropping bug

My friend asked me if I could create a simple feature where upon a player’s death, their hats…

  • become CanCollide = true so they don’t fall through the map,
  • linger on the map for a little bit even after the died player respawns, and
  • are able to be picked up by other players during this lingering time.

Well that’s pretty easy to do. But there’s a bug when parenting the hats to workspace. For some reason, one hat will necessarily stay in the character.

https://gyazo.com/c4f0c42ea19047d7abf227e34a4d6561

In this gif, pay close attention to the selected hats in the Explorer when the character dies. You’ll see that one appears to stay behind. If you look at the output, however, it doesn’t seem to show an issue. It’s printing the GetFullName() immediately after parenting each hat to workspace.

This makes me think there’s some sort of bug where one hat is immediately re-parented to the character. It also seems that the collision property gets reverted as well. I tested doing this while the humanoid was alive as well, and the bug was NOT there.

Here's the code if you think that's where the issue lies.

I made a server-side script in StarterCharacterScripts that has a listener for the humanoid’s death. It gets all the accessories, makes their Handles CanCollide = true, parents the accessories themselves to workspace, and copies a pickup/debris script into them.

Path in DataModel:

image

DropHats code:

print("--- New character ---")
local Character = script.Parent

Character:WaitForChild("Humanoid").Died:Connect(
	function()
		local List = Character:GetChildren()
		for i = 1, #List do
			local Item = List[i]
			if Item and Item:IsA("Accessory") then
				Item.Parent = workspace
				print(Item:GetFullName(),"- attempted parent change")
				Item.Handle.CanCollide = true
				
				local Pickup_Debris = script.Pickup:Clone() -- Allows a dropped hat to be picked up, and also cleans up the dropped hat after 20 seconds if not picked up
				Pickup_Debris.Parent = Item
				Pickup_Debris.Disabled = false
			end
		end
	end
)

I put the pickup/debris code in a separate script because the above script is deleted when the character respawns. This makes it so the touch connection and cleanup wait don’t terminate then.

Pickup code:

local Accessory = script.Parent

Accessory.Handle.Touched:Connect(
	function(OtherPart)
		local HitHumanoid = OtherPart.Parent:FindFirstChildOfClass("Humanoid")
		if HitHumanoid and HitHumanoid.Health > 0 then
			print(Accessory:GetFullName(), "picked up")
			HitHumanoid:AddAccessory(Accessory)
			Accessory.Handle.CanCollide = false
			script:Destroy() -- Delete this script when the hat is picked up
		end
	end
)

wait(20)

if script.Parent then -- If the script hasn't been deleted after twenty seconds (how could this ever be nil?)...
	script.Parent:Destroy() -- ... then delete the hat
end

I also have a LocalScript in StarterPlayerScripts that kills the LocalPlayer when E is pressed and deletes the LocalPlayer’s hats when = is pressed. This is purely for testing purposes however.

game:GetService("UserInputService").InputBegan:Connect(
	function(InputItem)
		if InputItem.UserInputType == Enum.UserInputType.Keyboard then
			if InputItem.KeyCode == Enum.KeyCode.E then
				print("LocalPlayer died")
				game.Players.LocalPlayer.Character.Humanoid.Health=0
			elseif InputItem.KeyCode == Enum.KeyCode.Equals then
				print("LocalPlayer deleted worn hats")
				for _,Hat in pairs(game.Players.LocalPlayer.Character.Humanoid:GetAccessories()) do
					Hat:Destroy()
				end
			end
		end
	end
)

And a .rbxl file to test with for yourself:
PickupDiedPlayersHats.rbxl (22.3 KB)

I even tried having the script just clone each hat and delete the original. The issue didn’t change.

1 Like

One thing I noticed was that the 1 hat I had was always getting picked back up when I died. Not sure why. I’m still debugging

I don’t think this is the case. I specfically coded it so that if a humanoid is dead, it can’t pick up hats. Screenshot your output after respawning though.

1 Like

I think I found a solution, but I’ll make sure it’s good before I post it. I’ll edit this post.

I got it! Here’s what I did. Basically I cloned the handle then added a new accessory:

--DropHats Script
print("--- New character ---")
local char = script.Parent

char:WaitForChild("Humanoid").Died:Connect(function()
	for _,v in pairs(char:GetChildren()) do
		if v:IsA("Accessory") then
			local name = v.Name
			local hat = Instance.new("Accessory", workspace)
			hat.Name = name
			hat.AttachmentForward = v.AttachmentForward
			hat.AttachmentPos = v.AttachmentPos
			hat.AttachmentRight = v.AttachmentRight
			hat.AttachmentUp = v.AttachmentUp
			v.Handle.CanCollide = true
			v.Handle:FindFirstChild("HairAttachment"):remove()
			local debris = script.Pickup:Clone()
			debris.Parent = v.Handle
			debris.Disabled = false
			v.Handle.Parent = hat
			
			
		end
	end
end)
--Pickup Script
local Accessory = script.Parent

Accessory.Touched:Connect(
	function(OtherPart)
		local HitHumanoid = OtherPart.Parent:FindFirstChildOfClass("Humanoid")
		if HitHumanoid and HitHumanoid.Health > 0 then
			print(Accessory:GetFullName(), "picked up")
			HitHumanoid:AddAccessory(Accessory)
			Accessory.CanCollide = false
			script:Destroy() -- Delete this script when the hat is picked up
		end
	end
)

wait(20)

if script.Parent then -- If the script hasn't been deleted after twenty seconds (how could this ever be nil?) ...
	script.Parent.Parent:Destroy() -- ... then delete the hat
end

My bad! Change the DropHats script to this:

print("--- New character ---")
local char = script.Parent

char:WaitForChild("Humanoid").Died:Connect(function()
	for _,v in pairs(char:GetChildren()) do
		if v:IsA("Accessory") then
			local name = v.Name
			local hat = Instance.new("Accessory", workspace)
			hat.Name = name
			hat.AttachmentForward = v.AttachmentForward
			hat.AttachmentPos = v.AttachmentPos
			hat.AttachmentRight = v.AttachmentRight
			hat.AttachmentUp = v.AttachmentUp
			v.Handle.CanCollide = true
			local debris = script.Pickup:Clone()
			debris.Parent = v.Handle
			debris.Disabled = false
			v.Handle.Parent = hat
			
			
		end
	end
end)

The problem with the first one was that when you died twice and then tried to pick up the hat it wouldn’t pick up. If this works, please mark as solution! It helps

Accessories have built-in logic that automatically equips the accessory when a player touches it. That’s probably what’s happening here. The engine automatically adds a TouchInterest when you re-parent it to the workspace. I added Item.Handle.TouchInterest:Destroy() after your Item.Handle.CanCollide = true line and it seems to have fixed it.

Well this is interesting. Why do Humanoids always have the quirkiest behavior? I wish there was at least some documentation for stuff like this.

While removing the TouchInterest does fix the issue of the accessory being re-equipped after death, it also makes it so future touch connections don’t work. I’m opting instead for a cross between IGOTHISLOL’s solution and this - just parenting the Handle itself to workspace while deleting the accessory, but then making it so when touched, the Handle is put in a new accessory and equipped.

2 Likes