EzBan Obby Anti-Cheat (Server-Sided)

Welcome to EzBan Obby Anti-Cheat!

Table of contents:

Features


Multiple speed hack features
  • Flat Plane position detection (Moving on XZ axes combined; can be customized)
  • XYZ Position detection
  • WalkSpeed detection
  • JumpHeight detection
Anti-area-enter feature
  • Automatically punish hackers when they enter areas they shouldn’t
  • Uses parts so you can easily visualize areas that you created
Timed-area feature
  • Automatically punish hackers when they get from one area to another area too quickly
  • Automatically punish hackers if they skip an area (teleport hacking)
  • Uses parts so you can easily visualize areas that you created
Admin commands
  • 4 different commands which can be used in roblox chat by admins
    • /Ban - Bans a player
    • /Unban - Unbans a player
    • /BanNotInGame - Bans a player who isn’t in the game as you
    • /AddAdmin - Adds an admin who can use all commands besides this one
  • “Super admins” that can use /AddAdmin - allows secure command use
Easy to modify
  • There are currently 15 configuration options
    • Enabled Features - Always have the option of being able to disable features if you don’t want them
    • Teleporting Value Folder - Don’t let players get punished for using a in-game teleport feature
    • Custom Detection Messages - Notify players with custom messages if they area detected of hacking
    • Punishment Type - 3 different ways to punish hackers
    • Ban Length - Control the length of bans
    • Allowed Difference - Don’t let players get accidentally punished for being laggy
    • Expected Player Speed - Expected player WalkSpeed
    • Expected Player Jump Height - Expected player JumpHeight
    • Super Admin UserIds - Add super admins
    • Update Speed - Control how often the anti-cheat will check for hacking
    • Area Touching Update Speed - Control how often the anti-cheat will check for hacking for illegal/timed areas
    • Max Free Fall - Control the maximum amount of time a player can free fall for before being punished
    • Minimum Time Between Areas - Control the minimum time between timed areas
    • Backwards Enabled - Allow players to go backwards in areas
    • Flat Plane - Control what axis are checked for flat plane detection
Fully server sided
  • Hackers won’t be able to edit or even view any part of the anti-cheat
Uses Roblox's Ban API
  • Some benefits include:
    • Alt account detection
    • See specific reasoning of bans in your game page in creator hub
  • For more read: Click me!

Open-Source Code


You can review the code here if you want or you can view it in roblox studio through the download link

Hierarchy

Hierarchy for the anti-cheat:

Screen Shot 2024-08-15 at 5.44.23 PM

Code
"EzBanAntiCheat" code
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------ Configuration options in "EzBanConfiguration" script ------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------

local ServerStorage = game:GetService("ServerStorage")
local ServerScriptService = game:GetService("ServerScriptService")
local TextService = game:GetService("TextService")
local TextChatService = game:GetService("TextChatService")
local DataStoreService = game:GetService("DataStoreService")
local PlayerService = game:GetService("Players")

local antiCheatScripts = ServerScriptService:FindFirstChild("EzBanAntiCheatScripts")
local antiCheatObjects = ServerStorage:FindFirstChild("EzBanAntiCheatObjects")

local startup = require(antiCheatScripts:FindFirstChild("EzBanStartup"))

local adminDataStore = DataStoreService:GetDataStore("AdminDataStore")

local illegalParts = antiCheatObjects:FindFirstChild("Illegal area parts"):GetChildren()
local timedParts = antiCheatObjects:FindFirstChild("Timed area parts"):GetChildren()

local disableAdminDisregardForTesting = false

local printDebounceLength = 10

warn("EzBan Obby Anti-Cheat is online")

--[[ ConfigurationOptions:

[1] = superAdminUserIds
[2] = customDetectionMessages
[3] = punishmentType
[4] = banLength
[5] = maxFreeFall
[6] = updateSpeed
[7] = minimumTimeBetweenAreas
[8] = backwardsEnabled
[9] = expectedPlayerSpeed
[10] = expectedPlayerJumpHeight
[11] = allowedDifference
[12] = teleportingValue
[13] = flatPlane
[14] = enabledFeatures
[15] = areaTouchingUpdateSpeed

]]
	
local superAdminUserIds
local customDetectionMessages
local punishmentType
local banLength
local maxFreeFall
local updateSpeed
local minimumTimeBetweenAreas
local backwardsEnabled
local expectedPlayerSpeed
local expectedPlayerJumpHeight
local allowedDifference
local teleportingValueFolder
local flatPlane
local enabledFeatures
local areaTouchingUpdateSpeed

local configLoadSuccess = pcall(function()
	local configOptions = startup.GetConfig()

	superAdminUserIds = configOptions[1][2]
	customDetectionMessages = configOptions[2][2]
	punishmentType = configOptions[3][2]
	banLength = configOptions[4][2]
	maxFreeFall = configOptions[5][2]
	updateSpeed = configOptions[6][2]
	minimumTimeBetweenAreas = configOptions[7][2]
	backwardsEnabled = configOptions[8][2]
	expectedPlayerSpeed = configOptions[9][2]
	expectedPlayerJumpHeight = configOptions[10][2]
	allowedDifference = configOptions[11][2]
	teleportingValueFolder = configOptions[12][2]
	flatPlane = configOptions[13][2]
	enabledFeatures = configOptions[14][2]
	areaTouchingUpdateSpeed = configOptions[15][2]
end)

if not configLoadSuccess then
	error("Failed to load configuration options to EZ Ban Anti-Cheat!")
