Fracture Hub V5.1 | luna.dev
Key Required

Fracture Hub V5.1 | luna.dev

Broken Bones IVLunadevLunadev 360 views 1K executesUpdated Oct 2, 2026
5.0/ 5(2 votes)
20100% Positive
Lua Script•1556 lines (71063 chars)
-- luna.dev | broken bones iv
-- money request method adapted from a russian-language bb4 rejoin farm script; its author was not identified in the provided source.
-- open-source, standalone game extension for the luna.dev universal script hub.
-- settings are stored in luna.dev/scripts/broken_bones_iv.json; shared disclaimer acceptance
-- and main menu preferences are stored in luna.dev/settings.json.
-- bundled releases can be superseded by user-approved updates in the future main hub.
-- no remote loader, forced extension update, or robux purchase is used by this file.

local money_amount = 1e300 -- money to request; change this to a smaller finite amount if needed
local fuel_target = 300 -- regular fuel upgrade cap; values above 300 have caused kicks and are blocked here
local purchase_delay = 0.05 -- optional seconds between purchases; 0 skips this wait, not server response time

local players = game:GetService("Players")
local storage = game:GetService("ReplicatedStorage")
local input = game:GetService("UserInputService")
local runservice = game:GetService("RunService")
local lighting = game:GetService("Lighting")
local teleport = game:GetService("TeleportService")
local virtualuser = game:GetService("VirtualUser")
local player = players.LocalPlayer
local camera = workspace.CurrentCamera
local remotes = storage:FindFirstChild("Functions") or storage:FindFirstChild("RemoteFunctions") or storage
local update = remotes and remotes:FindFirstChild("UpdateStat")
local load = remotes and remotes:FindFirstChild("LoadData")

local upgrades = {
    {"breaks", 1000}, {"sprains", 750}, {"dislocations", 500},
    {"flight", 800}, {"speed", 600}, {"jump", 400},
    {"elasticity", 640}, {"friction", 480}, {"cooldown", 700}, {"fuel", 900}
}
local utilities = {
    {"Helium Balloon", {10000, 50000, 100000, 175000, 250000}},
    {"Helicopter Hat", {125000, 200000, 300000, 450000, 600000}},
    {"Rocket Boots", {400000, 650000, 1000000, 1500000, 2250000}},
    {"Jetpack", {1250000, 2500000, 5000000, 10000000, 20000000}},
    {"Gravity Bubble", {50000000, 100000000, 250000000, 500000000, 1000000000}}
}
local maps = {
    {0, 25000, 100000, 250000, 500000, 850000, 1350000, 2250000, 3500000, 5000000},
    {750000, 1100000, 1400000, 1800000, 2200000, 2750000, 4000000, 6500000, 10000000, 15000000},
    {1000000, 1500000, 2000000, 3600000, 8000000, 12500000, 20000000, 35000000, 60000000, 150000000}
}

local function create(kind, parent, properties)
    local object = Instance.new(kind)
    for name, value in pairs(properties) do object[name] = value end
    object.Parent = parent
    return object
end

local purple = Color3.fromRGB(189, 150, 253)
local pale = Color3.fromRGB(238, 233, 248)
local muted = Color3.fromRGB(177, 167, 195)
local surface = Color3.fromRGB(28, 23, 43)
local function round(object, radius)
    create("UICorner", object, {CornerRadius = UDim.new(0, radius or 5)})
    return object
end
local function label(parent, text, size, color, height)
    return create("TextLabel", parent, {
        Size = UDim2.new(1, 0, 0, height or 21), BackgroundTransparency = 1,
        Font = Enum.Font.Gotham, Text = text, TextSize = size or 12,
        TextColor3 = color or pale, TextXAlignment = Enum.TextXAlignment.Left,
        TextWrapped = true,
        AutomaticSize = parent:IsA("ScrollingFrame") and Enum.AutomaticSize.Y or Enum.AutomaticSize.None
    })
end
local function button(parent, text, callback, height)
    local item = round(create("TextButton", parent, {
        Size = UDim2.new(1, 0, 0, height or 34), BackgroundColor3 = surface,
        BorderSizePixel = 0, Font = Enum.Font.GothamMedium, Text = text,
        TextColor3 = pale, TextSize = 12, TextWrapped = true, AutoButtonColor = true
    }))
    item.MouseButton1Click:Connect(callback)
    return item
end
local function gethumanoid()
    local character = player.Character
    return character and character:FindFirstChildOfClass("Humanoid")
end
local function getroot()
    local character = player.Character
    return character and character:FindFirstChild("HumanoidRootPart")
end
local function fetch()
    if not (load and load:IsA("RemoteFunction")) then return nil end
    local ok, result = pcall(load.InvokeServer, load)
    if ok and type(result) == "table" then return result end
end
local function request(stat, amount, replace)
    if not (update and update:IsA("RemoteFunction")) then
        return false, "UpdateStat is unavailable"
    end
    local ok, accepted = pcall(update.InvokeServer, update, stat, amount, replace)
    if not ok then return false, tostring(accepted) end
    if accepted == false then return false, "Server explicitly rejected " .. stat end
    if accepted == nil then return true, "No confirmation returned" end
    return true, "Response received"
end
local function mirror(stat, amount, replace)
    if type(getgc) ~= "function" then return false end
    local ok, objects = pcall(getgc, true)
    if not ok or type(objects) ~= "table" then return false end
    local changed = false
    for _, object in ipairs(objects) do
        if type(object) == "table" and rawget(object, "fuellevel") ~= nil
            and rawget(object, "totalbreaks") ~= nil and rawget(object, "money") ~= nil
            and rawget(object, "breakslevel") ~= nil then
            local old = rawget(object, stat)
            if replace or type(old) ~= "number" then
                rawset(object, stat, amount)
            else
                rawset(object, stat, old + amount)
            end
            changed = true
        end
    end
    return changed
end

local http = game:GetService("HttpService")
local environment = _G
if type(getgenv) == "function" then
    local ok, result = pcall(getgenv)
    if ok and type(result) == "table" then environment = result end
end

local extension = {
    id = "broken_bones_iv",
    name = "Broken Bones IV",
    version = "5.1.0",
    developer = "Luna.dev",
    kind = "game_specific",
    bundled = true,
    standalone = true,
    settings_path = "luna.dev/scripts/broken_bones_iv.json",
    shared_settings_path = "luna.dev/settings.json",
    update_policy = "user_choice"
}
local folder = "luna.dev"
local scriptsfolder = folder .. "/scripts"
local settingsfile = folder .. "/settings.json"
local extensionfile = extension.settings_path
local legacyfile = "luna.dev_settings.json"
local consentkey = "luna_dev_accepted_v1"
local globalinfo = "Shared Luna.dev disclaimer and main menu settings. Deleting this file resets global preferences and disclaimer acceptance; preserved legacy settings may be imported again. Extension preferences remain separate. Do not store passwords here."
local extensioninfo = "Broken Bones IV extension preferences. Deleting this file resets only this extension's saved settings. Luna.dev preserves legacy files during migration."
local file_storage = type(readfile) == "function" and type(writefile) == "function" and type(makefolder) == "function"
local jsonorder = {
    _info = 1, scope = 2, version = 3, disclaimer = 4, global = 5,
    main_menu = 6, migrations = 7, values = 3, restore = 4, startup = 1, respawn = 2,
    accepted = 1
}
local function prettyjson(value, depth)
    if type(value) ~= "table" then return http:JSONEncode(value) end
    local count, biggest, array = 0, 0, true
    for key in pairs(value) do
        count += 1
        if type(key) ~= "number" or key % 1 ~= 0 or key < 1 then
            array = false
        else
            biggest = math.max(biggest, key)
        end
    end
    if count == 0 then return "{}" end
    array = array and biggest == count
    local padding, inner = string.rep("  ", depth), string.rep("  ", depth + 1)
    local lines = {}
    if array then
        for index = 1, count do
            table.insert(lines, inner .. prettyjson(value[index], depth + 1))
        end
        return "[\n" .. table.concat(lines, ",\n") .. "\n" .. padding .. "]"
    end
    local keys = {}
    for key in pairs(value) do table.insert(keys, key) end
    table.sort(keys, function(a, b)
        local left, right = jsonorder[a] or 100, jsonorder[b] or 100
        if left ~= right then return left < right end
        return tostring(a) < tostring(b)
    end)
    for _, key in ipairs(keys) do
        table.insert(lines, inner .. http:JSONEncode(tostring(key)) .. ": "
            .. prettyjson(value[key], depth + 1))
    end
    return "{\n" .. table.concat(lines, ",\n") .. "\n" .. padding .. "}"
end
local function rawfile(path)
    if type(readfile) ~= "function" then return nil end
    local ok, content = pcall(readfile, path)
    if ok and type(content) == "string" then return content end
