Help with anti firetouchinterest exploit function script

Hi. Today and yesterday, I asked ChatGPT to write me a script that kicks exploiters for using firetouchinterest function in an executor. I have a friend to test it, so we began testing that anti cheat. He executed firetouchinterest in my game when he was 8 studs away the part which had this script inside and got kicked. The script has a function that disables that anti cheat for some amount of time (or permanently) if you’ll run _G.DisableAntiCheatDetection(game.Players.MyUsername, 100) - it disables ALL in-game anti cheat detections for 100 seconds.

So, the problem is that when I give my friend invincibility from anti cheat detections for 5 seconds, he fires firetouchinterest function instantly (when the timer still runs). It works like it should to - he doesn’t get kicked. Then, when the timer expires, he uses it again - and he doesn’t get any kick. The issue is that when the player uses firetouchinterest, it NEVER makes the game think that you left that part, so it won’t detect firetouchinterest until you’ll MANUALLY jump on the part and leave it. Because of leaving the part manually, it will make the game think that you left the part, because you actually left it.

Because of that issue, I’m asking y’all here. Can you fix the script and make it detect firetouchinterest even after the timer ends? I asked ChatGPT for 4 hours straight and he didn’t help me.

Here’s the script which I put in the part:

local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local Part = script.Parent

local MaxTouchDistance = 8
local TouchDebounce = 0.3

local LastTouchTimes = {}
local IsTouchingPart = {}
local AbuseDuringExempt = {}

local function IsExempt(Player)
	local State = _G.PlayerStates and _G.PlayerStates[Player]
	return State and type(State.ExemptUntil) == "number" and tick() <= State.ExemptUntil
end

Players.PlayerAdded:Connect(function(Player)
	LastTouchTimes[Player] = 0
	IsTouchingPart[Player] = false
	AbuseDuringExempt[Player] = false
	
	Player.CharacterAdded:Connect(function(Character)
		LastTouchTimes[Player] = 0
		IsTouchingPart[Player] = false
		AbuseDuringExempt[Player] = false
		
		local Humanoid = Character:WaitForChild("Humanoid")
		Humanoid.Died:Connect(function()
			LastTouchTimes[Player] = 0
			IsTouchingPart[Player] = false
			AbuseDuringExempt[Player] = false
		end)
	end)
end)

Players.PlayerRemoving:Connect(function(Player)
	LastTouchTimes[Player] = nil
	IsTouchingPart[Player] = nil
	AbuseDuringExempt[Player] = nil
end)

Part.Touched:Connect(function(Hit)
	local Player = Players:GetPlayerFromCharacter(Hit.Parent)
	if not Player then return end
	
	local Character = Player.Character
	if not Character then return end
	
	local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
	if not HumanoidRootPart then return end
	
	local CurrentTime = tick()
	if CurrentTime - (LastTouchTimes[Player] or 0) < TouchDebounce then return end
	LastTouchTimes[Player] = CurrentTime
	
	local Distance = (Part.Position - HumanoidRootPart.Position).Magnitude
	local Exempt = IsExempt(Player)
	
	if Distance <= MaxTouchDistance + Part.Size.Magnitude / 2 then
		-- Player physically touched the part
		IsTouchingPart[Player] = true
		-- Reset abuse flag if not exempt
		if not Exempt then
			AbuseDuringExempt[Player] = false
		end
		return
	end
	
	if Distance > MaxTouchDistance then
		if Exempt then
			-- Exploit during exemption: mark abuse and force leaving
			AbuseDuringExempt[Player] = true
			IsTouchingPart[Player] = false -- Force "left"
		else
			-- Kick immediately on abuse outside exemption
			if AbuseDuringExempt[Player] then
				Player:Kick("Exploits detected: Firetouchinterest (post-exemption).")
				return
			end
			
			Player:Kick("Exploits detected: Firetouchinterest.")
		end
	end
end)

