
Key RequiredUniversal

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



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