end
local function readjson(path)
    local raw = rawfile(path)
    if raw == nil then return nil end
    local ok, data = pcall(http.JSONDecode, http, raw)
    if ok and type(data) == "table" then return data end
    return nil, "Invalid JSON in " .. path .. "; preserving the existing file."
end
local function makefolders()
    if not file_storage then return false, "Executor file storage or folder creation is unavailable" end
    for _, path in ipairs({folder, scriptsfolder}) do
        local present = false
        if type(isfolder) == "function" then
            local ok, found = pcall(isfolder, path)
            present = ok and found == true
        end
        if not present then
            local ok, err = pcall(makefolder, path)
            if not ok then
                if type(isfolder) ~= "function" then return false, tostring(err) end
                local checked, found = pcall(isfolder, path)
                if not checked or not found then return false, tostring(err) end
            end
        end
    end
    return true
end
local function writejson(path, data)
    local ok, err = makefolders()
    if not ok then return false, err end
    local encoded, content = pcall(prettyjson, data, 0)
    if not encoded then return false, tostring(content) end
    local written, reason = pcall(writefile, path, content)
    if not written then return false, tostring(reason) end
    return true
end
local function defaultglobal()
    return {_info = globalinfo, scope = "luna.dev", version = 1,
        disclaimer = {accepted = false, version = 1}, global = {}, main_menu = {}, migrations = {}}
end
local function defaultextension()
    return {_info = extensioninfo, version = 1, values = {},
        restore = {startup = {}, respawn = {}}}
end
local legacy, legacyerror = readjson(legacyfile)
local shareddata, globalerror = readjson(settingsfile)
local had_new_global = shareddata ~= nil
if globalerror then
    warn("Luna.dev: " .. globalerror)
    return
end
if not shareddata then
    shareddata = defaultglobal()
    if type(legacy) == "table" and legacy.scope == "luna.dev" then
        for key, value in pairs(legacy) do
            if key ~= "scripts" and key ~= "_info" then shareddata[key] = value end
        end
    end
end
shareddata._info = globalinfo
shareddata.scope = "luna.dev"
shareddata.version = 1
if type(shareddata.disclaimer) ~= "table" then shareddata.disclaimer = {} end
if type(shareddata.migrations) ~= "table" then shareddata.migrations = {} end

local extensiondata, extensionerror = readjson(extensionfile)
local had_new_extension = extensiondata ~= nil
if extensionerror then
    warn("Luna.dev: " .. extensionerror)
    return
end
if not had_new_global and had_new_extension then
    shareddata = defaultglobal()
end
local migratedextension = false
if not extensiondata then
    extensiondata = defaultextension()
    local oldprefs = type(legacy) == "table" and type(legacy.scripts) == "table"
        and legacy.scripts.broken_bones_iv or nil
    if type(oldprefs) == "table" and shareddata.migrations.broken_bones_iv ~= true then
        for key, value in pairs(oldprefs) do
            if key == "restore" and type(value) == "table" then
                extensiondata.restore = value
            else
                extensiondata.values[key] = value
            end
        end
        migratedextension = true
    end
end
extensiondata._info = extensioninfo
extensiondata.version = 1
if type(extensiondata.values) ~= "table" then extensiondata.values = {} end
if type(extensiondata.restore) ~= "table" then extensiondata.restore = {} end
if type(extensiondata.restore.startup) ~= "table" then extensiondata.restore.startup = {} end
if type(extensiondata.restore.respawn) ~= "table" then extensiondata.restore.respawn = {} end
local savedbb4 = extensiondata.values
local function preferred(key, default)
    local value = savedbb4[key]
    if type(value) == "string" or type(value) == "number" then return tostring(value) end
    return tostring(default)
end
local function saveglobal(change)
    local latest, err = readjson(settingsfile)
    if err then return false, err end
    latest = latest or shareddata or defaultglobal()
    if type(latest.disclaimer) ~= "table" then latest.disclaimer = {} end
    if type(latest.migrations) ~= "table" then latest.migrations = {} end
    latest.scope, latest.version, latest._info = "luna.dev", 1, globalinfo
    change(latest)
    local ok, reason = writejson(settingsfile, latest)
    if ok then shareddata = latest end
    return ok, reason
end
local savedfile_raw = rawfile(extensionfile)
if migratedextension then
    local ok, err = writejson(extensionfile, extensiondata)
    if ok then
        savedfile_raw = rawfile(extensionfile)
        saveglobal(function(data) data.migrations.broken_bones_iv = true end)
    else
        warn("Luna.dev: legacy BB4 settings loaded in memory, but migration could not be saved: " .. tostring(err))
    end
end

local acceptednotice = shareddata.disclaimer.accepted == true
if not acceptednotice then
    local oldjson = readjson("luna.dev_accepted.json")
    local oldkey = "luna_dev_bb4_consent_v1_" .. tostring(player.UserId)
    local oldtxt = rawfile("luna_dev_bb4_consent_" .. tostring(player.UserId) .. ".txt")
    local oldaccept = (type(oldjson) == "table" and oldjson.scope == "luna.dev" and oldjson.accepted == true)
        or (type(legacy) == "table" and type(legacy.disclaimer) == "table" and legacy.disclaimer.accepted == true)
        or oldtxt == "accepted_v1" or environment[oldkey] == true or environment[consentkey] == true
    if oldaccept and not had_new_extension then
        acceptednotice = true
        local ok, err = saveglobal(function(data)
            data.disclaimer.accepted = true
            data.disclaimer.version = 1
        end)
        if not ok then warn("Luna.dev: old disclaimer accepted but new file could not be written: " .. tostring(err)) end
    end
end
if acceptednotice then environment[consentkey] = true end

if not acceptednotice then
    local consentgui = create("ScreenGui", nil, {
        Name = "LunaDevBB4Consent", ResetOnSpawn = false,
        DisplayOrder = 1000, ZIndexBehavior = Enum.ZIndexBehavior.Sibling
    })
    local parentedconsent = pcall(function() consentgui.Parent = game:GetService("CoreGui") end)
    if not parentedconsent then consentgui.Parent = player:WaitForChild("PlayerGui") end
    local shade = create("Frame", consentgui, {
        Size = UDim2.fromScale(1, 1), BackgroundColor3 = Color3.fromRGB(8, 7, 13),
        BackgroundTransparency = 0.23, BorderSizePixel = 0, Active = true
    })
    local viewport = workspace.CurrentCamera.ViewportSize
    local panel = round(create("Frame", shade, {
        AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
        Size = UDim2.fromOffset(math.max(210, math.min(440, viewport.X - 24)),
            math.max(220, math.min(316, viewport.Y - 24))),
        BackgroundColor3 = Color3.fromRGB(19, 17, 29), BorderSizePixel = 0
    }), 12)
    create("UIStroke", panel, {Color = Color3.fromRGB(128, 76, 216), Thickness = 1.3})
    local heading = label(panel, "luna.dev  /  before you continue", 16, purple, 35)
    heading.Position, heading.Size = UDim2.fromOffset(16, 13), UDim2.new(1, -32, 0, 35)
    heading.Font = Enum.Font.GothamBold
    local warning = create("ScrollingFrame", panel, {
        Position = UDim2.fromOffset(16, 57), Size = UDim2.new(1, -32, 1, -164),
        BackgroundTransparency = 1, BorderSizePixel = 0,
        AutomaticCanvasSize = Enum.AutomaticSize.Y, CanvasSize = UDim2.fromOffset(0, 0),
        ScrollBarThickness = 3, ScrollBarImageColor3 = purple
    })
    local message = label(warning,
        "Third-party scripts may violate game rules and lead to kicks, bans, lost progress, or other penalties. Luna.dev cannot guarantee safety and is not responsible for game-issued penalties from using this menu.\n\nBy selecting Accept, you take responsibility for choosing to run it.",
        13, pale, 132)
    message.Size = UDim2.new(1, -8, 0, 0)
    message.AutomaticSize, message.TextYAlignment = Enum.AutomaticSize.Y, Enum.TextYAlignment.Top
    local persistence = label(panel,
        "Acceptance is shared through luna.dev/settings.json. Extension preferences are saved separately.",
        10, muted, 35)
    persistence.Position = UDim2.new(0, 16, 1, -102)
    persistence.Size = UDim2.new(1, -32, 0, 34)
    local accept = button(panel, "ACCEPT & CONTINUE", function() end, 43)
    accept.Position = UDim2.new(0, 16, 1, -55)
    accept.Size, accept.BackgroundColor3 = UDim2.new(0.5, -21, 0, 43), Color3.fromRGB(116, 65, 202)
    accept.Font = Enum.Font.GothamBold
    local decline = button(panel, "DECLINE & EXIT", function() end, 43)
    decline.Position, decline.Size = UDim2.new(0.5, 5, 1, -55), UDim2.new(0.5, -21, 0, 43)
    decline.Font = Enum.Font.GothamBold
    local decision, chosen = Instance.new("BindableEvent"), false
    accept.MouseButton1Click:Connect(function()
        if chosen then return end
        chosen = true
        decision:Fire(true)
    end)
    decline.MouseButton1Click:Connect(function()
        if chosen then return end
        chosen = true
        decision:Fire(false)
    end)
    local allowed = decision.Event:Wait()
    consentgui:Destroy()
    decision:Destroy()
    if not allowed then return end
    environment[consentkey] = true
    local ok, err = saveglobal(function(data)
        data.disclaimer.accepted = true
        data.disclaimer.version = 1
    end)
    if not ok then warn("Luna.dev: disclaimer acceptance could not be saved: " .. tostring(err)) end
