Universal Tool Asisted Speedrun TAS Script
Key RequiredUniversal

Universal Tool Asisted Speedrun TAS Script

Universal ScriptAlizAliz 1.3K views 746 executesUpdated Feb 15, 2026
5.0/ 5(2 votes)
20100% Positive
Lua Script•897 lines (30256 chars)
local UserInputService = game:GetService("UserInputService")
local RunService = game:GetService("RunService")
local HttpService = game:GetService("HttpService")
local Players = game:GetService("Players")
local Workspace = game:GetService("Workspace")
local StarterGui = game:GetService("StarterGui")

local LocalPlayer = Players.LocalPlayer
local Mouse = LocalPlayer:GetMouse()

local Keybinds = {
	AddSavestate = "One",
	RemoveSavestate = "Two",
	BackSavestate = "Eight",
	GoFrameBack = "R",
	GoFrameForward = "F",
	GoBack = "Four",
	GoForward = "Five",
	SaveRun = "Six",
	UserPause = "CapsLock",
	CollisionToggler = "C",
	ResetToNormal = "Delete",
	ViewTAS = "Zero",
	LoadRun = "Minus"
}

local Savestates = {}
local PlayerInfo = {}
local TimePaused = 0
local Pause = true
local ViewingTAS = false
local TimePauseHolder
local TimeStart
local FrameCountLabel
local SavestatesCountLabel
local TimeTextLabel
local TASNameBox
local CapLockPauseLabel
local KeyBindFrame
local HUD
local MainConnectionLoop
local KeybindsConnect
local InputEndConnect
local DiedConnect
local PlaybackConnection
local Initialize

local notifArgs
local NotifFunction = Instance.new("BindableFunction", script)
NotifFunction.OnInvoke = function(answer, notif)
	if answer == "Initialize" or answer == "Initialize Again" then
		Initialize()
	else
		notifArgs = nil
	end
end

local function notify(text, title, dur, button, button2)
	local ARGS
	ARGS = {
		Title = title or "",
		Text = text or "bro forgot to put text",
		Duration = dur or 5,
		Button1 = button or nil,
		Button2 = button2 or nil,
		Callback = NotifFunction
	}
	notifArgs = ARGS
	StarterGui:SetCore("SendNotification", notifArgs)
end

local function getCurrentCFrame()
	if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
		return LocalPlayer.Character.HumanoidRootPart.CFrame
	end
	return CFrame.new()
end

local function getCurrentVelocity()
	if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
		return LocalPlayer.Character.HumanoidRootPart.Velocity
	end
	return Vector3.new()
end

local function getCurrentCameraCFrame()
	return Workspace.CurrentCamera.CFrame
end

local function ReturnPlayerInfo()
	return {
		TCFrame = {
			posX = getCurrentCFrame().X,
			posY = getCurrentCFrame().Y,
			posZ = getCurrentCFrame().Z,
			lookX = getCurrentCFrame().LookVector.X,
			lookY = getCurrentCFrame().LookVector.Y,
			lookZ = getCurrentCFrame().LookVector.Z,
			upX = getCurrentCFrame().UpVector.X,
			upY = getCurrentCFrame().UpVector.Y,
			upZ = getCurrentCFrame().UpVector.Z,
			rightX = getCurrentCFrame().RightVector.X,
			rightY = getCurrentCFrame().RightVector.Y,
			rightZ = getCurrentCFrame().RightVector.Z
		},
		TCameraCFrame = {
			camPosX = getCurrentCameraCFrame().X,
			camPosY = getCurrentCameraCFrame().Y,
			camPosZ = getCurrentCameraCFrame().Z,
			camLookX = getCurrentCameraCFrame().LookVector.X,
			camLookY = getCurrentCameraCFrame().LookVector.Y,
			camLookZ = getCurrentCameraCFrame().LookVector.Z,
			camUpX = getCurrentCameraCFrame().UpVector.X,
			camUpY = getCurrentCameraCFrame().UpVector.Y,
			camUpZ = getCurrentCameraCFrame().UpVector.Z,
			camRightX = getCurrentCameraCFrame().RightVector.X,
			camRightY = getCurrentCameraCFrame().RightVector.Y,
			camRightZ = getCurrentCameraCFrame().RightVector.Z
		},
		TVelocity = {
			vX = getCurrentVelocity().X,
			vY = getCurrentVelocity().Y,
			vZ = getCurrentVelocity().Z
		},
		CFrame = getCurrentCFrame(),
		CameraCFrame = getCurrentCameraCFrame(),
		Velocity = getCurrentVelocity(),
		Animation = CurrentAnimationState,
		Time = tick() - TimeStart - TimePaused
	}
end

local function UpdateGUIText()
	if SavestatesCountLabel then
		SavestatesCountLabel.Text = "Savestates: " .. #Savestates
	end
	if FrameCountLabel then
		FrameCountLabel.Text = "Frames: " .. #PlayerInfo
	end
end

