Skip to content
RoRoScripts
F3x Skybox maker gui 2 preview
UnknownUniversal

F3x Skybox maker gui 2

Script
Universal ScriptDudeBruhTsTuff
Views
290
Likes
2
Dislikes
0
Added
Script
--original by idkwhattypehere
--edited by FEScriptzzBOII

local Players = game:GetService("Players")
local UIS = game:GetService("UserInputService")
local HttpService = game:GetService("HttpService")
local ContentProvider = game:GetService("ContentProvider")
local RS = game:GetService("ReplicatedStorage")

local player = Players.LocalPlayer
local playerGui = player:WaitForChild("PlayerGui")

local MESH_ID = "rbxassetid://111891702759441"
local RED = Color3.fromRGB(255, 0, 4)
local BLACK = Color3.fromRGB(0, 0, 0)
local WHITE = Color3.fromRGB(255, 255, 255)
local GREEN = Color3.fromRGB(0, 255, 100)
local FONT = Enum.Font.TitilliumWeb
local env = (getgenv and getgenv()) or _G

if playerGui:FindFirstChild("F3XSkyboxStudio") then
	playerGui.F3XSkyboxStudio:Destroy()
end


local S = {
	part = nil,
	texture = "",
	scale = 1000,
	spinOn = false,
	spin = Vector3.new(0, 20, 0), -- deg/sec per axis
	rot = Vector3.new(0, 0, 0),
	vortex = false,
	vortexPower = 1,
	pulse = 6, -- % scale pulse in vortex
	fade = true, -- vortex: smooth colour crossfade (false = hard disco cuts)
	fadeTime = 1.5, -- seconds per colour
	follow = false,
	lock = true,
	strict = false, -- Fork3X / strict mode
	spinSpeed = 1, -- master multiplier for the spin sliders
	light = false, -- vortex light: brightness boost on the mesh
	bright = 1.5,
	volume = 5, -- HD Admin ;volume value
	pitch = 1, -- HD Admin ;pitch value
	hdPrefix = ";",
	pos = Vector3.new(0, 0, 0),
	anim = { on = false, fps = 4, loop = true, frames = {}, idx = 0, last = 0, current = nil },
	busy = false,
}
local sent = {} -- last values pushed to the server


local screen = Instance.new("ScreenGui")
screen.Name = "F3XSkyboxStudio"
screen.ResetOnSpawn = false
screen.Parent = playerGui

local function new(class, props, parent)
	local o = Instance.new(class)
	for k, v in pairs(props) do o[k] = v end
	o.Parent = parent
	return o
end

local function box(props, parent)
	props.BackgroundColor3 = props.BackgroundColor3 or BLACK
	props.BorderColor3 = RED
	props.BorderSizePixel = 1
	return new("Frame", props, parent)
end

local function label(parent, text, pos, size, align)
	return new("TextLabel", {
		Text = text, Position = pos, Size = size, BackgroundTransparency = 1,
		TextColor3 = WHITE, TextScaled = true, Font = FONT,
		TextXAlignment = align or Enum.TextXAlignment.Left,
	}, parent)
end

local function button(parent, text, pos, size, cb)
	local b = new("TextButton", {
		Text = text, Position = pos, Size = size, BackgroundColor3 = BLACK,
		BorderColor3 = RED, BorderSizePixel = 1, TextColor3 = WHITE,
		TextScaled = true, Font = FONT, AutoButtonColor = false,
	}, parent)
	b.MouseEnter:Connect(function() b.BackgroundColor3 = Color3.fromRGB(40, 0, 0) end)
	b.MouseLeave:Connect(function() b.BackgroundColor3 = BLACK end)
	if cb then b.MouseButton1Click:Connect(cb) end
	return b
end

local function input(parent, placeholder, pos, size)
	return new("TextBox", {
		Text = "", PlaceholderText = placeholder, PlaceholderColor3 = Color3.fromRGB(150, 150, 150),
		Position = pos, Size = size, BackgroundColor3 = WHITE, BorderColor3 = RED,
		BorderSizePixel = 1, TextColor3 = BLACK, TextScaled = true, Font = FONT,
		ClearTextOnFocus = false,
	}, parent)
end

local function toggle(parent, pos, size, text, init, cb)
	local state = init
	local b = button(parent, "", pos, size)
	local function refresh()
		b.Text = text .. ": " .. (state and "ON" or "OFF")
		b.TextColor3 = state and GREEN or WHITE
	end
	b.MouseButton1Click:Connect(function()
		state = not state
		refresh()
		cb(state)
	end)
	refresh()
	return function(v) state = v; refresh() end
end