end

local existing = game:GetService("CoreGui"):FindFirstChild("LunaDevBrokenBonesIV")
if not existing then
    local pg = player:FindFirstChild("PlayerGui")
    existing = pg and pg:FindFirstChild("LunaDevBrokenBonesIV")
end
if existing then
    existing.Enabled = true
    return
end

local gui = create("ScreenGui", nil, {
    Name = "LunaDevBrokenBonesIV", ResetOnSpawn = false, IgnoreGuiInset = false,
    ZIndexBehavior = Enum.ZIndexBehavior.Sibling
})
local parented = pcall(function() gui.Parent = game:GetService("CoreGui") end)
if not parented then gui.Parent = player:WaitForChild("PlayerGui") end
local function windowdimensions()
    local viewport = (workspace.CurrentCamera or camera).ViewportSize
    return math.max(200, math.min(520, viewport.X - 24)),
        math.max(200, math.min(420, viewport.Y - 24))
end
local initialwidth, initialheight = windowdimensions()
local window = round(create("Frame", gui, {
    AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
    Size = UDim2.fromOffset(initialwidth, initialheight),
    BackgroundColor3 = Color3.fromRGB(19, 17, 29),
    BorderSizePixel = 0, Active = true
}), 12)
create("UISizeConstraint", window, {MaxSize = Vector2.new(520, 420), MinSize = Vector2.new(200, 44)})
create("UIStroke", window, {Color = Color3.fromRGB(104, 67, 166), Thickness = 1.2})
local header = round(create("Frame", window, {
    Size = UDim2.new(1, 0, 0, 44), BackgroundColor3 = Color3.fromRGB(32, 25, 51),
    BorderSizePixel = 0, Active = true
}), 12)
local brand = label(header, "luna.dev", 14, purple, 44)
brand.Position, brand.Size = UDim2.fromOffset(12, 0), UDim2.fromOffset(69, 44)
brand.TextWrapped = false
brand.Font = Enum.Font.GothamBold
local unsaveddot = round(create("Frame", header, {
    Position = UDim2.fromOffset(81, 19), Size = UDim2.fromOffset(7, 7),
    BackgroundColor3 = Color3.fromRGB(192, 132, 252), BorderSizePixel = 0,
    Visible = false
}), 7)
local subtitle = label(header, "/ broken bones iv", 12, muted, 44)
subtitle.Position, subtitle.Size = UDim2.fromOffset(100, 0), UDim2.new(1, -194, 1, 0)
subtitle.TextWrapped = false
subtitle.TextTruncate = Enum.TextTruncate.AtEnd
local x = round(create("TextButton", header, {
    AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -8, 0, 7),
    Size = UDim2.fromOffset(29, 29), BorderSizePixel = 0,
    BackgroundColor3 = Color3.fromRGB(65, 36, 64), Text = "×",
    TextColor3 = pale, Font = Enum.Font.GothamBold, TextSize = 17
}), 7)
local minimize = round(create("TextButton", header, {
    AnchorPoint = Vector2.new(1, 0), Position = UDim2.new(1, -42, 0, 7),
    Size = UDim2.fromOffset(29, 29), BorderSizePixel = 0,
    BackgroundColor3 = surface, Text = "−", TextColor3 = pale,
    Font = Enum.Font.GothamBold, TextSize = 17
}), 7)
local body = create("Frame", window, {
    Position = UDim2.fromOffset(12, 50), Size = UDim2.new(1, -24, 1, -59),
    BackgroundTransparency = 1
})
local tabs = create("ScrollingFrame", body, {
    Size = UDim2.new(1, 0, 0, 35), BackgroundTransparency = 1,
    ScrollBarThickness = 0, ScrollingDirection = Enum.ScrollingDirection.X,
    CanvasSize = UDim2.fromOffset(0, 0), AutomaticCanvasSize = Enum.AutomaticSize.X
})
create("UIListLayout", tabs, {FillDirection = Enum.FillDirection.Horizontal, Padding = UDim.new(0, 6), SortOrder = Enum.SortOrder.LayoutOrder})
local content = create("Frame", body, {
    Position = UDim2.fromOffset(0, 42), Size = UDim2.new(1, 0, 1, -103),
    BackgroundTransparency = 1
})
local status = label(body, "Ready. You can keep using other tabs during purchases.", 11, muted, 34)
status.Position = UDim2.new(0, 0, 1, -58)
status.TextYAlignment = Enum.TextYAlignment.Center
local progress = round(create("Frame", body, {
    Position = UDim2.new(0, 0, 1, -16), Size = UDim2.new(1, 0, 0, 5),
    BackgroundColor3 = surface, BorderSizePixel = 0
}), 4)
local bar = round(create("Frame", progress, {
    Size = UDim2.fromScale(0, 1), BackgroundColor3 = purple,
    BorderSizePixel = 0
}), 4)

local pages = {}
local nav = {}
local inputfields = {}
local restoring = {startup = {}, respawn = {}}
local restorekeys = {"fly", "noclip", "infinitejump", "speed", "jump", "looplaunch", "gravity", "fullbright", "freecam", "antiafk"}
for _, phase in ipairs({"startup", "respawn"}) do
    for _, key in ipairs(restorekeys) do
        restoring[phase][key] = extensiondata.restore[phase][key] == true
    end
end
local updateunsaved = function() end
local settingbaseline
local forceunsaved = false
local active = true
local running = false
local cancelled = false
local flags = {fly = false, noclip = false, infinitejump = false, fullbright = false,
    antiafk = false, freecam = false, gravity = false, speed = false, jump = false, looplaunch = false}
local togglecontrols = {}
local connections = {}
local saved = {gravity = workspace.Gravity, brightness = lighting.Brightness,
    ambient = lighting.Ambient, outdoor = lighting.OutdoorAmbient,
    clock = lighting.ClockTime, fov = camera.FieldOfView}
local settings = {speed = 80, jump = 100, fly = 65, gravity = 45, launch = 200, launchinterval = 0.25, fov = 90}
for key in pairs(settings) do
    local value = tonumber(savedbb4[key])
    if value and value == value and value ~= math.huge and value ~= -math.huge
        and (key ~= "launchinterval" or value > 0) then
        settings[key] = value
    end
end
local saved_delay = tonumber(savedbb4.purchase_delay)
if saved_delay and saved_delay == saved_delay and saved_delay ~= math.huge and saved_delay >= 0 then
    purchase_delay = saved_delay
end
local lastlaunch = 0
local originals = setmetatable({}, {__mode = "k"})

local function notice(message) if active then status.Text = tostring(message) end end
local function addconnection(connection) table.insert(connections, connection) end
local function switch(name)
    for title, page in pairs(pages) do page.Visible = title == name end
    for title, tab in pairs(nav) do
        tab.BackgroundColor3 = title == name and Color3.fromRGB(88, 53, 146) or surface
    end