end

local recentlyPrintedWarningsUserIDs = {}

local adminInfo = {}

adminInfo.playerUserIdIsAdmin = {}
adminInfo.playerUserIdIsSuperAdmin = {}

function adminInfo:getSuperAdminStatus(player:Player)
	if table.find(self.playerUserIdIsAdmin, player.UserId) then
		return true
	else
		return false
	end
end

function adminInfo:getAdminStatus(player:Player)
	if table.find(self.playerUserIdIsSuperAdmin, player.UserId) then
		return true
	else
		return false
	end
end

function adminInfo:updateInfo(player:Player)
	local userID = player.UserId
	
	-- Admin status --
	
	local adminStatus = false
	
	local success = pcall(function()
		adminStatus = adminDataStore:GetAsync(tostring(userID).."_")
	end)
	
	if not success then
		warn("Couldn't get admin status for "..player.Name)
	end
	
	-- Super admin status --
	
	local superAdminStatus = false
	
	for _, UserID in pairs(superAdminUserIds) do
		if player.UserId == userID then
			superAdminStatus = true
		end
	end
	
	-- Update values --
	
	if adminStatus == true then
		table.insert(self.playerUserIdIsAdmin, userID)
	end
	
	if superAdminStatus == true then
		table.insert(self.playerUserIdIsSuperAdmin, userID)
	end
	
	-- If player is a super admin but doesn't have regular admin status give it to them
	if superAdminStatus == true and adminStatus == false then
		table.insert(self.playerUserIdIsAdmin, userID)
	end
end

local function hackingDetected(player: Player, displayMsg: string?, privateMsg: string?, teleportBackToPosition: Vector3?, warningMessage: string?)

	local userID = player.UserId
	
	local playerIsAdmin = adminInfo:getAdminStatus(player)

	-- Check if player is admin, don't allow the ban if the player is an admin
	
	if disableAdminDisregardForTesting == true then
		playerIsAdmin = false
	end
	
	if playerIsAdmin == false then
		local invalidPunishmentType = false
		
		if punishmentType == 1 then -- Ban Player
			
			local config = {
				UserIDs = {userID},
				Duration = banLength,
				DisplayReason = displayMsg,
				PrivateReason = ("EZBan Anti-Cheat detected this user of hacking; specifics: ".. privateMsg)
			}
			
			PlayerService:BanAsync(config)
			
		elseif punishmentType == 2 then -- Kick Player
			
			player:Kick(displayMsg)
			
		elseif punishmentType == 3 then -- Teleport Player
			
			if teleportBackToPosition then
				local character = player.Character or player.CharacterAdded:Wait()
				
				if character:FindFirstChild("HumanoidRootPart") then
					character:FindFirstChild("HumanoidRootPart").Position = teleportBackToPosition
				end
			else
				local character = player.Character or player.CharacterAdded:Wait()
				
				if character:FindFirstChild("Humanoid") then
					character:FindFirstChild("Humanoid").Health = 0
				end
			end
			
		else
			invalidPunishmentType = true
			warn("Punishment type number "..punishmentType.." is invalid")
		end
		
		if not invalidPunishmentType then
			local userIDIndex = table.find(recentlyPrintedWarningsUserIDs, player.UserId)

			if not userIDIndex then
				warn("HACKER DETECTED, USERNAME: ", player.Name, "REASON: ", displayMsg)
				
				if warningMessage then
					warn(warningMessage)
				end

				table.insert(recentlyPrintedWarningsUserIDs, player.UserId)

				task.wait(printDebounceLength)

				table.remove(recentlyPrintedWarningsUserIDs, userIDIndex)
			end
		end
		
		-- wait until player is attempted to be punished before checking for hacking again
		wait(1)
	end
end

-- Commands

local function onChatted(message:string, player:Player, playerIsAdmin:boolean, playerIsSuperAdmin:boolean)

	local userID = player.UserId
	
	-- if message contains "/" and player is an admin then remove the "/" and make a table of the arguments

	if message.find(message,"/") and playerIsAdmin == true then
		-- remove "/""
		local message = string.gsub(message,"/","")
		local args = string.split(message, ",")
		-- command
		local command = args[1]
		-- Make command lowercase
		command = string.lower(command)
		-- If command is /ban then continue with script
		if command == "ban" then
			-- Check how many arguments there are
			if #args == 3 then
				-- other arguments
				local bannedPlayer = args[2]
				local reason = args[3]
				-- remove spaces for banned player's name
				bannedPlayer = bannedPlayer:gsub(" ", "")

				local foundPlayer = PlayerService:FindFirstChild(tostring(bannedPlayer))
				
				local playerToBanIsAdmin = adminDataStore:GetAsync(tostring(foundPlayer.UserId).."_")

				if playerIsAdmin then
					if not playerToBanIsAdmin then
						local config = {
							UserIDs = {foundPlayer.UserId},
							Duration = banLength,
							DisplayReason = "An admin has banned you from this game",
							PrivateReason = ("Admin named: '"..player.Name.."' with UserId: '"..player.UserId.."' banned this player")
						}

						PlayerService:BanAsync(config)
					else
						warn("You can't ban an admin")
					end
				end
			else
				warn("sytax error: wrong amount of arguments (should be 3)")
			end
		elseif command == "unban" then
			-- Check how many arguments there are
			if #args == 2 then
				-- Player userid
				local unbannedPlayerUserId = args[2]

				local success, errorMessage = pcall(function()
					local config = {
						UserIds = unbannedPlayerUserId,
						ApplyToUniverse = true
					}
					PlayerService:UnbanAsync(config)
				end)

				if not success then
					warn("Anti-Cheat datastore failed to save!, player not unbanned!")
					warn(errorMessage)
				end
			else
				-- wrong amount of arguments
				warn("sytax error: wrong amount of arguments (should be 2)")
			end
		elseif command == "bannotingame" then
			-- Check how many arguments there are
			if #args == 2 then
				-- Player userid
				local bannedPlayerUserId = args[2]
				
				local playerToBanIsAdmin = adminDataStore:GetAsync(tostring(bannedPlayerUserId).."_")

				if playerIsAdmin then
					if not playerToBanIsAdmin then
						local config = {
							UserIDs = {bannedPlayerUserId},
							Duration = banLength,
							DisplayReason = "An admin has banned you from this game",
							PrivateReason = ("Admin named: '"..player.Name.."' with UserId: '"..player.UserId.."' banned this player")
						}

						PlayerService:BanAsync(config)
					else
						warn("You can't ban an admin")
					end
				end
			else
				-- wrong amount of arguments
				warn("sytax error: wrong amount of arguments (should be 2)")
			end
		elseif command == "addadmin" then

			if #args == 2 then
				if playerIsSuperAdmin == true then
					local adminToAddUserId = args[2]

					local success, errorMessage = pcall(function()
						adminDataStore:SetAsync(adminToAddUserId.."_", true)
					end)

					if not success then
						warn("/add admin failed")
						warn(errorMessage)
					end
				end
			else 
				-- wrong amount of arguments
				warn("sytax error: wrong amount of arguments (should be 2)")
			end
		end
	end