RunService.Heartbeat:Connect(function()
	for _, Player in ipairs(Players:GetPlayers()) do
		local Character = Player.Character
		if not Character then continue end
		
		local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
		if not HumanoidRootPart then continue end
		
		local Distance = (Part.Position - HumanoidRootPart.Position).Magnitude
		local CloseEnough = Distance <= MaxTouchDistance + Part.Size.Magnitude / 2
		local Exempt = IsExempt(Player)
		
		-- Manual check: if script thinks player is touching but distance is now too far, mark left immediately
		if IsTouchingPart[Player] and not CloseEnough then
			IsTouchingPart[Player] = false
			
			-- Kick if abuse happened during exemption and exemption expired
			if not Exempt and AbuseDuringExempt[Player] then
				Player:Kick("Exploits detected: Firetouchinterest (left part after exemption).")
				AbuseDuringExempt[Player] = false
			end
		end
	end
end)

It works fine, but if you give yourself invincibility, INSTANTLY use firetouchinterest and then the timer expires - it won’t work, because it never registered that it left the part (until you touch and leave that part manually).

Here’s the script from ServerScriptService, where the _G.DisableAntiCheatDetection function comes from:

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

local MaxAllowedAirTime = 2
local MaxAllowedTeleportDistance = 10
local TeleportIntervalInSeconds = 0.1
local GracePeriod = 5
local FallingSpeedThreshold = -50
local PostLadderForgivenessTime = 3
local LadderCloseRange = 8
local GraceJumpTime = 1
local JumpCooldown = 0.2
local JumpValidationDelay = 0.1
local MinUngroundedTimeForKick = 0.2

local PlayerStates = {}
_G.PlayerStates = PlayerStates

local function IsTouchingTruss(HumanoidRootPart)
	if not HumanoidRootPart then return false end
	for _, Part in ipairs(HumanoidRootPart:GetTouchingParts()) do
		if Part:IsA("TrussPart") then return true end
	end
	return false
end

local function IsNearTruss(HumanoidRootPart)
	if not HumanoidRootPart then return false end
	for _, Part in ipairs(workspace:GetPartBoundsInRadius(HumanoidRootPart.Position, LadderCloseRange)) do
		if Part:IsA("TrussPart") then return true end
	end
	return false
end

local function IsGrounded(Humanoid)
	return Humanoid and Humanoid.FloorMaterial ~= Enum.Material.Air
end

local function IsFallingFast(HumanoidRootPart)
	return HumanoidRootPart and HumanoidRootPart.Velocity.Y < FallingSpeedThreshold
end

local function IsClimbing(Humanoid)
	return Humanoid and Humanoid:GetState() == Enum.HumanoidStateType.Climbing
end

local function InitPlayer(Player)
	PlayerStates[Player] = {
		LastPosition = nil,
		LastCheckingTime = 0,
		FlyingTime = 0,
		SpawnTime = tick(),
		LastLeftLadderTime = nil,
		LastJumpTime = 0,
		LastGrounded = true,
		ExemptUntil = 0,
		UngroundedStartTime = nil
	}
end

local function RemovePlayer(Player)
	PlayerStates[Player] = nil
end

_G.DisableAntiCheatDetection = function(Player, Duration)
	local State = PlayerStates[Player]
	if State then
		State.ExemptUntil = tick() + (Duration or math.huge)
	end
end

Players.PlayerAdded:Connect(function(Player)
	InitPlayer(Player)

	Player.CharacterAdded:Connect(function(Character)
		local Humanoid = Character:WaitForChild("Humanoid")
		local HumanoidRootPart = Character:WaitForChild("HumanoidRootPart")
		InitPlayer(Player)

		local State = PlayerStates[Player]
		State.SpawnTime = tick()
		State.LastJumpTime = 0
		State.LastGrounded = true
		State.UngroundedStartTime = nil

		Humanoid.StateChanged:Connect(function(_, NewState)
			local Now = tick()
			if NewState == Enum.HumanoidStateType.Jumping then
				if Now - State.SpawnTime < GraceJumpTime then return end
				if Now - State.LastJumpTime < JumpCooldown then return end
				State.LastJumpTime = Now

				task.delay(JumpValidationDelay, function()
					if not Player.Character then return end
					local CurrentHumanoid = Player.Character:FindFirstChildOfClass("Humanoid")
					local HRP = Player.Character:FindFirstChild("HumanoidRootPart")
					if not CurrentHumanoid or not HRP then return end

					local Grounded = IsGrounded(CurrentHumanoid)
					local TouchingLadder = IsTouchingTruss(HRP)
					local NearLadder = IsNearTruss(HRP)
					local Climbing = IsClimbing(CurrentHumanoid)
					local FullyGrounded = Grounded or TouchingLadder or NearLadder or Climbing

					local StateNow = PlayerStates[Player]
					if not StateNow then return end

					if tick() <= StateNow.ExemptUntil then return end

					local UngroundedTime = StateNow.UngroundedStartTime and (tick() - StateNow.UngroundedStartTime) or 0
					if not FullyGrounded and UngroundedTime >= MinUngroundedTimeForKick then
						Player:Kick("Exploits detected: Infinite jump.")
					end
				end)
			end
		end)
	end)
end)