local function FormatTime(TimeValue)
	local m = math.floor(TimeValue / 60)
	local s = math.floor(TimeValue % 60)
	local ms = math.floor((TimeValue * 1000) % 1000)
	local msStr = tostring(ms)
	local sStr = tostring(s)

	while #msStr < 3 do msStr = '0' .. msStr end
	while #sStr < 2 do sStr = '0' .. sStr end

	return m .. ":" .. sStr .. "." .. msStr
end


local function UpdateTimeGUI()
	if TimeTextLabel then
		local TimePlayed = tick() - TimeStart - TimePaused
		TimeTextLabel.Text = FormatTime(TimePlayed)
	end
end

local function SetCharacterState(InfoState)
	if not LocalPlayer.Character or not LocalPlayer.Character:FindFirstChild("HumanoidRootPart") or not LocalPlayer.Character:FindFirstChild("Humanoid") then return end

	local Hum = LocalPlayer.Character.Humanoid
	local RootPart = LocalPlayer.Character.HumanoidRootPart

	local characterCFrame = InfoState.CFrame
	if not characterCFrame and InfoState.TCFrame then
		local charPos = Vector3.new(InfoState.TCFrame.posX, InfoState.TCFrame.posY, InfoState.TCFrame.posZ)
		local charLook = Vector3.new(InfoState.TCFrame.lookX, InfoState.TCFrame.lookY, InfoState.TCFrame.lookZ)
		local charUp = Vector3.new(InfoState.TCFrame.upX, InfoState.TCFrame.upY, InfoState.TCFrame.upZ)
		local charRight = Vector3.new(InfoState.TCFrame.rightX, InfoState.TCFrame.rightY, InfoState.TCFrame.rightZ)
		characterCFrame = CFrame.fromMatrix(charPos, charRight, charUp, -charLook)
	end

	local cameraCFrame = InfoState.CameraCFrame
	if not cameraCFrame and InfoState.TCameraCFrame then
		local camPos = Vector3.new(InfoState.TCameraCFrame.camPosX, InfoState.TCameraCFrame.camPosY, InfoState.TCameraCFrame.camPosZ)
		local camLook = Vector3.new(InfoState.TCameraCFrame.camLookX, InfoState.TCameraCFrame.camLookY, InfoState.TCameraCFrame.camLookZ)
		local camUp = Vector3.new(InfoState.TCameraCFrame.camUpX, InfoState.TCameraCFrame.camUpY, InfoState.TCameraCFrame.camUpZ)
		local camRight = Vector3.new(InfoState.TCameraCFrame.camRightX, InfoState.TCameraCFrame.camRightY, InfoState.TCameraCFrame.camRightZ)
		cameraCFrame = CFrame.fromMatrix(camPos, camRight, camUp, -camLook)
	end

	local velocity = InfoState.Velocity
	if not velocity and InfoState.TVelocity then
		velocity = Vector3.new(InfoState.TVelocity.vX, InfoState.TVelocity.vY, InfoState.TVelocity.vZ)
	end

	if characterCFrame then
		RootPart.CFrame = characterCFrame
	end
	if velocity then
		RootPart.Velocity = velocity
	end
	if cameraCFrame then
		Workspace.CurrentCamera.CFrame = cameraCFrame
	end

	CurrentAnimationState = InfoState.Animation or { Name = "Idle", Weight = 0 }

	if CurrentAnimationState.Name and Hum then
		local Animator = Hum:FindFirstChildOfClass("Animator")
		if Animator then
			local PlayingTracks = Animator:GetPlayingAnimationTracks()
			for _, track in ipairs(PlayingTracks) do
				if track.Name == CurrentAnimationState.Name then
					track:AdjustSpeed(CurrentAnimationState.Weight or 1)
					track.TimePosition = 0
					break
				end
			end
		end
	end
end

local function getTASName()
	return TASNameBox.Text
end

