ultimate teleporter
Key RequiredUniversal

ultimate teleporter

Universal ScriptThePenguinThePenguin 8.8K views 42 executesUpdated Sep 11, 2025
5.0/ 5(1 votes)
10100% Positive
Lua Script•1601 lines (57645 chars)
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local UserInputService = game:GetService("UserInputService")
local TweenService = game:GetService("TweenService")
local StarterGui = game:GetService("StarterGui")
local GuiService = game:GetService("GuiService")
local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")
local mouse = player:GetMouse()

-- ======= STATE MANAGEMENT =======
local waypoints = {}  -- Array of { id, name, cframe, enabled, order, seq }
local waypointCounter = 0
local automationRunning = false
local automationPaused = false
local automationStopRequested = false
local automationTask = nil
local lastSaveTime = 0
local saveDebounceTime = 1.0
local autosaveEnabled = true
local pendingSave = false
local presets = {}
local minimized = false
local showMarkers = true
local animateTeleport = false
local teleportDuration = 0.4
local autosaveDelay = 1.5
local autosaveVersion = 0
local shuffleMode = false
local wrapEnabled = true

-- ======= UI CONSTANTS =======
local COLOR_BG = Color3.fromRGB(30, 30, 30)
local COLOR_PANEL = Color3.fromRGB(22, 22, 22)
local COLOR_BTN = Color3.fromRGB(50, 50, 50)
local COLOR_BTN_ACTIVE = Color3.fromRGB(80, 80, 80)
local COLOR_TEXT = Color3.fromRGB(230, 230, 230)
local COLOR_ON = Color3.fromRGB(0, 180, 0)
local COLOR_OFF = Color3.fromRGB(180, 0, 0)
local COLOR_WARNING = Color3.fromRGB(255, 170, 0)
local COLOR_INFO = Color3.fromRGB(0, 170, 255)

-- ======= CONFIGURATION =======
local WAYPOINT_PREFIX = "wp_"
local MARKER_PREFIX = "wp_marker_"
local HOTKEY_TOGGLE = Enum.KeyCode.RightControl
local HOTKEY_ALT_TOGGLE = Enum.KeyCode.T  -- Ctrl+T

-- ======= UTILITY FUNCTIONS =======
local function safeGetCharacter()
    return player.Character or player.CharacterAdded:Wait()
end

local function getHRP()
    local ch = player.Character
    if not ch then return nil end
    return ch:FindFirstChild("HumanoidRootPart") 
        or ch:FindFirstChild("Torso") 
        or ch:FindFirstChild("UpperTorso")
end

local function generateId()
    waypointCounter = waypointCounter + 1
    return WAYPOINT_PREFIX .. tostring(waypointCounter) .. "_" .. HttpService:GenerateGUID(false)
end

local function tryZeroVelocity(part)
    if not part or not part:IsA("BasePart") then return end
    pcall(function()
        part.Velocity = Vector3.new(0, 0, 0)
        part.RotVelocity = Vector3.new(0, 0, 0)
        if part:FindFirstChild("AssemblyLinearVelocity") then
            pcall(function() part.AssemblyLinearVelocity = Vector3.new(0, 0, 0) end)
        end
    end)
end

local function teleportToCFrame(cf, opts)
    local hrp = getHRP()
    if not hrp then
        return false, "Character or HRP not found"
    end
    
    local success, err = pcall(function()
        -- Place slightly above to reduce clipping into ground
        local target = cf + Vector3.new(0, 2, 0)
        
        -- Try to zero velocity first
        tryZeroVelocity(hrp)
        
        if opts and opts.animate then
            local dur = math.max(0.05, tonumber(opts.duration) or teleportDuration)
            local tweenInfo = TweenInfo.new(dur, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut)
            local tw = TweenService:Create(hrp, tweenInfo, {CFrame = target})
            tw:Play()
            tw.Completed:Wait()
        else
            hrp.CFrame = target
        end
        
        -- Tiny nudge of Humanoid to ensure physics settle
        local human = hrp.Parent:FindFirstChildOfClass("Humanoid")
        if human then
            pcall(function() human:ChangeState(Enum.HumanoidStateType.Physics) end)
        end
    end)
    
    if not success then
        return false, ("Teleport failed: %s"):format(tostring(err))
    end
    return true, "Teleported"
end

local function reindexWaypoints()
    for i, w in ipairs(waypoints) do
        w.order = i
        if not w.seq then w.seq = i end
    end
end

local function sortWaypoints()
    table.sort(waypoints, function(a,b)
        return (a.order or 0) < (b.order or 0)
    end)
end

-- ======= DEBOUNCED SAVE IMPLEMENTATION =======
local function scheduleSave()
    local currentTime = tick()
    
    -- If we saved recently, mark as pending and schedule for later
    if currentTime - lastSaveTime < saveDebounceTime then
        pendingSave = true
        task.delay(saveDebounceTime, function()
            if pendingSave then
                scheduleSave()
            end
        end)
        return
    end
    
    -- If we're not pending, save immediately
    if not pendingSave then
        lastSaveTime = currentTime
        if autosaveEnabled then
            local ok, msg = saveWaypointsToFile("untitled_teleporter_autosave.json")
            if ok then
                print("Autosaved waypoints")
                setInfo("Waypoints autosaved")
            else
                warn("Autosave failed: " .. tostring(msg))
            end
        end
    else
        -- We were pending, so save now and reset
        pendingSave = false
        lastSaveTime = currentTime
        if autosaveEnabled then
            local ok, msg = saveWaypointsToFile("untitled_teleporter_autosave.json")
            if ok then
                print("Autosaved waypoints (debounced)")
                setInfo("Waypoints autosaved")
            else
                warn("Autosave failed: " .. tostring(msg))
            end
        end
    end
end

-- ======= MARKER MANAGEMENT =======
local function createMarkerForWaypoint(wp)
    if not wp or not wp.id then return end
    local existing = workspace:FindFirstChild(MARKER_PREFIX .. wp.id)
    if existing then existing:Destroy() end
    
    local part = Instance.new("Part")
    part.Name = MARKER_PREFIX .. wp.id
    part.Size = Vector3.new(1, 1, 1)
    part.Anchored = true
    part.CanCollide = false
    part.Transparency = 0.5
    part.CastShadow = false
    part.TopSurface = Enum.SurfaceType.Smooth
    part.Position = wp.cframe.Position + Vector3.new(0, 0.6, 0)
    part.Parent = workspace
    
    local bg = Instance.new("BillboardGui")
    bg.Name = "Label"
    bg.Size = UDim2.new(0, 120, 0, 28)
    bg.Adornee = part
    bg.AlwaysOnTop = true
    bg.Parent = part
    
    local label = Instance.new("TextLabel")
    label.Size = UDim2.new(1, 0, 1, 0)
    label.BackgroundTransparency = 1
    label.Text = wp.name
    label.TextColor3 = COLOR_TEXT
    label.Font = Enum.Font.SourceSansBold
    label.TextSize = 14
    label.Parent = bg
    
    local mesh = Instance.new("SpecialMesh", part)
    mesh.MeshType = Enum.MeshType.Sphere
    part.Shape = Enum.PartType.Ball
    
    -- Add a subtle pulsing animation
    local pulse = Instance.new("NumberValue")
    pulse.Name = "Pulse"
    pulse.Value = 1
    pulse.Parent = part
    
    local pulseAnimation = TweenService:Create(
        pulse,
        TweenInfo.new(1.5, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut, -1),
        {Value = 1.2}
    )
    pulseAnimation:Play()
    
    pulse:GetPropertyChangedSignal("Value"):Connect(function()
        part.Size = Vector3.new(pulse.Value, pulse.Value, pulse.Value)
    end)
    
    return part