Players.PlayerRemoving:Connect(RemovePlayer)

RunService.Heartbeat:Connect(function(Dt)
	local Now = tick()

	for _, Player in ipairs(Players:GetPlayers()) do
		local State = PlayerStates[Player]
		if not State then continue end

		local Character = Player.Character
		if not Character then continue end

		local HumanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
		local Humanoid = Character:FindFirstChildOfClass("Humanoid")
		if not HumanoidRootPart or not Humanoid then continue end

		local Position = HumanoidRootPart.Position
		local Grounded = IsGrounded(Humanoid)
		local TouchingLadder = IsTouchingTruss(HumanoidRootPart)
		local NearLadder = IsNearTruss(HumanoidRootPart)
		local Climbing = IsClimbing(Humanoid)
		local FallingFast = IsFallingFast(HumanoidRootPart)
		local Exempt = Now <= (State.ExemptUntil or 0)

		if Exempt then
			State.LastPosition = Position
			State.LastCheckingTime = Now
			State.FlyingTime = 0
			State.LastLeftLadderTime = nil
			State.LastGrounded = true
			State.UngroundedStartTime = nil
			continue
		end

		local FullyGrounded = Grounded or TouchingLadder or NearLadder or Climbing

		if FullyGrounded then
			State.UngroundedStartTime = nil
		else
			if not State.UngroundedStartTime then
				State.UngroundedStartTime = Now
			end
		end

		State.LastGrounded = FullyGrounded

		if Now - State.SpawnTime < GracePeriod then
			State.LastPosition = Position
			State.LastCheckingTime = Now
			State.FlyingTime = 0
			continue
		end

		local RecentlyLeftLadder = State.LastLeftLadderTime and (Now - State.LastLeftLadderTime <= PostLadderForgivenessTime)

		if not TouchingLadder and not NearLadder then
			if not State.LastLeftLadderTime then
				State.LastLeftLadderTime = Now
			end
		else
			State.LastLeftLadderTime = nil
		end

		if TouchingLadder or NearLadder or Climbing then
			State.FlyingTime = 0
		else
			if not Grounded and not FallingFast then
				State.FlyingTime += Dt
				if State.FlyingTime >= MaxAllowedAirTime then
					Player:Kick("Exploits detected: Flying.")
					continue
				end
			else
				State.FlyingTime = 0
			end
		end

		if State.LastPosition and Now - State.LastCheckingTime >= TeleportIntervalInSeconds then
			local Delta = Position - State.LastPosition
			local Horizontal = Vector3.new(Delta.X, 0, Delta.Z).Magnitude
			local Vertical = math.abs(Delta.Y)

			local ClosestLadder = math.huge
			for _, Part in ipairs(workspace:GetDescendants()) do
				if Part:IsA("TrussPart") then
					local Dist = (Position - Part.Position).Magnitude
					if Dist < ClosestLadder then ClosestLadder = Dist end
				end
			end

			local LadderForgiveness = RecentlyLeftLadder and ClosestLadder <= LadderCloseRange and Horizontal <= MaxAllowedTeleportDistance * 2 and Vertical <= MaxAllowedTeleportDistance * 2

			if Horizontal > MaxAllowedTeleportDistance and not FallingFast and not LadderForgiveness then
				Player:Kick("Exploits detected: Teleporting.")
				continue
			end

			State.LastPosition = Position
			State.LastCheckingTime = Now
		elseif not State.LastPosition then
			State.LastPosition = Position
			State.LastCheckingTime = Now
		end
	end
end)

Can anyone fix that issue? Firetouchinterest never makes the game’s logic think that you left the part, so if you used firetouchinterest while you’re invincible from anti cheat detections and then the timer expires, it won’t kick you because it thinks that you’re still standing on the part.