.Touched firing multiple times for no reason

I’m working on a sandbox-style tycoon game and one of the things you can buy are teleporters which will teleport you to another random teleporter pad when touched. I’m trying to add a teleport cooldown for each individual teleporter so that the player doesn’t get sent directly back the moment the touch the new teleporter they just got sent to. I had a Universal cooldown which prevent that player from being teleported by all teleporters for a short period of time, and that worked perfectly. What I would like to do however, is have a cooldown per teleporter so that theoretically a player could leave one teleporter and instantly hope on another without having to wait for the cooldown. I’ve tried having a global dictionary that keeps track of all the cooldowns and I’ve tried having cooldowns in the scope of each teleporter function that are triggered by bindable events/functions which all made perfect sense in my head. The only issue is that for some reason with these methods, the .Touched event will sometimes fire multiple times causing many issues.

functions["Teleporter"] = function(teleporter)
	local pad = teleporter:WaitForChild("Pad")
	local objects = teleporter:FindFirstAncestor("Plot").Objects
	local possibleTeleportLocations = {}
	local maid = newMaid()
	local activeColor = pad.Color
	local inactiveColor = Color3.new(activeColor.R * 0.5, activeColor.G * 0.5, activeColor.B * 0.5)
	local active = false
	
	local function checkActive()
		active = #possibleTeleportLocations > 0
		if active then
			pad.Color = activeColor
			pad.Material = Enum.Material.Neon
		else
			pad.Color = inactiveColor
			pad.Material = Enum.Material.SmoothPlastic
		end
	end
	
	local function addTeleporter(newTeleporter)
		if newTeleporter.Name == teleporter.Name and newTeleporter ~= teleporter and not possibleTeleportLocations[newTeleporter] then
			table.insert(possibleTeleportLocations, newTeleporter)
			checkActive()
			maid[newTeleporter] = newTeleporter.AncestryChanged:Connect(function()
				if not newTeleporter:IsDescendantOf(game) then
					maid[newTeleporter] = nil
					table.remove(possibleTeleportLocations, table.find(possibleTeleportLocations, newTeleporter))
					checkActive()
				end
			end)
		end
	end
	
	maid:GiveTask(objects.ChildAdded:Connect(addTeleporter))
	
	for _, newTeleporter in pairs(objects:GetChildren()) do
		addTeleporter(newTeleporter)
	end
	
	local debounce = true
	
	pad.Touched:Connect(function(hit)
		if active and debounce then
			local character = hit:FindFirstAncestorOfClass("Model")
			if character and not activeTeleporters[teleporter][character] then
				local rootPart = character:FindFirstChild("HumanoidRootPart")
				local humanoid = character:FindFirstChild("Humanoid")
				if rootPart and humanoid and humanoid.Health > 0 then
					debounce = false
					local chosenTeleporter = possibleTeleportLocations[math.random(1, #possibleTeleportLocations)]
					activeTeleporters[chosenTeleporter][character] = true
					local newPosition = chosenTeleporter.Pad.Position
					local newCFrame = CFrame.new(newPosition.X, newPosition.Y + (rootPart.Position.Y - pad.Position.Y), newPosition.Z) * CFrame.Angles(rootPart.CFrame:ToOrientation())
					character:SetPrimaryPartCFrame(newCFrame)
					debounce = true
					
					task.wait(2)
					
					if activeTeleporters[chosenTeleporter] then
						activeTeleporters[chosenTeleporter][character] = nil
					end
				end
			end
		end
	end)
	
	teleporter.AncestryChanged:Connect(function()
		if not teleporter:IsDescendantOf(game) then
			for i = 1, #possibleTeleportLocations do
				table.remove(possibleTeleportLocations, 1)
			end
			maid:DoCleaning()
			--[[for character, a in pairs(activeTeleporters[teleporter]) do
				activeTeleporters[teleporter][character] = nil
			end]]
			activeTeleporters[teleporter] = nil
		end
	end)
	
	checkActive()
	
	activeTeleporters[teleporter] = {}
end

There’s all the code that handles the teleporter but I’m fairly certain the issue is within the “pad.Touched” function. Any help with this would be greatly appreciated.

2 Likes

Add a debounce.

1 Like

There already is, but I get the same behavior with or without it.

3 Likes

What you would want to do is something like:

pad.Touched:Connect(function(hit)
  if active and not debounce then
-- code here
 end

• I would change the local debounce = true variable located above pad.Touched, to local debounce = false because it is later set to true in the script after the player is teleported.

• Also move debounce = false located below: if rootPart and humanoid and humanoid.Health > 0 then to after task.wait(2)

I Hope this helped.

1 Like

A debounce works, sure, but the ideal method, and much preferred by experienced devs, is disconnecting and later reconnecting the events.

local Part = ...
local TouchEvent,UntouchEvent
local Touched,Untouched
Touched = function(Hit)
  print(("%s Touched the Part!"):format(Hit and Hit.Name or "Unknown"))
  TouchEvent:Disconnect()
  UntouchEvent = Part.TouchEnded:Connect(Untouched)
end
Untouched = function(Hit)
  print(("%s Stopped touching the part!"):format(Hit and Hit.Name or "Unknown"))
  UntouchEvent:Disconnect()
  TouchEvent = Part.Touched:Connect(Touched)
end
Part.Touched:Connect(Touched)
-- If I recall correctly this is how it's done.

I think the problem is that you have too much going on after the touch is fired and before you set debounce = false. Move that up right after: if active and debounce then

I thought there was no guarantee that both touched and untouched would be fired at the appropriate times? Wouldn’t this lead to them being disconnected if the untouched event doesn’t fire, then them having to “untouch” it again for it to work properly?

I’m fairly positive it shouldn’t, but there’s always a questionable chance I suppose. I’ve used it before and found it to be fairly effective in most scenarios, so it’s worth a shot (especially considering this isn’t something such as a sword.)

On the other hand, the OP could also simply do something along the lines of:

local Part = ...
local DebounceTime = 5
local TouchEvent, Touched
local Cycle = function(...)
  TouchEvent = Part.Touched:Connect(Touched)
end
Touched = function(Hit)
  print(("%s touched the part!"):format(Hit and Hit.Name or "N/A"))
  task.delay(DebounceTime, Cycle)
  TouchEvent:Disconnect()
end
TouchEvent = Part.Touched:Connect(Touched)

That should be plenty for a mockup, and it’s rather easy to see the concept behind it.

2 Likes