end

local oldCharacterX = 0
local oldCharacterY = 0
local oldCharacterZ = 0
local oldCharacterPosition1 = 0
local oldCharacterPosition2 = 0
local oldCharacterVector = Vector3.zero

local newCharacterX = 0
local newCharacterY = 0
local newCharacterZ = 0
local newCharacterPosition1 = 0
local newCharacterPosition2 = 0
local newCharacterVector = Vector3.zero

-- Check for hacking in previous games / check for speed hacks

PlayerService.PlayerAdded:Connect(function(player)
	local Character = player.Character or player.CharacterAdded:Wait()
	local userID = player.UserId
	
	adminInfo:updateInfo(player)
	
	local playerIsAdmin = adminInfo:getAdminStatus(player)
	local playerIsSuperAdmin = adminInfo:getSuperAdminStatus(player)
	
	if disableAdminDisregardForTesting == true then
		playerIsAdmin = false
		playerIsSuperAdmin = false
	end

	
	if enabledFeatures["AdminCommands"] == true then
		player.Chatted:Connect(function(message)
			onChatted(message, player, playerIsAdmin, playerIsSuperAdmin)
		end)
	end

	-- Speed hacks, check if humanoid is moving too fast

	-- only works for first character

	local humanoidRootPart = Character:FindFirstChild("HumanoidRootPart")
	local humanoid = Character:FindFirstChild("Humanoid")

	local characterIsDead = false
	
	player.CharacterAdded:Connect(function(_character)
		-- Update new character components as character has been respawned with new parts
		Character = _character
		humanoidRootPart = _character:FindFirstChild("HumanoidRootPart")
		humanoid = _character:FindFirstChild("Humanoid")
		-- wait until anti-cheat has updated character CFrame and ignored respawn completly
		task.wait(updateSpeed + 1)
		characterIsDead = false
	end)

	player.CharacterRemoving:Connect(function()
		characterIsDead = true
	end)

	local timeInFreefall = 0

	local humanoidState

	-- Update state everytime it changes
	humanoid.StateChanged:Connect(function()
		humanoidState = Character.Humanoid:GetState()
	end)
	
	local function GetPlayerTeleportingValue(folder:Folder)
		if folder:FindFirstChild(player.Name) then
			local boolValue = folder:FindFirstChild(player.Name)
			
			if boolValue:IsA("BoolValue") then
				return boolValue.Value
			else
				error("Object in teleporting folder is a "..typeof(boolValue).."; expected a BoolValue")
				return false
			end
		else
			error("Teleporting folder does not contain a BoolValue with the same name as the player")
			return false
		end
	end
	
	-- Checks character movement on individual axes
	local function CheckCharacterXYZ(newCharacterPosition, oldCharacterPosition, expectedPlayerSpeedNew)
		if enabledFeatures["XYZPositionChecking"] == true then
			local playerMovementAmount = newCharacterPosition - oldCharacterPosition

			local maxAllowedMovement = expectedPlayerSpeedNew + (allowedDifference * updateSpeed)
			local maxNegativeAllowedMovement = (expectedPlayerSpeedNew * -1) - (allowedDifference * updateSpeed)

			if playerMovementAmount > maxAllowedMovement or playerMovementAmount < maxNegativeAllowedMovement then
				if teleportingValueFolder ~= "NoValueFound" then
					if characterIsDead == false and GetPlayerTeleportingValue(teleportingValueFolder) == false then
						return "PlayerCheating"
					end
				else
					return "PlayerCheating"
				end
			end
		end
		return "PlayerNotCheating"
	end
	
	-- Checks character movement on flat plane added
	local function CheckCharacterFlatPlane(newCharacterPosition1, oldCharacterPosition1, newCharacterPosition2, oldCharacterPosition2, expectedPlayerSpeedNew)
		if enabledFeatures["FlatPlanePositionChecking"] == true then
			local playerMovementAmount = (newCharacterPosition1 - oldCharacterPosition1) + (newCharacterPosition2 - oldCharacterPosition2)
			
			--Multiply by 2 because playerMovementAmount is X+Y not only one axis
			local maxAllowedMovement = expectedPlayerSpeedNew * 2 + (allowedDifference * updateSpeed)
			local maxNegativeAllowedMovement = (expectedPlayerSpeedNew * -2) - (allowedDifference * updateSpeed)
			
			if playerMovementAmount > maxAllowedMovement or playerMovementAmount < maxNegativeAllowedMovement then
				if teleportingValueFolder ~= "NoValueFound" then
					if characterIsDead == false and GetPlayerTeleportingValue(teleportingValueFolder) == false then
						return "PlayerCheating"
					end
				else
					return "PlayerCheating"
				end
			end
		end
		return "PlayerNotCheating"
	end
	
	local function CheckForSpeedHack()
		if player.Character then
			if player.Character.Humanoid then
				-- Check walkspeed/jumpheight
				
				local walkSpeed = player.Character.Humanoid.WalkSpeed
				
				if enabledFeatures["WalkSpeedChecking"] == true and walkSpeed > expectedPlayerSpeed then

					local warningMessage = (player.Name.. " has increased walkspeed")
					
					local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected of increasing their WalkSpeed; "..
						"Detected WalkSpeed: '"..walkSpeed.. "' Expected WalkSpeed: '"..expectedPlayerSpeed.."'")
					
					hackingDetected(player, customDetectionMessages["WalkSpeedMsg"], specificInfo, nil, warningMessage)
				end
				
				local jumpHeight = player.Character.Humanoid.JumpHeight

				if enabledFeatures["JumpHeightChecking"] == true and jumpHeight > expectedPlayerJumpHeight then
					local warningMessage = (player.Name.. " has increased jump height")
					
					local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected of increasing their JumpHeight; "..
						"Detected JumpHeight: '"..jumpHeight.. "' Expected JumpHeight: '"..expectedPlayerJumpHeight.."'")
					
					hackingDetected(player, customDetectionMessages["JumpHeightMsg"], specificInfo, nil, warningMessage)
				end

				if humanoidState == Enum.HumanoidStateType.Freefall then
					-- Add update speed, ex. (updateSpeed = 0.5) time in freefall += 0.5
					timeInFreefall += updateSpeed

					if timeInFreefall >= maxFreeFall then
						-- Placeholder, something other then freefall (doesn't actually ragdoll)
						humanoidState = Enum.HumanoidStateType.Ragdoll
					end
				else
					-- Reset time if not in freefall
					timeInFreefall = 0
				end

				-- If updated faster then multiply variables, ex. (expected speed = 16, update speed = 0.5) 16*0.5 = 8 -> player can go 8 studs in 0.5 seconds

				local expectedPlayerSpeedNew = expectedPlayerSpeed * updateSpeed

				-- Update character CFrame

				newCharacterX = humanoidRootPart.CFrame.X
				newCharacterY = humanoidRootPart.CFrame.Y
				newCharacterZ = humanoidRootPart.CFrame.Z
				
				newCharacterVector = humanoidRootPart.Position
				
				newCharacterPosition1 = humanoidRootPart.CFrame[flatPlane[1]]
				newCharacterPosition2 = humanoidRootPart.CFrame[flatPlane[2]]

				if humanoidState ~= Enum.HumanoidStateType.Freefall then

					if CheckCharacterFlatPlane(newCharacterPosition1, oldCharacterPosition1, newCharacterPosition2, oldCharacterPosition2, expectedPlayerSpeedNew) == "PlayerCheating" then
						if characterIsDead == false then
							local concatenatedAxes = tostring(flatPlane[1])..tostring(flatPlane[2])
							local commaConcatenatedAxes = tostring(flatPlane[1])..", "..tostring(flatPlane[2])
							
							local warningMessage = (player.Name.. " is moving too quickly along "..concatenatedAxes.."-axes")
							
							local oldCharacterPositionDisplay = (oldCharacterPosition1..", "..oldCharacterPosition2)
							local newCharacterPositionDisplay = (newCharacterPosition1..", "..newCharacterPosition2)
							
							local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected speed hacking on flat plane ("..concatenatedAxes.."-axes); "..
								"first recorded position ("..commaConcatenatedAxes.."): '"..oldCharacterPositionDisplay.."'"..
								"second recorded position ("..commaConcatenatedAxes.."): '"..newCharacterPositionDisplay.."'"..
								"time between positions: '"..updateSpeed.."'")
							
							hackingDetected(player, customDetectionMessages["SpeedFlatPlaneMsg"], specificInfo, oldCharacterVector, warningMessage)
						end
					elseif CheckCharacterXYZ(newCharacterX, oldCharacterX, expectedPlayerSpeedNew) == "PlayerCheating" then
						if characterIsDead == false then
							
							local warningMessage = (player.Name.. " is moving too quickly along x-axis")
							
							local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected speed hacking on X-Axis; "..
								"first recorded position: '"..oldCharacterX.."' second recorded position: '"..newCharacterX.."' time between positions: '"..updateSpeed.."'")
							
							hackingDetected(player, customDetectionMessages["SpeedXMsg"], specificInfo, oldCharacterVector, warningMessage)
						end
					elseif CheckCharacterXYZ(newCharacterY, oldCharacterY, expectedPlayerSpeedNew) == "PlayerCheating" then
						if characterIsDead == false and timeInFreefall >= maxFreeFall then

							local warningMessage = (player.Name.. " is moving too quickly along y-axis")
							
							local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected speed hacking on Y-Axis; "..
								"first recorded position: '"..oldCharacterY.."' second recorded position: '"..newCharacterY.."' time between positions: '"..updateSpeed.."'")
							
							hackingDetected(player, customDetectionMessages["SpeedYMsg"], specificInfo, oldCharacterVector, warningMessage)
						end
					elseif CheckCharacterXYZ(newCharacterZ, oldCharacterZ, expectedPlayerSpeedNew) == "PlayerCheating" then
						if characterIsDead == false then

							local warningMessage = (player.Name.. " is moving too quickly along z-axis")
							
							local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' was detected speed hacking on Z-Axis; "..
								"first recorded position: '"..oldCharacterZ.."' second recorded position: '"..newCharacterZ.."' time between positions: '"..updateSpeed.."'")
							
							hackingDetected(player, customDetectionMessages["SpeedZMsg"], specificInfo, oldCharacterVector, warningMessage)
						end
					end
				end

				-- Update character CFrame
				oldCharacterX = humanoidRootPart.CFrame.X
				oldCharacterY = humanoidRootPart.CFrame.Y
				oldCharacterZ = humanoidRootPart.CFrame.Z
				
				oldCharacterVector = humanoidRootPart.Position
				
				oldCharacterPosition1 = humanoidRootPart.CFrame[flatPlane[1]]
				oldCharacterPosition2 = humanoidRootPart.CFrame[flatPlane[2]]
			end
		end
	end
	
	-- repeat forever until player leaves game
	if not playerIsAdmin then
		repeat
			CheckForSpeedHack()
			task.wait(updateSpeed)
		until not player
	end
	-- NO CODE PAST THIS POINT IN THIS FUNCTION