local function SetUpGui()
	if HUD then HUD:Destroy() end

	HUD = Instance.new("ScreenGui", LocalPlayer.PlayerGui)
	HUD.Name = "TASRecorderGUI"
	HUD.ResetOnSpawn = false
	HUD.ZIndexBehavior = Enum.ZIndexBehavior.Sibling

	local MainFrame = Instance.new("Frame", HUD)
	MainFrame.AnchorPoint = Vector2.new(0.5, 1)
	MainFrame.Position = UDim2.new(0.5, 0, 1, -10)
	MainFrame.Size = UDim2.new(0, 250, 0, 110)
	MainFrame.BackgroundColor3 = Color3.fromRGB(30, 30, 30)
	MainFrame.BackgroundTransparency = 0.3
	MainFrame.BorderColor3 = Color3.fromRGB(200, 200, 200)
	MainFrame.BorderSizePixel = 1

	local UIListLayout = Instance.new("UIListLayout", MainFrame)
	UIListLayout.Padding = UDim.new(0, 5)
	UIListLayout.SortOrder = Enum.SortOrder.LayoutOrder

	TimeTextLabel = Instance.new("TextLabel", MainFrame)
	TimeTextLabel.Name = "TimeText"
	TimeTextLabel.LayoutOrder = 1
	TimeTextLabel.Size = UDim2.new(1, -10, 0, 20)
	TimeTextLabel.Position = UDim2.new(0, 5, 0, 5)
	TimeTextLabel.BackgroundTransparency = 1
	TimeTextLabel.Font = Enum.Font.SourceSansBold
	TimeTextLabel.Text = "0:00.000"
	TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
	TimeTextLabel.TextScaled = true
	TimeTextLabel.TextXAlignment = Enum.TextXAlignment.Left

	SavestatesCountLabel = Instance.new("TextLabel", MainFrame)
	SavestatesCountLabel.Name = "SavestatesCount"
	SavestatesCountLabel.LayoutOrder = 2
	SavestatesCountLabel.Size = UDim2.new(1, -10, 0, 15)
	SavestatesCountLabel.BackgroundTransparency = 1
	SavestatesCountLabel.Font = Enum.Font.SourceSans
	SavestatesCountLabel.Text = "Savestates: 0"
	SavestatesCountLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
	SavestatesCountLabel.TextScaled = true
	SavestatesCountLabel.TextXAlignment = Enum.TextXAlignment.Left

	FrameCountLabel = Instance.new("TextLabel", MainFrame)
	FrameCountLabel.Name = "FrameCount"
	FrameCountLabel.LayoutOrder = 3
	FrameCountLabel.Size = UDim2.new(1, -10, 0, 15)
	FrameCountLabel.BackgroundTransparency = 1
	FrameCountLabel.Font = Enum.Font.SourceSans
	FrameCountLabel.Text = "Frames: 0"
	FrameCountLabel.TextColor3 = Color3.fromRGB(220, 220, 220)
	FrameCountLabel.TextScaled = true
	FrameCountLabel.TextXAlignment = Enum.TextXAlignment.Left

	TASNameBox = Instance.new("TextBox", MainFrame)
	TASNameBox.Name = "TAS Name"
	TASNameBox.LayoutOrder = 4
	TASNameBox.Size = UDim2.new(1, -10, 0, 15)
	TASNameBox.BackgroundTransparency = .75
	TASNameBox.Font = Enum.Font.SourceSans
	TASNameBox.PlaceholderText = "TAS Name"
	TASNameBox.Text = ""
	TASNameBox.TextColor3 = Color3.fromRGB(220, 220, 220)
	TASNameBox.TextScaled = true
	TASNameBox.TextXAlignment = Enum.TextXAlignment.Center

	local KeybindsButton = Instance.new("TextButton", MainFrame)
	KeybindsButton.Name = "KeybindsButton"
	KeybindsButton.LayoutOrder = 4
	KeybindsButton.Size = UDim2.new(1, -10, 0, 20)
	KeybindsButton.BackgroundColor3 = Color3.fromRGB(80, 80, 80)
	KeybindsButton.TextColor3 = Color3.fromRGB(255, 255, 255)
	KeybindsButton.Text = "Show Keybinds"
	KeybindsButton.Font = Enum.Font.SourceSansBold
	KeybindsButton.TextScaled = true

	KeyBindFrame = Instance.new("Frame", HUD)
	KeyBindFrame.Name = "KeyBindFrame"
	KeyBindFrame.AnchorPoint = Vector2.new(0.5, 1)
	KeyBindFrame.Position = UDim2.new(0.5, 0, 1, -120)
	KeyBindFrame.Size = UDim2.new(0, 250, 0, 200)
	KeyBindFrame.BackgroundColor3 = Color3.fromRGB(40, 40, 40)
	KeyBindFrame.BackgroundTransparency = 0.2
	KeyBindFrame.BorderColor3 = Color3.fromRGB(200, 200, 200)
	KeyBindFrame.BorderSizePixel = 1
	KeyBindFrame.Visible = false
	KeyBindFrame.ClipsDescendants = true

	local KeybindListLayout = Instance.new("UIListLayout", KeyBindFrame)
	KeybindListLayout.Padding = UDim.new(0, 2)
	KeybindListLayout.HorizontalAlignment = Enum.HorizontalAlignment.Center
	KeybindListLayout.SortOrder = Enum.SortOrder.LayoutOrder

	local function CreateKeybindLabel(Key, Action)
		local Label = Instance.new("TextLabel", KeyBindFrame)
		Label.Name = Action .. "Label"
		Label.Size = UDim2.new(1, -10, 0, 15)
		Label.Position = UDim2.new(0, 5, 0, 0)
		Label.BackgroundTransparency = 1
		Label.Font = Enum.Font.SourceSans
		Label.Text = Key .. " : " .. Action
		Label.TextColor3 = Color3.fromRGB(230, 230, 230)
		Label.TextScaled = true
		Label.TextXAlignment = Enum.TextXAlignment.Left
		return Label
	end

	CapLockPauseLabel = CreateKeybindLabel(Keybinds.UserPause, "Pause/Unpause")
	CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
	CreateKeybindLabel(Keybinds.AddSavestate, "Add Savestate")
	CreateKeybindLabel(Keybinds.RemoveSavestate, "Remove Savestate")
	CreateKeybindLabel(Keybinds.BackSavestate, "Go To Last Savestate")
	CreateKeybindLabel(Keybinds.GoFrameBack, "Go Frame Back")
	CreateKeybindLabel(Keybinds.GoFrameForward, "Go Frame Forward")
	CreateKeybindLabel(Keybinds.GoBack, "Go Back")
	CreateKeybindLabel(Keybinds.GoForward, "Go Forward")
	CreateKeybindLabel(Keybinds.SaveRun, "Save Run")
	CreateKeybindLabel(Keybinds.CollisionToggler, "Toggle Collision")
	CreateKeybindLabel(Keybinds.ViewTAS, "View TAS")
	CreateKeybindLabel(Keybinds.ResetToNormal, "Stop Recording")
	CreateKeybindLabel(Keybinds.LoadRun, "Load Run")


	KeybindsButton.MouseButton1Click:Connect(function()
		KeyBindFrame.Visible = not KeyBindFrame.Visible
		if KeyBindFrame.Visible then
			KeybindsButton.Text = "Hide Keybinds"
		else
			KeybindsButton.Text = "Show Keybinds"
		end
	end)