local function sliderRow(parent, y, name, min, max, init, cb)
	local current = init
	label(parent, name, UDim2.new(0, 4, 0, y), UDim2.new(0, 34, 0, 18))
	local bar = new("Frame", {
		Size = UDim2.new(1, -100, 0, 6), Position = UDim2.new(0, 40, 0, y + 6),
		BackgroundColor3 = Color3.fromRGB(45, 45, 45), BorderSizePixel = 0,
	}, parent)
	local fill = new("Frame", { Size = UDim2.new(0, 0, 1, 0), BackgroundColor3 = RED, BorderSizePixel = 0 }, bar)
	local hit = new("TextButton", {
		Text = "", BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 18), Position = UDim2.new(0, 0, 0.5, -9),
	}, bar)
	local tb = input(parent, "", UDim2.new(1, -56, 0, y), UDim2.new(0, 52, 0, 18))

	local function show(v)
		current = v
		tb.Text = tostring(math.floor(v * 100 + 0.5) / 100)
		fill.Size = UDim2.new(math.clamp((v - min) / (max - min), 0, 1), 0, 1, 0)
	end
	local function set(v)
		show(v)
		cb(v)
	end
	local dragging = false
	local function fromX(x)
		local a = math.clamp((x - bar.AbsolutePosition.X) / bar.AbsoluteSize.X, 0, 1)
		set(min + (max - min) * a)
	end
	hit.InputBegan:Connect(function(i)
		if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then
			dragging = true
			fromX(i.Position.X)
		end
	end)
	UIS.InputChanged:Connect(function(i)
		if dragging and (i.UserInputType == Enum.UserInputType.MouseMovement or i.UserInputType == Enum.UserInputType.Touch) then
			fromX(i.Position.X)
		end
	end)
	UIS.InputEnded:Connect(function(i)
		if i.UserInputType == Enum.UserInputType.MouseButton1 or i.UserInputType == Enum.UserInputType.Touch then
			dragging = false
		end
	end)
	tb.FocusLost:Connect(function()
		local n = tonumber(tb.Text)
		if n then set(n) else show(current) end
	end)
	show(init)
	return function(v) show(v); cb(v) end
end


local function makeWindow(titleText, pos, size, closable)
	local frame = box({ Size = size, Position = pos, Active = true, Draggable = true }, screen)
	local bar = new("TextLabel", {
		Size = UDim2.new(1, 0, 0, 16), BackgroundColor3 = BLACK, BorderColor3 = RED, BorderSizePixel = 1,
		Text = titleText, TextColor3 = WHITE, TextScaled = true, Font = FONT,
	}, frame)
	local body = new("Frame", {
		Size = UDim2.new(1, 0, 1, -16), Position = UDim2.new(0, 0, 0, 16), BackgroundTransparency = 1,
	}, frame)
	local minimized = false
	local minBtn = button(frame, "-", UDim2.new(1, closable and -34 or -17, 0, 0), UDim2.new(0, 16, 0, 16))
	minBtn.ZIndex = 3
	minBtn.MouseButton1Click:Connect(function()
		minimized = not minimized
		body.Visible = not minimized
		minBtn.Text = minimized and "+" or "-"
		frame.Size = minimized and UDim2.new(size.X.Scale, size.X.Offset, 0, 16) or size
	end)
	if closable then
		local x = button(frame, "x", UDim2.new(1, -17, 0, 0), UDim2.new(0, 16, 0, 16), function() frame.Visible = false end)
		x.ZIndex = 3
	end
	return frame, body
end


local cachedEndpoint
local statusLabel

local function status(text)
	if statusLabel then statusLabel.Text = text end
end

local function scan(root)
	if not root then return nil end
	for _, o in ipairs(root:GetDescendants()) do
		if o.Name == "SyncAPI" then
			local ep = o:FindFirstChild("ServerEndpoint")
			if ep then return ep end
		end
	end
end

local function findEndpoint()
	if cachedEndpoint and cachedEndpoint.Parent then return cachedEndpoint end
	cachedEndpoint = scan(player) or scan(player.Character) or scan(RS)
	if not cachedEndpoint then -- fork fallback: any RemoteFunction called ServerEndpoint
		for _, root in ipairs({ player, player.Character, RS }) do
			if root then
				for _, o in ipairs(root:GetDescendants()) do
					if o:IsA("RemoteFunction") and o.Name == "ServerEndpoint" then
						cachedEndpoint = o
						break
					end
				end
			end
			if cachedEndpoint then break end
		end
	end
	return cachedEndpoint
end

local function call(...)
	local ep = findEndpoint()
	if not ep then
		status("Equip F3X first")
		return false
	end
	local args = table.pack(...)
	local tries = S.strict and 3 or 1
	local ok, res
	for _ = 1, tries do
		ok, res = pcall(function() return ep:InvokeServer(table.unpack(args, 1, args.n)) end)
		if ok then break end
		task.wait(0.1)
	end
	if not ok then warn("[Skybox] " .. tostring(res)) end
	if S.strict then task.wait(0.05) end
	return ok, res