end
local function page(name)
    local view = create("ScrollingFrame", content, {
        Size = UDim2.fromScale(1, 1), BackgroundTransparency = 1, Visible = false,
        BorderSizePixel = 0, ScrollBarThickness = 3, ScrollBarImageColor3 = purple,
        CanvasSize = UDim2.fromOffset(0, 0), AutomaticCanvasSize = Enum.AutomaticSize.Y
    })
    create("UIPadding", view, {PaddingRight = UDim.new(0, 7), PaddingBottom = UDim.new(0, 6)})
    create("UIListLayout", view, {Padding = UDim.new(0, 7), SortOrder = Enum.SortOrder.LayoutOrder})
    local tab = round(create("TextButton", tabs, {
        Size = UDim2.fromOffset(math.max(72, #name * 8 + 23), 31),
        BackgroundColor3 = surface, BorderSizePixel = 0, Text = name,
        TextColor3 = pale, TextSize = 12, Font = Enum.Font.GothamMedium
    }))
    tab.MouseButton1Click:Connect(function() switch(name) end)
    pages[name] = view
    nav[name] = tab
    return view
end
local function field(parent, caption, initial)
    label(parent, caption, 12, muted)
    local box = round(create("TextBox", parent, {
        Size = UDim2.new(1, 0, 0, 34), BackgroundColor3 = surface,
        BorderSizePixel = 0, Text = tostring(initial), ClearTextOnFocus = false,
        PlaceholderText = tostring(initial), Font = Enum.Font.Code, TextSize = 13,
        TextColor3 = pale, TextXAlignment = Enum.TextXAlignment.Center
    }))
    table.insert(inputfields, box)
    box:GetPropertyChangedSignal("Text"):Connect(function() updateunsaved() end)
    return box
end
local function finite(text)
    local value = tonumber(text)
    if not value or value ~= value or value == math.huge or value == -math.huge then return nil end
    return value
end
local function number(text, low, high)
    local value = finite(text)
    if not value then return nil end
    return math.clamp(value, low, high)
end
local function setflag(key, enabled)
    flags[key] = enabled
    local item = togglecontrols[key]
    if item then
        item.control.Text = item.title .. (enabled and "   ON" or "   OFF")
        item.control.BackgroundColor3 = enabled and Color3.fromRGB(72, 47, 115) or surface
    end
end
local function toggle(parent, title, key, callback)
    local control = button(parent, title .. "   OFF", function()
        local enabled = not flags[key]
        setflag(key, enabled)
        if callback then callback(enabled) end
    end)
    togglecontrols[key] = {control = control, title = title, callback = callback}
    return control
end
local function refreshdata()
    local data = fetch()
    if data then
        notice("Money: " .. tostring(data.money) .. " | Fuel: " .. tostring(data.fuellevel) .. " | Breaks: " .. tostring(data.totalbreaks))
    else
        notice("Couldn't load server stats.")
    end
    return data
end

local economy = page("Economy")
label(economy, "Money, upgrades, utilities and maps", 14, pale, 25).Font = Enum.Font.GothamBold
label(economy, "Give Money is optional. Auto-buy works separately. After the second money request, rejoin, inject Luna.dev again and start background auto-buy.", 11, muted, 58)
local cashbox = field(economy, "Money request", preferred("money_amount", money_amount))
local delaybox = field(economy, "Auto-buy delay (seconds; default 0.05)", purchase_delay)
local upgrade_status = label(economy, "Upgrade levels: checking...", 11, muted, 156)
upgrade_status.TextYAlignment = Enum.TextYAlignment.Top
local function showlevels(data)
    if not active or not data then return end
    local rows = {"UPGRADE LEVELS — LOADDATA"}
    local complete, unknown = 0, 0
    for _, item in ipairs(upgrades) do
        local current = tonumber(data[item[1] .. "level"])
        local name = item[1]:sub(1, 1):upper() .. item[1]:sub(2)
        if current then
            local level = math.floor(current)
            if level >= 300 then complete += 1 end
            table.insert(rows, string.format("%s: %d/300%s", name, level,
                level >= 300 and "  MAX" or "  (" .. tostring(300 - level) .. " remaining)"))
        else
            unknown += 1
            table.insert(rows, name .. ": unknown — not assumed to be level 0")
        end
    end
    local humanoid = gethumanoid()
    if humanoid then
        table.insert(rows, string.format("Current character: WalkSpeed %.2f · JumpPower %.2f",
            humanoid.WalkSpeed, humanoid.JumpPower))
    end
    table.insert(rows, string.format("Maxed: %d/10 · Unknown: %d", complete, unknown))
    upgrade_status.Text = table.concat(rows, "\n")
    return complete, unknown
end
local scanbusy = false
local function scanupgrades()
    if scanbusy or not active then return end
    scanbusy = true
    task.spawn(function()
        local data = fetch()
        scanbusy = false
        if not active then return end
        if data then
            local complete, unknown = showlevels(data)
            if not running then
                notice(string.format("Upgrade check: %d/10 at 300, %d unknown. Speed and Jump are listed separately above.",
                    complete, unknown))
            end
        elseif not running then
            upgrade_status.Text = "Unable to read upgrade levels from LoadData. No purchase requests sent."
            notice("Could not read upgrade levels. Check the game connection, then retry.")
        end
    end)
end
button(economy, "CHECK ALL UPGRADE LEVELS", scanupgrades, 38)
local money_busy = false
local money_presses = 0
local moneybutton
local start
local function updateeconomybuttons()
    if not active then return end
    if moneybutton then
        moneybutton.Text = money_busy and "SENDING MONEY REQUEST..."
            or (money_presses == 0 and "GIVE MONEY  ·  FIRST PRESS"
            or "GIVE MONEY AGAIN  ·  DISCONNECT")
        moneybutton.Active = not money_busy and not running and money_presses < 2
        moneybutton.AutoButtonColor = moneybutton.Active
        moneybutton.BackgroundColor3 = moneybutton.Active and Color3.fromRGB(116, 65, 202)
            or Color3.fromRGB(65, 49, 89)
    end
    if start then
        start.Text = running and "STOP AUTO-BUY" or "START AUTO-BUY IN BACKGROUND"
        start.Active = not money_busy
        start.AutoButtonColor = start.Active
        start.BackgroundColor3 = running and Color3.fromRGB(91, 47, 108)
            or (start.Active and Color3.fromRGB(66, 47, 110) or surface)
        start.TextColor3 = start.Active and pale or muted
    end
end
moneybutton = button(economy, "GIVE MONEY  ·  FIRST PRESS", function()
    if money_busy or running or money_presses >= 2 then return end
    local amount = finite(cashbox.Text)
    if not amount or amount <= 0 then
        notice("Enter a positive, finite money amount.")
        return
    end
    money_busy = true
    money_presses += 1
    updateeconomybuttons()
    notice("Sending money request " .. money_presses .. "/2...")
    task.spawn(function()
        local ok, response = request("money", amount, false)
        if not active then return end
        if money_presses == 2 then
            notice(ok and "Money request sent. Disconnecting; rejoin manually."
                or "Money request failed: " .. tostring(response) .. ". Disconnecting; rejoin manually.")
            task.wait(0.3)
            if active then
                player:Kick("Luna.dev | Money requests sent. Rejoin Broken Bones IV, inject Luna.dev again, then open Economy and press START AUTO-BUY IN BACKGROUND to purchase upgrades, utilities and maps.")
            end
            return
        end
        money_busy = false
        updateeconomybuttons()
        if not ok then
            notice("Money request failed: " .. tostring(response) .. ". Auto-buy is still available.")
        elseif response == "No confirmation returned" then
            notice("Request sent with no confirmation returned. Rejoin to check whether the balance saved.")
        else
            notice("Money request returned a response. You can request again or start auto-buy.")
        end
    end)
end, 58)
moneybutton.Font = Enum.Font.GothamBold
moneybutton.TextSize = 15
create("UIStroke", moneybutton, {Color = Color3.fromRGB(183, 133, 255), Thickness = 1.4})
local function applydelay()
    local delay = finite(delaybox.Text)
    if not delay or delay < 0 then
        notice("Enter a non-negative, finite delay in seconds (example: 0.05).")
        return false
    end
    purchase_delay = delay
    notice(purchase_delay == 0
        and "No added timer. Each purchase still waits for two server responses."
        or "Auto-buy delay: " .. tostring(purchase_delay) .. "s between purchases.")
    return true
end
button(economy, "APPLY AUTO-BUY DELAY", applydelay)
label(economy, "0 removes the extra wait, but each purchase still needs two server responses. The game may also throttle requests; this cannot be made instant by changing a timer.", 11, muted, 56)
start = button(economy, "START AUTO-BUY IN BACKGROUND", function()
    if running then
        cancelled = true
        notice("Stopping after the current request...")
        return
    end
    if money_busy then
        notice("Wait until the current money request finishes.")
        return
    end
    if not applydelay() then return end
    running = true
    cancelled = false
    updateeconomybuttons()
    bar.Size = UDim2.fromScale(0, 1)
    task.spawn(function()
        local ok, message = pcall(function()
            notice("Reading current upgrade levels before purchasing...")
            local data = fetch()
            if not data then error("Unable to read current upgrade levels; no purchases started", 0) end
            local complete, unknown = showlevels(data)
            if unknown > 0 then
                error("Some upgrade levels are missing from LoadData. No purchases started; check the upgrade reader.", 0)
            end
            if complete == #upgrades then
                notice("All regular upgrades already report 300/300. Checking utilities and maps...")
            else
                notice(string.format("Starting with %d/10 upgrades maxed. Speed %s/300 · Jump %s/300.",
                    complete, tostring(data.speedlevel), tostring(data.jumplevel)))
            end
            local total, done = 0, 0
            local remoteelapsed = 0
            local incomplete = {}
            for _, item in ipairs(upgrades) do
                total += 300 - math.clamp(math.floor(tonumber(data[item[1] .. "level"]) or 0), 0, 300)
            end
            for _, item in ipairs(utilities) do
                total += 5 - math.clamp(math.floor(tonumber(data[item[1]]) or 0), 0, 5)
            end
            for index, costs in ipairs(maps) do
                for stage in ipairs(costs) do
                    if not data["map" .. index .. "stage" .. stage] then total += 1 end
                end
            end
            local function check()
                if cancelled or not active then error("Auto-buy stopped", 0) end
            end
            local function purchase(stat, amount, replace, cost)
                check()
                if cost > 0 then
                    local started = os.clock()
                    local paid, err = request("money", -cost, false)
                    remoteelapsed += os.clock() - started
                    if not paid then error(err, 0) end
                end
                check()
                local started = os.clock()
                local accepted, err = request(stat, amount, replace)
                remoteelapsed += os.clock() - started
                if not accepted then error(err, 0) end
                done += 1
                if active then
                    bar.Size = UDim2.fromScale(total > 0 and done / total or 1, 1)
                    if done % 15 == 0 or done == total then
                        notice(string.format("Sent %s  ·  %d/%d  ·  remote wait %.2fs/item", stat, done, total, remoteelapsed / done))
                    end
                    if done % 30 == 0 then
                        local latest = fetch()
                        if latest then showlevels(latest) end
                    end
                end
                if purchase_delay > 0 then
                    task.wait(purchase_delay)
                end
            end
            for _, item in ipairs(upgrades) do
                check()
                local stat = item[1] .. "level"
                local snapshot = fetch()
                if snapshot then
                    data = snapshot
                    showlevels(data)
                end
                local reported = tonumber(data[stat])
                if not reported then error("Cannot read " .. stat .. "; stopping rather than guessing its level", 0) end
                local level = math.clamp(math.floor(reported), 0, 300)
                while level < 300 do
                    local nextlevel = level + 1
                    local cost = level < 100 and item[2] * nextlevel ^ 2
                        or math.floor(10 ^ (nextlevel / 100) * 1000 * item[2])
                    purchase(stat, 1, false, cost)
                    level = nextlevel
                end
                local latest = fetch()
                if latest then
                    showlevels(latest)
                    local actual = tonumber(latest[stat])
                    if not actual or actual < 300 then
                        table.insert(incomplete, stat .. " " .. tostring(actual or "unknown") .. "/300")
                    end
                else
                    table.insert(incomplete, stat .. " (could not verify)")
                end
            end
            for _, item in ipairs(utilities) do
                local level = math.clamp(math.floor(tonumber(data[item[1]]) or 0), 0, 5)
                while level < 5 do
                    local nextlevel = level + 1
                    purchase(item[1], nextlevel, true, item[2][nextlevel])
                    level = nextlevel
                end
            end
            for index, costs in ipairs(maps) do
                for stage, cost in ipairs(costs) do
                    local stat = "map" .. index .. "stage" .. stage
                    if not data[stat] then purchase(stat, true, true, cost) end
                end
            end
            check()
            local final = fetch()
            if final then
                showlevels(final)
                table.clear(incomplete)
                for _, item in ipairs(upgrades) do
                    local stat = item[1] .. "level"
                    local actual = tonumber(final[stat])
                    if not actual or actual < 300 then
                        local itemtext = stat .. " " .. tostring(actual or "unknown") .. "/300"
                        if not table.find(incomplete, itemtext) then
                            table.insert(incomplete, itemtext)
                        end
                    end
                end
            else
                table.insert(incomplete, "final server stats unavailable")
            end
            if #incomplete > 0 then
                notice("Not yet 300: " .. table.concat(incomplete, ", ") .. ". Recheck the shop; you can rerun auto-buy from reported levels.")
            else
                notice("All regular upgrades reported 300/300. Rejoin only if the original game shop is still showing cached levels.")
            end
        end)
        if active and not ok then notice(message) end
        running = false
        updateeconomybuttons()
    end)
end, 46)
updateeconomybuttons()
scanupgrades()
button(economy, "REFRESH SERVER STATS", function()
    task.spawn(function()
        local data = refreshdata()
        if data then showlevels(data) end
    end)
end)
local utilitystatus = label(economy, "Already-owned level 5 utilities are skipped.", 11, muted, 28)
button(economy, "CHECK UTILITY LEVELS", function()
    task.spawn(function()
        local data = fetch()
        if not data then notice("Couldn't load utility levels.") return end
        local summary = {}
        for _, item in ipairs(utilities) do
            local current = math.clamp(math.floor(tonumber(data[item[1]]) or 0), 0, 5)
            table.insert(summary, item[1] .. " " .. current .. "/5")
        end
        if active then utilitystatus.Text = table.concat(summary, "  ·  ") end
    end)
end)

local abilities = page("Abilities")
label(abilities, "Fuel affects all five utilities", 14, pale, 25).Font = Enum.Font.GothamBold
label(abilities, "Helium Balloon · Helicopter Hat · Rocket Boots · Jetpack · Gravity Bubble", 12, muted, 33)
local fuelbox = field(abilities, "Fuel level (maximum 300)", preferred("fuel_target", fuel_target))
label(abilities, "Above 300 may trigger a kick. This menu blocks any value over 300.", 11, Color3.fromRGB(255, 172, 126), 37)
button(abilities, "APPLY FUEL LEVEL (MAX 300)", function()
    task.spawn(function()
        local target = tonumber(fuelbox.Text)
        if not target or target ~= target or target == math.huge or target == -math.huge
            or target % 1 ~= 0 or target < 0 or target > fuel_target then
            notice("Fuel must be a whole number from 0 to 300. Higher values are blocked.")
            return
        end
        local data = fetch()
        if not data then notice("Couldn't confirm your current fuel level.") return end
        if (tonumber(data.fuellevel) or 0) >= target then
            notice("Fuel is already level " .. tostring(data.fuellevel) .. ". No request needed.")
            return
        end
        local ok, err = request("fuellevel", target, true)
        if not ok then notice(err .. ". No further request sent.") return end
        local verified = fetch()
        if not verified or (tonumber(verified.fuellevel) or 0) < target then
            notice("Requested fuel level wasn't confirmed; check server stats.")
            return
        end
        local synced = mirror("fuellevel", target, true)
        notice(synced and "Fuel level updated. Start a new run to refill the green bar."
            or "Fuel updated. Rejoin if the UI is stale, then start a new run.")
    end)
end, 40)
label(abilities, "Level 300 is the normal currency upgrade ceiling, not infinite fuel. All utilities share this bonus; a run that's already active keeps its original countdown.", 11, muted, 62)

local experience = page("XP helper")
label(experience, "Levels are calculated from total breaks", 14, pale, 25).Font = Enum.Font.GothamBold
label(experience, "Direct totalbreaks requests caused kicks. XP helper is read-only: earn breaks through regular runs, then refresh progress here.", 11, Color3.fromRGB(255, 172, 126), 61)
local levelbox = field(experience, "Target level (1–1395)", preferred("xp_target", 500))
local estimate = label(experience, "Enter a target to calculate your remaining breaks.", 12, muted, 67)
local function breaksforlevel(level)
    if level <= 1 then return 0 end
    if level < 100 then
        return math.ceil(((level - 1) * 6.141934860599844) ^ 2.1552185512626503)
    end
    return math.ceil(10 ^ (level / 100) * 100000)
end
local function levelforbreaks(total)
    if total <= 0 then return 1 end
    if total < 1000000 then
        return math.floor(total ^ 0.463990067 / 6.141934860599844 + 1)
    end
    return math.floor(math.log10(total / 100000) * 100)
end
local function formatvalue(value)
    if value < 1e9 then return string.format("%.0f", value) end
    return string.format("%.5g", value)
end
local xploading = false
button(experience, "REFRESH XP PROGRESS", function()
    if xploading then return end
    local target = finite(levelbox.Text)
    if not target or target % 1 ~= 0 or target < 1 or target > 1395 then
        notice("Enter a whole target level from 1 to 1395")
        return
    end
    xploading = true
    task.spawn(function()
        local data = fetch()
        xploading = false
        if not active then return end
        if not data or type(data.totalbreaks) ~= "number" then
            notice("Couldn't read current total breaks")
            return
        end
        local current = math.max(0, data.totalbreaks)
        local required = breaksforlevel(target)
        estimate.Text = "Current level: " .. formatvalue(levelforbreaks(current))
            .. "   •   Total breaks: " .. formatvalue(current)
            .. "\nTarget level: " .. tostring(target)
            .. "   •   Needed: " .. formatvalue(math.max(0, required - current))
        notice(current >= required and "Target level reached." or "XP progress refreshed. No stat changes sent.")
    end)
end, 40)

local movement = page("Movement")
label(movement, "Local movement controls", 14, pale, 25).Font = Enum.Font.GothamBold
label(movement, "Suggested starting values (not safe/anti-kick guarantees): walk 24–80, jump 50–120, fly 30–100, gravity 50–196.2, launch 100–500. No script-imposed upper limits; extreme values can kick you or destabilize physics.", 11, Color3.fromRGB(255, 172, 126), 98)
local speedbox = field(movement, "Walk speed · suggested 24–80", settings.speed)
local jumpbox = field(movement, "Jump power · suggested 50–120", settings.jump)
local flybox = field(movement, "Fly speed · suggested 30–100", settings.fly)
local gravitybox = field(movement, "Local gravity · suggested 50–196.2", settings.gravity)
local launchbox = field(movement, "Launch strength · suggested 100–500", settings.launch)
local intervalbox = field(movement, "Loop launch interval (seconds) · suggested 0.25–1", settings.launchinterval)
local function applymovement()
    local values = {
        speed = finite(speedbox.Text), jump = finite(jumpbox.Text),
        fly = finite(flybox.Text), gravity = finite(gravitybox.Text),
        launch = finite(launchbox.Text), launchinterval = finite(intervalbox.Text)
    }
    for key, value in pairs(values) do
        if not value then return false, "Invalid " .. key .. ": use a finite number" end
    end
    if values.launchinterval <= 0 then return false, "Loop interval must be greater than zero" end
    for key, value in pairs(values) do settings[key] = value end
    return true
end
local function applyandnotice()
    local ok, err = applymovement()
    if not ok then notice(err) return false end
    if flags.gravity then
        local applied, reason = pcall(function() workspace.Gravity = settings.gravity end)
        if not applied then setflag("gravity", false) notice("Gravity rejected: " .. tostring(reason)) return false end
    end
    notice("Movement values updated. Extreme values may fail or trigger a game kick.")
    return true
end
button(movement, "APPLY MOVEMENT VALUES", applyandnotice)
toggle(movement, "Walk speed override", "speed", function(on)
    if on then
        if not applymovement() then setflag("speed", false) notice("Enter finite movement values first") return end
        local humanoid = gethumanoid()
        if humanoid and not originals[humanoid] then
            originals[humanoid] = {speed = humanoid.WalkSpeed, jump = humanoid.JumpPower,
                usejump = humanoid.UseJumpPower}
        end
    else
        local humanoid = gethumanoid()
        if humanoid and originals[humanoid] then humanoid.WalkSpeed = originals[humanoid].speed end
    end
end)
toggle(movement, "Jump power override", "jump", function(on)
    if on then
        if not applymovement() then setflag("jump", false) notice("Enter finite movement values first") return end
        local humanoid = gethumanoid()
        if humanoid and not originals[humanoid] then
            originals[humanoid] = {speed = humanoid.WalkSpeed, jump = humanoid.JumpPower,
                usejump = humanoid.UseJumpPower}
        end
    else
        local humanoid = gethumanoid()
        if humanoid and originals[humanoid] then
            humanoid.JumpPower = originals[humanoid].jump
            humanoid.UseJumpPower = originals[humanoid].usejump
        end
    end
end)
toggle(movement, "Infinite jump", "infinitejump")
toggle(movement, "Fly (WASD / Space / Ctrl)", "fly", function(on)
    if on and not applymovement() then
        setflag("fly", false)
        notice("Enter finite movement values first")
        return
    end
    notice(on and "Fly enabled; use movement keys." or "Fly disabled.")
end)
toggle(movement, "Noclip", "noclip")
toggle(movement, "Gravity override", "gravity", function(on)
    if on and not applymovement() then
        setflag("gravity", false)
        notice("Enter finite movement values first")
        return
    end
    local ok, err = pcall(function() workspace.Gravity = on and settings.gravity or saved.gravity end)
    if not ok then setflag("gravity", false) notice("Gravity rejected: " .. tostring(err)) end
end)
local function launchonce()
    local root = getroot()
    if not root then return false, "Character isn't ready" end
    if root.Anchored then return false, "Character is anchored" end
    local ok, err = pcall(function()
        root.AssemblyLinearVelocity += workspace.CurrentCamera.CFrame.LookVector * settings.launch
    end)
    if not ok then return false, "Launch rejected: " .. tostring(err) end
    return true
end
button(movement, "LAUNCH IN LOOK DIRECTION", function()
    local ok, err = applymovement()
    if not ok then notice(err) return end
    local launched, reason = launchonce()
    notice(launched and "Launched in camera look direction." or reason)
end)
toggle(movement, "LOOP LAUNCH IN LOOK DIRECTION", "looplaunch", function(on)
    if on then
        local ok, err = applymovement()
        if not ok then setflag("looplaunch", false) notice(err) return end
        lastlaunch = 0
        notice("Loop launch enabled. Toggle OFF to stop; fly may overwrite velocity.")
    else
        notice("Loop launch stopped.")
    end
end)
local mapbox = field(movement, "Map (1–3) / stage (1–10), e.g. 3,10", preferred("map_stage", "1,1"))
button(movement, "TELEPORT TO MAP STAGE", function()
    local a, b = mapbox.Text:match("^%s*(%d+)%s*,%s*(%d+)%s*$")
    local map, stage = tonumber(a), tonumber(b)
    if not map or not stage or map < 1 or map > 3 or stage < 1 or stage > 10 then
        notice("Use map,stage (example: 3,10)") return
    end
    local folder = storage:FindFirstChild("Map" .. map .. "Spawns")
    local spot = folder and folder:FindFirstChild("Stage" .. stage)
    local root = getroot()
    if root and spot and spot:IsA("Vector3Value") then
        root.CFrame = CFrame.new(spot.Value + Vector3.new(0, 5, 0))
        notice("Local teleport sent to map " .. map .. ", stage " .. stage)
    else
        notice("Spawn or character wasn't found")
    end
end)

local gameplay = page("Gameplay")
label(gameplay, "Physics and session tools", 14, pale, 25).Font = Enum.Font.GothamBold
button(gameplay, "LOCAL RAGDOLL", function()
    local humanoid = gethumanoid()
    if humanoid then humanoid:ChangeState(Enum.HumanoidStateType.Physics) end
end)
button(gameplay, "STAND UP", function()
    local humanoid = gethumanoid()
    if humanoid then humanoid:ChangeState(Enum.HumanoidStateType.GettingUp) end
end)
toggle(gameplay, "Anti-AFK", "antiafk")
label(gameplay, "The game controls the normal crash/ragdoll cycle. Local physics may be overridden while a run is active.", 11, muted, 48)

local visuals = page("Visuals")
label(visuals, "Local visual controls", 14, pale, 25).Font = Enum.Font.GothamBold
local fovbox = field(visuals, "Field of view", settings.fov)
toggle(visuals, "Fullbright", "fullbright", function(on)
    lighting.Brightness = on and 4 or saved.brightness
    lighting.Ambient = on and Color3.new(1, 1, 1) or saved.ambient
    lighting.OutdoorAmbient = on and Color3.new(1, 1, 1) or saved.outdoor
    lighting.ClockTime = on and 12 or saved.clock
end)
button(visuals, "APPLY FOV", function()
    settings.fov = number(fovbox.Text, 40, 120) or 90
    camera.FieldOfView = settings.fov
end)
button(visuals, "RESET FOV", function() camera.FieldOfView = saved.fov end)
toggle(visuals, "Freecam (RMB + WASD)", "freecam", function(on)
    if on then
        camera.CameraType = Enum.CameraType.Scriptable
    else
        camera.CameraType = Enum.CameraType.Custom
        camera.CameraSubject = gethumanoid()
    end
end)

local restorepage = page("Restore")
label(restorepage, "Feature restoration", 14, pale, 25).Font = Enum.Font.GothamBold
label(restorepage, "Choose each feature's startup and respawn behaviour. Respawn only restores features that were enabled before death. Changes save only when you press Save Settings.", 11, muted, 76)
local restorelabels = {
    fly = "Fly", noclip = "Noclip", infinitejump = "Infinite jump",
    speed = "WalkSpeed", jump = "JumpPower", looplaunch = "Loop launch",
    gravity = "Gravity", fullbright = "Fullbright", freecam = "Freecam", antiafk = "Anti-AFK"
}
for _, key in ipairs(restorekeys) do
    label(restorepage, restorelabels[key], 12, pale, 22).Font = Enum.Font.GothamMedium
    for _, phase in ipairs({"startup", "respawn"}) do
        local control
        local function repaint()
            control.Text = (phase == "startup" and "On injection" or "After respawn")
                .. (restoring[phase][key] and "  ·  ON" or "  ·  OFF")
            control.BackgroundColor3 = restoring[phase][key]
                and Color3.fromRGB(72, 47, 115) or surface
        end
        control = button(restorepage, "", function()
            restoring[phase][key] = not restoring[phase][key]
            repaint()
            updateunsaved()
        end, 30)
        repaint()
    end
end

local function settingssignature()
    local pieces = {}
    for _, entry in ipairs(inputfields) do table.insert(pieces, entry.Text) end
    for _, phase in ipairs({"startup", "respawn"}) do
        for _, key in ipairs(restorekeys) do
            table.insert(pieces, restoring[phase][key] and "1" or "0")
        end
    end
    return http:JSONEncode(pieces)
end
local function isdirty()
    return forceunsaved or (settingbaseline ~= nil and settingssignature() ~= settingbaseline)
end
updateunsaved = function()
    if not active then return end
    unsaveddot.Visible = isdirty()
end
local function confirmation(title, description, yes, no)
    local overlay = create("Frame", gui, {
        Size = UDim2.fromScale(1, 1), BackgroundColor3 = Color3.fromRGB(5, 5, 9),
        BackgroundTransparency = 0.25, BorderSizePixel = 0, Active = true,
        ZIndex = 90
    })
    local panel = round(create("Frame", overlay, {
        AnchorPoint = Vector2.new(0.5, 0.5), Position = UDim2.fromScale(0.5, 0.5),
        Size = UDim2.fromOffset(math.max(215, math.min(405, camera.ViewportSize.X - 22)), 205),
        BackgroundColor3 = Color3.fromRGB(24, 20, 36), BorderSizePixel = 0,
        ZIndex = 91
    }), 10)
    create("UIStroke", panel, {Color = purple, Thickness = 1.2})
    local headline = label(panel, title, 15, pale, 31)
    headline.Position, headline.Size, headline.ZIndex = UDim2.fromOffset(14, 13), UDim2.new(1, -28, 0, 31), 92
    headline.Font = Enum.Font.GothamBold
    local explanation = label(panel, description, 12, muted, 78)
    explanation.Position, explanation.Size, explanation.ZIndex = UDim2.fromOffset(14, 51), UDim2.new(1, -28, 0, 78), 92
    explanation.TextYAlignment = Enum.TextYAlignment.Top
    local done = Instance.new("BindableEvent")
    local decision
    local left = button(panel, no or "KEEP EDITING", function()
        if decision ~= nil then return end
        decision = false
        done:Fire(false)
    end, 42)
    left.Position, left.Size, left.ZIndex = UDim2.new(0, 14, 1, -55), UDim2.new(0.5, -21, 0, 42), 92
    local right = button(panel, yes or "CONTINUE", function()
        if decision ~= nil then return end
        decision = true
        done:Fire(true)
    end, 42)
    right.Position, right.Size, right.ZIndex = UDim2.new(0.5, 7, 1, -55), UDim2.new(0.5, -21, 0, 42), 92
    right.BackgroundColor3 = Color3.fromRGB(116, 65, 202)
    local result = done.Event:Wait()
    overlay:Destroy()
    done:Destroy()
    return result
end
local function requestclose()
    if isdirty() and not confirmation("Unsaved settings",
        "Closing Luna.dev will discard changes made since the last Save Settings.",
        "CLOSE WITHOUT SAVING", "KEEP EDITING") then
        return
    end
    gui:Destroy()
end

local options = page("Settings")
label(options, "Menu and session", 14, pale, 25).Font = Enum.Font.GothamBold
label(options, "Universal acceptance: luna.dev/settings.json. This extension: luna.dev/scripts/broken_bones_iv.json. All files are readable, indented JSON. Older settings files are retained.", 11, muted, 80)
local settingsstatus = label(options, "Changes are not saved automatically. The dot beside luna.dev means your settings have unsaved changes.", 11, muted, 58)
local function extensionprefs()
    local amount, delay = finite(cashbox.Text), finite(delaybox.Text)
    local fuel, xp, view = finite(fuelbox.Text), finite(levelbox.Text), finite(fovbox.Text)
    local move = {
        speed = finite(speedbox.Text), jump = finite(jumpbox.Text),
        fly = finite(flybox.Text), gravity = finite(gravitybox.Text),
        launch = finite(launchbox.Text), launchinterval = finite(intervalbox.Text)
    }
    if not amount or amount <= 0 or not delay or delay < 0 then
        return nil, "Enter a positive money amount and non-negative auto-buy delay."
    end
    if not fuel or fuel % 1 ~= 0 or fuel < 0 or fuel > 300 then
        return nil, "Fuel target must be a whole number between 0 and 300."
    end
    if not xp or xp % 1 ~= 0 or xp < 1 or xp > 1395 then
        return nil, "XP target must be a whole number between 1 and 1395."
    end
    if not view or view < 40 or view > 120 then
        return nil, "FOV must be between 40 and 120."
    end
    for key, value in pairs(move) do
        if not value then return nil, "Invalid movement value: " .. key end
    end
    if move.launchinterval <= 0 then return nil, "Launch interval must be positive." end
    local map, stage = mapbox.Text:match("^%s*(%d+)%s*,%s*(%d+)%s*$")
    if not map or not stage or tonumber(map) > 3 or tonumber(map) < 1
        or tonumber(stage) > 10 or tonumber(stage) < 1 then
        return nil, "Map/stage must be 1,1 through 3,10."
    end
    local values = {
        money_amount = cashbox.Text, purchase_delay = delaybox.Text,
        fuel_target = fuelbox.Text, xp_target = levelbox.Text,
        fov = fovbox.Text, map_stage = mapbox.Text,
        speed = speedbox.Text, jump = jumpbox.Text, fly = flybox.Text,
        gravity = gravitybox.Text, launch = launchbox.Text,
        launchinterval = intervalbox.Text
    }
    local restore = {startup = {}, respawn = {}}
    for _, phase in ipairs({"startup", "respawn"}) do
        for _, key in ipairs(restorekeys) do
            restore[phase][key] = restoring[phase][key] == true
        end
    end
    return {values = values, restore = restore}, {delay = delay, move = move, fov = view}
end
local function resetextensionfile()
    local default = defaultextension()
    local ok, err = writejson(extensionfile, default)
    if ok then
        savedfile_raw = rawfile(extensionfile)
        forceunsaved = true
        updateunsaved()
        settingsstatus.Text = "Saved BB4 preferences reset. Current inputs remain unchanged. Reinject to use defaults."
        notice("Only BB4 settings were reset; your current session remains unchanged.")
    end
    return ok, err
end
local function savesettings()
    local prefs, values = extensionprefs()
    if not prefs then notice(values) return end
    local currentraw = rawfile(extensionfile)
    if currentraw ~= savedfile_raw then
        if not confirmation("Saved settings changed",
            "This extension's saved file was reset or changed elsewhere. Saving now will overwrite that change.",
            "SAVE ANYWAY", "CANCEL") then return end
    end
    local data, err = readjson(extensionfile)
    if err then notice(err) return end
    data = data or defaultextension()
    data._info, data.version = extensioninfo, 1
    data.values, data.restore = prefs.values, prefs.restore
    local ok, reason = writejson(extensionfile, data)
    if not ok then
        settingsstatus.Text = "Couldn't save: " .. tostring(reason)
        notice("Save Settings failed: " .. tostring(reason))
        return
    end
    savedfile_raw = rawfile(extensionfile)
    purchase_delay = values.delay
    for key, value in pairs(values.move) do settings[key] = value end
    settings.fov = values.fov
    settingbaseline, forceunsaved = settingssignature(), false
    updateunsaved()
    settingsstatus.Text = "Saved to luna.dev/scripts/broken_bones_iv.json. Other extensions are unchanged."
    notice("BB4 settings saved.")
end
button(options, "SAVE SETTINGS", savesettings, 42)
button(options, "RESET SAVED BB4 SETTINGS", function()
    if not confirmation("Reset Broken Bones IV?",
        "Reset only this extension's saved JSON. Your current session remains unchanged. Older files are preserved.",
        "RESET BB4", "CANCEL") then return end
    local ok, err = resetextensionfile()
    if not ok then notice("Couldn't reset BB4 settings: " .. tostring(err)) end
end, 36)

button(options, "REJOIN TO REFRESH GAME DATA", function()
    cancelled = true
    notice("Rejoining...")
    if #players:GetPlayers() > 1 then
        teleport:TeleportToPlaceInstance(game.PlaceId, game.JobId, player)
    else
        teleport:Teleport(game.PlaceId, player)
    end
end)
button(options, "DESTROY LUNA.DEV MENU", requestclose)
label(options, "Money method credit: original Russian-language BB4 ReJoin Farm script (author unknown; no credit was listed in the supplied code).", 11, muted, 58)
label(options, "RightShift hides the window without discarding changes. Minimize keeps purchases active; closing warns about unsaved settings and cancels purchases. The second Give Money press disconnects you.", 11, muted, 64)

local dragging, origin, initial = false, nil, nil
addconnection(header.InputBegan:Connect(function(pointer)
    if pointer.UserInputType ~= Enum.UserInputType.MouseButton1 and pointer.UserInputType ~= Enum.UserInputType.Touch then return end
    dragging = true
    origin, initial = pointer.Position, window.Position
    addconnection(pointer.Changed:Connect(function()
        if pointer.UserInputState == Enum.UserInputState.End then dragging = false end
    end))
end))
addconnection(input.InputChanged:Connect(function(pointer)
    if not dragging then return end
    if pointer.UserInputType ~= Enum.UserInputType.MouseMovement and pointer.UserInputType ~= Enum.UserInputType.Touch then return end
    local delta = pointer.Position - origin
    window.Position = UDim2.new(initial.X.Scale, initial.X.Offset + delta.X,
        initial.Y.Scale, initial.Y.Offset + delta.Y)
end))
local function fitwindow()
    local width, height = windowdimensions()
    window.Size = UDim2.fromOffset(width, body.Visible and height or 44)
end
minimize.MouseButton1Click:Connect(function()
    body.Visible = not body.Visible
    fitwindow()
    minimize.Text = body.Visible and "−" or "+"
end)
addconnection(camera:GetPropertyChangedSignal("ViewportSize"):Connect(fitwindow))
x.MouseButton1Click:Connect(requestclose)
addconnection(input.InputBegan:Connect(function(key, processed)
    if key.KeyCode == Enum.KeyCode.RightShift and not processed then
        window.Visible = not window.Visible
    end
    if flags.infinitejump and not processed and key.KeyCode == Enum.KeyCode.Space then
        local humanoid = gethumanoid()
        if humanoid then humanoid:ChangeState(Enum.HumanoidStateType.Jumping) end
    end
end))
addconnection(player.Idled:Connect(function()
    if not flags.antiafk then return end
    pcall(function()
        virtualuser:CaptureController()
        virtualuser:ClickButton2(Vector2.new())
    end)
end))
local lastparts = {}
addconnection(runservice.Heartbeat:Connect(function(delta)
    if not active then return end
    local humanoid = gethumanoid()
    local root = getroot()
    if humanoid and (flags.speed or flags.jump) and not originals[humanoid] then
        originals[humanoid] = {speed = humanoid.WalkSpeed, jump = humanoid.JumpPower,
            usejump = humanoid.UseJumpPower}
    end
    if flags.speed and humanoid then
        local ok, err = pcall(function() humanoid.WalkSpeed = settings.speed end)
        if not ok then setflag("speed", false) notice("Walk speed rejected: " .. tostring(err)) end
    end
    if flags.jump and humanoid then
        local ok, err = pcall(function()
            humanoid.UseJumpPower = true
            humanoid.JumpPower = settings.jump
        end)
        if not ok then setflag("jump", false) notice("Jump power rejected: " .. tostring(err)) end
    end
    if flags.gravity then
        local ok, err = pcall(function() workspace.Gravity = settings.gravity end)
        if not ok then setflag("gravity", false) notice("Gravity rejected: " .. tostring(err)) end
    end
    if flags.noclip and player.Character then
        for _, part in ipairs(player.Character:GetDescendants()) do
            if part:IsA("BasePart") then
                if lastparts[part] == nil then lastparts[part] = part.CanCollide end
                part.CanCollide = false
            end
        end
    elseif next(lastparts) then
        for part, collide in pairs(lastparts) do
            if part.Parent then part.CanCollide = collide end
            lastparts[part] = nil
        end
    end
    if flags.fly and root and not root.Anchored then
        local direction = Vector3.zero
        local cf = camera.CFrame
        if input:IsKeyDown(Enum.KeyCode.W) then direction += cf.LookVector end
        if input:IsKeyDown(Enum.KeyCode.S) then direction -= cf.LookVector end
        if input:IsKeyDown(Enum.KeyCode.D) then direction += cf.RightVector end
        if input:IsKeyDown(Enum.KeyCode.A) then direction -= cf.RightVector end
        if input:IsKeyDown(Enum.KeyCode.Space) then direction += Vector3.yAxis end
        if input:IsKeyDown(Enum.KeyCode.LeftControl) then direction -= Vector3.yAxis end
        local ok, err = pcall(function()
            root.AssemblyLinearVelocity = direction.Magnitude > 0 and direction.Unit * settings.fly or Vector3.zero
        end)
        if not ok then setflag("fly", false) notice("Fly rejected: " .. tostring(err)) end
    end
    if flags.looplaunch then
        lastlaunch += delta
        if lastlaunch >= settings.launchinterval then
            lastlaunch = 0
            local ok, err = launchonce()
            if not ok then
                setflag("looplaunch", false)
                notice(err .. ". Loop launch stopped.")
            end
        end
    end
    if flags.freecam and camera then
        local direction = Vector3.zero
        local cf = camera.CFrame
        if input:IsKeyDown(Enum.KeyCode.W) then direction += cf.LookVector end
        if input:IsKeyDown(Enum.KeyCode.S) then direction -= cf.LookVector end
        if input:IsKeyDown(Enum.KeyCode.D) then direction += cf.RightVector end
        if input:IsKeyDown(Enum.KeyCode.A) then direction -= cf.RightVector end
        if input:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) then
            local mouse = input:GetMouseDelta()
            cf *= CFrame.Angles(0, -mouse.X * 0.002, 0)
            cf *= CFrame.Angles(-mouse.Y * 0.002, 0, 0)
        end
        camera.CFrame = cf + (direction.Magnitude > 0 and direction.Unit * delta * 80 or Vector3.zero)
    end
end))
local characterkeys = {fly = true, noclip = true, infinitejump = true,
    speed = true, jump = true, looplaunch = true}
local restoreonrespawn = nil
local function activatefeature(key)
    if flags[key] then return end
    local item = togglecontrols[key]
    if not item then return end
    setflag(key, true)
    if item.callback then item.callback(true) end
end
addconnection(player.CharacterRemoving:Connect(function()
    local snapshot = {}
    for _, key in ipairs(restorekeys) do
        snapshot[key] = restoring.respawn[key] and flags[key] == true
        if characterkeys[key] then setflag(key, false) end
    end
    restoreonrespawn = snapshot
end))
addconnection(player.CharacterAdded:Connect(function(character)
    local snapshot = restoreonrespawn
    restoreonrespawn = nil
    if not snapshot then return end
    task.spawn(function()
        local humanoid = character:WaitForChild("Humanoid", 15)
        local root = character:WaitForChild("HumanoidRootPart", 15)
        if not active or player.Character ~= character or not humanoid or not root then return end
        for _, key in ipairs(restorekeys) do
            if snapshot[key] then activatefeature(key) end
        end
    end)
end))

local registry = environment.luna_dev_extensions
if type(registry) ~= "table" then
    registry = {}
    environment.luna_dev_extensions = registry
end
registry[extension.id] = {
    metadata = extension,
    gui = gui,
    isDirty = isdirty,
    resetSaved = resetextensionfile,
    close = requestclose,
    settingsPath = extensionfile
}
settingbaseline = settingssignature()
updateunsaved()
task.spawn(function()
    local character = player.Character or player.CharacterAdded:Wait()
    if not active then return end
    if not character:WaitForChild("Humanoid", 15) or not character:WaitForChild("HumanoidRootPart", 15) then return end
    if not active or player.Character ~= character then return end
    for _, key in ipairs(restorekeys) do
        if restoring.startup[key] then activatefeature(key) end
    end
end)

gui.Destroying:Connect(function()
    if registry[extension.id] and registry[extension.id].gui == gui then
        registry[extension.id] = nil
    end
    active = false
    cancelled = true
    for _, connection in ipairs(connections) do connection:Disconnect() end
    for part, collide in pairs(lastparts) do
        if part.Parent then part.CanCollide = collide end
    end
    workspace.Gravity = saved.gravity
    lighting.Brightness = saved.brightness
    lighting.Ambient = saved.ambient
    lighting.OutdoorAmbient = saved.outdoor
    lighting.ClockTime = saved.clock
    camera.FieldOfView = saved.fov
    if flags.freecam then
        camera.CameraType = Enum.CameraType.Custom
        camera.CameraSubject = gethumanoid()
    end
    local humanoid = gethumanoid()
    if humanoid and originals[humanoid] then
        if flags.speed then pcall(function() humanoid.WalkSpeed = originals[humanoid].speed end) end
        if flags.jump then
            pcall(function()
                humanoid.JumpPower = originals[humanoid].jump
                humanoid.UseJumpPower = originals[humanoid].usejump
            end)
        end
    end
end)
switch("Economy")

Features & Information

A feature-packed utility script made for **Broken Bones IV**. Includes: • Money and auto-buy utilities • Movement and launch controls • Gravity and ability features • Character utilities • Adjustable settings • Custom UI • Additional quality-of-life features **Version:** V5.1 Primarily developed and tested using **Potassium**. Other executors may work, but compatibility is not guaranteed. Broken Bones IV updates may occasionally break individual features. If something stops working after a game update, check for a newer script version.
#keyless#open source#quality of life#money#autobuy

Community Comments (0)

No comments yet for this script.

More Scripts for Broken Bones IV