end)

-- Function for filtering through illegalParts

local function IsPlayerTouchingIllegalParts()
	for _, currentObject in pairs(illegalParts) do
		
		local touchingParts = workspace:GetPartBoundsInBox(currentObject.CFrame, currentObject.Size)
		
		for _, part in pairs(touchingParts) do
			if part.Parent:FindFirstChild("Humanoid") then
				if PlayerService:GetPlayerFromCharacter(part.Parent) then
					
					local player = PlayerService:GetPlayerFromCharacter(part.Parent)
					
					local warningMessage = (player.Name.." is touching an illegal area part (".. currentObject.Name.. ")")
					
					local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' touched illegal area named: '"..currentObject.Name.."'")
					
					hackingDetected(player, customDetectionMessages["IllegalAreaMsg"], specificInfo, warningMessage)
				end
			end
		end
	end
end

-- Function for filtering through timedParts

local currentArea = nil
local timeAtLastArea = 0

local function IsPlayerTouchingTimedParts()
	for _, currentObject in pairs(timedParts) do

		local touchingParts = workspace:GetPartBoundsInBox(currentObject.CFrame, currentObject.Size)
		
		for _, part in pairs(touchingParts) do
			if part.Parent:FindFirstChild("Humanoid") then
				if PlayerService:GetPlayerFromCharacter(part.Parent) then
					
					local player = PlayerService:GetPlayerFromCharacter(part.Parent)
					
					-- Only get the numbers in the name
					local name = currentObject.Name
					-- Remove characters
					local objectNumber = string.gsub(name, "%D", "")
					
					if objectNumber then
						objectNumber = tonumber(objectNumber)
					end
					
					-- If current area is equal to the old area then disregard
					if objectNumber ~= currentArea then
						-- If the name is resetlist then set the current area to nil/lobby

						--Allow names with uppercase letters and allow words after reset list
						if string.lower(string.sub(currentObject.Name, 1, 9)) == "resetlist" then
							currentArea = nil
							timeAtLastArea = 0
						elseif currentArea == nil or objectNumber == currentArea + 1 or (objectNumber == currentArea - 1 and backwardsEnabled == true) then
							
							-- If time at last area plus the minimum time to get between areas is greater then the current time then punish player
							
							if timeAtLastArea + minimumTimeBetweenAreas > os.time() then
								
								local warningMessage = (player.Name.." arrived at a timed area too quickly; area number "..objectNumber)
								
								local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId..
									"' started at area '"..currentArea.. "' and arrived at area '"..objectNumber.."' in '"..
									os.time() - timeAtLastArea.."' seconds; "..(timeAtLastArea + minimumTimeBetweenAreas) - os.time().." seconds too quickly")
								
								hackingDetected(player, customDetectionMessages["SpeedTimedAreaMsg"], specificInfo, nil, warningMessage)
							else
								currentArea = objectNumber
								timeAtLastArea = os.time()
							end
						else

							-- Player skipped an area
							local warningMessage = (player.Name.." skipped a timed area; area number "..objectNumber"")
							
							local specificInfo = ("Player named: '"..player.Name.."' with UserId: '"..player.UserId.."' started at area '"..currentArea.. "' and skipped to area '"..objectNumber.."'")
							
							hackingDetected(player, customDetectionMessages["SkipTimedAreaMsg"], specificInfo, nil, warningMessage)
						end
					end
				end
			end
		end
	end