end

local function syncMesh(part, props)
	if S.strict then -- strict: one property per request
		for k, v in pairs(props) do
			call("SyncMesh", { { Part = part, [k] = v } })
			task.wait(0.08)
		end
	else
		local e = { Part = part }
		for k, v in pairs(props) do e[k] = v end
		call("SyncMesh", { e })
	end
end

local function assetUrl(id)
	id = tostring(id or ""):gsub("%s", "")
	if id == "" then return nil end
	if id:match("^%d+$") then return "rbxassetid://" .. id end
	return id
end

local texCache = {}

local function httpGet(url)
	local req = env.request or env.http_request or (env.syn and env.syn.request) or (env.http and env.http.request)
	if req then
		local ok, res = pcall(req, { Url = url, Method = "GET" })
		if ok and res and res.StatusCode == 200 then return res.Body end
	end
	local ok, body = pcall(function() return game:HttpGet(url) end)
	if ok and type(body) == "string" then return body end
end

local function resolveTexture(text)
	text = tostring(text or ""):gsub("%s", "")
	if text == "" then return nil end
	local num = text:match("^%d+$") or text:match("rbxassetid://(%d+)") or text:match("[?&]id=(%d+)")
	if not num then return text end
	if texCache[num] then return "rbxassetid://" .. texCache[num] end

	status("Resolving " .. num .. "...")
	local img

	local ok, objs = pcall(function() return game:GetObjects("rbxassetid://" .. num) end)
	if ok and type(objs) == "table" then
		for _, o in ipairs(objs) do
			local d = ((o:IsA("Decal") or o:IsA("Texture")) and o)
				or o:FindFirstChildWhichIsA("Decal", true)
				or o:FindFirstChildWhichIsA("Texture", true)
			if d then
				local t = tostring(d.Texture)
				img = t:match("id=(%d+)") or t:match("rbxassetid://(%d+)")
			end
			o:Destroy()
			if img then break end
		end
	end

	if not img then
		local body = httpGet("https://assetdelivery.roblox.com/v1/asset/?id=" .. num)
		if body then
			if body:sub(1, 30):find("<roblox") then
				img = body:match("asset/%?id=(%d+)") or body:match("rbxassetid://(%d+)")
			else
				img = num -- raw image data, so it already is an image id
			end
		end
	end

	if img then
		texCache[num] = img
		texCache[img] = img
		status(img == num and "Image id ok" or ("Decal " .. num .. " -> image " .. img))
		return "rbxassetid://" .. img
	end
	status("Can't resolve decal here, use an image id")
	return "rbxassetid://" .. num
end

local function hasSky()
	return S.part ~= nil and S.part.Parent ~= nil
end

local function createSky()
	if S.busy then return end
	local url = assetUrl(S.texture)
	if not url then status("Paste a texture id first") return end
	local char = player.Character
	local root = char and char:FindFirstChild("HumanoidRootPart")
	if not root then status("No character") return end
	S.busy = true

	local cf = root.CFrame + Vector3.new(0, 6, 0)
	local ok, part = call("CreatePart", "Normal", cf, workspace)
	if not (ok and typeof(part) == "Instance") then
		task.wait(0.2)
		part = nil
		for _, p in ipairs(workspace:GetChildren()) do
			if p:IsA("BasePart") and (p.Position - cf.Position).Magnitude < 1 then part = p break end
		end
	end
	if not part then
		status("Create failed (F3X equipped?)")
		S.busy = false
		return
	end


	call("SetName", { part }, "Sky")
	call("CreateMeshes", { { Part = part } })
	task.wait(0.1)
	local okM = call("SyncMesh", { { Part = part, MeshId = MESH_ID } })
	local okT = call("SyncMesh", { { Part = part, TextureId = url } })
	call("SyncMesh", { { Part = part, Scale = Vector3.new(S.scale, S.scale, S.scale) } })
	call("SyncAnchor", { { Part = part, Anchored = true } })
	call("SyncCollision", { { Part = part, CanCollide = false } }) -- no collisions
	if S.lock then call("SetLocked", { part }, true) end
	if not (okM and okT) then status("Mesh/texture request failed (see output)") end

	S.part = part
	S.pos = part.Position
	S.rot = Vector3.new(0, 0, 0)
	sent = { TextureId = url, Scale = Vector3.new(S.scale, S.scale, S.scale), VertexColor = Vector3.new(1, 1, 1), pos = S.pos }
	S.busy = false
	status("Skybox set")
end