end

local function removeAllMarkers()
    for _, obj in ipairs(workspace:GetChildren()) do
        if obj:IsA("BasePart") and obj.Name:sub(1, #MARKER_PREFIX) == MARKER_PREFIX then
            obj:Destroy()
        end
    end
end

local function updateMarkers()
    removeAllMarkers()
    if not showMarkers then return end
    for _, wp in ipairs(waypoints) do
        pcall(createMarkerForWaypoint, wp)
    end
end

-- ======= FILE I/O FUNCTIONS =======
local function hasFileAccess()
    return typeof(writefile) == "function" and typeof(readfile) == "function"
end

local function saveWaypointsToFile(filename)
    if not hasFileAccess() then
        return false, "File system not available"
    end
    
    local exportTbl = {}
    for _, w in ipairs(waypoints) do
        local rx, ry, rz = w.cframe:ToOrientation()
        table.insert(exportTbl, {
            id = w.id,
            name = w.name,
            cf = {
                pos = { x = w.cframe.X, y = w.cframe.Y, z = w.cframe.Z },
                orientation = { rx = rx, ry = ry, rz = rz }
            },
            enabled = w.enabled,
            order = w.order,
            seq = w.seq
        })
    end
    
    local ok, json = pcall(function() 
        return HttpService:JSONEncode(exportTbl) 
    end)
    
    if not ok then
        return false, "Failed to encode waypoints"
    end
    
    local success, err = pcall(function() 
        writefile(filename, json) 
    end)
    
    if not success then
        return false, "Write failed: " .. tostring(err)
    end
    
    presets[filename] = { waypoints = exportTbl, savedAt = os.time() }
    return true, "Saved to " .. filename
end

local function loadWaypointsFromFile(filename, replaceExisting)
    if not hasFileAccess() then
        return false, "File system not available"
    end
    
    if not isfile(filename) then
        return false, "File not found"
    end
    
    local success, content = pcall(function() 
        return readfile(filename) 
    end)
    
    if not success then
        return false, "Read failed: " .. tostring(content)
    end
    
    local ok, decoded = pcall(function() 
        return HttpService:JSONDecode(content) 
    end)
    
    if not ok or type(decoded) ~= "table" then
        return false, "Invalid JSON format"
    end
    
    if replaceExisting then
        waypoints = {}
        removeAllMarkers()
    end
    
    local imported = 0
    for _, entry in ipairs(decoded) do
        if type(entry) == "table" and entry.cf and entry.cf.pos then
            local p = entry.cf.pos
            local rx = entry.cf.orientation and entry.cf.orientation.rx or 0
            local ry = entry.cf.orientation and entry.cf.orientation.ry or 0
            local rz = entry.cf.orientation and entry.cf.orientation.rz or 0
            
            if tonumber(p.x) and tonumber(p.y) and tonumber(p.z) then
                local cf = CFrame.new(tonumber(p.x), tonumber(p.y), tonumber(p.z)) * 
                          CFrame.Angles(tonumber(rx), tonumber(ry), tonumber(rz))
                
                local wp = {
                    id = entry.id or generateId(),
                    name = entry.name or ("Area " .. tostring(#waypoints + 1)),
                    cframe = cf,
                    enabled = (entry.enabled ~= false),
                    order = entry.order or (#waypoints + 1),
                    seq = entry.seq or (#waypoints + 1)
                }
                table.insert(waypoints, wp)
                imported = imported + 1
            end
        end
    end
    
    reindexWaypoints()
    updateMarkers()
    return true, ("Imported %d waypoints"):format(imported)
end

-- ======= AUTOSAVE SYSTEM =======
local function scheduleAutosave()
    autosaveVersion = autosaveVersion + 1
    local myVersion = autosaveVersion
    
    task.delay(autosaveDelay, function()
        if myVersion ~= autosaveVersion then return end
        
        if autosaveEnabled and hasFileAccess() then
            local ok, msg = saveWaypointsToFile("untitled_teleporter_autosave.json")
            if ok then
                print("Autosaved waypoints")
            else
                warn("Autosave failed: " .. tostring(msg))
            end
        end
    end)
end

-- ======= GUI CREATION =======
local guiName = "UntitledTeleporterGui"
local screenGui = playerGui:FindFirstChild(guiName)
if screenGui and screenGui:IsA("ScreenGui") then
    screenGui:Destroy()
end

screenGui = Instance.new("ScreenGui")
screenGui.Name = guiName
screenGui.ResetOnSpawn = false
screenGui.Parent = playerGui
screenGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
screenGui.Enabled = true
screenGui.DisplayOrder = 50

-- Main UI Structure
local main = Instance.new("Frame")
main.Name = "Main"
main.Size = UDim2.new(0, 720, 0, 520)
main.Position = UDim2.new(0.5, -360, 0.5, -260)
main.AnchorPoint = Vector2.new(0.5, 0.5)
main.BackgroundColor3 = COLOR_BG
main.BorderSizePixel = 0
main.Parent = screenGui
main.Active = true
Instance.new("UICorner", main).CornerRadius = UDim.new(0, 12)

-- Header (for dragging)
local header = Instance.new("Frame")
header.Name = "Header"
header.Size = UDim2.new(1, 0, 0, 48)
header.Position = UDim2.new(0, 0, 0, 0)
header.BackgroundTransparency = 1
header.Parent = main

local title = Instance.new("TextLabel")
title.Size = UDim2.new(1, -120, 1, 0)
title.Position = UDim2.new(0, 12, 0, 0)
title.BackgroundTransparency = 1
title.Text = "Untitled Teleporter (Enhanced)"
title.Font = Enum.Font.SourceSansBold
title.TextSize = 20
title.TextColor3 = COLOR_TEXT
title.TextXAlignment = Enum.TextXAlignment.Left
title.Parent = header

local minimizeBtn = Instance.new("TextButton")
minimizeBtn.Name = "Minimize"
minimizeBtn.Size = UDim2.new(0, 36, 0, 28)
minimizeBtn.Position = UDim2.new(1, -44, 0, 10)
minimizeBtn.AnchorPoint = Vector2.new(0, 0)
minimizeBtn.Text = "—"
minimizeBtn.Font = Enum.Font.SourceSansBold
minimizeBtn.TextSize = 20
minimizeBtn.BackgroundColor3 = COLOR_BTN
minimizeBtn.TextColor3 = COLOR_TEXT
minimizeBtn.Parent = header
Instance.new("UICorner", minimizeBtn)

local closeBtn = Instance.new("TextButton")
closeBtn.Name = "Close"
closeBtn.Size = UDim2.new(0, 36, 0, 28)
closeBtn.Position = UDim2.new(1, -88, 0, 10)
closeBtn.Text = "X"
closeBtn.Font = Enum.Font.SourceSansBold
closeBtn.TextSize = 18
closeBtn.BackgroundColor3 = COLOR_BTN
closeBtn.TextColor3 = COLOR_TEXT
closeBtn.Parent = header
Instance.new("UICorner", closeBtn)

local hint = Instance.new("TextLabel")
hint.Size = UDim2.new(0, 200, 0, 18)
hint.Position = UDim2.new(0, 12, 0, 28)
hint.BackgroundTransparency = 1
hint.Text = "RightControl toggles visibility • Drag header to move"
hint.TextSize = 12
hint.Font = Enum.Font.SourceSans
hint.TextColor3 = Color3.fromRGB(170,170,170)
hint.TextXAlignment = Enum.TextXAlignment.Left
hint.Parent = header

-- Tabs
local tabNames = {"Set Waypoint", "Teleport", "Automation"}
local tabsFrame = Instance.new("Frame")
tabsFrame.Size = UDim2.new(1, -24, 0, 40)
tabsFrame.Position = UDim2.new(0, 12, 0, 58)
tabsFrame.BackgroundTransparency = 1
tabsFrame.Parent = main

local tabButtons = {}
for i, name in ipairs(tabNames) do
    local btn = Instance.new("TextButton")
    btn.Size = UDim2.new(0, 220, 1, 0)
    btn.Position = UDim2.new(0, (i-1)*228, 0, 0)
    btn.Text = name
    btn.Font = Enum.Font.SourceSans
    btn.TextSize = 16
    btn.BackgroundColor3 = COLOR_BTN
    btn.TextColor3 = COLOR_TEXT
    btn.AutoButtonColor = true
    btn.Parent = tabsFrame
    Instance.new("UICorner", btn)
    tabButtons[i] = btn
end

-- Content Area
local content = Instance.new("Frame")
content.Size = UDim2.new(1, -24, 1, -148)
content.Position = UDim2.new(0, 12, 0, 110)
content.BackgroundColor3 = COLOR_PANEL
content.Parent = main
Instance.new("UICorner", content).CornerRadius = UDim.new(0, 8)

local infoLabel = Instance.new("TextLabel")
infoLabel.Size = UDim2.new(1, -24, 0, 32)
infoLabel.Position = UDim2.new(0, 12, 1, -40)
infoLabel.BackgroundTransparency = 1
infoLabel.Text = "Waypoints are local-only. Teleports may be restricted by the game."
infoLabel.TextWrapped = true
infoLabel.TextColor3 = Color3.fromRGB(200,200,200)
infoLabel.Font = Enum.Font.SourceSans
infoLabel.TextSize = 12
infoLabel.Parent = main

local function setInfo(text)
    infoLabel.Text = tostring(text or "")
end

local function clearContent()
    for _,c in ipairs(content:GetChildren()) do
        c:Destroy()
    end
end

local function createScrollingList(parent, size, pos)
    local scroll = Instance.new("ScrollingFrame")
    scroll.Size = size or UDim2.new(1, -20, 1, -20)
    scroll.Position = pos or UDim2.new(0, 10, 0, 10)
    scroll.BackgroundTransparency = 1
    scroll.ScrollBarThickness = 8
    scroll.AutomaticCanvasSize = Enum.AutomaticSize.Y
    scroll.Parent = parent
    scroll.CanvasSize = UDim2.new(0,0,0,0)
    
    local layout = Instance.new("UIListLayout")
    layout.SortOrder = Enum.SortOrder.LayoutOrder
    layout.Padding = UDim.new(0, 6)
    layout.Parent = scroll
    
    return scroll
end

-- Local references for lists to allow refreshes
local waypointListFrame, teleportListFrame, automationListFrame

-- ======= TAB BUILDERS =======
local function refreshAllTabs()
    if tabButtons[1].BackgroundColor3 == COLOR_BTN_ACTIVE then
        buildSetWaypointTab()
    elseif tabButtons[2].BackgroundColor3 == COLOR_BTN_ACTIVE then
        buildTeleportTab()
    else
        buildAutomationTab()
    end
end

local function buildSetWaypointTab()
    clearContent()
    
    -- Top controls
    local topRow = Instance.new("Frame")
    topRow.Size = UDim2.new(1, -24, 0, 88)
    topRow.Position = UDim2.new(0, 12, 0, 12)
    topRow.BackgroundTransparency = 1
    topRow.Parent = content
    
    local nameBox = Instance.new("TextBox")
    nameBox.Size = UDim2.new(0, 260, 0, 34)
    nameBox.Position = UDim2.new(0, 0, 0, 0)
    nameBox.PlaceholderText = "Waypoint name (optional)"
    nameBox.Text = ""
    nameBox.ClearTextOnFocus = false
    nameBox.Parent = topRow
    
    local addBtn = Instance.new("TextButton")
    addBtn.Size = UDim2.new(0, 180, 0, 34)
    addBtn.Position = UDim2.new(0, 280, 0, 0)
    addBtn.Text = "Add Current Position"
    addBtn.Font = Enum.Font.SourceSans
    addBtn.TextSize = 14
    addBtn.BackgroundColor3 = COLOR_BTN
    addBtn.TextColor3 = COLOR_TEXT
    addBtn.Parent = topRow
    Instance.new("UICorner", addBtn)
    
    local addMouseBtn = Instance.new("TextButton")
    addMouseBtn.Size = UDim2.new(0, 140, 0, 34)
    addMouseBtn.Position = UDim2.new(0, 468, 0, 0)
    addMouseBtn.Text = "Add At Mouse"
    addMouseBtn.Parent = topRow
    Instance.new("UICorner", addMouseBtn)
    
    local exportBtn = Instance.new("TextButton")
    exportBtn.Size = UDim2.new(0, 100, 0, 34)
    exportBtn.Position = UDim2.new(0, 618, 0, 0)
    exportBtn.Text = "Export"
    exportBtn.Parent = topRow
    Instance.new("UICorner", exportBtn)
    
    local importBtn = Instance.new("TextButton")
    importBtn.Size = UDim2.new(0, 100, 0, 34)
    importBtn.Position = UDim2.new(0, 618, 0, 46)
    importBtn.Text = "Import"
    importBtn.Parent = topRow
    Instance.new("UICorner", importBtn)
    
    local markersToggle = Instance.new("TextButton")
    markersToggle.Size = UDim2.new(0, 120, 0, 34)
    markersToggle.Position = UDim2.new(0, 392, 0, 46)
    markersToggle.Text = showMarkers and "Markers: ON" or "Markers: OFF"
    markersToggle.Parent = topRow
    Instance.new("UICorner", markersToggle)
    
    markersToggle.MouseButton1Click:Connect(function()
        showMarkers = not showMarkers
        markersToggle.Text = showMarkers and "Markers: ON" or "Markers: OFF"
        updateMarkers()
    end)
    
    local animateToggle = Instance.new("TextButton")
    animateToggle.Size = UDim2.new(0, 120, 0, 34)
    animateToggle.Position = UDim2.new(0, 280, 0, 46)
    animateToggle.Text = animateTeleport and ("Animate: ON ("..tostring(teleportDuration).."s)") or "Animate: OFF"
    animateToggle.Parent = topRow
    Instance.new("UICorner", animateToggle)
    
    animateToggle.MouseButton1Click:Connect(function()
        animateTeleport = not animateTeleport
        animateToggle.Text = animateTeleport and ("Animate: ON ("..tostring(teleportDuration).."s)") or "Animate: OFF"
    end)
    
    local durationBox = Instance.new("TextBox")
    durationBox.Size = UDim2.new(0, 80, 0, 34)
    durationBox.Position = UDim2.new(0, 408, 0, 0)
    durationBox.PlaceholderText = "Tween s"
    durationBox.Text = tostring(teleportDuration)
    durationBox.ClearTextOnFocus = false
    durationBox.Parent = topRow
    
    durationBox.FocusLost:Connect(function()
        local n = tonumber(durationBox.Text)
        if n and n > 0 then
            teleportDuration = n
            animateToggle.Text = animateTeleport and ("Animate: ON ("..tostring(teleportDuration).."s)") or "Animate: OFF"
        else
            durationBox.Text = tostring(teleportDuration)
        end
    end)
    
    -- File Management Section
    local fileSection = Instance.new("Frame")
    fileSection.Size = UDim2.new(1, -24, 0, 120)
    fileSection.Position = UDim2.new(0, 12, 0, 108)
    fileSection.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
    fileSection.Parent = content
    Instance.new("UICorner", fileSection).CornerRadius = UDim.new(0, 8)
    
    local fileTitle = Instance.new("TextLabel")
    fileTitle.Size = UDim2.new(1, 0, 0, 24)
    fileTitle.Position = UDim2.new(0, 0, 0, 0)
    fileTitle.BackgroundTransparency = 1
    fileTitle.Text = "File Management"
    fileTitle.TextColor3 = COLOR_TEXT
    fileTitle.Font = Enum.Font.SourceSansBold
    fileTitle.TextSize = 16
    fileTitle.Parent = fileSection
    
    local filenameBox = Instance.new("TextBox")
    filenameBox.Size = UDim2.new(1, -24, 0, 30)
    filenameBox.Position = UDim2.new(0, 12, 0, 32)
    filenameBox.PlaceholderText = "Enter filename (e.g., waypoints.json)"
    filenameBox.Text = "waypoints.json"
    filenameBox.ClearTextOnFocus = false
    filenameBox.Parent = fileSection
    
    local fileButtonRow = Instance.new("Frame")
    fileButtonRow.Size = UDim2.new(1, -24, 0, 40)
    fileButtonRow.Position = UDim2.new(0, 12, 0, 72)
    fileButtonRow.BackgroundTransparency = 1
    fileButtonRow.Parent = fileSection
    
    local saveFileBtn = Instance.new("TextButton")
    saveFileBtn.Size = UDim2.new(0, 100, 1, 0)
    saveFileBtn.Position = UDim2.new(0, 0, 0, 0)
    saveFileBtn.Text = "Save"
    saveFileBtn.Parent = fileButtonRow
    Instance.new("UICorner", saveFileBtn)
    
    local loadFileBtn = Instance.new("TextButton")
    loadFileBtn.Size = UDim2.new(0, 100, 1, 0)
    loadFileBtn.Position = UDim2.new(0, 110, 0, 0)
    loadFileBtn.Text = "Load"
    loadFileBtn.Parent = fileButtonRow
    Instance.new("UICorner", loadFileBtn)
    
    local saveStatus = Instance.new("TextLabel")
    saveStatus.Size = UDim2.new(0, 120, 1, 0)
    saveStatus.Position = UDim2.new(0, 220, 0, 0)
    saveStatus.BackgroundTransparency = 1
    saveStatus.Text = ""
    saveStatus.TextColor3 = COLOR_TEXT
    saveStatus.Font = Enum.Font.SourceSans
    saveStatus.TextSize = 14
    saveStatus.Parent = fileButtonRow
    
    -- Autosave Settings
    local autosaveSection = Instance.new("Frame")
    autosaveSection.Size = UDim2.new(1, -24, 0, 60)
    autosaveSection.Position = UDim2.new(0, 12, 0, 240)
    autosaveSection.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
    autosaveSection.Parent = content
    Instance.new("UICorner", autosaveSection).CornerRadius = UDim.new(0, 8)
    
    local autosaveTitle = Instance.new("TextLabel")
    autosaveTitle.Size = UDim2.new(1, 0, 0, 24)
    autosaveTitle.Position = UDim2.new(0, 0, 0, 0)
    autosaveTitle.BackgroundTransparency = 1
    autosaveTitle.Text = "Autosave Settings"
    autosaveTitle.TextColor3 = COLOR_TEXT
    autosaveTitle.Font = Enum.Font.SourceSansBold
    autosaveTitle.TextSize = 16
    autosaveTitle.Parent = autosaveSection
    
    local autosaveToggle = Instance.new("TextButton")
    autosaveToggle.Size = UDim2.new(0, 120, 0, 30)
    autosaveToggle.Position = UDim2.new(0, 12, 0, 30)
    autosaveToggle.Text = "Autosave: " .. (autosaveEnabled and "ON" or "OFF")
    autosaveToggle.BackgroundColor3 = autosaveEnabled and COLOR_ON or COLOR_OFF
    autosaveToggle.Parent = autosaveSection
    Instance.new("UICorner", autosaveToggle)
    
    autosaveToggle.MouseButton1Click:Connect(function()
        autosaveEnabled = not autosaveEnabled
        autosaveToggle.Text = "Autosave: " .. (autosaveEnabled and "ON" or "OFF")
        autosaveToggle.BackgroundColor3 = autosaveEnabled and COLOR_ON or COLOR_OFF
        if autosaveEnabled then
            setInfo("Autosave enabled")
        else
            setInfo("Autosave disabled")
        end
    end)
    
    local autosaveNowBtn = Instance.new("TextButton")
    autosaveNowBtn.Size = UDim2.new(0, 100, 0, 30)
    autosaveNowBtn.Position = UDim2.new(0, 140, 0, 30)
    autosaveNowBtn.Text = "Save Now"
    autosaveNowBtn.Parent = autosaveSection
    Instance.new("UICorner", autosaveNowBtn)
    
    autosaveNowBtn.MouseButton1Click:Connect(function()
        local filename = filenameBox.Text or "waypoints.json"
        local success, msg = saveWaypointsToFile(filename)
        saveStatus.Text = success and "Saved!" or "Save failed"
        saveStatus.TextColor3 = success and COLOR_ON or COLOR_OFF
        setInfo(msg)
    end)
    
    -- JSON Box
    local jsonBox = Instance.new("TextBox")
    jsonBox.Size = UDim2.new(1, -24, 0, 120)
    jsonBox.Position = UDim2.new(0, 12, 0, 312)
    jsonBox.MultiLine = true
    jsonBox.ClearTextOnFocus = false
    jsonBox.Text = ""
    jsonBox.PlaceholderText = "Exported JSON appears here. Paste JSON then press Import."
    jsonBox.TextWrapped = true
    jsonBox.Parent = content
    Instance.new("UICorner", jsonBox)
    
    -- Waypoint List
    waypointListFrame = createScrollingList(content, UDim2.new(1, -24, 1, -452), UDim2.new(0, 12, 0, 444))
    
    local function refreshWaypointList()
        waypointListFrame:ClearAllChildren()
        reindexWaypoints()
        for i, wp in ipairs(waypoints) do
            local frame = Instance.new("Frame")
            frame.Size = UDim2.new(1, -12, 0, 56)
            frame.BackgroundTransparency = 1
            frame.LayoutOrder = i
            frame.Parent = waypointListFrame
            
            -- name text box
            local nameBoxLocal = Instance.new("TextBox")
            nameBoxLocal.Size = UDim2.new(0.46, 0, 1, 0)
            nameBoxLocal.Position = UDim2.new(0, 0, 0, 0)
            nameBoxLocal.Text = wp.name
            nameBoxLocal.ClearTextOnFocus = false
            nameBoxLocal.Parent = frame
            
            nameBoxLocal.FocusLost:Connect(function()
                if nameBoxLocal.Text ~= "" then
                    wp.name = nameBoxLocal.Text
                    scheduleSave() -- Use debounced save
                else
                    nameBoxLocal.Text = wp.name
                end
                updateMarkers()
                refreshAllTabs()
            end)
            
            -- Teleport button
            local tpBtn = Instance.new("TextButton")
            tpBtn.Size = UDim2.new(0, 100, 0, 34)
            tpBtn.Position = UDim2.new(0.5, 8, 0.12, 0)
            tpBtn.Text = "Teleport"
            tpBtn.Parent = frame
            Instance.new("UICorner", tpBtn)
            
            tpBtn.MouseButton1Click:Connect(function()
                local ok, msg = teleportToCFrame(wp.cframe, {animate = animateTeleport, duration = teleportDuration})
                setInfo(msg)
            end)
            
            -- Duplicate button
            local dupBtn = Instance.new("TextButton")
            dupBtn.Size = UDim2.new(0, 70, 0, 30)
            dupBtn.Position = UDim2.new(0.72, 8, 0.12, 0)
            dupBtn.Text = "Dup"
            dupBtn.Parent = frame
            Instance.new("UICorner", dupBtn)
            
            dupBtn.MouseButton1Click:Connect(function()
                local newWp = {
                    id = generateId(),
                    name = wp.name .. " (copy)",
                    cframe = wp.cframe,
                    enabled = wp.enabled,
                    order = #waypoints + 1,
                    seq = wp.seq
                }
                table.insert(waypoints, newWp)
                reindexWaypoints()
                refreshWaypointList()
                updateMarkers()
                scheduleSave()
                setInfo(("Duplicated '%s'"):format(wp.name))
            end)
            
            -- Up/Down reorder
            local upBtn = Instance.new("TextButton")
            upBtn.Size = UDim2.new(0, 36, 0, 30)
            upBtn.Position = UDim2.new(0.81, 8, 0.12, 0)
            upBtn.Text = "▲"
            upBtn.Parent = frame
            Instance.new("UICorner", upBtn)
            
            upBtn.MouseButton1Click:Connect(function()
                if i > 1 then
                    waypoints[i], waypoints[i-1] = waypoints[i-1], waypoints[i]
                    reindexWaypoints()
                    refreshAllTabs()
                    scheduleSave()
                end
            end)
            
            local downBtn = Instance.new("TextButton")
            downBtn.Size = UDim2.new(0, 36, 0, 30)
            downBtn.Position = UDim2.new(0.81, 52, 0.12, 0)
            downBtn.Text = "▼"
            downBtn.Parent = frame
            Instance.new("UICorner", downBtn)
            
            downBtn.MouseButton1Click:Connect(function()
                if i < #waypoints then
                    waypoints[i], waypoints[i+1] = waypoints[i+1], waypoints[i]
                    reindexWaypoints()
                    refreshAllTabs()
                    scheduleSave()
                end
            end)
            
            -- Delete with confirmation
            local delBtn = Instance.new("TextButton")
            delBtn.Size = UDim2.new(0, 100, 0, 34)
            delBtn.Position = UDim2.new(0.9, -8, 0.12, 0)
            delBtn.Text = "Delete"
            delBtn.Parent = frame
            Instance.new("UICorner", delBtn)
            
            delBtn.MouseButton1Click:Connect(function()
                -- small confirm overlay
                local confirm = Instance.new("Frame")
                confirm.Size = UDim2.new(0, 300, 0, 100)
                confirm.Position = UDim2.new(0.5, -150, 0.5, -50)
                confirm.BackgroundColor3 = Color3.fromRGB(24,24,24)
                confirm.Parent = screenGui
                Instance.new("UICorner", confirm).CornerRadius = UDim.new(0, 8)
                
                local label = Instance.new("TextLabel")
                label.Size = UDim2.new(1, -20, 0, 48)
                label.Position = UDim2.new(0, 10, 0, 8)
                label.BackgroundTransparency = 1
                label.Text = ("Delete waypoint '%s'?"):format(wp.name)
                label.TextWrapped = true
                label.TextColor3 = COLOR_TEXT
                label.Parent = confirm
                
                local yes = Instance.new("TextButton")
                yes.Size = UDim2.new(0, 120, 0, 36)
                yes.Position = UDim2.new(0.5, -130, 1, -44)
                yes.Text = "Yes"
                yes.BackgroundColor3 = COLOR_ON
                yes.Parent = confirm
                Instance.new("UICorner", yes)
                
                local no = Instance.new("TextButton")
                no.Size = UDim2.new(0, 120, 0, 36)
                no.Position = UDim2.new(0.5, 10, 1, -44)
                no.Text = "No"
                no.BackgroundColor3 = COLOR_BTN
                no.Parent = confirm
                Instance.new("UICorner", no)
                
                yes.MouseButton1Click:Connect(function()
                    for idx, entry in ipairs(waypoints) do
                        if entry.id == wp.id then
                            table.remove(waypoints, idx)
                            break
                        end
                    end
                    confirm:Destroy()
                    refreshWaypointList()
                    refreshAllTabs()
                    updateMarkers()
                    scheduleSave()
                    setInfo(("Deleted waypoint '%s'"):format(wp.name))
                end)
                
                no.MouseButton1Click:Connect(function()
                    confirm:Destroy()
                end)
            end)
        end
    end
    
    -- Event Handlers
    addBtn.MouseButton1Click:Connect(function()
        local hrp = getHRP()
        if not hrp then
            setInfo("Unable to record position: character not present.")
            return
        end
        
        local givenName = nameBox.Text
        local wp = {
            id = generateId(),
            name = (givenName ~= "" and givenName) or ("Area " .. tostring(#waypoints + 1)),
            cframe = hrp.CFrame,
            enabled = true,
            order = #waypoints + 1,
            seq = #waypoints + 1
        }
        table.insert(waypoints, wp)
        reindexWaypoints()
        nameBox.Text = ""
        refreshWaypointList()
        refreshAllTabs()
        updateMarkers()
        scheduleSave()
        setInfo(("Added waypoint '%s'"):format(wp.name))
    end)
    
    addMouseBtn.MouseButton1Click:Connect(function()
        local hit = mouse.Hit
        if not hit then
            setInfo("Unable to get mouse position.")
            return
        end
        
        local givenName = nameBox.Text
        local wp = {
            id = generateId(),
            name = (givenName ~= "" and givenName) or ("Mouse " .. tostring(#waypoints + 1)),
            cframe = CFrame.new(hit.Position),
            enabled = true,
            order = #waypoints + 1,
            seq = #waypoints + 1
        }
        table.insert(waypoints, wp)
        reindexWaypoints()
        nameBox.Text = ""
        refreshWaypointList()
        refreshAllTabs()
        updateMarkers()
        scheduleSave()
        setInfo(("Added waypoint at mouse position '%s'"):format(wp.name))
    end)
    
    exportBtn.MouseButton1Click:Connect(function()
        local exportTbl = {}
        for _, w in ipairs(waypoints) do
            local rx, ry, rz = w.cframe:ToOrientation()
            table.insert(exportTbl, {
                id = w.id,
                name = w.name,
                cf = {
                    pos = { x = w.cframe.X, y = w.cframe.Y, z = w.cframe.Z },
                    orientation = { rx = rx, ry = ry, rz = rz }
                },
                enabled = w.enabled,
                order = w.order,
                seq = w.seq
            })
        end
        
        local ok, json = pcall(function() return HttpService:JSONEncode(exportTbl) end)
        if ok then
            jsonBox.Text = json
            setInfo("Exported waypoints JSON to box")
        else
            setInfo("Failed to encode waypoints for export.")
        end
    end)
    
    importBtn.MouseButton1Click:Connect(function()
        local txt = jsonBox.Text
        if not txt or txt == "" then
            setInfo("Paste JSON into the box before pressing Import.")
            return
        end
        
        local ok, decoded = pcall(function() return HttpService:JSONDecode(txt) end)
        if not ok or type(decoded) ~= "table" then
            setInfo("Invalid JSON for import.")
            return
        end
        
        local imported = 0
        for _, entry in ipairs(decoded) do
            if type(entry) == "table" and entry.cf and entry.cf.pos then
                local p = entry.cf.pos
                local rx = entry.cf.orientation and entry.cf.orientation.rx or 0
                local ry = entry.cf.orientation and entry.cf.orientation.ry or 0
                local rz = entry.cf.orientation and entry.cf.orientation.rz or 0
                
                if tonumber(p.x) and tonumber(p.y) and tonumber(p.z) then
                    local cf = CFrame.new(tonumber(p.x), tonumber(p.y), tonumber(p.z)) * 
                              CFrame.Angles(tonumber(rx), tonumber(ry), tonumber(rz))
                    
                    local wp = {
                        id = entry.id or generateId(),
                        name = entry.name or ("Area " .. tostring(#waypoints + 1)),
                        cframe = cf,
                        enabled = (entry.enabled ~= false),
                        order = entry.order or (#waypoints + 1),
                        seq = entry.seq or (#waypoints + 1)
                    }
                    table.insert(waypoints, wp)
                    imported = imported + 1
                end
            end
        end
        
        reindexWaypoints()
        refreshWaypointList()
        updateMarkers()
        scheduleSave()
        setInfo(("Imported %d waypoint(s)."):format(imported))
    end)
    
    saveFileBtn.MouseButton1Click:Connect(function()
        local filename = filenameBox.Text or "waypoints.json"
        local success, msg = saveWaypointsToFile(filename)
        saveStatus.Text = success and "Saved!" or "Save failed"
        saveStatus.TextColor3 = success and COLOR_ON or COLOR_OFF
        setInfo(msg)
    end)
    
    loadFileBtn.MouseButton1Click:Connect(function()
        local filename = filenameBox.Text or "waypoints.json"
        local success, msg = loadWaypointsFromFile(filename, false) -- false = append to existing
        if success then
            refreshWaypointList()
            refreshAllTabs()
            saveStatus.Text = "Loaded!"
            saveStatus.TextColor3 = COLOR_ON
        else
            saveStatus.Text = "Load failed"
            saveStatus.TextColor3 = COLOR_OFF
        end
        setInfo(msg)
    end)
    
    refreshWaypointList()
end

function buildTeleportTab()
    clearContent()
    teleportListFrame = createScrollingList(content, UDim2.new(1, -24, 1, -24), UDim2.new(0, 12, 0, 12))
    
    reindexWaypoints()
    for i, wp in ipairs(waypoints) do
        local frame = Instance.new("Frame")
        frame.Size = UDim2.new(1, -12, 0, 48)
        frame.BackgroundTransparency = 1
        frame.LayoutOrder = i
        frame.Parent = teleportListFrame
        
        local nameLabel = Instance.new("TextLabel")
        nameLabel.Size = UDim2.new(0.46, 0, 1, 0)
        nameLabel.Text = wp.name
        nameLabel.BackgroundTransparency = 1
        nameLabel.TextColor3 = COLOR_TEXT
        nameLabel.TextXAlignment = Enum.TextXAlignment.Left
        nameLabel.Parent = frame
        
        local tpBtn = Instance.new("TextButton")
        tpBtn.Size = UDim2.new(0, 120, 0, 34)
        tpBtn.Position = UDim2.new(0.5, 8, 0.12, 0)
        tpBtn.Text = "Teleport"
        tpBtn.Parent = frame
        Instance.new("UICorner", tpBtn)
        
        tpBtn.MouseButton1Click:Connect(function()
            local ok, msg = teleportToCFrame(wp.cframe, {animate = animateTeleport, duration = teleportDuration})
            setInfo(msg)
        end)
        
        local dupBtn = Instance.new("TextButton")
        dupBtn.Size = UDim2.new(0, 70, 0, 30)
        dupBtn.Position = UDim2.new(0.72, 8, 0.12, 0)
        dupBtn.Text = "Dup"
        dupBtn.Parent = frame
        Instance.new("UICorner", dupBtn)
        
        dupBtn.MouseButton1Click:Connect(function()
            local newWp = {
                id = generateId(),
                name = wp.name .. " (copy)",
                cframe = wp.cframe,
                enabled = wp.enabled,
                order = #waypoints + 1,
                seq = wp.seq
            }
            table.insert(waypoints, newWp)
            reindexWaypoints()
            buildTeleportTab()
            updateMarkers()
            scheduleSave()
            setInfo(("Duplicated '%s'"):format(wp.name))
        end)
        
        local delBtn = Instance.new("TextButton")
        delBtn.Size = UDim2.new(0, 120, 0, 34)
        delBtn.Position = UDim2.new(0.5, 136, 0.12, 0)
        delBtn.Text = "Delete"
        delBtn.Parent = frame
        Instance.new("UICorner", delBtn)
        
        delBtn.MouseButton1Click:Connect(function()
            -- small confirm overlay
            local confirm = Instance.new("Frame")
            confirm.Size = UDim2.new(0, 300, 0, 100)
            confirm.Position = UDim2.new(0.5, -150, 0.5, -50)
            confirm.BackgroundColor3 = Color3.fromRGB(24,24,24)
            confirm.Parent = screenGui
            Instance.new("UICorner", confirm).CornerRadius = UDim.new(0, 8)
            
            local label = Instance.new("TextLabel")
            label.Size = UDim2.new(1, -20, 0, 48)
            label.Position = UDim2.new(0, 10, 0, 8)
            label.BackgroundTransparency = 1
            label.Text = ("Delete waypoint '%s'?"):format(wp.name)
            label.TextWrapped = true
            label.TextColor3 = COLOR_TEXT
            label.Parent = confirm
            
            local yes = Instance.new("TextButton")
            yes.Size = UDim2.new(0, 120, 0, 36)
            yes.Position = UDim2.new(0.5, -130, 1, -44)
            yes.Text = "Yes"
            yes.BackgroundColor3 = COLOR_ON
            yes.Parent = confirm
            Instance.new("UICorner", yes)
            
            local no = Instance.new("TextButton")
            no.Size = UDim2.new(0, 120, 0, 36)
            no.Position = UDim2.new(0.5, 10, 1, -44)
            no.Text = "No"
            no.BackgroundColor3 = COLOR_BTN
            no.Parent = confirm
            Instance.new("UICorner", no)
            
            yes.MouseButton1Click:Connect(function()
                for idx, entry in ipairs(waypoints) do
                    if entry.id == wp.id then
                        table.remove(waypoints, idx)
                        break
                    end
                end
                confirm:Destroy()
                reindexWaypoints()
                buildTeleportTab()
                updateMarkers()
                scheduleSave()
                setInfo(("Deleted waypoint '%s'"):format(wp.name))
            end)
            
            no.MouseButton1Click:Connect(function()
                confirm:Destroy()
            end)
        end)
    end
end

function buildAutomationTab()
    clearContent()
    
    -- header controls
    local headerRow = Instance.new("Frame")
    headerRow.Size = UDim2.new(1, -24, 0, 48)
    headerRow.Position = UDim2.new(0, 12, 0, 12)
    headerRow.BackgroundTransparency = 1
    headerRow.Parent = content
    
    local toggleBtn = Instance.new("TextButton")
    toggleBtn.Size = UDim2.new(0, 160, 1, 0)
    toggleBtn.Position = UDim2.new(0, 0, 0, 0)
    toggleBtn.Text = "Automation: OFF"
    toggleBtn.BackgroundColor3 = COLOR_OFF
    toggleBtn.Parent = headerRow
    Instance.new("UICorner", toggleBtn)
    
    local delayBox = Instance.new("TextBox")
    delayBox.Size = UDim2.new(0, 100, 1, 0)
    delayBox.Position = UDim2.new(0, 176, 0, 0)
    delayBox.PlaceholderText = "Delay (sec)"
    delayBox.Text = "2"
    delayBox.ClearTextOnFocus = false
    delayBox.Parent = headerRow
    
    local startBox = Instance.new("TextBox")
    startBox.Size = UDim2.new(0, 110, 1, 0)
    startBox.Position = UDim2.new(0, 292, 0, 0)
    startBox.PlaceholderText = "Start index (1..N)"
    startBox.Text = "1"
    startBox.ClearTextOnFocus = false
    startBox.Parent = headerRow
    
    local shuffleBtn = Instance.new("TextButton")
    shuffleBtn.Size = UDim2.new(0, 120, 1, 0)
    shuffleBtn.Position = UDim2.new(0, 420, 0, 0)
    shuffleBtn.Text = "Shuffle: OFF"
    shuffleBtn.BackgroundColor3 = COLOR_BTN
    shuffleBtn.Parent = headerRow
    Instance.new("UICorner", shuffleBtn)
    
    shuffleBtn.MouseButton1Click:Connect(function()
        shuffleMode = not shuffleMode
        shuffleBtn.Text = "Shuffle: " .. (shuffleMode and "ON" or "OFF")
        shuffleBtn.BackgroundColor3 = shuffleMode and COLOR_ON or COLOR_BTN
    end)
    
    local wrapToggle = Instance.new("TextButton")
    wrapToggle.Size = UDim2.new(0, 120, 1, 0)
    wrapToggle.Position = UDim2.new(0, 550, 0, 0)
    wrapToggle.Text = "Wrap: ON"
    wrapToggle.BackgroundColor3 = COLOR_BTN
    wrapToggle.Parent = headerRow
    Instance.new("UICorner", wrapToggle)
    
    wrapToggle.MouseButton1Click:Connect(function()
        wrapEnabled = not wrapEnabled
        wrapToggle.Text = "Wrap: " .. (wrapEnabled and "ON" or "OFF")
    end)
    
    local pauseBtn = Instance.new("TextButton")
    pauseBtn.Size = UDim2.new(0, 100, 1, 0)
    pauseBtn.Position = UDim2.new(0, 680, 0, 0)
    pauseBtn.Text = "Pause"
    pauseBtn.BackgroundColor3 = COLOR_WARNING
    pauseBtn.Parent = headerRow
    Instance.new("UICorner", pauseBtn)
    pauseBtn.Visible = false
    
    pauseBtn.MouseButton1Click:Connect(function()
        if automationRunning then
            automationPaused = not automationPaused
            pauseBtn.Text = automationPaused and "Resume" or "Pause"
            setInfo(automationPaused and "Automation paused" or "Automation resumed")
        end
    end)
    
    local function updateToggleVisual(on)
        toggleBtn.Text = "Automation: " .. (on and "ON" or "OFF")
        toggleBtn.BackgroundColor3 = on and COLOR_ON or COLOR_OFF
        pauseBtn.Visible = on
    end
    
    -- automation runner
    local function runAutomation()
        if automationTask then return end
        automationStopRequested = false
        automationPaused = false
        
        automationTask = task.spawn(function()
            while not automationStopRequested do
                -- collect enabled waypoints and sort by order
                local enabled = {}
                for _, w in ipairs(waypoints) do
                    if w.enabled then
                        table.insert(enabled, w)
                    end
                end
                
                if #enabled == 0 then
                    setInfo("No enabled waypoints for automation. Stopping.")
                    automationRunning = false
                    updateToggleVisual(false)
                    break
                end
                
                if shuffleMode then
                    -- Fisher-Yates shuffle a copy
                    local shuffled = {}
                    for i=1,#enabled do shuffled[i] = enabled[i] end
                    for i = #shuffled, 2, -1 do
                        local j = math.random(1, i)
                        shuffled[i], shuffled[j] = shuffled[j], shuffled[i]
                    end
                    enabled = shuffled
                else
                    table.sort(enabled, function(a,b) 
                        local aSeq = tonumber(a.seq) or 0
                        local bSeq = tonumber(b.seq) or 0
                        if aSeq ~= bSeq then
                            return aSeq < bSeq
                        end
                        return (a.order or 0) < (b.order or 0) 
                    end)
                end
                
                local delay = math.max(0.1, tonumber(delayBox.Text) or 1)
                local startIndex = math.clamp(tonumber(startBox.Text) or 1, 1, #enabled)
                
                -- iterate from startIndex for full cycle
                for offset = 0, (#enabled - 1) do
                    if automationStopRequested then break end
                    
                    -- Wait if paused
                    while automationPaused and not automationStopRequested do
                        RunService.Heartbeat:Wait()
                    end
                    
                    if automationStopRequested then break end
                    
                    local idx = ((startIndex - 1 + offset) % #enabled) + 1
                    local target = enabled[idx]
                    if target and target.enabled then
                        local ok, msg = teleportToCFrame(target.cframe, {animate = animateTeleport, duration = teleportDuration})
                        setInfo(("Auto -> %s : %s"):format(target.name, msg))
                    end
                    
                    -- wait "delay" seconds but exit early if stopped
                    local elapsed = 0
                    while elapsed < delay do
                        if automationStopRequested then break end
                        if not automationPaused then
                            elapsed += RunService.Heartbeat:Wait()
                        else
                            RunService.Heartbeat:Wait()
                        end
                    end
                end
                
                if not wrapEnabled then
                    break
                end
            end
            
            automationTask = nil
            automationRunning = false
            automationPaused = false
            updateToggleVisual(false)
            setInfo("Automation stopped.")
        end)
    end
    
    toggleBtn.MouseButton1Click:Connect(function()
        automationRunning = not automationRunning
        updateToggleVisual(automationRunning)
        if automationRunning then
            runAutomation()
            setInfo("Automation started.")
        else
            automationStopRequested = true
            setInfo("Automation stopping...")
        end
    end)
    
    -- list of waypoints for automation
    local function refreshAutomationList()
        if automationListFrame then automationListFrame:Destroy() end
        automationListFrame = createScrollingList(content, UDim2.new(1, -24, 1, -84), UDim2.new(0, 12, 0, 72))
        reindexWaypoints()
        for i, wp in ipairs(waypoints) do
            local frame = Instance.new("Frame")
            frame.Size = UDim2.new(1, -12, 0, 64)
            frame.BackgroundTransparency = 1
            frame.LayoutOrder = i
            frame.Parent = automationListFrame
            
            local nameLabel = Instance.new("TextLabel")
            nameLabel.Size = UDim2.new(0.36, 0, 1, 0)
            nameLabel.Text = wp.name
            nameLabel.BackgroundTransparency = 1
            nameLabel.TextColor3 = COLOR_TEXT
            nameLabel.TextXAlignment = Enum.TextXAlignment.Left
            nameLabel.Parent = frame
            
            local toggle = Instance.new("TextButton")
            toggle.Size = UDim2.new(0, 64, 0, 34)
            toggle.Position = UDim2.new(0.36, 8, 0.08, 0)
            toggle.Text = wp.enabled and "ON" or "OFF"
            toggle.BackgroundColor3 = wp.enabled and COLOR_ON or COLOR_OFF
            toggle.Parent = frame
            Instance.new("UICorner", toggle)
            
            toggle.MouseButton1Click:Connect(function()
                wp.enabled = not wp.enabled
                toggle.Text = wp.enabled and "ON" or "OFF"
                toggle.BackgroundColor3 = wp.enabled and COLOR_ON or COLOR_OFF
                scheduleSave()
            end)
            
            local orderBox = Instance.new("TextBox")
            orderBox.Size = UDim2.new(0, 80, 0, 34)
            orderBox.Position = UDim2.new(0.36, 84, 0.08, 0)
            orderBox.Text = tostring(wp.order or i)
            orderBox.ClearTextOnFocus = false
            orderBox.Parent = frame
            
            orderBox.FocusLost:Connect(function()
                local n = tonumber(orderBox.Text)
                if n then
                    wp.order = math.floor(math.max(0, n))
                    refreshAutomationList()
                else
                    orderBox.Text = tostring(wp.order or i)
                end
            end)
            
            local seqBox = Instance.new("TextBox")
            seqBox.Size = UDim2.new(0, 80, 0, 34)
            seqBox.Position = UDim2.new(0.36, 172, 0.08, 0)
            seqBox.Text = tostring(wp.seq or i)
            seqBox.PlaceholderText = "Seq #"
            seqBox.ClearTextOnFocus = false
            seqBox.Parent = frame
            
            seqBox.FocusLost:Connect(function()
                local n = tonumber(seqBox.Text)
                if n then
                    wp.seq = math.floor(math.max(0, n))
                    scheduleSave()
                else
                    seqBox.Text = tostring(wp.seq or i)
                end
            end)
            
            local tpBtn = Instance.new("TextButton")
            tpBtn.Size = UDim2.new(0, 96, 0, 34)
            tpBtn.Position = UDim2.new(0.62, 8, 0.08, 0)
            tpBtn.Text = "Teleport"
            tpBtn.Parent = frame
            Instance.new("UICorner", tpBtn)
            
            tpBtn.MouseButton1Click:Connect(function()
                local ok, msg = teleportToCFrame(wp.cframe, {animate = animateTeleport, duration = teleportDuration})
                setInfo(msg)
            end)
            
            local delBtn = Instance.new("TextButton")
            delBtn.Size = UDim2.new(0, 96, 0, 34)
            delBtn.Position = UDim2.new(0.82, -8, 0.08, 0)
            delBtn.Text = "Delete"
            delBtn.Parent = frame
            Instance.new("UICorner", delBtn)
            
            delBtn.MouseButton1Click:Connect(function()
                -- small confirm overlay
                local confirm = Instance.new("Frame")
                confirm.Size = UDim2.new(0, 300, 0, 100)
                confirm.Position = UDim2.new(0.5, -150, 0.5, -50)
                confirm.BackgroundColor3 = Color3.fromRGB(24,24,24)
                confirm.Parent = screenGui
                Instance.new("UICorner", confirm).CornerRadius = UDim.new(0, 8)
                
                local label = Instance.new("TextLabel")
                label.Size = UDim2.new(1, -20, 0, 48)
                label.Position = UDim2.new(0, 10, 0, 8)
                label.BackgroundTransparency = 1
                label.Text = ("Delete waypoint '%s'?"):format(wp.name)
                label.TextWrapped = true
                label.TextColor3 = COLOR_TEXT
                label.Parent = confirm
                
                local yes = Instance.new("TextButton")
                yes.Size = UDim2.new(0, 120, 0, 36)
                yes.Position = UDim2.new(0.5, -130, 1, -44)
                yes.Text = "Yes"
                yes.BackgroundColor3 = COLOR_ON
                yes.Parent = confirm
                Instance.new("UICorner", yes)
                
                local no = Instance.new("TextButton")
                no.Size = UDim2.new(0, 120, 0, 36)
                no.Position = UDim2.new(0.5, 10, 1, -44)
                no.Text = "No"
                no.BackgroundColor3 = COLOR_BTN
                no.Parent = confirm
                Instance.new("UICorner", no)
                
                yes.MouseButton1Click:Connect(function()
                    for idx, entry in ipairs(waypoints) do
                        if entry.id == wp.id then
                            table.remove(waypoints, idx)
                            break
                        end
                    end
                    confirm:Destroy()
                    refreshAutomationList()
                    buildSetWaypointTab()
                    buildTeleportTab()
                    updateMarkers()
                    scheduleSave()
                    setInfo(("Deleted waypoint '%s'"):format(wp.name))
                end)
                
                no.MouseButton1Click:Connect(function()
                    confirm:Destroy()
                end)
            end)
        end
    end
    
    refreshAutomationList()
end

-- ======= TAB SWITCHING =======
for i, btn in ipairs(tabButtons) do
    btn.MouseButton1Click:Connect(function()
        for _, b in ipairs(tabButtons) do b.BackgroundColor3 = COLOR_BTN end
        btn.BackgroundColor3 = COLOR_BTN_ACTIVE
        if i == 1 then buildSetWaypointTab() elseif i == 2 then buildTeleportTab() else buildAutomationTab() end
    end)
end

-- default
tabButtons[1].BackgroundColor3 = COLOR_BTN_ACTIVE
buildSetWaypointTab()

-- ======= MAKE UI DRAGGABLE (HEADER) =======
local dragging, dragStart, startPos, dragConnection
local function updateDrag(input)
    local delta = input.Position - dragStart
    local newPos = startPos + UDim2.new(0, delta.X, 0, delta.Y)
    main.Position = newPos
end

header.InputBegan:Connect(function(input)
    if input.UserInputType == Enum.UserInputType.MouseButton1 then
        dragging = true
        dragStart = input.Position
        startPos = main.Position
        input.Changed:Connect(function()
            if input.UserInputState == Enum.UserInputState.End then
                dragging = false
            end
        end)
    end
end)

UserInputService.InputChanged:Connect(function(input)
    if dragging and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then
        updateDrag(input)
    end
end)

-- ======= MINIMIZE / CLOSE / HOTKEY =======
minimizeBtn.MouseButton1Click:Connect(function()
    minimized = not minimized
    content.Visible = not minimized
    tabsFrame.Visible = not minimized
    hint.Visible = not minimized
    main.Size = minimized and UDim2.new(0, 300, 0, 68) or UDim2.new(0, 720, 0, 520)
    minimizeBtn.Text = minimized and "+" or "—"
end)

closeBtn.MouseButton1Click:Connect(function()
    screenGui.Enabled = false
end)

-- Toggle visibility by RightControl (can change key if desired)
UserInputService.InputBegan:Connect(function(input, gameProcessed)
    if gameProcessed then return end
    if input.KeyCode == HOTKEY_TOGGLE then
        screenGui.Enabled = not screenGui.Enabled
    end
    -- Alternative hotkey: Ctrl+T
    if input.KeyCode == HOTKEY_ALT_TOGGLE and 
       (UserInputService:IsKeyDown(Enum.KeyCode.LeftControl) or 
        UserInputService:IsKeyDown(Enum.KeyCode.RightControl)) then
        screenGui.Enabled = not screenGui.Enabled
    end
end)

-- ======= CLEANUP ON CHARACTER REMOVAL =======
player.CharacterRemoving:Connect(function()
    -- stop automation safely
    automationStopRequested = true
    automationRunning = false
    if automationTask then
        -- allow the task to finish naturally
    end
end)

-- ======= FINAL =======
setInfo("Untitled Teleporter Enhanced ready. Drag header to move UI. RightControl toggles visibility.")
print("Untitled Teleporter Enhanced loaded successfully!")

Features & Information

manual save waypoints (auto save dosent work) auto teleport to waypoints make waypoints teleport to waypoints manually (u can auto and manual) no key fully made by ai (ChatGPT 5 Think longer tool + GLM 4.5) bugs kinda suck
#universal#teleport#best universal#admin#inf yield

Community Comments (2)

ThePenguinThePenguinSep 11, 2025

it sucks a bit

hi_hi_hihi_hi_hiSep 14, 2025

Teleporting makes you not able to move again, no need for that much in gui

More Scripts for Universal Scripts