end

local function UserPauseToggle()
	if ViewingTAS then return end
	Pause = not Pause
	if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
		LocalPlayer.Character.HumanoidRootPart.Anchored = Pause
	end

	if Pause then
		TimePauseHolder = tick()
		if TimeTextLabel then TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 0) end
		if CapLockPauseLabel then
			CapLockPauseLabel.Text = Keybinds.UserPause .. " : Paused"
			CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
		end
	else
		if TimePauseHolder then
			TimePaused = TimePaused + (tick() - TimePauseHolder)
			TimePauseHolder = nil
		end
		if TimeTextLabel then TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 255) end
		if CapLockPauseLabel then
			CapLockPauseLabel.Text = Keybinds.UserPause .. " : Unpaused"
			CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 255)
		end
	end
end

local function AddSavestate()
	local CurrentPlayerInfoCopy = {}
	for i = 1, #PlayerInfo do
		local frame = PlayerInfo[i]
		local frameCopy = {}

		for key, value in pairs(frame) do
			frameCopy[key] = value
		end

		if frame.TCFrame and not frameCopy.CFrame then
			local charPos = Vector3.new(frame.TCFrame.posX, frame.TCFrame.posY, frame.TCFrame.posZ)
			local charLook = Vector3.new(frame.TCFrame.lookX, frame.TCFrame.lookY, frame.TCFrame.lookZ)
			local charUp = Vector3.new(frame.TCFrame.upX, frame.TCFrame.upY, frame.TCFrame.upZ)
			local charRight = Vector3.new(frame.TCFrame.rightX, frame.TCFrame.rightY, frame.TCFrame.rightZ)
			frameCopy.CFrame = CFrame.fromMatrix(charPos, charRight, charUp, -charLook)
		end

		if frame.TCameraCFrame and not frameCopy.CameraCFrame then
			local camPos = Vector3.new(frame.TCameraCFrame.camPosX, frame.TCameraCFrame.camPosY, frame.TCameraCFrame.camPosZ)
			local camLook = Vector3.new(frame.TCameraCFrame.camLookX, frame.TCameraCFrame.camLookY, frame.TCameraCFrame.camLookZ)
			local camUp = Vector3.new(frame.TCameraCFrame.camUpX, frame.TCameraCFrame.camUpY, frame.TCameraCFrame.camUpZ)
			local camRight = Vector3.new(frame.TCameraCFrame.camRightX, frame.TCameraCFrame.camRightY, frame.TCameraCFrame.camRightZ)
			frameCopy.CameraCFrame = CFrame.fromMatrix(camPos, camRight, camUp, -camLook)
		end

		if frame.TVelocity and not frameCopy.Velocity then
			frameCopy.Velocity = Vector3.new(frame.TVelocity.vX, frame.TVelocity.vY, frame.TVelocity.vZ)
		end

		table.insert(CurrentPlayerInfoCopy, frameCopy)
	end

	table.insert(Savestates, CurrentPlayerInfoCopy)
	PlayerInfo = {}
	UpdateGUIText()
end