local function deleteSky()
	if not hasSky() then S.part = nil status("No skybox") return end
	local part = S.part
	S.part = nil
	S.anim.on = false
	call("SetLocked", { part }, false)
	call("Remove", { part })
	if part.Parent then call("Remove", { part }) end
	status(part.Parent and "Delete may have failed" or "Skybox deleted")
end


local main, mainBody = makeWindow("F3x skybox", UDim2.new(0.45, 0, 0.3, 0), UDim2.new(0, 190, 0, 118), false)

local mainBox = input(mainBody, "paste ur texture id here", UDim2.new(0, 4, 0, 4), UDim2.new(1, -8, 0, 22))
local setBtn, delBtn, optBtn

setBtn = button(mainBody, "Set", UDim2.new(0, 4, 0, 30), UDim2.new(0.5, -6, 0, 22), function()
	local url = resolveTexture(mainBox.Text)
	if not url then status("Paste a texture id first") return end
	S.texture = url
	if hasSky() then
		status("Updated")
	else
		createSky()
	end
end)
delBtn = button(mainBody, "Delete", UDim2.new(0.5, 2, 0, 30), UDim2.new(0.5, -6, 0, 22), deleteSky)
optBtn = button(mainBody, "Options", UDim2.new(0, 4, 0, 56), UDim2.new(1, -8, 0, 22))
statusLabel = label(mainBody, "Equip F3X, paste id, Set", UDim2.new(0, 4, 0, 82), UDim2.new(1, -8, 0, 14))

-- live: editing the id updates an existing skybox after a short pause
local typeVer = 0
mainBox:GetPropertyChangedSignal("Text"):Connect(function()
	typeVer += 1
	local v = typeVer
	task.delay(0.6, function()
		if v == typeVer and hasSky() and mainBox.Text ~= "" then
			S.texture = resolveTexture(mainBox.Text) or S.texture
		end
	end)
end)

local opt, optBody = makeWindow("Options", UDim2.new(0.45, 0, 0.3, 126), UDim2.new(0, 290, 0, 240), true)
opt.Visible = false
optBtn.MouseButton1Click:Connect(function() opt.Visible = not opt.Visible end)

local tabNames = { "Spin", "Vortex", "Size", "Music", "Anim", "Images", "Misc" }
local pages, tabBtns = {}, {}
local function showTab(name)
	for n, p in pairs(pages) do p.Visible = (n == name) end
	for n, b in pairs(tabBtns) do b.TextColor3 = (n == name) and RED or WHITE end
