-- TeleportAntiCheat (ModuleScript in ServerScriptService)

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

local TeleportAC = {}

--// SETTINGS
TeleportAC.Settings = {
    MaxSpeedStudsPerSecond = 120,   -- Max legit speed (walk + jump + small lag)
    MaxVerticalJump = 60,           -- Max legit vertical change in one frame
    MinCheckDelta = 0.05,           -- Don't check if dt is too tiny
    KickOnDetection = true,         -- true = kick, false = just call callback
    DebugPrint = false,             -- print detections to output
}

-- callback: function(player, reason)
TeleportAC.OnFlag = nil

-- internal player data
local playerData = {}

--// UTIL
local function getRoot(character)
    return character and character:FindFirstChild("HumanoidRootPart")
end

local function flagPlayer(player, reason)
    if TeleportAC.Settings.DebugPrint then
        warn("[TeleportAC] ".. player.Name .. " flagged: " .. reason)
    end

    if TeleportAC.OnFlag then
        TeleportAC.OnFlag(player, reason)
    end

    if TeleportAC.Settings.KickOnDetection then
        player:Kick("[AntiCheat] Illegal teleport detected. (" .. reason .. ")")
    end
end

--// PUBLIC: mark a legit teleport destination
--  TeleportAntiCheat:AllowTeleport(player, destinationPosition, radius, duration)
function TeleportAC:AllowTeleport(player, position, radius, duration)
    local data = playerData[player]
    if not data then return end

    table.insert(data.AllowedTeleports, {
        Position = position,
        Radius = radius or 10,
        ExpiresAt = tick() + (duration or 2),
    })
end

--// INTERNAL: check if current pos matches any allowed teleport
local function isInAllowedTeleport(player, position)
    local data = playerData[player]
    if not data then return false end

    local now = tick()
    local allowed = data.AllowedTeleports

    for i = #allowed, 1, -1 do
        local info = allowed[i]
        if now > info.ExpiresAt then
            table.remove(allowed, i)
        else
            if (position - info.Position).Magnitude <= info.Radius then
                -- consume this allowed teleport so it can't be abused
                table.remove(allowed, i)
                return true
            end
        end
    end

    return false
end

--// INTERNAL: update loop
local function updateAll(dt)
    if dt < TeleportAC.Settings.MinCheckDelta then
        return
    end

    for player, data in pairs(playerData) do
        local character = player.Character
        local root = getRoot(character)

        if root then
            local pos = root.Position

            if data.LastPosition then
                local dist = (pos - data.LastPosition).Magnitude
                local speed = dist / dt
                local verticalDelta = math.abs(pos.Y - data.LastPosition.Y)

                -- check for allowed teleports first
                if isInAllowedTeleport(player, pos) then
                    data.LastPosition = pos
                    data.LastTime = tick()
                    continue
                end

                -- speed check
                if speed > TeleportAC.Settings.MaxSpeedStudsPerSecond then
                    flagPlayer(player, "Speed/Teleport: " .. math.floor(speed) .. " studs/s")
                -- vertical spike check (like suddenly way up in the air)
                elseif verticalDelta > TeleportAC.Settings.MaxVerticalJump then
                    flagPlayer(player, "Vertical teleport: ".. math.floor(verticalDelta) .. " studs")
                end
            end

            data.LastPosition = pos
            data.LastTime = tick()
        end
    end
end

--// PLAYER HANDLERS
local function setupPlayer(player)
    playerData[player] = {
        LastPosition = nil,
        LastTime = tick(),
        AllowedTeleports = {},
    }

    player.CharacterAdded:Connect(function(char)
        -- reset tracking when they respawn
        local root = getRoot(char)
        if root then
            local data = playerData[player]
            if data then
                data.LastPosition = root.Position
                data.LastTime = tick()
            end
        end
    end)
end

local function removePlayer(player)
    playerData[player] = nil
end

--// START
function TeleportAC.Start()
    for _, plr in ipairs(Players:GetPlayers()) do
        setupPlayer(plr)
    end
    Players.PlayerAdded:Connect(setupPlayer)
    Players.PlayerRemoving:Connect(removePlayer)

    RunService.Heartbeat:Connect(updateAll)
end

return TeleportAC

Embed on website

To embed this project on your website, copy the following code and paste it into your website's HTML: