-- BUX · Roblox Studio plugin v1.0.0 -- https://www.usebux.fun -- -- Adds a "BUX" tab to Studio's Plugins toolbar with three buttons: -- Board inserts a 16×9-stud screen showing this experience's live numbers and its BUX coin, if one exists -- Ticker inserts a 32×3-stud wall scrolling the most-played Roblox experiences right now -- Check asks the BUX feed about this place once and prints the answer in the Output window -- -- Install: save this file into your Plugins folder (Plugins tab → Plugins Folder), then restart Studio. -- In your place: Game Settings → Security → Allow HTTP Requests = On, and publish the place. -- -- House rules, built in: everything is read-only. No wallets, no links, no buy buttons, no prompts, -- nothing that takes or gives Robux. The board shows numbers; trading happens off Roblox. local HOST = "https://www.usebux.fun" local ChangeHistoryService = game:GetService("ChangeHistoryService") local Selection = game:GetService("Selection") local HttpService = game:GetService("HttpService") local INK = Color3.fromRGB(17, 17, 24) local YELLOW = Color3.fromRGB(255, 205, 40) local SKY_TOP = Color3.fromRGB(58, 162, 255) local SKY_BOTTOM = Color3.fromRGB(178, 225, 255) local WHITE = Color3.fromRGB(255, 255, 255) local GREEN = Color3.fromRGB(72, 170, 78) local GREY = Color3.fromRGB(90, 96, 116) local HEAD = Enum.Font.FredokaOne local BODY = Enum.Font.GothamBold local FEED_SOURCE = [==[ -- BoardFeed · BUX -- Reads https://www.usebux.fun/api/board every RefreshSeconds and paints the board's screen. -- Runs on the server (HttpService only works there). Read-only: it never prompts a purchase, -- never opens a link and never touches Robux. Needs Game Settings → Security → Allow HTTP Requests. local HttpService = game:GetService("HttpService") local model = script.Parent local gui = model:WaitForChild("Screen"):WaitForChild("BoardGui") local bg = gui:WaitForChild("Bg") local status = bg:WaitForChild("Header"):WaitForChild("Status") local title = bg:WaitForChild("Title") local stats = bg:WaitForChild("Stats") local coin = bg:WaitForChild("Coin") local spark = bg:WaitForChild("Spark") local DEFAULT_HOST = "https://www.usebux.fun" local history = {} local function short(n) if type(n) ~= "number" then return "—" end local a = math.abs(n) if a >= 1e9 then return string.format("%.2fB", n / 1e9) elseif a >= 1e6 then return string.format("%.2fM", n / 1e6) elseif a >= 1e4 then return string.format("%.1fK", n / 1e3) end return string.format("%d", math.floor(n + 0.5)) end local function dollars(p) if type(p) ~= "number" then return "" elseif p >= 1 then return string.format("$%.2f", p) elseif p >= 0.01 then return string.format("$%.4f", p) end return string.format("$%.8f", p) end local function clock(t) t = tonumber(t) or os.time() return string.format("%02d:%02d UTC", math.floor(t % 86400 / 3600), math.floor(t % 3600 / 60)) end local function paintSpark() local high, low = -math.huge, math.huge for _, v in ipairs(history) do high = math.max(high, v) low = math.min(low, v) end for i = 1, 24 do local bar = spark:FindFirstChild("Bar" .. i) local v = history[#history - 24 + i] if bar then if v then local h = (high > low) and (0.25 + 0.75 * (v - low) / (high - low)) or 0.6 bar.Size = UDim2.new(1 / 24, -4, h, 0) bar.BackgroundTransparency = (i == 24) and 0 or 0.45 else bar.Size = UDim2.new(1 / 24, -4, 0.08, 0) bar.BackgroundTransparency = 0.8 end end end end local function paint(d) if d.ok ~= true then status.Text = string.upper(tostring(d.error or "NO DATA")) return end title.Text = tostring(d.name or "Unknown experience") stats.Playing.Value.Text = short(d.playing) stats.Visits.Value.Text = short(d.visits) stats.Likes.Value.Text = (type(d.likes) == "number") and (string.format("%d", d.likes) .. "%") or "—" if type(d.playing) == "number" then table.insert(history, d.playing) if #history > 48 then table.remove(history, 1) end end paintSpark() local c = d.coin if type(c) == "table" then coin.Ticker.Text = "$" .. tostring(c.ticker or "?") coin.Price.Text = dollars(c.priceUsd) local change = (type(c.change24h) == "number") and string.format("%+.1f%% 24h", c.change24h) or "— 24h" local cap = (type(c.mcapUsd) == "number") and ("$" .. short(c.mcapUsd)) or "—" coin.Line.Text = change .. " · market cap " .. cap else coin.Ticker.Text = "NO COIN YET" coin.Price.Text = "" coin.Line.Text = "This experience has no BUX coin yet." end status.Text = "LIVE · " .. clock(d.at) end local function target() local host = model:GetAttribute("Host") if type(host) ~= "string" or host == "" then host = DEFAULT_HOST end local universe = model:GetAttribute("Universe") if type(universe) ~= "number" or universe <= 0 then universe = game.GameId end return host .. "/api/board?universe=" .. string.format("%.0f", universe), universe end while model.Parent do local url, universe = target() if universe == 0 then status.Text = "PUBLISH THE PLACE" title.Text = "Unpublished place" else local ok, body = pcall(HttpService.GetAsync, HttpService, url, true) if ok then local decoded, data = pcall(HttpService.JSONDecode, HttpService, body) if decoded and type(data) == "table" then paint(data) else status.Text = "BAD REPLY" end else local message = tostring(body) if string.find(message, "not enabled", 1, true) then status.Text = "TURN ON HTTP REQUESTS" else status.Text = "OFFLINE · RETRYING" end end end local every = model:GetAttribute("RefreshSeconds") task.wait((type(every) == "number" and every >= 15) and every or 30) end ]==] local TICKER_FEED_SOURCE = [==[ -- TickerFeed · BUX -- Reads the most-played Roblox experiences from https://www.usebux.fun/api/board?floor=1 once a minute -- and stores them on the model as JSON. TickerClient draws them on each player's screen. local HttpService = game:GetService("HttpService") local model = script.Parent local DEFAULT_HOST = "https://www.usebux.fun" while model.Parent do local host = model:GetAttribute("Host") if type(host) ~= "string" or host == "" then host = DEFAULT_HOST end local ok, body = pcall(HttpService.GetAsync, HttpService, host .. "/api/board?floor=1", true) if ok then local decoded, data = pcall(HttpService.JSONDecode, HttpService, body) if decoded and type(data) == "table" and type(data.floor) == "table" then local lines = {} for i, g in ipairs(data.floor) do if i > 20 then break end table.insert(lines, { name = tostring(g.name), playing = tonumber(g.playing) or 0 }) end model:SetAttribute("Floor", HttpService:JSONEncode(lines)) model:SetAttribute("FloorAt", tonumber(data.at) or os.time()) model:SetAttribute("FloorError", "") else model:SetAttribute("FloorError", "BAD REPLY") end else local message = tostring(body) model:SetAttribute("FloorError", string.find(message, "not enabled", 1, true) and "TURN ON HTTP REQUESTS" or "OFFLINE · RETRYING") end task.wait(60) end ]==] local TICKER_CLIENT_SOURCE = [==[ -- TickerClient · BUX (a Script with RunContext = Client, so it runs on every player's device) -- Draws the floor TickerFeed stored on the model and scrolls it across the wall, forever. local HttpService = game:GetService("HttpService") local TweenService = game:GetService("TweenService") local model = script.Parent local gui = model:WaitForChild("Wall"):WaitForChild("TickerGui") local track = gui:WaitForChild("Window"):WaitForChild("Track") local layout = track:WaitForChild("Layout") local tween local function short(n) if n >= 1e6 then return string.format("%.2fM", n / 1e6) elseif n >= 1e4 then return string.format("%.1fK", n / 1e3) end return string.format("%d", n) end local function escape(s) s = string.gsub(s, "&", "&") s = string.gsub(s, "<", "<") s = string.gsub(s, ">", ">") return s end local function item(order, value) local label = Instance.new("TextLabel") label.Name = "Item" label.LayoutOrder = order label.BackgroundTransparency = 1 label.AutomaticSize = Enum.AutomaticSize.X label.Size = UDim2.new(0, 0, 1, 0) label.Font = Enum.Font.FredokaOne label.TextSize = 52 label.TextColor3 = Color3.fromRGB(255, 255, 255) label.RichText = true label.Text = value label.Parent = track return label end local function rebuild() local raw = model:GetAttribute("Floor") if type(raw) ~= "string" or raw == "" then return end local ok, lines = pcall(HttpService.JSONDecode, HttpService, raw) if not ok or type(lines) ~= "table" or #lines == 0 then return end if tween then tween:Cancel() end for _, child in ipairs(track:GetChildren()) do if child:IsA("TextLabel") then child:Destroy() end end for copy = 1, 2 do for i, g in ipairs(lines) do local value = string.format('%s %s playing ● ', escape(tostring(g.name)), short(tonumber(g.playing) or 0)) item((copy - 1) * 1000 + i, value) end end task.wait(0.2) -- let the layout measure the labels local width = layout.AbsoluteContentSize.X track.Size = UDim2.new(0, width, 1, 0) track.Position = UDim2.new(0, 0, 0, 0) local half = width / 2 tween = TweenService:Create(track, TweenInfo.new(half / 110, Enum.EasingStyle.Linear, Enum.EasingDirection.InOut, -1), { Position = UDim2.new(0, -half, 0, 0) }) tween:Play() end local function showError() local message = model:GetAttribute("FloorError") if type(message) == "string" and message ~= "" and model:GetAttribute("Floor") == nil then for _, child in ipairs(track:GetChildren()) do if child:IsA("TextLabel") then child.Text = message end end end end model:GetAttributeChangedSignal("Floor"):Connect(rebuild) model:GetAttributeChangedSignal("FloorError"):Connect(showError) rebuild() showError() ]==] local function new(className, props, parent) local inst = Instance.new(className) for key, value in pairs(props) do inst[key] = value end if parent then inst.Parent = parent end return inst end local function round(parent, radius) return new("UICorner", { CornerRadius = UDim.new(0, radius) }, parent) end local function outline(parent, thickness) return new("UIStroke", { Thickness = thickness, Color = INK, ApplyStrokeMode = Enum.ApplyStrokeMode.Border }, parent) end local function text(parent, name, value, position, size, font, color, align) return new("TextLabel", { Name = name, Text = value, Position = position, Size = size, BackgroundTransparency = 1, Font = font, TextColor3 = color, TextScaled = true, TextXAlignment = align or Enum.TextXAlignment.Left, }, parent) end local function part(name, size, cframe, color, parent) return new("Part", { Name = name, Size = size, CFrame = cframe, Color = color, Material = Enum.Material.SmoothPlastic, Anchored = true, TopSurface = Enum.SurfaceType.Smooth, BottomSurface = Enum.SurfaceType.Smooth, }, parent) end local function addScript(name, source, parent, client) local s = new("Script", { Name = name, Source = source }, parent) if client then s.RunContext = Enum.RunContext.Client end return s end local function buildBoard() local model = new("Model", { Name = "BUXBoard" }) model:SetAttribute("Host", HOST) model:SetAttribute("Universe", 0) -- 0 = this experience (game.GameId). Put another universe id here to show that game instead. model:SetAttribute("RefreshSeconds", 30) local screen = part("Screen", Vector3.new(16, 9, 0.6), CFrame.new(0, 9.5, 0), INK, model) part("Frame", Vector3.new(16.8, 9.8, 0.5), CFrame.new(0, 9.5, 0.3), YELLOW, model) part("LegLeft", Vector3.new(0.8, 5.2, 0.8), CFrame.new(-5.5, 2.6, 0.3), INK, model) part("LegRight", Vector3.new(0.8, 5.2, 0.8), CFrame.new(5.5, 2.6, 0.3), INK, model) part("Base", Vector3.new(14, 0.6, 3), CFrame.new(0, 0.3, 0.3), GREEN, model) local gui = new("SurfaceGui", { Name = "BoardGui", Face = Enum.NormalId.Front, SizingMode = Enum.SurfaceGuiSizingMode.FixedSize, CanvasSize = Vector2.new(800, 450), LightInfluence = 0, ClipsDescendants = true, }, screen) local bg = new("Frame", { Name = "Bg", Size = UDim2.fromScale(1, 1), BackgroundColor3 = WHITE, BorderSizePixel = 0 }, gui) new("UIGradient", { Color = ColorSequence.new(SKY_TOP, SKY_BOTTOM), Rotation = 90 }, bg) local header = new("Frame", { Name = "Header", Size = UDim2.new(1, 0, 0, 64), BackgroundColor3 = INK, BorderSizePixel = 0 }, bg) text(header, "Logo", "BUX", UDim2.fromOffset(20, 8), UDim2.fromOffset(110, 48), HEAD, YELLOW) text(header, "Status", "CONNECTING", UDim2.new(1, -350, 0, 16), UDim2.fromOffset(330, 32), BODY, WHITE, Enum.TextXAlignment.Right) local title = text(bg, "Title", "Loading…", UDim2.fromOffset(24, 76), UDim2.new(1, -48, 0, 62), HEAD, WHITE) new("UIStroke", { Thickness = 3, Color = INK, ApplyStrokeMode = Enum.ApplyStrokeMode.Contextual }, title) local stats = new("Frame", { Name = "Stats", Position = UDim2.fromOffset(24, 150), Size = UDim2.new(1, -48, 0, 110), BackgroundTransparency = 1 }, bg) new("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 14), SortOrder = Enum.SortOrder.LayoutOrder }, stats) local names = { Playing = "PLAYING NOW", Visits = "VISITS", Likes = "LIKED" } for index, key in ipairs({ "Playing", "Visits", "Likes" }) do local tile = new("Frame", { Name = key, LayoutOrder = index, Size = UDim2.new(1 / 3, -10, 1, 0), BackgroundColor3 = WHITE }, stats) round(tile, 14) outline(tile, 3) text(tile, "Label", names[key], UDim2.fromOffset(14, 10), UDim2.new(1, -28, 0, 22), BODY, GREY) text(tile, "Value", "—", UDim2.fromOffset(14, 38), UDim2.new(1, -28, 0, 58), HEAD, INK) end local coin = new("Frame", { Name = "Coin", Position = UDim2.fromOffset(24, 276), Size = UDim2.new(1, -48, 0, 104), BackgroundColor3 = YELLOW }, bg) round(coin, 14) outline(coin, 3) text(coin, "Ticker", "NO COIN YET", UDim2.fromOffset(18, 10), UDim2.new(0.5, -18, 0, 50), HEAD, INK) text(coin, "Price", "", UDim2.new(0.5, 0, 0, 10), UDim2.new(0.5, -18, 0, 50), HEAD, INK, Enum.TextXAlignment.Right) text(coin, "Line", "Waiting for the feed.", UDim2.fromOffset(18, 66), UDim2.new(1, -36, 0, 26), BODY, INK) local spark = new("Frame", { Name = "Spark", Position = UDim2.fromOffset(24, 392), Size = UDim2.new(1, -48, 0, 44), BackgroundTransparency = 1 }, bg) for i = 1, 24 do new("Frame", { Name = "Bar" .. i, AnchorPoint = Vector2.new(0, 1), Position = UDim2.new((i - 1) / 24, 2, 1, 0), Size = UDim2.new(1 / 24, -4, 0.08, 0), BackgroundColor3 = INK, BackgroundTransparency = 0.8, BorderSizePixel = 0, }, spark) end addScript("BoardFeed", FEED_SOURCE, model, false) model.WorldPivot = CFrame.new(0, 0, 0) return model end local function buildTicker() local model = new("Model", { Name = "BUXTicker" }) model:SetAttribute("Host", HOST) local wall = part("Wall", Vector3.new(32, 3, 0.6), CFrame.new(0, 9, 0), INK, model) part("Trim", Vector3.new(32.8, 3.8, 0.5), CFrame.new(0, 9, 0.3), YELLOW, model) part("PostLeft", Vector3.new(0.8, 7.2, 0.8), CFrame.new(-14, 3.6, 0.3), INK, model) part("PostRight", Vector3.new(0.8, 7.2, 0.8), CFrame.new(14, 3.6, 0.3), INK, model) local gui = new("SurfaceGui", { Name = "TickerGui", Face = Enum.NormalId.Front, SizingMode = Enum.SurfaceGuiSizingMode.FixedSize, CanvasSize = Vector2.new(1280, 120), LightInfluence = 0, ClipsDescendants = true, }, wall) new("Frame", { Name = "Bg", Size = UDim2.fromScale(1, 1), BackgroundColor3 = INK, BorderSizePixel = 0 }, gui) local tag = new("Frame", { Name = "Tag", Size = UDim2.new(0, 170, 1, 0), BackgroundColor3 = YELLOW, BorderSizePixel = 0, ZIndex = 3 }, gui) text(tag, "Label", "BUX", UDim2.fromOffset(16, 18), UDim2.new(1, -32, 1, -36), HEAD, INK, Enum.TextXAlignment.Center).ZIndex = 4 local window = new("Frame", { Name = "Window", Position = UDim2.fromOffset(186, 0), Size = UDim2.new(1, -186, 1, 0), BackgroundTransparency = 1, ClipsDescendants = true, ZIndex = 2 }, gui) local track = new("Frame", { Name = "Track", Size = UDim2.new(0, 1094, 1, 0), BackgroundTransparency = 1, ZIndex = 2 }, window) new("UIListLayout", { Name = "Layout", FillDirection = Enum.FillDirection.Horizontal, SortOrder = Enum.SortOrder.LayoutOrder, VerticalAlignment = Enum.VerticalAlignment.Center }, track) new("TextLabel", { Name = "Item", BackgroundTransparency = 1, AutomaticSize = Enum.AutomaticSize.X, Size = UDim2.new(0, 0, 1, 0), Font = HEAD, TextSize = 52, TextColor3 = WHITE, Text = "CONNECTING…", ZIndex = 2, }, track) addScript("TickerFeed", TICKER_FEED_SOURCE, model, false) addScript("TickerClient", TICKER_CLIENT_SOURCE, model, true) model.WorldPivot = CFrame.new(0, 0, 0) return model end local function placeInFront(model, distance) local camera = workspace.CurrentCamera local look = camera.CFrame.LookVector local flat = Vector3.new(look.X, 0, look.Z) if flat.Magnitude < 0.01 then flat = Vector3.new(0, 0, -1) end flat = flat.Unit local spot = camera.CFrame.Position + flat * distance local ground = Vector3.new(spot.X, 0, spot.Z) local hit = workspace:Raycast(spot + Vector3.new(0, 60, 0), Vector3.new(0, -600, 0)) if hit then ground = hit.Position end -- face the camera: a part's Front face points along its LookVector model:PivotTo(CFrame.lookAt(ground, ground - flat)) end local function insert(kind) local recording = ChangeHistoryService:TryBeginRecording("Insert BUX " .. kind) local ok, result = pcall(function() local model = (kind == "Board") and buildBoard() or buildTicker() placeInFront(model, (kind == "Board") and 28 or 40) model.Parent = workspace Selection:Set({ model }) return model end) if recording then ChangeHistoryService:FinishRecording(recording, ok and Enum.FinishRecordingOperation.Commit or Enum.FinishRecordingOperation.Cancel) end if not ok then warn("[BUX] Could not insert the " .. kind .. ": " .. tostring(result)) return end print("[BUX] " .. kind .. " inserted. Press Play to see it live.") if not HttpService.HttpEnabled then warn("[BUX] Allow HTTP Requests is off. Turn it on in Game Settings → Security, or the " .. kind .. " stays on CONNECTING.") end if game.GameId == 0 then warn("[BUX] This place isn't published yet. Publish it so the board knows which experience it belongs to.") end end local function check() local universe = game.GameId local url = HOST .. "/api/board?floor=1" .. ((universe ~= 0) and ("&universe=" .. string.format("%.0f", universe)) or "") local ok, body = pcall(HttpService.GetAsync, HttpService, url, true) if not ok then warn("[BUX] The feed didn't answer: " .. tostring(body)) return end local decoded, data = pcall(HttpService.JSONDecode, HttpService, body) if not decoded or type(data) ~= "table" then warn("[BUX] The feed sent something that isn't JSON.") return end if data.name then print(string.format("[BUX] %s · %s playing · %s", tostring(data.name), tostring(data.playing), tostring(data.status))) else print("[BUX] Feed is up. Publish this place to see its own numbers.") end if type(data.floor) == "table" and data.floor[1] then print(string.format("[BUX] Most played right now: %s · %s playing", tostring(data.floor[1].name), tostring(data.floor[1].playing))) end end local toolbar = plugin:CreateToolbar("BUX") local boardButton = toolbar:CreateButton("BUXBoard", "Insert a live BUX board for this experience", "", "Board") local tickerButton = toolbar:CreateButton("BUXTicker", "Insert a wall that scrolls the most-played experiences", "", "Ticker") local checkButton = toolbar:CreateButton("BUXCheck", "Ask the BUX feed about this place and print the answer", "", "Check") for _, button in ipairs({ boardButton, tickerButton, checkButton }) do button.ClickableWhenViewportHidden = true end boardButton.Click:Connect(function() insert("Board") end) tickerButton.Click:Connect(function() insert("Ticker") end) checkButton.Click:Connect(check)