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!")