end
for i, n in ipairs(tabNames) do
	tabBtns[n] = button(optBody, n, UDim2.new((i - 1) / #tabNames, 1, 0, 0), UDim2.new(1 / #tabNames, -2, 0, 18), function() showTab(n) end)
	pages[n] = new("Frame", {
		Size = UDim2.new(1, 0, 1, -22), Position = UDim2.new(0, 0, 0, 22), BackgroundTransparency = 1, Visible = false,
	}, optBody)
end


do
	local p = pages.Spin
	local setT = toggle(p, UDim2.new(0, 4, 0, 0), UDim2.new(1, -8, 0, 24), "Spin skybox", S.spinOn, function(v) S.spinOn = v end)
	sliderRow(p, 32, "Speed", 0, 10, S.spinSpeed, function(v) S.spinSpeed = v end)
	sliderRow(p, 54, "X", -360, 360, S.spin.X, function(v) S.spin = Vector3.new(v, S.spin.Y, S.spin.Z) end)
	sliderRow(p, 76, "Y", -360, 360, S.spin.Y, function(v) S.spin = Vector3.new(S.spin.X, v, S.spin.Z) end)
	sliderRow(p, 98, "Z", -360, 360, S.spin.Z, function(v) S.spin = Vector3.new(S.spin.X, S.spin.Y, v) end)
	label(p, "Speed multiplies X/Y/Z (deg/s). Type any number. Live.", UDim2.new(0, 4, 0, 124), UDim2.new(1, -8, 0, 14))
	button(p, "Reset rotation", UDim2.new(0, 4, 0, 144), UDim2.new(1, -8, 0, 22), function() S.rot = Vector3.new(0, 0, 0) end)
end

do
	local p = pages.Vortex
	toggle(p, UDim2.new(0, 4, 0, 0), UDim2.new(1, -8, 0, 22), "Vortex disco", S.vortex, function(v) S.vortex = v end)
	sliderRow(p, 26, "Power", 0.2, 3, S.vortexPower, function(v) S.vortexPower = v end)
	sliderRow(p, 46, "Pulse", 0, 30, S.pulse, function(v) S.pulse = v end)
	toggle(p, UDim2.new(0, 4, 0, 68), UDim2.new(1, -8, 0, 20), "Fade", S.fade, function(v) S.fade = v end)
	sliderRow(p, 90, "Time", 0.2, 5, S.fadeTime, function(v) S.fadeTime = math.max(v, 0.1) end)
	toggle(p, UDim2.new(0, 4, 0, 114), UDim2.new(1, -8, 0, 20), "Vortex light", S.light, function(v) S.light = v end)
	sliderRow(p, 136, "Bright", 0, 5, S.bright, function(v) S.bright = v end)
	label(p, "Light: skybox brightness (1 = normal, above 1 glows, 0 = black). Fade ON blends colours, OFF hard-cuts.", UDim2.new(0, 4, 0, 158), UDim2.new(1, -8, 0, 40))
end


do
	local p = pages.Size
	local setSize
	setSize = sliderRow(p, 4, "Size", 10, 3000, S.scale, function(v) S.scale = math.max(v, 1) end)
	label(p, "Type any number in the box, or drag the line.", UDim2.new(0, 4, 0, 28), UDim2.new(1, -8, 0, 14))
	for i, v in ipairs({ 100, 500, 1000, 2000 }) do
		button(p, tostring(v), UDim2.new((i - 1) / 4, 2, 0, 50), UDim2.new(0.25, -4, 0, 22), function() setSize(v) end)
	end
end


local frameBoxes = {}
local setPlayBtn
do
	local p = pages.Anim
	for i = 1, 8 do
		local col, rowN = (i - 1) % 4, math.floor((i - 1) / 4)
		local tb = input(p, "frame " .. i, UDim2.new(col / 4, 2, 0, rowN * 26), UDim2.new(0.25, -4, 0, 22))
		frameBoxes[i] = tb
	end
	sliderRow(p, 58, "FPS", 1, 30, S.anim.fps, function(v) S.anim.fps = math.max(v, 0.5) end)

	local playBtn
	local function refreshPlay()
		playBtn.Text = S.anim.on and "Stop" or "Play"
		playBtn.TextColor3 = S.anim.on and GREEN or WHITE
	end
	setPlayBtn = refreshPlay
	playBtn = button(p, "Play", UDim2.new(0, 4, 0, 84), UDim2.new(0.5, -6, 0, 24), function()
		if S.anim.on then
			S.anim.on = false
		else
			if not hasSky() then status("Create the skybox first") return end
			S.anim.frames = {}
			local list = {}
			for _, tb in ipairs(frameBoxes) do
				local url = resolveTexture(tb.Text)
				if url then
					table.insert(S.anim.frames, url)
					local d = Instance.new("Decal")
					d.Texture = url
					table.insert(list, d)
				end
			end
			if #S.anim.frames < 2 then status("Add at least 2 frames") return end
			task.spawn(function() -- preload so frames don't flicker for you
				pcall(function() ContentProvider:PreloadAsync(list) end)
				for _, d in ipairs(list) do d:Destroy() end
			end)
			S.anim.idx = 0
			S.anim.last = 0
			S.anim.on = true
		end
		refreshPlay()
	end)
	toggle(p, UDim2.new(0.5, 2, 0, 84), UDim2.new(0.5, -6, 0, 24), "Loop", S.anim.loop, function(v) S.anim.loop = v end)
	button(p, "Clear frames", UDim2.new(0, 4, 0, 114), UDim2.new(1, -8, 0, 22), function()
		for _, tb in ipairs(frameBoxes) do tb.Text = "" end
	end)
end


do
	local p = pages.Images
	local searchBox = input(p, "search decals...", UDim2.new(0, 4, 0, 0), UDim2.new(1, -112, 0, 22))
	local goBtn = button(p, "Go", UDim2.new(1, -106, 0, 0), UDim2.new(0, 36, 0, 22))
	local mode = "sky"
	local modeBtn = button(p, "-> Sky", UDim2.new(1, -66, 0, 0), UDim2.new(0, 62, 0, 22), function() end)
	modeBtn.MouseButton1Click:Connect(function()
		mode = (mode == "sky") and "frame" or "sky"
		modeBtn.Text = (mode == "sky") and "-> Sky" or "-> Frame"
	end)

	local scroll = new("ScrollingFrame", {
		Size = UDim2.new(1, -8, 0, 150), Position = UDim2.new(0, 4, 0, 26), BackgroundColor3 = Color3.fromRGB(15, 15, 15),
		BorderColor3 = RED, BorderSizePixel = 1, ScrollBarThickness = 4, CanvasSize = UDim2.new(0, 0, 0, 0),
		AutomaticCanvasSize = Enum.AutomaticSize.Y,
	}, p)
	new("UIGridLayout", { CellSize = UDim2.new(0, 60, 0, 60), CellPadding = UDim2.new(0, 4, 0, 4) }, scroll)
	local moreBtn = button(p, "More", UDim2.new(0, 4, 0, 180), UDim2.new(1, -8, 0, 20))

	local page, lastKw = 1, ""
	local function pick(id)
		task.spawn(function()
			if mode == "sky" then
				local url = resolveTexture(id) -- decal id -> real image id
				S.texture = url
				local img = url and url:match("%d+")
				if img then mainBox.Text = img end
				if not hasSky() then createSky() end
			else
				for _, tb in ipairs(frameBoxes) do
					if tb.Text == "" then tb.Text = tostring(id) break end
				end
			end
		end)
	end

	local function search(kw, pg)
		local req = env.request or env.http_request or (env.syn and env.syn.request) or (env.http and env.http.request)
		if not req then
			status("No HTTP request function here")
			return
		end
		local url = ("https://apis.roblox.com/toolbox-service/v1/marketplace/13?keyword=%s&limit=30&pageNumber=%d")
			:format(HttpService:UrlEncode(kw), pg)
		local ok, res = pcall(req, { Url = url, Method = "GET" })
		if not (ok and res and res.StatusCode == 200) then
			status("Search failed")
			return
		end
		local okJ, data = pcall(HttpService.JSONDecode, HttpService, res.Body)
		if not okJ or type(data) ~= "table" or type(data.data) ~= "table" then
			status("Bad response")
			return
		end
		if pg == 1 then
			for _, c in ipairs(scroll:GetChildren()) do
				if c:IsA("ImageButton") then c:Destroy() end
			end
		end
		for _, d in ipairs(data.data) do
			local id = d.id
			if id then
				local b = new("ImageButton", {
					Image = ("rbxthumb://type=Asset&id=%d&w=150&h=150"):format(id),
					BackgroundColor3 = Color3.fromRGB(30, 30, 30), BorderColor3 = RED, BorderSizePixel = 1,
				}, scroll)
				new("TextLabel", {
					Size = UDim2.new(1, 0, 0, 12), Position = UDim2.new(0, 0, 1, -12), BackgroundColor3 = BLACK,
					BackgroundTransparency = 0.3, Text = tostring(id), TextColor3 = WHITE, TextScaled = true, Font = FONT,
				}, b)
				b.MouseButton1Click:Connect(function() pick(id) end)
			end
		end
		status(#data.data .. " results")
	end

	local function go()
		lastKw = searchBox.Text
		page = 1
		search(lastKw, page)
	end
	goBtn.MouseButton1Click:Connect(go)
	searchBox.FocusLost:Connect(function(enter) if enter then go() end end)
	moreBtn.MouseButton1Click:Connect(function()
		page += 1
		search(lastKw, page)
	end)
end

do
	local p = pages.Misc
	toggle(p, UDim2.new(0, 4, 0, 0), UDim2.new(1, -8, 0, 22), "Fork3X strict mode", S.strict, function(v) S.strict = v end)
	toggle(p, UDim2.new(0, 4, 0, 26), UDim2.new(1, -8, 0, 22), "Lock part", S.lock, function(v)
		S.lock = v
		if hasSky() then call("SetLocked", { S.part }, v) end
	end)
	toggle(p, UDim2.new(0, 4, 0, 52), UDim2.new(1, -8, 0, 22), "Follow me", S.follow, function(v) S.follow = v end)
	button(p, "Rescan F3X tool", UDim2.new(0, 4, 0, 78), UDim2.new(1, -8, 0, 22), function()
		cachedEndpoint = nil
		status(findEndpoint() and "F3X found" or "F3X not found")
	end)
	label(p, "Strict: one property per request, retries and delays for stricter servers.", UDim2.new(0, 4, 0, 104), UDim2.new(1, -8, 0, 28))
	label(p, "HD prefix", UDim2.new(0, 4, 0, 138), UDim2.new(0, 60, 0, 20))
	local pre = input(p, ";", UDim2.new(0, 68, 0, 138), UDim2.new(0, 40, 0, 20))
	pre.Text = S.hdPrefix
	pre.FocusLost:Connect(function()
		if pre.Text ~= "" then S.hdPrefix = pre.Text end
	end)
end


do
	local p = pages.Music
	local hdRemote

	local function findHD()
		if hdRemote and hdRemote.Parent then return hdRemote end
		for _, root in ipairs({ RS, game }) do
			for _, o in ipairs(root:GetDescendants()) do
				if o.Name == "RequestCommandSilent" and (o:IsA("RemoteFunction") or o:IsA("RemoteEvent")) then
					hdRemote = o
					return o
				end
			end
		end
	end

	local function hd(text)
		local r = findHD()
		if not r then
			status("HD Admin remote not found")
			return
		end
		task.spawn(function()
			local ok, err = pcall(function()
				if r:IsA("RemoteFunction") then return r:InvokeServer(text) end
				r:FireServer(text)
			end)
			status(ok and ("Sent " .. text) or "HD request failed")
			if not ok then warn("[Skybox] " .. tostring(err)) end
		end)
	end

	local function cmd(name, arg)
		return S.hdPrefix .. name .. (arg and (" " .. arg) or "")
	end

	local idBox = input(p, "music id", UDim2.new(0, 4, 0, 0), UDim2.new(1, -112, 0, 22))
	local function play(id)
		id = tostring(id or ""):match("%d+")
		if not id then status("Enter a music id") return end
		idBox.Text = id
		hd(cmd("music", id))
	end
	button(p, "Play", UDim2.new(1, -106, 0, 0), UDim2.new(0, 50, 0, 22), function() play(idBox.Text) end)
	button(p, "Stop", UDim2.new(1, -54, 0, 0), UDim2.new(0, 50, 0, 22), function() hd(cmd("stopmusic")) end)

	-- volume: live, sent as ;volume <number> shortly after you stop dragging
	local volVer = 0
	sliderRow(p, 26, "Vol", 0, 10, S.volume, function(v)
		S.volume = v
		volVer += 1
		local ver = volVer
		task.delay(0.4, function()
			if ver == volVer then hd(cmd("volume", tostring(math.floor(v * 100 + 0.5) / 100))) end
		end)
	end)

	-- pitch: live, sent as ;pitch <number>
	local pitchVer = 0
	local setPitch
	setPitch = sliderRow(p, 48, "Pitch", 0.1, 5, S.pitch, function(v)
		if v < 0.1 then setPitch(0.1) return end -- minimum 0.1, no upper limit (type any number)
		S.pitch = v
		pitchVer += 1
		local ver = pitchVer
		task.delay(0.4, function()
			if ver == pitchVer then hd(cmd("pitch", tostring(math.floor(v * 100 + 0.5) / 100))) end
		end)
	end)


	local searchBox = input(p, "search store audio...", UDim2.new(0, 4, 0, 72), UDim2.new(1, -50, 0, 22))
	local goBtn = button(p, "Go", UDim2.new(1, -44, 0, 72), UDim2.new(0, 40, 0, 22))
	local list = new("ScrollingFrame", {
		Size = UDim2.new(1, -8, 0, 76), Position = UDim2.new(0, 4, 0, 98), BackgroundColor3 = Color3.fromRGB(15, 15, 15),
		BorderColor3 = RED, BorderSizePixel = 1, ScrollBarThickness = 4, CanvasSize = UDim2.new(0, 0, 0, 0),
		AutomaticCanvasSize = Enum.AutomaticSize.Y,
	}, p)
	new("UIListLayout", { Padding = UDim.new(0, 2) }, list)
	local moreBtn = button(p, "More", UDim2.new(0, 4, 0, 178), UDim2.new(1, -8, 0, 20))

	local page, lastKw = 1, ""
	local function search(kw, pg)
		local url = ("https://apis.roblox.com/toolbox-service/v1/marketplace/3?keyword=%s&limit=30&pageNumber=%d")
			:format(HttpService:UrlEncode(kw), pg)
		local body = httpGet(url)
		if not body then
			status("Search failed (no HTTP?)")
			return
		end
		local ok, data = pcall(HttpService.JSONDecode, HttpService, body)
		if not ok or type(data) ~= "table" or type(data.data) ~= "table" then
			status("Bad response")
			return
		end
		local ids = {}
		for _, d in ipairs(data.data) do
			if d.id then table.insert(ids, d.id) end
		end
		if #ids == 0 then
			status("No results")
			return
		end

		
		local names = {}
		local body2 = httpGet("https://apis.roblox.com/toolbox-service/v1/items/details?assetIds=" .. table.concat(ids, ","))
		if body2 then
			local ok2, det = pcall(HttpService.JSONDecode, HttpService, body2)
			if ok2 and type(det) == "table" and type(det.data) == "table" then
				for _, it in ipairs(det.data) do
					local a = it.asset
					if type(a) == "table" and a.id then names[a.id] = a.name end
				end
			end
		end

		if pg == 1 then
			for _, c in ipairs(list:GetChildren()) do
				if c:IsA("TextButton") then c:Destroy() end
			end
		end
		for _, id in ipairs(ids) do
			local b = button(list, (names[id] or "Audio") .. "  [" .. id .. "]", UDim2.new(0, 0, 0, 0), UDim2.new(1, -6, 0, 18), function()
				play(id)
			end)
			b.TextXAlignment = Enum.TextXAlignment.Left
		end
		status(#ids .. " tracks, click to play")
	end

	local function go()
		lastKw = searchBox.Text
		page = 1
		search(lastKw, page)
	end
	goBtn.MouseButton1Click:Connect(go)
	searchBox.FocusLost:Connect(function(enter) if enter then go() end end)
	moreBtn.MouseButton1Click:Connect(function()
		page += 1
		search(lastKw, page)
	end)
end

showTab("Spin")

local RunService = game:GetService("RunService")

local function angular(now)
	local ang = Vector3.new(0, 0, 0)
	if S.spinOn then ang += S.spin * S.spinSpeed end
	if S.vortex then
		local k = S.vortexPower
		ang += Vector3.new(math.sin(now * 0.7) * 150 * k, 200 * k, math.cos(now * 0.9) * 150 * k)
	end
	return ang
end

local function hsvColor(i)
	return Color3.fromHSV((i * 0.381966) % 1, 1, 1)
end


local function fx(now)
	local scale = S.scale
	local col = Vector3.new(1, 1, 1)
	if S.vortex then
		local k = S.vortexPower
		scale = scale * (1 + (S.pulse / 100) * math.sin(now * 3 * k))
		local t = now * k / math.max(S.fadeTime, 0.1)
		local i = math.floor(t)
		local a = t - i
		local c = hsvColor(i)
		if S.fade then
			a = a * a * (3 - 2 * a)
			c = c:Lerp(hsvColor(i + 1), a)
		end
		col = Vector3.new(c.R, c.G, c.B)
	end
	if S.light then col = col * S.bright end -- vortex light: >1 brightens, <1 dims
	return Vector3.new(scale, scale, scale), col
end

local function cfFor(pos, rot)
	return CFrame.new(pos) * CFrame.Angles(math.rad(rot.X), math.rad(rot.Y), math.rad(rot.Z))
end


local lastR = os.clock()
local renderConn = RunService.RenderStepped:Connect(function()
	local now = os.clock()
	local dt = math.min(now - lastR, 0.1)
	lastR = now
	local part = S.part
	if not (part and part.Parent) then return end

	if part.CanCollide then part.CanCollide = false end -- local guarantee, even if the server ignores it
	S.rot += angular(now) * dt
	if S.follow then
		local char = player.Character
		local root = char and char:FindFirstChild("HumanoidRootPart")
		if root then S.pos = root.Position end
	end

	local cf = cfFor(S.pos, S.rot)
	if part.CFrame ~= cf then part.CFrame = cf end

	local mesh = part:FindFirstChildOfClass("SpecialMesh")
	if mesh then
		local sv, col = fx(now)
		if mesh.Scale ~= sv then mesh.Scale = sv end
		if mesh.VertexColor ~= col then mesh.VertexColor = col end
	end
end)
screen.Destroying:Connect(function() renderConn:Disconnect() end)

local inflight = 0
local function fire(...)
	if S.strict then
		call(...)
		return true
	end
	if inflight >= 4 then return false end
	inflight += 1
	local args = table.pack(...)
	task.spawn(function()
		call(table.unpack(args, 1, args.n))
		inflight -= 1
	end)
	return true
end


task.spawn(function()
	while screen.Parent do
		local t0 = os.clock()
		local part = S.part

		if part and not part.Parent then
			S.part = nil
			S.anim.on = false
			setPlayBtn()
			status("Skybox was removed")
			part = nil
		end

		if part and not S.busy then
			-- animation frame advance
			if S.anim.on and #S.anim.frames > 0 and t0 - S.anim.last >= 1 / S.anim.fps then
				S.anim.last = t0
				S.anim.idx += 1
				if S.anim.idx > #S.anim.frames then
					if S.anim.loop then
						S.anim.idx = 1
					else
						S.anim.idx = #S.anim.frames
						S.anim.on = false
						setPlayBtn()
					end
				end
				S.anim.current = S.anim.frames[S.anim.idx]
			end

		
			local url = (S.anim.on and S.anim.current) or assetUrl(S.texture)
			if url and url ~= sent.TextureId then
				syncMesh(part, { TextureId = url })
				sent.TextureId = url
			end

		
			local sv, col = fx(t0)
			local props = {}
			if sent.Scale ~= sv then props.Scale = sv end
			if sent.VertexColor ~= col then props.VertexColor = col end
			if next(props) then
				if S.strict then
					syncMesh(part, props)
					for k, v in pairs(props) do sent[k] = v end
				else
					local e = { Part = part }
					for k, v in pairs(props) do e[k] = v end
					if fire("SyncMesh", { e }) then
						for k, v in pairs(props) do sent[k] = v end
					end
				end
			end

			
			local cf = cfFor(S.pos, S.rot)
			if sent.CF ~= cf then
				if fire("SyncMove", { { Part = part, CFrame = cf } }) then sent.CF = cf end
			end
		end

		task.wait(math.max(0, 1 / 30 - (os.clock() - t0)))
	end
end)

Features

  • make a skybox using f3x and its fully fe
F3x Skybox maker gui 2 · RoScripts