Detect CoreGui, External UI Method

My AC Server: Discord

This method detects when a player scrolls the mouse wheel but the camera zoom does not change. If the cursor is not over a scrollable GUI element, the behavior is flagged as suspicious.

Source:

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

local LocalPlayer = Players.LocalPlayer
local Camera = workspace.CurrentCamera

local scrollWindowStart = 0
local scrollCount = 0


local function findInteractiveGui(gui)
	while gui do
		if (gui:IsA("ScrollingFrame") or gui:IsA("TextBox") or gui:IsA("TextButton")) and gui.Visible then
			return gui
		end
		gui = gui.Parent
	end
end


local function isMouseOverSurfaceGui()
	local mouse = UserInputService:GetMouseLocation() - GuiService:GetGuiInset()
	local ray = Camera:ScreenPointToRay(mouse.X, mouse.Y)

	local params = RaycastParams.new()
	params.FilterType = Enum.RaycastFilterType.Blacklist

	local character = LocalPlayer.Character
	if character then
		params.FilterDescendantsInstances = {character}
	end

	local result = workspace:Raycast(ray.Origin, ray.Direction * 1000, params)
	if not result then
		return false
	end

	local part = result.Instance

	for _, surface in ipairs(part:GetDescendants()) do
		if surface:IsA("SurfaceGui") and surface.Enabled then
			for _, obj in ipairs(surface:GetDescendants()) do
				if obj:IsA("ScrollingFrame") and obj.Visible then
					return true, obj
				end
			end
		end
	end

	return false
end


local function isMouseInUI()
	local mouse = UserInputService:GetMouseLocation() - GuiService:GetGuiInset()
	local playerGui = LocalPlayer:WaitForChild("PlayerGui")

	for _, gui in ipairs(playerGui:GetGuiObjectsAtPosition(mouse.X, mouse.Y)) do
		local found = findInteractiveGui(gui)
		if found then
			return true, found
		end
	end

	return isMouseOverSurfaceGui()
end


local function getCameraDistance()
	local character = LocalPlayer.Character
	if not character then return 0 end

	local head = character:FindFirstChild("Head")
	if not head then return 0 end

	return (Camera.CFrame.Position - head.Position).Magnitude
end


local function isCameraBlocked()
	local character = LocalPlayer.Character
	if not character then return false end

	local head = character:FindFirstChild("Head")
	if not head then return false end

	local params = RaycastParams.new()
	params.FilterDescendantsInstances = {character}
	params.FilterType = Enum.RaycastFilterType.Blacklist

	local direction = Camera.CFrame.Position - head.Position
	return workspace:Raycast(head.Position, direction, params) ~= nil
end


local function isShiftLock()
	return UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter
end


local function onMouseWheel(input)
	if input.UserInputType ~= Enum.UserInputType.MouseWheel then
		return
	end

	if isMouseInUI() or isShiftLock() or isCameraBlocked() then
		return
	end

	local before = getCameraDistance()

	RunService.RenderStepped:Wait()

	local maxDelta = 0
	local totalDelta = 0

	for _ = 1, 5 do
		RunService.RenderStepped:Wait()

		local after = getCameraDistance()
		local delta = after - before

		totalDelta += delta
		maxDelta = math.max(maxDelta, math.abs(delta))

		before = after
	end

	local minZoom = LocalPlayer.CameraMinZoomDistance
	local maxZoom = LocalPlayer.CameraMaxZoomDistance
	local distance = getCameraDistance()

	if distance <= minZoom + 0.2 or distance >= maxZoom - 0.2 then
		return
	end

	local threshold = math.max(0.02, distance * 0.003)

	if maxDelta < threshold and math.abs(totalDelta) < threshold then
		local now = tick()

		if now - scrollWindowStart > 0.5 then
			scrollWindowStart = now
			scrollCount = 0
		end

		scrollCount += 1

		if scrollCount >= 5 then
			warn("detected!")
		end
	end
end


UserInputService.InputChanged:Connect(onMouseWheel)

Showcase: Watch Roblox-2026-05-19T18_04_40.682Z | Streamable

Todo:

Check Roblox Menu Openning: It will disable all input connection so u dont have to
Check Console Openning
Check Roblox Modals (PromptPurchase, PromptGameInvite,…)
Check MicroProfiler, Performance Stats (maybe whitelist topbar? dont detect if mouse pos is in topbar)
Custom ChatFrame & PlayerList Frame to put it in PlayerGui

7 Likes

From your current code, you must whitelist any CoreGui that is not in the EscMenu, such as

  • Party
  • Inventory
  • Emotes
  • Music (draggable)
  • Switch avatar
  • Self view
    You have mentioned others that also need to be whitelisted via your current Todo list but ones i mentioned were missing.

Video for reference

Also technically i assume this can be easily bypassed but attempting is good i suppose!

ty for the information, i didn’t know that Roblox had added so many things like that T.T

so are you gonna white list them

How do you whitelist them? I’m trying to figure it out but I cant.