end

-- Run functions

while true do
	if enabledFeatures["TimedPartChecking"] then
		IsPlayerTouchingTimedParts()
	end
	if enabledFeatures["IllegalPartChecking"] then
		IsPlayerTouchingIllegalParts()
	end
	
	task.wait(areaTouchingUpdateSpeed)
end
"EzBanStartup" code
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------ Configuration options in "EzBanConfiguration" script ------------------------------------
------------------------------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------------------------------

local configData = nil

local EzBanStartup = {}

function EzBanStartup.SendConfig(configOptions)
	configData = configOptions
end

function EzBanStartup.GetConfig()
	if configData then
		return configData
	else
		error("Missing configuration data; Anti-Cheat is not active")
	end
end

function EzBanStartup.CheckObjects()
	local errorOccured = false
	
	--[[ Check
	* Main Folder intact
	* Script Folder intact
	* Illegal area parts Folder intact
	* Timed area parts Folder intact
	* Configuration Script intact
	]]
	local mainFolder
	local scriptFolder
	local illegalAreaPartsFolder
	local timedAreaPartsFolder
	local configurationScript
	
	local success = pcall(function() 
		mainFolder = script.Parent.Parent
		scriptFolder = mainFolder:FindFirstChild("EzBanAntiCheatScripts")
		illegalAreaPartsFolder = mainFolder:FindFirstChild("EzBanAntiCheatObjects"):FindFirstChild("Illegal area parts")
		timedAreaPartsFolder = mainFolder:FindFirstChild("EzBanAntiCheatObjects"):FindFirstChild("Timed area parts")
		configurationScript = mainFolder:FindFirstChild("EzBanConfiguration")
	end)
	
	if not success then
		errorOccured = true
		error("A component of anti-cheat is missing; anti-cheat is corrupt!")
	end
	
	local function fail(missingPartName: string?)
		errorOccured = true
		if not missingPartName then
			error("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
		else
			warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
			error("Corrupt object: "..missingPartName)
		end
	end
	
	local function checkObject(object:Instance, expectedName:string, expectedType:string, errorName:string)
		if object.Name ~= expectedName or not object:IsA(expectedType) then
			fail(errorName)
		end
	end
	
	checkObject(mainFolder, "EzBan Obby Anti-Cheat", "Folder", "Main anti-cheat folder")
	
	checkObject(scriptFolder, "EzBanAntiCheatScripts", "Folder", "Anti-cheat script folder")
	
	checkObject(illegalAreaPartsFolder, "Illegal area parts", "Folder", "Illegal area parts folder")
	
	checkObject(timedAreaPartsFolder, "Timed area parts", "Folder", "Timed area parts folder")
	
	checkObject(configurationScript, "EzBanConfiguration", "Script", "Configuration script")
	
	return errorOccured
end

function EzBanStartup.CheckConfig(configOptions)
	local function checkOption(configOptionNumber: number, expectedType:string)
		local objectValue = configOptions[configOptionNumber][2]
		local objectName = configOptions[configOptionNumber][1]
		
		local recievedType = type(objectValue)
		
		if recievedType ~= expectedType then
			warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
			error("Expected a "..expectedType.." recieved a "..recievedType.." for configuration option "..objectName)
		end
	end
	
	--[[ ConfigurationOptions:
	
	[1] = superAdminUserIds
	[2] = customDetectionMessages
	[3] = punishmentType
	[4] = banLength
	[5] = maxFreeFall
	[6] = updateSpeed
	[7] = minimumTimeBetweenAreas
	[8] = backwardsEnabled
	[9] = expectedPlayerSpeed
	[10] = expectedPlayerJumpHeight
	[11] = allowedDifference
	[12] = teleportingValueFolder
	[13] = flatPlane
	[14] = enabledFeatures
	[15] = areaTouchingUpdateSpeed
	
	]]
	
	--configOptions[?][1] = name of config option; configOptions[?][2] = value of config option
	
	checkOption(1, "table")
	
	checkOption(2, "table")
	
	checkOption(3, "number")
	
	checkOption(4, "number")
	
	checkOption(5, "number")
	
	checkOption(6, "number")
	
	checkOption(7, "number")
	
	checkOption(8, "boolean")
	
	checkOption(9, "number")
	
	checkOption(10, "number")
	
	checkOption(11, "number")
	
	-- config option 12 is teleporting bool value folder but can also be nil if not used; can't use type()
	
	if configOptions[12][2] ~= "NoValueFound" and type(configOptions[12][2]) == "userdata" then
		-- Config option isn't nil
		if not configOptions[12][2]:IsA("Folder") then
			warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
			error("Corrupt object: Teleporting value folder")
		end
	end
	
	checkOption(13, "table")
	
	checkOption(14, "table")
	
	checkOption(15, "number")
end

return EzBanStartup
"EzBanConfiguration" code
------------------
-- Super Admins -- [1]
------------------

-- Notes: Super Admins can use the /addAdmin command, regular admins cannot

-- Type: Array containing UserIDs

-- Default: None

local superAdminUserIds = {
	1609672088
}

-------------------------------
-- Custom detection messages -- [2]
-------------------------------

-- Notes: If you want to display custom messages you can change the current ones here (DO NOT DELETE MESSAGES, instead put message = "")

-- Type: Dictionary containing strings

-- Default: None

local customDetectionMessages = {
	-- Player's walkspeed is too fast
	WalkSpeedMsg = "You were detected using speed hacks",
	-- Player's jump height is too high
	JumpHeightMsg = "You were detected using speed hacks",
	-- Player is moving too quickly along flat plane (configure which axes are used in "flat plane movement checking" [13])
	SpeedFlatPlaneMsg = "You were detected using speed hacks",
	-- Player is moving too quickly along only x-axis
	SpeedXMsg = "You were detected using speed hacks",
	-- Player is moving too quickly along only y-axis
	SpeedYMsg = "You were detected using speed hacks",
	-- Player is moving too quickly along only z-axis
	SpeedZMsg = "You were detected using speed hacks",
	-- Player entered an illegal area
	IllegalAreaMsg = "You were detected using teleport hacks",
	-- Player entered a new timed area too quickly
	SpeedTimedAreaMsg = "You were detected using speed hacks",
	-- Player skipped a timed area
	SkiptimedAreaMsg = "You were detected using teleport hacks"
}

---------------------
-- Punishment type -- [3]
---------------------

-- Notes: Selected punishment when player is detected of hacking

-- Type: Number (Option list index)

-- Default: 2

--[[ Options: 
	
	1 = Ban player (Most severe punishment)
	
	2 = Kick player from server (Normal punishment)
	
	3 = Teleport player back to XYZ coordinates they were at before they were detected of hacking (Least severe punishment)
	
	(option 3 only teleports player back if they are detected with the XYZ-Axes detection feature, otherwise the play will be killed/sent back to spawn location)
	
]]

local punishmentType = 2

-------------------
-- Length of ban -- [4]
-------------------

-- Notes: Ignore if punishment type is not "Ban player"; Set value to -1 for permanant ban

-- Type: Number (days)

-- Default: 1

local banLength = 1

-------------------------
-- Player Freefallling -- [5]
-------------------------

-- Notes: Maximum amount of time a player can freefall for

-- Type: Number (seconds)

-- Default: 10

local maxFreeFall = 10

--------------------------------
-- Player Speed Hack Checking -- [6]
--------------------------------

--[[ Notes: 

	-- How often the script will check the player's coordinates for speed hacking
	
	-- Very low numbers (<0.2) can cause laggy players to get banned very easily

]]

-- Type: Number (seconds)

-- Default: 1

local updateSpeed = 1

--------------------------------
-- Minimum time between areas -- [7]
--------------------------------

-- Notes: Player will be punished if they get to the next "Timed area part" before the entered time

-- Type: Number (seconds)

-- Default: 10

local minimumTimeBetweenAreas = 10

--------------------------------------
-- Player moving backwards in areas -- [8]
--------------------------------------

-- Notes: Can player go backwards in timed areas (EX: player goes from area 2 to 1)

-- Type: Boolean

-- Default: true

local backwardsEnabled = true

--------------------------------
-- Expected player walk speed -- [9]
--------------------------------

-- Notes: What WalkSpeed should the player character have in your game

-- Type: Number (Studs per second)

-- Default: 16

local expectedPlayerSpeed = 16

---------------------------------
-- Expected player jump height -- [10]
---------------------------------

-- Notes: What JumpHeight should the player character have in your game

-- Type: Number (Studs per second)

-- Default: 7.2

local expectedPlayerJumpHeight = 7.2

----------------------------------
-- Player movement speed change -- [11]
----------------------------------

-- Notes: As player can sometimes move quicker than usual due to lag spikes, allow player to speed up a little

-- Type: Number (Studs per second)

-- Default: 5

local allowedDifference = 5

-----------------------------
-- Game teleport mechanics -- [12]
-----------------------------

--[[
Notes: 
	
	-- If your game contains teleporting, the anti-cheat will detect the player of hacking whenever they teleport;
	To fix this follow the steps below
	
	-- How to create player teleporting values:
		1. Create a folder somewhere in "ServerStorage"
		2. Index the folder below (EX: teleportingValue = game:GetService("ServerStorage"):FindFirstChild("PlayerBoolValues"))
		3. When a player joins the game use a script to add a BoolValue to the folder
		4. Make the BoolValue's name the same as the player who joined
		5. Whenever the player is teleporting set their own bool value to true
	
]]

-- Type: Folder containing BoolValues that have the same name as the player

-- Default: nil

local teleportingValue = nil

----------------------------------
-- Flat plane movement checking -- [13]
----------------------------------

-- Notes: What axes does the player move along when walking

-- Type: Array

-- Default: {"X", "Z"}

--[[ Options:
	1. "X"
	2. "Y"
	3. "Z"
]]

local flatPlane = {"X", "Z"}

---------------------------------
-- Enabled anti-cheat features -- [14]
---------------------------------

-- Notes: Enable or disable anti-cheat checking features

-- Type: Dictionary

local enabledFeatures = {
	AdminCommands = true,
	FlatPlanePositionChecking = true,
	XYZPositionChecking = true,
	WalkSpeedChecking = true,
	JumpHeightChecking = true,
	IllegalPartChecking = true,
	TimedPartChecking = true
}

----------------------------------------
-- Player Illegal/Timed Area Checking -- [15]
----------------------------------------

-- Notes: How often the script will check if a player is touching illegal area parts or timed area parts

-- Type: Number (seconds)

-- Default: 0.1

local areaTouchingUpdateSpeed = 0.1

------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------
------------------------------------ End of configuration options ------------------------------------
------------------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------------------










































mainFolder = script.Parent

local Startup

local startupSuccess = pcall(function() 
	Startup = require(mainFolder:FindFirstChild("EzBanAntiCheatScripts"):FindFirstChild("EzBanStartup"))
end)

if not startupSuccess then
	warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
	error("Corrupt object: Startup module script")
end

local configurationOptions = {}

	--[[ ConfigurationOptions:
	
	[1] = superAdminUserIds
	[2] = customDetectionMessages
	[3] = punishmentType
	[4] = banLength
	[5] = maxFreeFall
	[6] = updateSpeed
	[7] = minimumTimeBetweenAreas
	[8] = backwardsEnabled
	[9] = expectedPlayerSpeed
	[10] = expectedPlayerJumpHeight
	[11] = allowedDifference
	[12] = teleportingValueFolder
	[13] = flatPlane
	[14] = enabledFeatures
	[15] = areaTouchingUpdateSpeed
	
	]]
	
if teleportingValue == nil then
	teleportingValue = "NoValueFound"
end

local configSuccess = pcall(function()
	table.insert(configurationOptions, {"superAdminUserIds", superAdminUserIds})

	table.insert(configurationOptions, {"customDetectionMessages", customDetectionMessages})

	table.insert(configurationOptions, {"punishmentType", punishmentType})

	table.insert(configurationOptions, {"banLength", banLength})

	table.insert(configurationOptions, {"maxFreeFall", maxFreeFall})

	table.insert(configurationOptions, {"updateSpeed", updateSpeed})

	table.insert(configurationOptions, {"minimumTimeBetweenAreas", minimumTimeBetweenAreas})

	table.insert(configurationOptions, {"backwardsEnabled", backwardsEnabled})

	table.insert(configurationOptions, {"expectedPlayerSpeed", expectedPlayerSpeed})

	table.insert(configurationOptions, {"expectedPlayerJumpHeight", expectedPlayerJumpHeight})

	table.insert(configurationOptions, {"allowedDifference", allowedDifference})

	table.insert(configurationOptions, {"teleportingValue", teleportingValue})
	
	table.insert(configurationOptions, {"flatPlane", flatPlane})
	
	table.insert(configurationOptions, {"enabledFeatures", enabledFeatures})
	
	table.insert(configurationOptions, {"areaTouchingUpdateSpeed", areaTouchingUpdateSpeed})
end)

if not configSuccess then
	warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
	error("Corrupt object: Couldn't load configuration options")
end

Startup.CheckConfig(configurationOptions)

local sendConfigSuccess = pcall(function()
	Startup.SendConfig(configurationOptions)
end)

if not sendConfigSuccess then
	warn("A component of anti-cheat is missing or changed; anti-cheat is corrupt!")
	error("Corrupt object: Couldn't send configuration options to anticheat")
end

Startup.CheckObjects()

mainFolder:FindFirstChild("EzBanAntiCheatScripts").Parent = game:GetService("ServerScriptService")
mainFolder:FindFirstChild("EzBanAntiCheatObjects").Parent = game:GetService("ServerStorage")

local antiCheatScript = game:GetService("ServerScriptService"):FindFirstChild("EzBanAntiCheatScripts"):FindFirstChild("EzBanAntiCheat")

antiCheatScript.Enabled = true

-- Configuration script/read me script not needed anymore
mainFolder:Destroy()

Updates


Latest update

V 3.4.2 - 11/24/24

  • Bug fix - Teleporting values checked for all hacking detection; not just XYZ checking
Earlier updates

V 3.4.1 - 8/16/24

  • Checks for errors in startup
  • Added configuration script
  • Added more configuration options
New configuration options
  • teleportingValueFolder
  • flatPlane
  • enabledFeatures
  • areaTouchingUpdateSpeed
  • Scripts hidden from client in server script service
  • Parts hidden from client in server storage
  • Uses new roblox Ban API
  • New character flat plane (X+Z) speed hack checking
  • Updated Read me
  • More lenient naming for timed area parts and commands
  • Created DevForum community resource

V 2.4.1 - 8/13/24

  • Bug fix - Player death doesn’t halt script on certain exceptions anymore

V 2.4.0 - 6/7/24

  • New player WalkSpeed speed hack check
  • Custom detection messages

V 2.3.0 - 3/17/24

  • New player JumpHeight speed hack check
  • Better code readability

V 2.2.0 - 3/15/24

  • Admin commands
  • XYZ position checking
  • Timed area parts

V 1.2.0 - 3/1/24

  • All hacking detection handled in 1 function

V 1.1.0 - 2/19/24

  • Variables at start of script to control things like BanLength

V 1.0.0 - 12/3/23

  • Illegal area part detection
  • Ban players when they get detected by anti-cheat

How To Use In Roblox Studio


  1. Download here: Click Me!
  2. Then retrieve it from your “toolbox” in roblox studio
  3. Open the “Read me” script to learn more

Please reply to this topic if you find anything wrong with it. Also, reply with a link to your game that uses EzBan : )

Would you consider using the anti-cheat
  • Maybe
  • Yes
  • Already have an anti-cheat
  • No - Please reply on what needs to be fixed!

0 voters

By the way if you were wondering this post is 43,340 characters; 4,050 words; and 1,473 lines long not including this. Nice.

3 Likes

Wow im amazed by how much this module offers, i don’t need it right now but i will probably have a use case for this in the future, insane work man literally keep it up!

:fire:

2 Likes

This is my first community resource so I wasn’t sure how people would react to it. Thanks a lot for the encouragement!

1 Like

Haha, im on the same boat bro i released my first community resource today as well here if you want to check it out:

But basically thats how it is, we gain experience over time and eventually we also get traction and get more comfortable on this type of stuff.

My personal recomendation would be to go around the Community Resources Tab and just chat and reply to other people’s resources, which also allows you to see what other people would want for something and you would get ideas for future resources as well.

1 Like

Adding onto this i would try and make anything i would find interesting and release the best ones and the ones i would find to be useful for most people, which would give you feedback