local function RemoveSavestate()
	if #Savestates > 1 then
		table.remove(Savestates)
		if Savestates[#Savestates] and #Savestates[#Savestates] > 0 then
			local lastFrame = Savestates[#Savestates][#Savestates[#Savestates]]
			PlayerInfo = {}
			Pause = true
			TimePauseHolder = tick()
			TimeStart = tick() - lastFrame.Time
			TimePaused = 0
			SetCharacterState(lastFrame)
			UpdateTimeGUI()
		else
			PlayerInfo = {}
			TimeStart = tick()
			TimePaused = 0
		end
		UpdateGUIText()
	end
end

local function BackSavestate()
	if #Savestates > 0 and Savestates[#Savestates] and #Savestates[#Savestates] > 0 then
		local InfoState = Savestates[#Savestates][#Savestates[#Savestates]]
		PlayerInfo = {}
		Pause = true
		if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
			LocalPlayer.Character.HumanoidRootPart.Anchored = true
		end
		TimePauseHolder = tick()
		TimeStart = tick() - InfoState.Time
		TimePaused = 0
		SetCharacterState(InfoState)
		if TimeTextLabel then TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 0) end
		if CapLockPauseLabel then
			CapLockPauseLabel.Text = Keybinds.UserPause .. " : Paused"
			CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
		end
		UpdateTimeGUI()
	end
end

local function GoFrameForward()
	if Pause then
		UserPauseToggle()
		RunService.Heartbeat:Wait()
		RunService.Heartbeat:Wait()
		UserPauseToggle()
	end
end

local isFrameForwardHeld = false
local function FrameForwardStart()
	isFrameForwardHeld = true
	GoFrameForward()
	while task.wait(0.05) and isFrameForwardHeld do
		GoFrameForward()
	end
end

local isGoForwardHeld = false
local function GoForwardStart()
	isGoForwardHeld = true
	GoFrameForward()
	while task.wait(0.0075) and isGoForwardHeld do
		GoFrameForward()
	end
end

local function GoFrameBack()
	if LocalPlayer.Character then
		local TargetFrameInfo = nil

		if #PlayerInfo > 1 then
			TargetFrameInfo = PlayerInfo[#PlayerInfo - 1]
			PlayerInfo[#PlayerInfo] = nil
		elseif #Savestates > 0 and #Savestates[#Savestates] > 1 then
			TargetFrameInfo = Savestates[#Savestates][#Savestates[#Savestates] - 1]
			Savestates[#Savestates][#Savestates[#Savestates]] = nil
		end

		if TargetFrameInfo then
			if not Pause then UserPauseToggle() end
			TimePauseHolder = tick()
			TimeStart = tick() - TargetFrameInfo.Time
			TimePaused = 0
			SetCharacterState(TargetFrameInfo)
			UpdateTimeGUI()
			UpdateGUIText()
		end
	end
end

local isFrameBackHeld = false
local function FrameBackStart()
	isFrameBackHeld = true
	GoFrameBack()
	while task.wait(0.05) and isFrameBackHeld do
		GoFrameBack()
	end
end

local isGoBackHeld = false
local function GoBackStart()
	isGoBackHeld = true
	GoFrameBack()
	while task.wait(0.0075) and isGoBackHeld do
		GoFrameBack()
	end
end

local function CollisionToggler()
	local Target = Mouse.Target
	if Target and Target:IsA("BasePart") then
		Target.CanCollide = not Target.CanCollide
		Target.Transparency = Target.CanCollide and 0 or 0.7
	end
end

local function LoadRun()
	local MapName = "Generic"
	if getTASName() ~= "" then
		MapName = getTASName()
	end
	local GameName = game:GetService("MarketplaceService"):GetProductInfoAsync(game.PlaceId).Name
	GameName = GameName:gsub("[^%w%s]", ""):gsub("%s+", "_")
	local FileName = GameName .. "_" .. MapName .. "_TAS.json"
	local FilePath = "TAS_Recorder/" .. FileName

	local loadedTAS = nil
	if readfile(FilePath) then
		loadedTAS = HttpService:JSONDecode(readfile(FilePath))
		print(loadedTAS)

		Savestates = {}
		PlayerInfo = {}

		Savestates = loadedTAS[1]
		PlayerInfo = loadedTAS[2]

		for i = 1, #PlayerInfo do
			local frame = PlayerInfo[i]
			if frame.TCFrame and frame.TCameraCFrame then
				local charPos = Vector3.new(frame.TCFrame.posX, frame.TCFrame.posY, frame.TCFrame.posZ)
				local charLook = Vector3.new(frame.TCFrame.lookX, frame.TCFrame.lookY, frame.TCFrame.lookZ)
				local charUp = Vector3.new(frame.TCFrame.upX, frame.TCFrame.upY, frame.TCFrame.upZ)
				local charRight = Vector3.new(frame.TCFrame.rightX, frame.TCFrame.rightY, frame.TCFrame.rightZ)
				frame.CFrame = CFrame.fromMatrix(charPos, charRight, charUp, -charLook)

				local camPos = Vector3.new(frame.TCameraCFrame.camPosX, frame.TCameraCFrame.camPosY, frame.TCameraCFrame.camPosZ)
				local camLook = Vector3.new(frame.TCameraCFrame.camLookX, frame.TCameraCFrame.camLookY, frame.TCameraCFrame.camLookZ)
				local camUp = Vector3.new(frame.TCameraCFrame.camUpX, frame.TCameraCFrame.camUpY, frame.TCameraCFrame.camUpZ)
				local camRight = Vector3.new(frame.TCameraCFrame.camRightX, frame.TCameraCFrame.camRightY, frame.TCameraCFrame.camRightZ)
				frame.CameraCFrame = CFrame.fromMatrix(camPos, camRight, camUp, -camLook)

				if frame.TVelocity then
					frame.Velocity = Vector3.new(frame.TVelocity.vX, frame.TVelocity.vY, frame.TVelocity.vZ)
				end
			end
		end

		if #PlayerInfo > 0 then
			local lastFrame = PlayerInfo[#PlayerInfo]
			TimeStart = tick()
			Pause = true
			if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
				LocalPlayer.Character.HumanoidRootPart.Anchored = true
			end
			TimePauseHolder = tick()
			TimePaused = 0
			SetCharacterState(lastFrame)
			if TimeTextLabel then TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 0) end
			if CapLockPauseLabel then
				CapLockPauseLabel.Text = Keybinds.UserPause .. " : Paused"
				CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
			end
		else
			TimeStart = tick()
		end

		UpdateGUIText()
		UpdateTimeGUI()
		AddSavestate()
		task.wait()
		RemoveSavestate()

		notify("TAS Loaded: "..FileName, "TAS Recorder")
	end
end

local function PrepareTasData()
	local FullTAS = {}

	for i = 1, #Savestates do
		for j = 1, #Savestates[i] do
			local Frame = Savestates[i][j]
			local MinFrame = {}
			MinFrame.CCFrame = {Frame.TCFrame.posX, Frame.TCFrame.posY, Frame.TCFrame.posZ, Frame.TCFrame.lookX, Frame.TCFrame.lookY, Frame.TCFrame.lookZ, Frame.TCFrame.upX, Frame.TCFrame.upY, Frame.TCFrame.upZ, Frame.TCFrame.rightX, Frame.TCFrame.rightY, Frame.TCFrame.rightZ}
			MinFrame.CCameraCFrame = {Frame.TCameraCFrame.camPosX, Frame.TCameraCFrame.camPosY, Frame.TCameraCFrame.camPosZ, Frame.TCameraCFrame.camLookX, Frame.TCameraCFrame.camLookY, Frame.TCameraCFrame.camLookZ, Frame.TCameraCFrame.camUpX, Frame.TCameraCFrame.camUpY, Frame.TCameraCFrame.camUpZ, Frame.TCameraCFrame.camRightX, Frame.TCameraCFrame.camRightY, Frame.TCameraCFrame.camRightZ}
			MinFrame.VVelocity = {Frame.TVelocity.vX, Frame.TVelocity.vY, Frame.TVelocity.vZ}
			MinFrame.AAnimation = Frame.Animation or {Name="Idle", Weight=0}
			MinFrame.time = Frame.Time

			table.insert(FullTAS, MinFrame)
		end
	end

	return FullTAS
end

local function SaveRun()
	local FullTAS = PrepareTasData()
	if #FullTAS > 0 then
		local MapName = "Generic"
		if getTASName() ~= "" then
			MapName = getTASName()
		end
		local GameName = game:GetService("MarketplaceService"):GetProductInfoAsync(game.PlaceId).Name
		GameName = GameName:gsub("[^%w%s]", ""):gsub("%s+", "_")

		local FileName = GameName .. "_" .. MapName .. "_TAS.json"
		local FilePath = "TAS_Recorder/" .. FileName

		local EncodedData = HttpService:JSONEncode({Savestates, ReturnPlayerInfo()})
		if writefile then
			if not isfolder("TAS_Recorder") then
				makefolder("TAS_Recorder")
			end
			writefile(FilePath, EncodedData)
			notify("TAS saved to: " .. FilePath, "TAS Recorder")
		else
			notify("File saving is not supported in this environment.", "TAS Recorder")
			print("TAS Data (JSON):")
			print(EncodedData)
		end
	else
		notify("No TAS data to save.", "TAS Recorder")
	end
end

local function ViewTASPlayback(TAS)
	if ViewingTAS or #TAS == 0 then return end
	ViewingTAS = true
	notify("Starting TAS Playback...")

	local StartTime = tick()
	local CurrentFrameIndex = 1

	local Character = LocalPlayer.Character
	if not Character or not Character:FindFirstChild("HumanoidRootPart") then
		notify("Character not found for playback.")
		ViewingTAS = false
		return
	end
	local RootPart = Character.HumanoidRootPart
	local Hum = Character.Humanoid

	local function StopPlayback()
		if PlaybackConnection then
			PlaybackConnection:Disconnect()
			PlaybackConnection = nil
		end
		RootPart.Anchored = false
		ViewingTAS = false
		notify("TAS Playback finished.")
		task.wait()
		if not Pause then UserPauseToggle() end
		RootPart.Anchored = true
	end

	RootPart.Anchored = false

	PlaybackConnection = RunService.Heartbeat:Connect(function()
		local ElapsedTime = tick() - StartTime
		local TargetFrame = nil

		while CurrentFrameIndex <= #TAS and TAS[CurrentFrameIndex].time <= ElapsedTime do
			TargetFrame = TAS[CurrentFrameIndex]
			CurrentFrameIndex = CurrentFrameIndex + 1
		end

		if TargetFrame then
			local cfData = TargetFrame.CCFrame
			local camData = TargetFrame.CCameraCFrame

			local charPos = Vector3.new(cfData[1], cfData[2], cfData[3])
			local charLook = Vector3.new(cfData[4], cfData[5], cfData[6])
			local charUp = Vector3.new(cfData[7], cfData[8], cfData[9])
			local charRight = Vector3.new(cfData[10], cfData[11], cfData[12])
			RootPart.CFrame = CFrame.fromMatrix(charPos, charRight, charUp, -charLook)

			local camPos = Vector3.new(camData[1], camData[2], camData[3])
			local camLook = Vector3.new(camData[4], camData[5], camData[6])
			local camUp = Vector3.new(camData[7], camData[8], camData[9])
			local camRight = Vector3.new(camData[10], camData[11], camData[12])
			Workspace.CurrentCamera.CFrame = CFrame.fromMatrix(camPos, camRight, camUp, -camLook)

			RootPart.Velocity = Vector3.new(TargetFrame.VVelocity[1], TargetFrame.VVelocity[2], TargetFrame.VVelocity[3])

			local AnimInfo = TargetFrame.AAnimation
			if AnimInfo and Hum then
				local Animator = Hum:FindFirstChildOfClass("Animator")
				if Animator then
					local Track = Animator:FindFirstChild(AnimInfo.Name, true)
					if Track and Track:IsA("AnimationTrack") then
						if not Track.IsPlaying then Track:Play() end
						Track:AdjustSpeed(AnimInfo.Weight or 1)
					end
				end
			end
		end

		if CurrentFrameIndex > #TAS then
			StopPlayback()
		end
	end)
end

local function DisconnectAll()
	if MainConnectionLoop then MainConnectionLoop:Disconnect() end
	if KeybindsConnect then KeybindsConnect:Disconnect() end
	if InputEndConnect then InputEndConnect:Disconnect() end
	if DiedConnect then DiedConnect:Disconnect() end
	if PlaybackConnection then PlaybackConnection:Disconnect() end
	if HUD then HUD:Destroy() end
	if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
		LocalPlayer.Character.HumanoidRootPart.Anchored = false
	end
	notify("TAS Recorder Stopped.", "TAS Recorder", math.huge, "Initialize Again", "Cancel")
end

function Initialize()
	Savestates = {}
	PlayerInfo = {}
	TimePaused = 0
	Pause = true
	TimeStart = tick()
	TimePauseHolder = tick()

	local initialCFrame = getCurrentCFrame()
	local initialCamCFrame = getCurrentCameraCFrame()
	local initialVelocity = getCurrentVelocity()

	table.insert(Savestates, {{
		TCFrame = {
			posX = initialCFrame.X,
			posY = initialCFrame.Y,
			posZ = initialCFrame.Z,
			lookX = initialCFrame.LookVector.X,
			lookY = initialCFrame.LookVector.Y,
			lookZ = initialCFrame.LookVector.Z,
			upX = initialCFrame.UpVector.X,
			upY = initialCFrame.UpVector.Y,
			upZ = initialCFrame.UpVector.Z,
			rightX = initialCFrame.RightVector.X,
			rightY = initialCFrame.RightVector.Y,
			rightZ = initialCFrame.RightVector.Z
		},
		TCameraCFrame = {
			camPosX = initialCamCFrame.X,
			camPosY = initialCamCFrame.Y,
			camPosZ = initialCamCFrame.Z,
			camLookX = initialCamCFrame.LookVector.X,
			camLookY = initialCamCFrame.LookVector.Y,
			camLookZ = initialCamCFrame.LookVector.Z,
			camUpX = initialCamCFrame.UpVector.X,
			camUpY = initialCamCFrame.UpVector.Y,
			camUpZ = initialCamCFrame.UpVector.Z,
			camRightX = initialCamCFrame.RightVector.X,
			camRightY = initialCamCFrame.RightVector.Y,
			camRightZ = initialCamCFrame.RightVector.Z
		},
		TVelocity = {
			vX = initialVelocity.X,
			vY = initialVelocity.Y,
			vZ = initialVelocity.Z
		},
		CFrame = initialCFrame,
		CameraCFrame = initialCamCFrame,
		Velocity = initialVelocity,
		Animation = { Name = "Idle", Weight = 0 },
		Time = 0
	}})

	SetUpGui()

	MainConnectionLoop = RunService.Heartbeat:Connect(function(deltaTime)
		if not Pause then
			UpdateTimeGUI()
			table.insert(PlayerInfo, ReturnPlayerInfo())
			UpdateGUIText()
		end
	end)

	KeybindsConnect = UserInputService.InputBegan:Connect(function(Input, Typing)
		if Typing then return end
		local KeyCode = Input.KeyCode

		if KeyCode == Enum.KeyCode[Keybinds.UserPause] then
			UserPauseToggle()
		elseif KeyCode == Enum.KeyCode[Keybinds.AddSavestate] then
			AddSavestate()
		elseif KeyCode == Enum.KeyCode[Keybinds.RemoveSavestate] then
			RemoveSavestate()
		elseif KeyCode == Enum.KeyCode[Keybinds.BackSavestate] then
			BackSavestate()
		elseif KeyCode == Enum.KeyCode[Keybinds.CollisionToggler] then
			CollisionToggler()
		elseif KeyCode == Enum.KeyCode[Keybinds.SaveRun] then
			SaveRun()
		elseif KeyCode == Enum.KeyCode[Keybinds.GoFrameForward] then
			task.spawn(FrameForwardStart)
		elseif KeyCode == Enum.KeyCode[Keybinds.GoFrameBack] then
			task.spawn(FrameBackStart)
		elseif KeyCode == Enum.KeyCode[Keybinds.GoForward] then
			task.spawn(GoForwardStart)
		elseif KeyCode == Enum.KeyCode[Keybinds.GoBack] then
			task.spawn(GoBackStart)
		elseif KeyCode == Enum.KeyCode[Keybinds.ResetToNormal] then
			DisconnectAll()
		elseif KeyCode == Enum.KeyCode[Keybinds.ViewTAS] then
			if not ViewingTAS then
				local CurrentTASData = PrepareTasData()
				ViewTASPlayback(CurrentTASData)
			end
		elseif KeyCode == Enum.KeyCode[Keybinds.LoadRun] then
			LoadRun()
		end
	end)

	InputEndConnect = UserInputService.InputEnded:Connect(function(Input, Typing)
		if Typing then return end
		local KeyCode = Input.KeyCode
		if KeyCode == Enum.KeyCode[Keybinds.GoFrameBack] then
			isFrameBackHeld = false
		elseif KeyCode == Enum.KeyCode[Keybinds.GoFrameForward] then
			isFrameForwardHeld = false
		elseif KeyCode == Enum.KeyCode[Keybinds.GoBack] then
			isGoBackHeld = false
		elseif KeyCode == Enum.KeyCode[Keybinds.GoForward] then
			isGoForwardHeld = false
		end
	end)

	if LocalPlayer.Character then
		local Hum = LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
		if Hum then
			DiedConnect = Hum.Died:Connect(function()
				task.wait(0.1)
				if not Pause then UserPauseToggle() end
				notify("Character died. Attempting to respawn and go to last savestate.")
				LocalPlayer.CharacterAdded:Wait()
				task.wait(1)
				BackSavestate()
				UserPauseToggle()
				if not Pause then UserPauseToggle() end
			end)
		end
	end

	notify("TAS Recorder Initialized.", "TAS Recorder")
	if LocalPlayer.Character and LocalPlayer.Character:FindFirstChild("HumanoidRootPart") then
		LocalPlayer.Character.HumanoidRootPart.Anchored = true
	end
	if TimeTextLabel then TimeTextLabel.TextColor3 = Color3.fromRGB(255, 255, 0) end
	if CapLockPauseLabel then
		CapLockPauseLabel.Text = Keybinds.UserPause .. " : Paused"
		CapLockPauseLabel.TextColor3 = Color3.fromRGB(255, 255, 0)
	end
	UserPauseToggle()
	if not Pause then UserPauseToggle() end
end

notify("TAS Recorder by Tomato, modified by Ali.", "TAS Recorder", 3)
notify("Make sure that the end of your TAS has a savestate (No frames)", "TAS Recorder Instructions", 5)
notify("TAS Recorder Loaded.", "TAS Recorder", math.huge, "Initialize", "Cancel")

Features & Information

(THIS IS A MODIFIED SCRIPT, I DID NOT MAKE THIS) Modified by me, original script by Tomato (https://scriptblox.com/script/Universal-Script-Universal-Tool-Assisted-Speedrun-Script-TAS-38770) Features: TAS Recording and Replaying Frame Step and Back Forward and Backward Movement Saving and Loading TAS Collision Toggler (MAKE SURE THE START AND END OF YOUR TAS HAS A SAVESTATE)
#TAS#Universal#Tool *isted Speedrun#Recording#Replay

Community Comments (2)

TomatoTomatoFeb 19, 2026

check out my new one i used your modified version as a base https://scriptblox.com/script/Universal-Script-Universal-TAS-Recorder-Redux-113811

Replying to @Tomato
AlizAlizFeb 20, 2026

@Tomato W!!

More Scripts for Universal Scripts