-- Action Recorder V1.3 local Players = game:GetService("Players") local UserInputService = game:GetService("UserInputService") local RunService = game:GetService("RunService") local GuiService = game:GetService("GuiService") local player = Players.LocalPlayer local camera = workspace.CurrentCamera --// CONFIG local RED = Color3.fromRGB(255, 40, 40) local BLACK = Color3.fromRGB(8, 8, 8) local DARK = Color3.fromRGB(16, 16, 16) local OUTLINE = 1.5 local FONT = Enum.Font.Code local TEXT_SIZE = 14 local MOVE_KEYS = { W = Vector3.new(0, 0, -1), A = Vector3.new(-1, 0, 0), S = Vector3.new(0, 0, 1), D = Vector3.new(1, 0, 0), } local NUMBER_KEYS = { One = true, Two = true, Three = true, Four = true, Five = true, Six = true, Seven = true, Eight = true, Nine = true, Zero = true } --// STATE local recordings = {} local nextId = 1 local currentRecordingId = nil local isRecording = false local isPlaying = false local isPlayingSchedule = false local recordStartTime = 0 local lastActionTime = 0 local rawActions = {} local schedule = {} local inScheduler = false local isMobile = false -- set by first-time popup local hasChosenDevice = false local draggingFrame, dragStart, frameStart = nil, nil, nil ---------------------------------------------------------------- -- HELPERS ---------------------------------------------------------------- local function create(class, props) local obj = Instance.new(class) for k, v in pairs(props) do obj[k] = v end return obj end local function addOutline(parent, thickness) local stroke = Instance.new("UIStroke") stroke.Color = RED stroke.Thickness = thickness or OUTLINE stroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border stroke.Parent = parent return stroke end local function makeButton(text, parent, size, pos) local btn = create("TextButton", { Text = text, Font = FONT, TextSize = TEXT_SIZE, TextColor3 = RED, BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = size, Position = pos, AutoButtonColor = false, Parent = parent }) addOutline(btn) return btn end local function makeTopBarDraggable(topBar, targetFrame) topBar.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then draggingFrame = targetFrame dragStart = input.Position frameStart = targetFrame.Position end end) end UserInputService.InputChanged:Connect(function(input) if draggingFrame and (input.UserInputType == Enum.UserInputType.MouseMovement or input.UserInputType == Enum.UserInputType.Touch) then local delta = input.Position - dragStart draggingFrame.Position = UDim2.new( frameStart.X.Scale, frameStart.X.Offset + delta.X, frameStart.Y.Scale, frameStart.Y.Offset + delta.Y ) end end) UserInputService.InputEnded:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then draggingFrame = nil end end) local function getEquippedToolName() local char = player.Character if not char then return nil end for _, t in ipairs(char:GetChildren()) do if t:IsA("Tool") then return t.Name end end return nil end local function equipToolByName(toolName) if not toolName then return end local char = player.Character local backpack = player:FindFirstChild("Backpack") if not char or not backpack then return end local hum = char:FindFirstChildOfClass("Humanoid") if not hum then return end for _, t in ipairs(char:GetChildren()) do if t:IsA("Tool") and t.Name == toolName then return end end local tool = backpack:FindFirstChild(toolName) if tool and tool:IsA("Tool") then hum:UnequipTools() task.wait() hum:EquipTool(tool) end end ---------------------------------------------------------------- -- DEVICE POPUP (first time only) ---------------------------------------------------------------- local function showDevicePopup(onChosen) -- Create a temporary ScreenGui for the popup local popupGui = create("ScreenGui", { Name = "DevicePopup", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, DisplayOrder = 100, Parent = player:WaitForChild("PlayerGui") }) local popup = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 320, 0, 160), Position = UDim2.new(0.5, -160, 0.5, -80), Parent = popupGui }) addOutline(popup, 2) create("TextLabel", { Text = "Are you on Mobile or Computer?", Font = FONT, TextSize = 16, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 50), Parent = popup }) local mobileBtn = makeButton("Mobile", popup, UDim2.new(0, 120, 0, 40), UDim2.new(0.5, -130, 1, -60)) local computerBtn = makeButton("Computer", popup, UDim2.new(0, 120, 0, 40), UDim2.new(0.5, 10, 1, -60)) local function choose(isMob) isMobile = isMob hasChosenDevice = true popupGui:Destroy() onChosen() end mobileBtn.MouseButton1Click:Connect(function() choose(true) end) computerBtn.MouseButton1Click:Connect(function() choose(false) end) end ---------------------------------------------------------------- -- BUILD UI ---------------------------------------------------------------- local screenGui = create("ScreenGui", { Name = "ActionRecorder", ResetOnSpawn = false, ZIndexBehavior = Enum.ZIndexBehavior.Sibling, Parent = player:WaitForChild("PlayerGui") }) local mainFrame = create("Frame", { Name = "Main", BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 560, 0, 400), Position = UDim2.new(0.5, -280, 0.5, -200), Visible = false, -- hidden until device is chosen Parent = screenGui }) addOutline(mainFrame, 2) local topBar = create("Frame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 34), Parent = mainFrame }) addOutline(topBar) makeTopBarDraggable(topBar, mainFrame) local newBtn = makeButton(" + NEW", topBar, UDim2.new(0, 90, 1, 0), UDim2.new(0, 0, 0, 0)) newBtn.TextXAlignment = Enum.TextXAlignment.Left local schedulerBtn = makeButton("Scheduler", topBar, UDim2.new(0, 90, 1, 0), UDim2.new(0, 95, 0, 0)) local title = create("TextLabel", { Text = "V1.3 Action Recorder", Font = FONT, TextSize = 15, TextColor3 = RED, BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(0, 210, 1, 0), Position = UDim2.new(0, 195, 0, 0), Parent = topBar }) addOutline(title) local killBtn = makeButton("Kill", topBar, UDim2.new(0, 55, 1, 0), UDim2.new(1, -115, 0, 0)) local minBtn = makeButton("Min", topBar, UDim2.new(0, 50, 1, 0), UDim2.new(1, -50, 0, 0)) local sideBar = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 155, 1, -34), Position = UDim2.new(0, 0, 0, 34), Parent = mainFrame }) addOutline(sideBar) local sideScroll = create("ScrollingFrame", { BackgroundTransparency = 1, BorderSizePixel = 0, Size = UDim2.new(1, -6, 1, -6), Position = UDim2.new(0, 3, 0, 3), CanvasSize = UDim2.new(0, 0, 0, 0), ScrollBarThickness = 3, ScrollBarImageColor3 = RED, Parent = sideBar }) create("UIListLayout", {Padding = UDim.new(0, 5), SortOrder = Enum.SortOrder.LayoutOrder, Parent = sideScroll}) local mainPage = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(1, -155, 1, -34), Position = UDim2.new(0, 155, 0, 34), Parent = mainFrame }) addOutline(mainPage) local emptyLabel = create("TextLabel", { Text = "No recording open", Font = FONT, TextSize = 16, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, 0), Parent = mainPage }) local recordingBar = create("Frame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 32), Visible = false, Parent = mainPage }) addOutline(recordingBar) local closeBtn = makeButton("X", recordingBar, UDim2.new(0, 32, 1, 0), UDim2.new(0, 0, 0, 0)) local nameBox = create("TextBox", { Text = "Recording 1", Font = FONT, TextSize = TEXT_SIZE, TextColor3 = RED, BackgroundColor3 = DARK, BorderSizePixel = 0, ClearTextOnFocus = false, Size = UDim2.new(1, -150, 1, 0), Position = UDim2.new(0, 38, 0, 0), Parent = recordingBar }) addOutline(nameBox) create("Frame", {BackgroundColor3 = RED, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 1), Position = UDim2.new(0, 0, 1, -1), Parent = nameBox}) local recordBtn = create("TextButton", { Text = "●", Font = FONT, TextSize = 22, TextColor3 = RED, BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(0, 36, 1, 0), Position = UDim2.new(1, -80, 0, 0), Parent = recordingBar }) addOutline(recordBtn) local playBtn = create("TextButton", { Text = "▶", Font = FONT, TextSize = 16, TextColor3 = RED, BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(0, 36, 1, 0), Position = UDim2.new(1, -38, 0, 0), Parent = recordingBar }) addOutline(playBtn) local actionListFrame = create("ScrollingFrame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(1, -8, 1, -40), Position = UDim2.new(0, 4, 0, 36), CanvasSize = UDim2.new(0, 0, 0, 0), ScrollBarThickness = 3, ScrollBarImageColor3 = RED, Visible = false, Parent = mainPage }) addOutline(actionListFrame) local actionLayout = create("UIListLayout", {Padding = UDim.new(0, 2), SortOrder = Enum.SortOrder.LayoutOrder, Parent = actionListFrame}) ---------------------------------------------------------------- -- SCHEDULER UI ---------------------------------------------------------------- local schedulerFrame = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0), Visible = false, Parent = mainPage }) local recBar = create("ScrollingFrame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, -8, 0, 34), Position = UDim2.new(0, 4, 0, 6), CanvasSize = UDim2.new(0, 0, 0, 0), ScrollBarThickness = 3, ScrollBarImageColor3 = RED, ScrollingDirection = Enum.ScrollingDirection.X, Parent = schedulerFrame }) addOutline(recBar) create("UIListLayout", {FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder, Parent = recBar}) local addDelayBtn = makeButton("+ Delay", schedulerFrame, UDim2.new(0, 80, 0, 26), UDim2.new(0, 8, 0, 46)) local addRepeatBtn = makeButton("+ Repeat", schedulerFrame, UDim2.new(0, 90, 0, 26), UDim2.new(0, 96, 0, 46)) create("TextLabel", { Text = " Sequence (blocks)", Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 20), Position = UDim2.new(0, 0, 0, 78), TextXAlignment = Enum.TextXAlignment.Left, Parent = schedulerFrame }) local seqScroll = create("ScrollingFrame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(1, -8, 1, -150), Position = UDim2.new(0, 4, 0, 100), CanvasSize = UDim2.new(0, 0, 0, 0), ScrollBarThickness = 3, ScrollBarImageColor3 = RED, Parent = schedulerFrame }) addOutline(seqScroll) create("UIListLayout", {Padding = UDim.new(0, 5), SortOrder = Enum.SortOrder.LayoutOrder, Parent = seqScroll}) local playSchedBtn = makeButton("Play Schedule", schedulerFrame, UDim2.new(0, 140, 0, 28), UDim2.new(0, 8, 1, -36)) local clearSchedBtn = makeButton("Clear", schedulerFrame, UDim2.new(0, 70, 0, 28), UDim2.new(0, 156, 1, -36)) local backBtn = makeButton("Back", schedulerFrame, UDim2.new(0, 70, 0, 28), UDim2.new(1, -80, 1, -36)) local minButton = create("TextButton", { Text = "AR", Font = FONT, TextSize = 24, TextColor3 = BLACK, BackgroundColor3 = RED, BorderSizePixel = 0, Size = UDim2.new(0, 68, 0, 68), Position = UDim2.new(1, -100, 1, -100), Visible = false, Parent = screenGui }) Instance.new("UICorner", minButton).CornerRadius = UDim.new(1, 0) local minStroke = Instance.new("UIStroke") minStroke.Color = BLACK minStroke.Thickness = 3 minStroke.Parent = minButton minButton.InputBegan:Connect(function(input) if input.UserInputType == Enum.UserInputType.MouseButton1 or input.UserInputType == Enum.UserInputType.Touch then draggingFrame = minButton dragStart = input.Position frameStart = minButton.Position end end) local confirmFrame = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 270, 0, 110), Position = UDim2.new(0.5, -135, 0.5, -55), Visible = false, ZIndex = 20, Parent = screenGui }) addOutline(confirmFrame, 2) create("TextLabel", { Text = "Delete this recording?", Font = FONT, TextSize = 15, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 45), Parent = confirmFrame }) local yesBtn = makeButton("Yes", confirmFrame, UDim2.new(0, 90, 0, 32), UDim2.new(0.5, -100, 1, -45)) local noBtn = makeButton("No", confirmFrame, UDim2.new(0, 90, 0, 32), UDim2.new(0.5, 10, 1, -45)) ---------------------------------------------------------------- -- APPLY MOBILE / COMPUTER SCALING ---------------------------------------------------------------- local function applyDeviceScaling() if isMobile then mainFrame.Size = UDim2.new(0.95, 0, 0.7, 0) mainFrame.Position = UDim2.new(0.025, 0, 0.15, 0) -- slightly larger text / buttons for touch TEXT_SIZE = 16 else mainFrame.Size = UDim2.new(0, 560, 0, 400) mainFrame.Position = UDim2.new(0.5, -280, 0.5, -200) end mainFrame.Visible = true end ---------------------------------------------------------------- -- SIDEBAR + ACTION LIST (copyable) ---------------------------------------------------------------- local function refreshSidebar() for _, child in ipairs(sideScroll:GetChildren()) do if child:IsA("Frame") then child:Destroy() end end for i, rec in ipairs(recordings) do local item = create("Frame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 52), LayoutOrder = i, Parent = sideScroll }) addOutline(item) create("TextLabel", { Text = rec.name, Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, -6, 0, 20), Position = UDim2.new(0, 4, 0, 3), TextXAlignment = Enum.TextXAlignment.Left, Parent = item }) local openBtn = makeButton("Open", item, UDim2.new(0.48, 0, 0, 22), UDim2.new(0.02, 0, 0, 26)) openBtn.TextSize = 12 local dupBtn = makeButton("Duplicate", item, UDim2.new(0.48, 0, 0, 22), UDim2.new(0.5, 0, 0, 26)) dupBtn.TextSize = 11 openBtn.MouseButton1Click:Connect(function() openRecording(rec.id) end) dupBtn.MouseButton1Click:Connect(function() duplicateRecording(rec.id) end) end sideScroll.CanvasSize = UDim2.new(0, 0, 0, #recordings * 57) end local function clearActionList() for _, c in ipairs(actionListFrame:GetChildren()) do if c:IsA("Frame") or c:IsA("TextBox") or c:IsA("TextLabel") then c:Destroy() end end actionListFrame.CanvasSize = UDim2.new(0, 0, 0, 0) end -- Copyable line (TextBox that is not editable) local function addSimpleLine(text) local box = create("TextBox", { Text = " " .. text, Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, BorderSizePixel = 0, ClearTextOnFocus = false, TextEditable = false, -- important: user can still select & copy Size = UDim2.new(1, -4, 0, 18), TextXAlignment = Enum.TextXAlignment.Left, Parent = actionListFrame }) end local function addMouseGroup(group) local container = create("Frame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, -4, 0, 20), Parent = actionListFrame }) addOutline(container) local expanded = false local arrow = create("TextButton", { Text = "▶", Font = FONT, TextSize = 12, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(0, 20, 1, 0), Parent = container }) create("TextLabel", { Text = string.format(" Mouse Sequence (%d moves)", #group), Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, -24, 1, 0), Position = UDim2.new(0, 20, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, Parent = container }) local children = {} arrow.MouseButton1Click:Connect(function() expanded = not expanded arrow.Text = expanded and "▼" or "▶" for _, child in ipairs(children) do child.Visible = expanded end actionListFrame.CanvasSize = UDim2.new(0, 0, 0, actionLayout.AbsoluteContentSize.Y + 8) end) for _, move in ipairs(group) do local box = create("TextBox", { Text = " " .. move.desc, Font = FONT, TextSize = 12, TextColor3 = Color3.fromRGB(200, 80, 80), BackgroundTransparency = 1, BorderSizePixel = 0, ClearTextOnFocus = false, TextEditable = false, Size = UDim2.new(1, -4, 0, 16), TextXAlignment = Enum.TextXAlignment.Left, Visible = false, Parent = actionListFrame }) table.insert(children, box) end end local function rebuildActionList(actions) clearActionList() addSimpleLine("Start") local i = 1 while i <= #actions do local act = actions[i] if act.data.type == "look" then local group = {act} local j = i + 1 while j <= #actions and actions[j].data.type == "look" do table.insert(group, actions[j]) j += 1 end addMouseGroup(group) i = j else addSimpleLine(act.desc) i += 1 end end addSimpleLine("Stop") actionListFrame.CanvasSize = UDim2.new(0, 0, 0, actionLayout.AbsoluteContentSize.Y + 10) end ---------------------------------------------------------------- -- RECORDING ---------------------------------------------------------------- local function showRecordingUI(rec) emptyLabel.Visible = false recordingBar.Visible = true actionListFrame.Visible = true schedulerFrame.Visible = false inScheduler = false nameBox.Text = rec.name rebuildActionList(rec.actions) end function openRecording(id) currentRecordingId = id for _, r in ipairs(recordings) do if r.id == id then showRecordingUI(r) break end end end function duplicateRecording(id) local original for _, r in ipairs(recordings) do if r.id == id then original = r break end end if not original then return end local copy = {id = nextId, name = original.name .. " (copy)", actions = table.clone(original.actions)} nextId += 1 table.insert(recordings, copy) refreshSidebar() refreshSchedulerBar() end local function deleteRecording(id) for i, r in ipairs(recordings) do if r.id == id then table.remove(recordings, i) break end end if currentRecordingId == id then currentRecordingId = nil recordingBar.Visible = false actionListFrame.Visible = false emptyLabel.Visible = true end refreshSidebar() refreshSchedulerBar() end local heldKeys = {} local lastMousePos = Vector2.zero local lastCamLook = Vector3.zero local lastSample = 0 local function startRecording() if isRecording or isPlaying or isPlayingSchedule or not currentRecordingId then return end isRecording = true rawActions = {} recordStartTime = tick() lastActionTime = recordStartTime lastSample = recordStartTime heldKeys = {} lastMousePos = UserInputService:GetMouseLocation() if camera then lastCamLook = camera.CFrame.LookVector end recordBtn.TextColor3 = Color3.fromRGB(255, 90, 90) clearActionList() addSimpleLine("Start") end local function stopRecording() if not isRecording then return end isRecording = false recordBtn.TextColor3 = RED local now = tick() if now - lastActionTime > 0.15 then table.insert(rawActions, { desc = string.format("Idle (%.2f seconds)", now - lastActionTime), data = {type = "idle", duration = now - lastActionTime, time = now - recordStartTime} }) end local final = {} for _, a in ipairs(rawActions) do table.insert(final, a) end for _, r in ipairs(recordings) do if r.id == currentRecordingId then r.actions = final break end end openRecording(currentRecordingId) end UserInputService.InputBegan:Connect(function(input, gp) if gp or not isRecording then return end local now = tick() local rel = now - recordStartTime if now - lastActionTime > 0.12 then table.insert(rawActions, { desc = string.format("Idle (%.2f seconds)", now - lastActionTime), data = {type = "idle", duration = now - lastActionTime, time = rel} }) end if input.UserInputType == Enum.UserInputType.Keyboard then local key = input.KeyCode.Name heldKeys[key] = true local data = {type = "keyDown", key = key, time = rel} if NUMBER_KEYS[key] then task.delay(0.05, function() local toolName = getEquippedToolName() if toolName then data.toolName = toolName end end) end table.insert(rawActions, {desc = key .. " Down", data = data}) elseif input.UserInputType == Enum.UserInputType.MouseButton1 then local pos = UserInputService:GetMouseLocation() table.insert(rawActions, { desc = string.format("LMB Click (%d, %d)", math.floor(pos.X), math.floor(pos.Y)), data = {type = "click", button = "LMB", x = pos.X, y = pos.Y, time = rel} }) elseif input.UserInputType == Enum.UserInputType.MouseButton2 then local pos = UserInputService:GetMouseLocation() table.insert(rawActions, { desc = string.format("RMB Click (%d, %d)", math.floor(pos.X), math.floor(pos.Y)), data = {type = "click", button = "RMB", x = pos.X, y = pos.Y, time = rel} }) end lastActionTime = now end) UserInputService.InputEnded:Connect(function(input, gp) if gp or not isRecording then return end if input.UserInputType == Enum.UserInputType.Keyboard then local key = input.KeyCode.Name if heldKeys[key] then heldKeys[key] = nil local now = tick() table.insert(rawActions, { desc = key .. " Up", data = {type = "keyUp", key = key, time = now - recordStartTime} }) lastActionTime = now end end end) UserInputService.InputChanged:Connect(function(input, gp) if gp or not isRecording then return end if input.UserInputType == Enum.UserInputType.MouseWheel then local now = tick() local rel = now - recordStartTime local delta = input.Position.Z table.insert(rawActions, { desc = string.format("Scroll %s", delta > 0 and "Up" or "Down"), data = {type = "scroll", delta = delta, time = rel} }) lastActionTime = now end end) RunService.RenderStepped:Connect(function() if not isRecording then return end local now = tick() if now - lastSample < 0.033 then return end lastSample = now local pos = UserInputService:GetMouseLocation() local camLook = camera and camera.CFrame.LookVector or Vector3.zero local moved = (pos - lastMousePos).Magnitude > 3 local looked = (camLook - lastCamLook).Magnitude > 0.02 if moved or looked then local rel = now - recordStartTime if now - lastActionTime > 0.08 then table.insert(rawActions, { desc = string.format("Idle (%.2f seconds)", now - lastActionTime), data = {type = "idle", duration = now - lastActionTime, time = rel} }) end table.insert(rawActions, { desc = string.format("Cursor (%d, %d)", math.floor(pos.X), math.floor(pos.Y)), data = { type = "look", x = pos.X, y = pos.Y, look = {camLook.X, camLook.Y, camLook.Z}, time = rel } }) lastMousePos = pos lastCamLook = camLook lastActionTime = now end end) ---------------------------------------------------------------- -- PLAYBACK (camera now follows the character) ---------------------------------------------------------------- local playConnection = nil local playStartTime = 0 local playIndex = 1 local activeKeys = {} local currentLook = nil local originalCameraType = nil local originalFOV = nil local function stopPlayback() isPlaying = false playBtn.Text = "▶" if playConnection then playConnection:Disconnect() playConnection = nil end activeKeys = {} currentLook = nil if originalCameraType and camera then camera.CameraType = originalCameraType originalCameraType = nil end if originalFOV and camera then camera.FieldOfView = originalFOV originalFOV = nil end local char = player.Character if char then local hum = char:FindFirstChildOfClass("Humanoid") if hum then hum:Move(Vector3.zero, true) end end end local function playRecordingById(id, onFinished) local rec for _, r in ipairs(recordings) do if r.id == id then rec = r break end end if not rec or #rec.actions == 0 then if onFinished then onFinished() end return end isPlaying = true playStartTime = tick() playIndex = 1 activeKeys = {} currentLook = nil local char = player.Character local humanoid = char and char:FindFirstChildOfClass("Humanoid") local root = char and char:FindFirstChild("HumanoidRootPart") if not humanoid or not root then if onFinished then onFinished() end return end if camera then originalCameraType = camera.CameraType originalFOV = camera.FieldOfView camera.CameraType = Enum.CameraType.Scriptable end playConnection = RunService.RenderStepped:Connect(function() if not isPlaying then return end local elapsed = tick() - playStartTime while playIndex <= #rec.actions do local act = rec.actions[playIndex] local t = act.data.time or 0 if t > elapsed then break end local d = act.data if d.type == "keyDown" then activeKeys[d.key] = true if d.toolName then equipToolByName(d.toolName) end elseif d.type == "keyUp" then activeKeys[d.key] = nil elseif d.type == "look" and d.look then currentLook = Vector3.new(d.look[1], d.look[2], d.look[3]) elseif d.type == "scroll" then if camera then local fov = camera.FieldOfView if d.delta > 0 then camera.FieldOfView = math.clamp(fov - 5, 20, 120) else camera.FieldOfView = math.clamp(fov + 5, 20, 120) end end end playIndex += 1 end -- Movement local dir = Vector3.zero for key, vec in pairs(MOVE_KEYS) do if activeKeys[key] then dir += vec end end if dir.Magnitude > 0 then humanoid:Move(dir.Unit, true) else humanoid:Move(Vector3.zero, true) end if activeKeys["Space"] then humanoid.Jump = true end -- CAMERA FOLLOW FIX -- Camera stays with the character while applying recorded look if camera and root then local head = char:FindFirstChild("Head") local camPos = (head and head.Position or root.Position) + Vector3.new(0, 1.5, 0) local look = currentLook and currentLook.Magnitude > 0.05 and currentLook or root.CFrame.LookVector camera.CFrame = CFrame.lookAt(camPos, camPos + look) end if playIndex > #rec.actions then local lastTime = rec.actions[#rec.actions].data.time or 0 if elapsed > lastTime + 0.25 then stopPlayback() if onFinished then onFinished() end end end end) end local function playRecording() if isPlaying or isRecording or isPlayingSchedule or not currentRecordingId then return end playBtn.Text = "■" playRecordingById(currentRecordingId, function() playBtn.Text = "▶" end) end ---------------------------------------------------------------- -- SCHEDULER ---------------------------------------------------------------- function refreshSchedulerBar() for _, c in ipairs(recBar:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end local totalWidth = 0 for i, rec in ipairs(recordings) do local chip = create("Frame", { BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 120, 0, 26), LayoutOrder = i, Parent = recBar }) addOutline(chip) create("TextLabel", { Text = rec.name, Font = FONT, TextSize = 12, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, -28, 1, 0), Position = UDim2.new(0, 4, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, TextTruncate = Enum.TextTruncate.AtEnd, Parent = chip }) local plus = makeButton("+", chip, UDim2.new(0, 24, 0, 22), UDim2.new(1, -26, 0.5, -11)) plus.TextSize = 16 plus.MouseButton1Click:Connect(function() table.insert(schedule, {type = "recording", id = rec.id, name = rec.name}) refreshSequenceList() end) totalWidth += 126 end recBar.CanvasSize = UDim2.new(0, totalWidth + 10, 0, 0) end function refreshSequenceList() for _, c in ipairs(seqScroll:GetChildren()) do if c:IsA("Frame") then c:Destroy() end end for i, block in ipairs(schedule) do local row = create("Frame", { BackgroundColor3 = DARK, BorderSizePixel = 0, Size = UDim2.new(1, -4, 0, 34), LayoutOrder = i, Parent = seqScroll }) addOutline(row) if block.type == "recording" then create("TextLabel", { Text = i .. ". [Recording] " .. block.name, Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(1, -40, 1, 0), Position = UDim2.new(0, 8, 0, 0), TextXAlignment = Enum.TextXAlignment.Left, TextTruncate = Enum.TextTruncate.AtEnd, Parent = row }) elseif block.type == "delay" then create("TextLabel", { Text = i .. ". [Delay]", Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(0, 90, 1, 0), Position = UDim2.new(0, 8, 0, 0), Parent = row }) local box = create("TextBox", { Text = tostring(block.seconds or 1), Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 50, 0, 22), Position = UDim2.new(0, 100, 0.5, -11), ClearTextOnFocus = false, Parent = row }) addOutline(box) create("TextLabel", { Text = "seconds", Font = FONT, TextSize = 12, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(0, 60, 1, 0), Position = UDim2.new(0, 155, 0, 0), Parent = row }) box.FocusLost:Connect(function() local n = tonumber(box.Text) if n and n >= 0 then block.seconds = n else box.Text = tostring(block.seconds or 1) end end) elseif block.type == "repeat" then create("TextLabel", { Text = i .. ". [Repeat]", Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(0, 90, 1, 0), Position = UDim2.new(0, 8, 0, 0), Parent = row }) local box = create("TextBox", { Text = tostring(block.times or 2), Font = FONT, TextSize = 13, TextColor3 = RED, BackgroundColor3 = BLACK, BorderSizePixel = 0, Size = UDim2.new(0, 40, 0, 22), Position = UDim2.new(0, 100, 0.5, -11), ClearTextOnFocus = false, Parent = row }) addOutline(box) create("TextLabel", { Text = "times (next block)", Font = FONT, TextSize = 12, TextColor3 = RED, BackgroundTransparency = 1, Size = UDim2.new(0, 120, 1, 0), Position = UDim2.new(0, 145, 0, 0), Parent = row }) box.FocusLost:Connect(function() local n = tonumber(box.Text) if n and n >= 1 then block.times = math.floor(n) else box.Text = tostring(block.times or 2) end end) end local removeBtn = makeButton("X", row, UDim2.new(0, 26, 0, 22), UDim2.new(1, -32, 0.5, -11)) removeBtn.TextSize = 14 removeBtn.MouseButton1Click:Connect(function() table.remove(schedule, i) refreshSequenceList() end) end seqScroll.CanvasSize = UDim2.new(0, 0, 0, #schedule * 39) end local function playSchedule() if isPlayingSchedule or isPlaying or isRecording or #schedule == 0 then return end isPlayingSchedule = true task.spawn(function() local i = 1 while i <= #schedule and isPlayingSchedule do playSchedBtn.Text = "Playing... (" .. i .. ")" local block = schedule[i] if block.type == "recording" then local finished = false playRecordingById(block.id, function() finished = true end) while not finished and isPlayingSchedule do task.wait(0.05) end elseif block.type == "delay" then local waitTime = block.seconds or 1 local start = tick() while tick() - start < waitTime and isPlayingSchedule do task.wait(0.05) end elseif block.type == "repeat" then local times = block.times or 2 local nextIdx = i + 1 if nextIdx <= #schedule then local nextBlock = schedule[nextIdx] for rep = 1, times do if not isPlayingSchedule then break end playSchedBtn.Text = "Playing... (" .. nextIdx .. ") x" .. rep if nextBlock.type == "recording" then local finished = false playRecordingById(nextBlock.id, function() finished = true end) while not finished and isPlayingSchedule do task.wait(0.05) end elseif nextBlock.type == "delay" then local waitTime = nextBlock.seconds or 1 local start = tick() while tick() - start < waitTime and isPlayingSchedule do task.wait(0.05) end end end i = nextIdx end end i += 1 end isPlayingSchedule = false playSchedBtn.Text = "Play Schedule" stopPlayback() end) end local function showScheduler() inScheduler = true emptyLabel.Visible = false recordingBar.Visible = false actionListFrame.Visible = false schedulerFrame.Visible = true refreshSchedulerBar() refreshSequenceList() end local function hideScheduler() inScheduler = false schedulerFrame.Visible = false if currentRecordingId then openRecording(currentRecordingId) else emptyLabel.Visible = true end end ---------------------------------------------------------------- -- BUTTONS ---------------------------------------------------------------- newBtn.MouseButton1Click:Connect(function() local rec = {id = nextId, name = "Recording " .. nextId, actions = {}} nextId += 1 table.insert(recordings, rec) refreshSidebar() refreshSchedulerBar() end) schedulerBtn.MouseButton1Click:Connect(function() if inScheduler then hideScheduler() else showScheduler() end end) killBtn.MouseButton1Click:Connect(function() screenGui:Destroy() end) minBtn.MouseButton1Click:Connect(function() mainFrame.Visible = false minButton.Visible = true end) minButton.MouseButton1Click:Connect(function() mainFrame.Visible = true minButton.Visible = false end) closeBtn.MouseButton1Click:Connect(function() confirmFrame.Visible = true end) yesBtn.MouseButton1Click:Connect(function() if currentRecordingId then deleteRecording(currentRecordingId) end confirmFrame.Visible = false end) noBtn.MouseButton1Click:Connect(function() confirmFrame.Visible = false end) nameBox.FocusLost:Connect(function() if currentRecordingId then for _, r in ipairs(recordings) do if r.id == currentRecordingId then r.name = nameBox.Text break end end refreshSidebar() refreshSchedulerBar() end end) recordBtn.MouseButton1Click:Connect(function() if isRecording then stopRecording() else startRecording() end end) playBtn.MouseButton1Click:Connect(function() if isPlaying then stopPlayback() else playRecording() end end) addDelayBtn.MouseButton1Click:Connect(function() table.insert(schedule, {type = "delay", seconds = 1}) refreshSequenceList() end) addRepeatBtn.MouseButton1Click:Connect(function() table.insert(schedule, {type = "repeat", times = 2}) refreshSequenceList() end) playSchedBtn.MouseButton1Click:Connect(function() if isPlayingSchedule then isPlayingSchedule = false stopPlayback() playSchedBtn.Text = "Play Schedule" else playSchedule() end end) clearSchedBtn.MouseButton1Click:Connect(function() schedule = {} refreshSequenceList() end) backBtn.MouseButton1Click:Connect(function() hideScheduler() end) ---------------------------------------------------------------- -- STARTUP ---------------------------------------------------------------- -- First-time device choice showDevicePopup(function() applyDeviceScaling() refreshSidebar() print("[Action Recorder] V1.3 ready") end)