M
Description
These are the script sources, Resonace, no key, no purchase required, for the game Ftap, just copy and paste it into your injector. There’s also a SAFE CONFIG there.
GAMEFling Things and People
UPDATEDOct 15, 2026
ACCESSKeyless
PLATFORMDesktop
Script source Luau
-- [[ BloxScripts Risk Notice ]]
-- This script has not been verified by BloxScripts.fun. Deal with caution.
--
-- Stay safe:
-- • Never log in on unofficial Roblox sites or lookalike domains.
-- • Real Roblox links use roblox.com (check the .com ending).
-- • Treat fake Roblox login / "claim reward" pages as phishing.
-- [[ End BloxScripts Risk Notice ]]
local sources = {}
sources.Intro = [====[
local Intro = {}
function Intro.Play()
local gui
local watchdog
local tweens = {}
local closed = false
local function close()
if closed then return end
closed = true
for _, tween in ipairs(tweens) do
pcall(function() tween:Cancel() end)
end
if gui then pcall(function() gui:Destroy() end) end
end
local ok, message = xpcall(function()
local TweenService = game:GetService("TweenService")
local Players = game:GetService("Players")
gui = Instance.new("ScreenGui")
gui.Name = "ResonanceIntro"
gui.ResetOnSpawn = false
gui.IgnoreGuiInset = true
gui.DisplayOrder = 2147483000
gui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
pcall(function() gui.ScreenInsets = Enum.ScreenInsets.None end)
local protect = protect_gui or protectgui or (syn and syn.protect_gui)
if type(protect) == "function" then pcall(protect, gui) end
local parented = false
local getRoot = gethui or get_hidden_gui
if type(getRoot) == "function" then
parented = pcall(function()
local parent = getRoot()
assert(parent, "GUI root unavailable")
gui.Parent = parent
end)
end
if not parented then
parented = pcall(function() gui.Parent = game:GetService("CoreGui") end)
end
if not parented then
local player = Players.LocalPlayer
local parent = player and player:WaitForChild("PlayerGui", 3)
assert(parent, "Intro GUI parent unavailable")
gui.Parent = parent
end
watchdog = task.delay(20, function()
watchdog = nil
close()
end)
local shade = Instance.new("Frame")
shade.Name = "Shade"
shade.Size = UDim2.fromScale(1, 1)
shade.BackgroundColor3 = Color3.fromRGB(3, 4, 10)
shade.BackgroundTransparency = 1
shade.BorderSizePixel = 0
shade.Active = true
shade.Parent = gui
local center = Instance.new("Frame")
center.Name = "Title"
center.AnchorPoint = Vector2.new(0.5, 0.5)
center.Position = UDim2.new(0.5, 0, 0.5, 18)
center.Size = UDim2.new(0.9, 0, 0.2, 0)
center.BackgroundTransparency = 1
center.Parent = shade
local bounds = Instance.new("UISizeConstraint")
bounds.MaxSize = Vector2.new(1100, 150)
bounds.Parent = center
local function animate(object, duration, properties)
if closed then return end
local tween = TweenService:Create(object, TweenInfo.new(duration, Enum.EasingStyle.Sine, Enum.EasingDirection.InOut), properties)
tweens[#tweens + 1] = tween
tween:Play()
end
local function label(name, zindex)
local text = Instance.new("TextLabel")
text.Name = name
text.Size = UDim2.fromScale(1, 1)
text.BackgroundTransparency = 1
text.Text = "cracked by Script hub FTAP https://t.me/Scriptftap"
text.Font = Enum.Font.GothamBold
text.TextScaled = true
text.TextWrapped = true
text.TextColor3 = Color3.fromRGB(255, 255, 255)
text.TextTransparency = 1
text.TextStrokeTransparency = 1
text.ZIndex = zindex
text.Parent = center
local limit = Instance.new("UITextSizeConstraint")
limit.MinTextSize = 16
limit.MaxTextSize = 40
limit.Parent = text
return text
end
local strokes = {}
local softGlow = {0.995, 0.985, 0.97, 0.945, 0.905, 0.84, 0.73, 0.48, 0.2}
local brightGlow = {0.987, 0.975, 0.955, 0.925, 0.87, 0.78, 0.64, 0.36, 0.08}
for i, width in ipairs({30, 24, 19, 15, 11, 8, 5, 3, 1}) do
local glow = label("Glow" .. i, i)
local stroke = Instance.new("UIStroke")
stroke.Color = Color3.fromRGB(100 + i * 13, 122 + i * 14, 255)
stroke.Thickness = width
stroke.Transparency = 1
stroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Contextual
stroke.Parent = glow
strokes[#strokes + 1] = stroke
end
local title = label("Text", 12)
local gradient = Instance.new("UIGradient")
gradient.Color = ColorSequence.new({
ColorSequenceKeypoint.new(0, Color3.fromRGB(156, 217, 255)),
ColorSequenceKeypoint.new(0.5, Color3.fromRGB(255, 255, 255)),
ColorSequenceKeypoint.new(1, Color3.fromRGB(186, 164, 255)),
})
gradient.Parent = title
animate(shade, 1.6, {BackgroundTransparency = 0.05})
task.wait(0.6)
if closed then return end
animate(title, 1.8, {TextTransparency = 0})
animate(center, 2.4, {Position = UDim2.fromScale(0.5, 0.5)})
for i, stroke in ipairs(strokes) do
animate(stroke, 1.8, {Transparency = softGlow[i]})
end
task.wait(1.8)
if closed then return end
for _ = 1, 2 do
for i, stroke in ipairs(strokes) do
animate(stroke, 1.2, {Transparency = brightGlow[i]})
end
task.wait(1.2)
if closed then return end
for i, stroke in ipairs(strokes) do
animate(stroke, 1.2, {Transparency = softGlow[i]})
end
task.wait(1.2)
if closed then return end
end
animate(title, 1.4, {TextTransparency = 1})
animate(center, 1.8, {Position = UDim2.new(0.5, 0, 0.5, -6)})
for _, stroke in ipairs(strokes) do animate(stroke, 1.4, {Transparency = 1}) end
task.wait(0.8)
if closed then return end
animate(shade, 1.4, {BackgroundTransparency = 1})
task.wait(1.4)
end, function(err) return tostring(err) end)
if watchdog then pcall(task.cancel, watchdog) end
close()
return ok, message
end
return Intro
]====]
--maincheat
sources.Library = [====[
local requireModule = ...
local cloneref = (cloneref or clonereference or function(instance: any)
return instance
end)
local CoreGui: CoreGui = cloneref(game:GetService("CoreGui"))
local GuiService: GuiService = cloneref(game:GetService("GuiService"))
local Players: Players = cloneref(game:GetService("Players"))
local RunService: RunService = cloneref(game:GetService("RunService"))
local SoundService: SoundService = cloneref(game:GetService("SoundService"))
local UserInputService: UserInputService = cloneref(game:GetService("UserInputService"))
local TextService: TextService = cloneref(game:GetService("TextService"))
local Teams: Teams = cloneref(game:GetService("Teams"))
local TweenService: TweenService = cloneref(game:GetService("TweenService"))
local getgenv = getgenv or function()
return shared
end
local setclipboard = setclipboard or nil
local protectgui = protectgui or (syn and syn.protect_gui) or function() end
local gethui = gethui or function()
return CoreGui
end
local LocalPlayer = Players.LocalPlayer or Players.PlayerAdded:Wait()
local Mouse = cloneref(LocalPlayer:GetMouse())
local Labels = {}
local Buttons = {}
local Toggles = {}
local Options = {}
local Tooltips = {}
local BaseURL = "https://raw.githubusercontent.com/deividcomsono/Obsidian/fb0b7b22604664ab94bea7c67e2a14ab1a47ec27/"
local CustomImageManager = {}
local CustomImageManagerAssets = {
TransparencyTexture = {
RobloxId = 139785960036434,
Path = "Obsidian/assets/TransparencyTexture.png",
URL = BaseURL .. "assets/TransparencyTexture.png",
Id = nil,
},
SaturationMap = {
RobloxId = 4155801252,
Path = "Obsidian/assets/SaturationMap.png",
URL = BaseURL .. "assets/SaturationMap.png",
Id = nil,
},
LoadingIcon = {
RobloxId = 97544096941083,
Path = "Obsidian/assets/LoadingIcon.png",
URL = BaseURL .. "assets/LoadingIcon.png",
Id = nil,
},
CheckIcon = {
RobloxId = 97682394690683,
Path = "Obsidian/assets/CheckIcon.png",
URL = BaseURL .. "assets/CheckIcon.png",
Id = nil,
},
}
do
local function RecursiveCreatePath(Path: string, IsFile: boolean?)
if not isfolder or not makefolder then
return
end
local Segments = Path:split("/")
local TraversedPath = ""
if IsFile then
table.remove(Segments, #Segments)
end
for _, Segment in ipairs(Segments) do
if not isfolder(TraversedPath .. Segment) then
makefolder(TraversedPath .. Segment)
end
TraversedPath = TraversedPath .. Segment .. "/"
end
return TraversedPath
end
function CustomImageManager.AddAsset(
AssetName: string,
RobloxAssetId: number,
URL: string,
ForceRedownload: boolean?
)
if CustomImageManagerAssets[AssetName] ~= nil then
error(string.format("Asset %q already exists", AssetName))
end
assert(typeof(RobloxAssetId) == "number", "RobloxAssetId must be a number")
CustomImageManagerAssets[AssetName] = {
RobloxId = RobloxAssetId,
Path = string.format("Obsidian/custom_assets/%s", AssetName),
URL = URL,
Id = nil,
}
CustomImageManager.DownloadAsset(AssetName, ForceRedownload)
end
function CustomImageManager.GetAsset(AssetName: string)
if not CustomImageManagerAssets[AssetName] then
return nil
end
local AssetData = CustomImageManagerAssets[AssetName]
if AssetData.Id then
return AssetData.Id
end
local AssetID = string.format("rbxassetid://%s", AssetData.RobloxId)
if getcustomasset then
local Success, NewID = pcall(getcustomasset, AssetData.Path)
if Success and NewID then
AssetID = NewID
end
end
AssetData.Id = AssetID
return AssetID
end
function CustomImageManager.DownloadAsset(AssetName: string, ForceRedownload: boolean?)
if not getcustomasset or not writefile or not isfile then
return false, "missing functions"
end
local AssetData = CustomImageManagerAssets[AssetName]
RecursiveCreatePath(AssetData.Path, true)
if ForceRedownload ~= true and isfile(AssetData.Path) then
return true, nil
end
local success, errorMessage = pcall(function()
writefile(AssetData.Path, game:HttpGet(AssetData.URL))
end)
return success, errorMessage
end
for AssetName, _ in CustomImageManagerAssets do
CustomImageManager.DownloadAsset(AssetName)
end
end
local Library = {
LocalPlayer = LocalPlayer,
IsRobloxFocused = true,
DevicePlatform = nil,
IsMobile = false,
ScreenGui = nil,
Floats = nil,
Overlay = nil,
Window = nil,
WindowContainer = nil,
SearchText = "",
Searching = false,
GlobalSearch = false,
LastSearchTab = nil,
ActiveTab = nil,
PreviousTab = nil,
Tabs = {},
TabButtons = {},
DependencyBoxes = {},
KeybindFrame = nil,
KeybindContainer = nil,
KeybindToggles = {},
Notifications = {},
NotifySide = "Right",
NotifyTweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
Dialogues = {},
ActiveDialog = nil,
ActiveLoading = nil,
ContextMenus = {},
Corners = {},
SpecificCorners = {},
TweenInfo = TweenInfo.new(0.1, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
TabTransitionInfo = TweenInfo.new(0.22, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
TabSwipeOffset = 26,
TabSwipeFrom = "bottom",
WindowAnimationInfo = TweenInfo.new(0.35, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
DropdownTransitionInfo = TweenInfo.new(0.18, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
KeyPickerTransitionInfo = TweenInfo.new(0.15, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
GroupboxTweenInfo = TweenInfo.new(0.2, Enum.EasingStyle.Quad, Enum.EasingDirection.Out),
RotatingChevronTweenInfo = TweenInfo.new(0.3, Enum.EasingStyle.Back, Enum.EasingDirection.Out),
Animations = {
ToggleWindow = false,
TabSwitch = false,
Groupbox = false,
Dropdown = false,
KeyPicker = false
},
Toggled = false,
Unloaded = false,
Labels = Labels,
Buttons = Buttons,
Toggles = Toggles,
Options = Options,
ToggleKeybind = Enum.KeyCode.RightControl,
ShowToggleFrameInKeybinds = true,
NotifyOnError = false,
ShowCustomCursor = true,
ForceCheckbox = false,
CantDragForced = false,
DraggableElements = {},
PopOutSnapDistance = 80,
PopOutDragThreshold = 8,
PopOutHoldTime = 0.15,
Signals = {},
UnloadSignals = {},
OriginalMinSize = Vector2.new(480, 360),
MinSize = Vector2.new(480, 360),
DPIScale = 1,
CornerRadius = 4,
IsLightTheme = false,
Scheme = {
BackgroundColor = Color3.fromRGB(15, 15, 15),
MainColor = Color3.fromRGB(25, 25, 25),
AccentColor = Color3.fromRGB(125, 85, 255),
OutlineColor = Color3.fromRGB(40, 40, 40),
FontColor = Color3.new(1, 1, 1),
Font = Font.fromEnum(Enum.Font.Code),
RedColor = Color3.fromRGB(255, 50, 50),
DestructiveColor = Color3.fromRGB(220, 38, 38),
DarkColor = Color3.new(0, 0, 0),
WhiteColor = Color3.new(1, 1, 1),
BackgroundImage = ""
},
Registry = {},
Scales = {},
ScalesOffset = {},
OriginalMouseIconEnabled = UserInputService.MouseIconEnabled,
ShowCursorBinding = string.sub(tostring({}), 10),
ImageManager = CustomImageManager,
Notify = nil, Toggle = nil
}
if RunService:IsStudio() then
if UserInputService.TouchEnabled and not UserInputService.MouseEnabled then
Library.IsMobile = true
Library.OriginalMinSize = Vector2.new(480, 240)
else
Library.IsMobile = false
Library.OriginalMinSize = Vector2.new(480, 360)
end
else
pcall(function()
Library.DevicePlatform = UserInputService:GetPlatform()
end)
Library.IsMobile = (Library.DevicePlatform == Enum.Platform.Android or Library.DevicePlatform == Enum.Platform.IOS)
Library.OriginalMinSize = Library.IsMobile and Vector2.new(480, 240) or Vector2.new(480, 360)
end
local Templates = {
Frame = {
BorderSizePixel = 0,
},
ImageLabel = {
BackgroundTransparency = 1,
BorderSizePixel = 0,
},
ImageButton = {
AutoButtonColor = false,
BorderSizePixel = 0,
},
ScrollingFrame = {
BorderSizePixel = 0,
},
TextLabel = {
BorderSizePixel = 0,
FontFace = "Font",
RichText = true,
TextColor3 = "FontColor",
},
TextButton = {
AutoButtonColor = false,
BorderSizePixel = 0,
FontFace = "Font",
RichText = true,
TextColor3 = "FontColor",
},
TextBox = {
BorderSizePixel = 0,
FontFace = "Font",
PlaceholderColor3 = function()
local H, S, V = Library.Scheme.FontColor:ToHSV()
return Color3.fromHSV(H, S, V / 2)
end,
Text = "",
TextColor3 = "FontColor",
},
UIListLayout = {
SortOrder = Enum.SortOrder.LayoutOrder,
},
UIStroke = {
ApplyStrokeMode = Enum.ApplyStrokeMode.Border,
},
Window = {
Title = "No Title",
Footer = "No Footer",
Position = UDim2.fromOffset(6, 6),
Size = UDim2.fromOffset(720, 600),
IconSize = UDim2.fromOffset(30, 30),
AutoShow = true,
Center = true,
Resizable = true,
AlwaysOnTop = false,
Snapping = false,
SnapDistance = 28,
SnapMargin = 8,
SnapAvoidCoreGui = true,
SearchbarSize = UDim2.fromScale(1, 1),
GlobalSearch = false,
CornerRadius = 4,
NotifySide = "Right",
ShowCustomCursor = true,
Font = Enum.Font.Code,
ToggleKeybind = Enum.KeyCode.RightControl,
ShowMobileButtons = true,
MobileButtonsSide = "Left",
UnlockMouseWhileOpen = true,
EnableSidebarResize = false,
EnableCompacting = true,
DisableCompactingSnap = false,
SidebarCompacted = false,
MinContainerWidth = 256,
MinSidebarWidth = 128,
SidebarCompactWidth = 48,
SidebarCollapseThreshold = 0.5,
CompactWidthActivation = 128,
BackgroundImage = "",
Animations = {
ToggleWindow = false,
TabSwitch = false,
Groupbox = false,
Dropdown = false,
KeyPicker = false,
},
TabTransitionTime = 0.22,
TabSwipeOffset = 26,
TabSwipeFrom = "bottom",
TabButtonsStyle = {
Gap = 0,
Padding = 0,
CornerRadius = 0,
Indicator = false,
IndicatorWidth = 2,
IndicatorHeight = 20,
},
},
Groupbox = {
Side = 1,
Name = "Groupbox",
IconName = nil,
Description = nil,
Visible = true,
Collapsed = false,
DisableCollapsing = false,
PopOut = true,
MaxPopOutHeight = nil,
PopOutWidth = nil,
},
Tabbox = {
Side = 1,
Name = nil,
PopOut = true,
MaxPopOutHeight = nil,
PopOutWidth = nil,
},
Dialog = {
Title = "Dialog",
Description = "Description",
AutoDismiss = true,
OutsideClickDismiss = true,
FooterButtons = {}
},
Loading = {
Title = "mspaint",
Icon = 95816097006870,
IconSize = UDim2.fromOffset(30, 30),
LoadingIcon = CustomImageManager.GetAsset("LoadingIcon"),
LoadingIconColor = nil,
LoadingIconTweenTime = 1,
CurrentStep = 0,
TotalSteps = 10,
ShowSidebar = false,
AutoResizeHeight = false,
AlwaysOnTop = true,
WindowWidth = 450,
WindowHeight = 275,
ContentWidth = 450,
SidebarWidth = 250,
},
Toggle = {
Text = "Toggle",
Default = false,
Callback = function() end,
Changed = function() end,
Risky = false,
Disabled = false,
Visible = true,
},
Input = {
Text = "Input",
Default = "",
Finished = false,
Numeric = false,
ClearTextOnFocus = true,
ClearTextOnBlur = false,
Placeholder = "",
AllowEmpty = true,
EmptyReset = "---",
Callback = function() end,
Changed = function() end,
VerifyValue = nil,
Disabled = false,
Visible = true,
},
Slider = {
Text = "Slider",
Default = 0,
Min = 0,
Max = 100,
Rounding = 0,
Prefix = "",
Suffix = "",
Callback = function() end,
Changed = function() end,
Disabled = false,
Visible = true,
AllowRightClickInput = true
},
Dropdown = {
Values = {},
DisabledValues = {},
ValueImages = {},
Multi = false,
DragSelect = false,
MaxVisibleDropdownItems = 8,
KeepDisabledValuePosition = false,
Callback = function() end,
Changed = function() end,
Disabled = false,
Visible = true,
},
Viewport = {
Object = nil,
Camera = nil,
Clone = true,
AutoFocus = true,
Interactive = false,
Height = 200,
Visible = true,
},
Image = {
Image = "",
Transparency = 0,
BackgroundTransparency = 0,
Color = Color3.new(1, 1, 1),
RectOffset = Vector2.zero,
RectSize = Vector2.zero,
ScaleType = Enum.ScaleType.Fit,
Height = 200,
Visible = true,
},
Video = {
Video = "",
Looped = false,
Playing = false,
Volume = 1,
Height = 200,
Visible = true,
},
UIPassthrough = {
Instance = nil,
Height = 24,
Visible = true,
},
KeyPicker = {
Text = "KeyPicker",
Default = "None",
DefaultModifiers = {},
Blacklisted = {},
BlacklistedModifiers = {},
Whitelisted = {},
WhitelistedModifiers = {},
Mode = "Toggle",
Modes = { "Always", "Toggle", "Hold" },
SyncToggleState = false,
Callback = function() end,
ChangedCallback = function() end,
Changed = function() end,
Clicked = function() end,
},
ColorPicker = {
Default = Color3.new(1, 1, 1),
Resizable = true,
Callback = function() end,
Changed = function() end,
},
}
local Places = {
Bottom = { 0, 1 },
Right = { 1, 0 },
}
local Sizes = {
Left = { 0.5, 1 },
Right = { 0.5, 1 },
}
local SideIndex = {
left = 1,
right = 2,
}
local SchemeReplaceAlias = {
RedColor = "Red",
WhiteColor = "White",
DarkColor = "Dark"
}
local SchemeAlias = {
Red = "RedColor",
White = "WhiteColor",
Dark = "DarkColor"
}
local function GetSchemeValue(Index)
if not Index then
return nil
end
local ReplaceAliasIndex = SchemeReplaceAlias[Index]
if ReplaceAliasIndex and Library.Scheme[ReplaceAliasIndex] ~= nil then
Library.Scheme[Index] = Library.Scheme[ReplaceAliasIndex]
Library.Scheme[ReplaceAliasIndex] = nil
return Library.Scheme[Index]
end
local AliasIndex = SchemeAlias[Index]
if AliasIndex and Library.Scheme[AliasIndex] ~= nil then
warn(string.format("Scheme Value %q is deprecated, please use %q instead.", Index, AliasIndex))
return Library.Scheme[AliasIndex]
end
return Library.Scheme[Index]
end
local function WaitForEvent(Event, Timeout, Condition)
local Bindable = Instance.new("BindableEvent")
local Connection = Event:Once(function(...)
if not Condition or typeof(Condition) == "function" and Condition(...) then
Bindable:Fire(true)
else
Bindable:Fire(false)
end
end)
task.delay(Timeout, function()
Connection:Disconnect()
Bindable:Fire(false)
end)
local Result = Bindable.Event:Wait()
Bindable:Destroy()
return Result
end
local function IsMouseInput(Input: InputObject, IncludeM2: boolean?)
return Input.UserInputType == Enum.UserInputType.MouseButton1
or (IncludeM2 == true and Input.UserInputType == Enum.UserInputType.MouseButton2)
or Input.UserInputType == Enum.UserInputType.Touch
end
local function IsClickInput(Input: InputObject, IncludeM2: boolean?)
return IsMouseInput(Input, IncludeM2)
and Input.UserInputState == Enum.UserInputState.Begin
and Library.IsRobloxFocused
end
local function IsHoverInput(Input: InputObject)
return (Input.UserInputType == Enum.UserInputType.MouseMovement or Input.UserInputType == Enum.UserInputType.Touch)
and Input.UserInputState == Enum.UserInputState.Change
end
local function IsDragInput(Input: InputObject, IncludeM2: boolean?)
return IsMouseInput(Input, IncludeM2)
and (Input.UserInputState == Enum.UserInputState.Begin or Input.UserInputState == Enum.UserInputState.Change)
and Library.IsRobloxFocused
end
local function IsMouseClickInput(Input: InputObject)
return Input.UserInputType == Enum.UserInputType.MouseButton1 or
Input.UserInputType == Enum.UserInputType.MouseButton2 or
Input.UserInputType == Enum.UserInputType.MouseButton3
end
local function IsMovementInput(Input: InputObject)
return (Input.UserInputType == Enum.UserInputType.MouseMovement or Input.UserInputType == Enum.UserInputType.Touch)
and Library.IsRobloxFocused
end
local function GetTableSize(Table: { [any]: any })
local Size = 0
for _, _ in Table do
Size += 1
end
return Size
end
local function IsSequentialArray(Table: { [any]: any })
for Key in Table do
if typeof(Key) ~= "number" or Key < 1 or Key % 1 ~= 0 then
return false
end
end
return true
end
local function StopTween(Tween: TweenBase, Destroy: boolean?)
if not Tween then
return
end
if Tween.PlaybackState == Enum.PlaybackState.Playing then
Tween:Cancel()
end
if Destroy == true then
pcall(Tween.Destroy, Tween)
end
end
local function Trim(Text: string)
return Text:match("^%s*(.-)%s*$")
end
local function Round(Value, Rounding)
assert(Rounding >= 0, "Invalid rounding number.")
if Rounding == 0 then
return math.floor(Value)
end
return tonumber(string.format("%." .. Rounding .. "f", Value))
end
local function FuzzyScore(Text: string, Search: string): (boolean, number)
if Search == "" then
return true, 0
end
if Text == "" then
return false, 0
end
local ExactIdx = Text:find(Search, 1, true)
if ExactIdx then
local PrevChar = ExactIdx > 1 and Text:sub(ExactIdx - 1, ExactIdx - 1) or ""
local AtBoundary = ExactIdx == 1 or PrevChar:match("[%s%p_]") ~= nil
return true, 1e5 - ExactIdx + (AtBoundary and 500 or 0) + (Search:len() * 5)
end
local TextLen, SearchLen = Text:len(), Search:len()
if SearchLen > TextLen then
return false, 0
end
local SearchIdx = 1
local Score = 0
local RunLength = 0
local LastMatchIdx = 0
for TextIdx = 1, TextLen do
if SearchIdx > SearchLen then
break
end
if Text:sub(TextIdx, TextIdx) == Search:sub(SearchIdx, SearchIdx) then
local PrevChar = TextIdx > 1 and Text:sub(TextIdx - 1, TextIdx - 1) or ""
local AtBoundary = TextIdx == 1 or PrevChar:match("[%s%p_]") ~= nil
RunLength = (LastMatchIdx == TextIdx - 1) and (RunLength + 1) or 1
Score += 1 + (AtBoundary and 6 or 0) + math.min(RunLength - 1, 5) * 3
LastMatchIdx = TextIdx
SearchIdx += 1
end
end
if SearchIdx <= SearchLen then
return false, 0
end
Score -= (LastMatchIdx - SearchLen) * 0.05
return true, Score
end
local function NormalizeSearch(Search: string): string
return (Search:gsub("%s+", ""))
end
local function TryFuzzyMatch(Text: any, Search: string): (boolean, number)
if typeof(Text) ~= "string" or Text == "" then
return false, 0
end
return FuzzyScore(Text:lower(), Search)
end
local function FuzzyMatchScore(Text: any, Search: string): number
if typeof(Text) ~= "string" or Text == "" then
return 0
end
local Normalized = NormalizeSearch(Text:lower())
local Matched, Score = FuzzyScore(Normalized, Search)
if not Matched then
return 0
end
if Normalized == Search then
Score += 1000
end
return Score
end
local function MatchesSearch(ElementInfo, Search: string, ForceMatch: boolean?): boolean
if not ElementInfo then
return false
end
if ForceMatch then
return true
end
if TryFuzzyMatch(ElementInfo.Text, Search) then
return true
end
if TryFuzzyMatch(ElementInfo.Tooltip, Search) then
return true
end
if TryFuzzyMatch(ElementInfo.DisabledTooltip, Search) then
return true
end
if typeof(ElementInfo.Values) == "table" then
local Checked = 0
for Key, Value in ElementInfo.Values do
Checked += 1
if Checked > 200 then
break
end
if TryFuzzyMatch(Value, Search) or (typeof(Value) ~= "string" and TryFuzzyMatch(tostring(Value), Search)) then
return true
end
if typeof(Key) == "string" and TryFuzzyMatch(Key, Search) then
return true
end
end
end
return false
end
local function GetPlayers(ExcludeLocalPlayer: boolean?)
local PlayerList = Players:GetPlayers()
if ExcludeLocalPlayer then
local Idx = table.find(PlayerList, LocalPlayer)
if Idx then
table.remove(PlayerList, Idx)
end
end
table.sort(PlayerList, function(Player1, Player2)
return Player1.Name:lower() < Player2.Name:lower()
end)
return PlayerList
end
local function GetTeams()
local TeamList = Teams:GetTeams()
table.sort(TeamList, function(Team1, Team2)
return Team1.Name:lower() < Team2.Name:lower()
end)
return TeamList
end
function Library:UpdateDependencyBoxes()
for _, Depbox in Library.DependencyBoxes do
Depbox:Update(true)
end
if Library.Searching then
Library:UpdateSearch(Library.SearchText)
end
end
function Library:UpdateAddons(Parent)
if not Parent or not Parent.Addons then
return
end
for _, Addon in Parent.Addons do
Addon:Update()
end
end
local function CheckDepbox(Box, Search, ForceVisible: boolean?)
local VisibleElements = 0
local BestScore = 0
for _, ElementInfo in Box.Elements do
if ElementInfo.Type == "Divider" then
ElementInfo.Holder.Visible = false
continue
elseif ElementInfo.SubButton then
local Visible = false
if MatchesSearch(ElementInfo, Search, ForceVisible) and ElementInfo.Visible then
Visible = true
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.Text, Search))
else
ElementInfo.Base.Visible = false
end
if MatchesSearch(ElementInfo.SubButton, Search, ForceVisible) and ElementInfo.SubButton.Visible then
Visible = true
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.SubButton.Text, Search))
else
ElementInfo.SubButton.Base.Visible = false
end
ElementInfo.Holder.Visible = Visible
if Visible then
VisibleElements += 1
end
continue
end
if ElementInfo.Text and MatchesSearch(ElementInfo, Search, ForceVisible) and ElementInfo.Visible then
ElementInfo.Holder.Visible = true
VisibleElements += 1
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.Text, Search))
else
ElementInfo.Holder.Visible = false
end
end
for _, Depbox in Box.DependencyBoxes do
if not Depbox.Visible then
continue
end
local DepVisible, DepScore = CheckDepbox(Depbox, Search, ForceVisible)
VisibleElements += DepVisible
if DepScore > BestScore then
BestScore = DepScore
end
end
Box.Holder.Visible = VisibleElements > 0
return VisibleElements, BestScore
end
local function RestoreDepbox(Box)
for _, ElementInfo in Box.Elements do
ElementInfo.Holder.Visible = ElementInfo.Visible ~= false
if ElementInfo.SubButton then
ElementInfo.Base.Visible = ElementInfo.Visible
ElementInfo.SubButton.Base.Visible = ElementInfo.SubButton.Visible
end
end
Box:Resize()
Box.Holder.Visible = true
for _, Depbox in Box.DependencyBoxes do
if not Depbox.Visible then
continue
end
RestoreDepbox(Depbox)
end
end
function SyncPopOutVisibility(Box: any)
if not Box.PopOutFloat then
return
end
Box.PopOutFloat.Visible = Box.BoxHolder.Visible ~= false and Box.Visible ~= false
end
local function DimPopOutClone(Root: GuiObject)
for _, Descendant in Root:QueryDescendants("TextLabel, TextButton, TextBox") do
Descendant.TextTransparency = math.max(Descendant.TextTransparency, 0.45)
end
for _, Descendant in Root:QueryDescendants("ImageLabel, ImageButton") do
Descendant.ImageTransparency = math.max(Descendant.ImageTransparency, 0.45)
end
for _, Descendant in Root:QueryDescendants("GuiButton") do
Descendant.Active = false
Descendant.AutoButtonColor = false
end
end
local function IsScreenPointOutsideMain(Point: Vector2): boolean
local MainFrame = Library.Window and Library.Window.MainFrame
if not MainFrame or not Library.Toggled or not MainFrame.Visible then
return true
end
return not Library:MouseIsOverFrame(MainFrame, Point)
end
local function GetTopFloatAt(Point: Vector2): GuiObject?
local Best: GuiObject? = nil
local BestOrder = -math.huge
local Floats = Library.Floats
for _, Surface in Library.DraggableElements do
if not Surface or not Surface.Parent or not Surface.Visible then
continue
end
if Floats and Surface.Parent ~= Floats then
continue
end
if not Library:MouseIsOverFrame(Surface, Point) then
continue
end
local SiblingIndex = tonumber(select(2, pcall(function() return Surface:GetSiblingIndex() end))) or 0
local Order = Surface.ZIndex * 100000 + SiblingIndex
if Order >= BestOrder then
BestOrder = Order
Best = Surface
end
end
return Best
end
local function GetPopOutBodyMaxHeight(Box: any, Reserved: number): number
local Float = Box.PopOutFloat
local ScreenGui = Library.ScreenGui
if not Float or not ScreenGui then
return math.huge
end
local Gap = 12 * Library.DPIScale
local MaxBottom = ScreenGui.AbsolutePosition.Y + ScreenGui.AbsoluteSize.Y - Gap
local Available = math.min(MaxBottom - Float.AbsolutePosition.Y, ScreenGui.AbsoluteSize.Y * 0.9)
local ScreenMax = math.max(0, Available / Library.DPIScale - Reserved)
local CustomMax = Box.PopOutMaxHeight
if typeof(CustomMax) == "number" then
return math.min(ScreenMax, math.max(0, CustomMax))
end
return ScreenMax
end
local function ApplySearchToTab(Tab, Search)
if not Tab then
return false, 0
end
local HasVisible = false
local BestScore = 0
local TabMatches = TryFuzzyMatch(Tab.Name, Search) or TryFuzzyMatch(Tab.Description, Search)
BestScore = math.max(BestScore, FuzzyMatchScore(Tab.Name, Search), FuzzyMatchScore(Tab.Description, Search))
for _, Groupbox in Tab.Groupboxes do
if Groupbox.Visible == false then
continue
end
local GroupboxMatches = TabMatches or (TryFuzzyMatch(Groupbox.Name, Search) or TryFuzzyMatch(Groupbox.Description, Search))
BestScore = math.max(BestScore, FuzzyMatchScore(Groupbox.Name, Search), FuzzyMatchScore(Groupbox.Description, Search))
local VisibleElements = 0
for _, ElementInfo in Groupbox.Elements do
if ElementInfo.Type == "Divider" then
ElementInfo.Holder.Visible = false
continue
elseif ElementInfo.SubButton then
local Visible = false
if MatchesSearch(ElementInfo, Search, GroupboxMatches) and ElementInfo.Visible then
Visible = true
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.Text, Search))
else
ElementInfo.Base.Visible = false
end
if MatchesSearch(ElementInfo.SubButton, Search, GroupboxMatches) and ElementInfo.SubButton.Visible then
Visible = true
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.SubButton.Text, Search))
else
ElementInfo.SubButton.Base.Visible = false
end
ElementInfo.Holder.Visible = Visible
if Visible then
VisibleElements += 1
end
continue
end
if ElementInfo.Text and MatchesSearch(ElementInfo, Search, GroupboxMatches) and ElementInfo.Visible then
ElementInfo.Holder.Visible = true
VisibleElements += 1
BestScore = math.max(BestScore, FuzzyMatchScore(ElementInfo.Text, Search))
else
ElementInfo.Holder.Visible = false
end
end
for _, Depbox in Groupbox.DependencyBoxes do
if not Depbox.Visible then
continue
end
local DepVisible, DepScore = CheckDepbox(Depbox, Search, GroupboxMatches)
VisibleElements += DepVisible
if DepScore > BestScore then
BestScore = DepScore
end
end
if VisibleElements > 0 then
Groupbox:Resize()
HasVisible = true
end
Groupbox.BoxHolder.Visible = VisibleElements > 0
SyncPopOutVisibility(Groupbox)
end
for _, Tabbox in Tab.Tabboxes do
local VisibleTabs = 0
local VisibleElements = {}
local SubTabScores = {}
for _, SubTab in Tabbox.Tabs do
VisibleElements[SubTab] = 0
local SubTabMatches = TabMatches or TryFuzzyMatch(SubTab.Name, Search)
local SubScore = FuzzyMatchScore(SubTab.Name, Search)
BestScore = math.max(BestScore, SubScore)
for _, ElementInfo in SubTab.Elements do
if ElementInfo.Type == "Divider" then
ElementInfo.Holder.Visible = false
continue
elseif ElementInfo.SubButton then
local Visible = false
if MatchesSearch(ElementInfo, Search, SubTabMatches) and ElementInfo.Visible then
Visible = true
local ElementScore = FuzzyMatchScore(ElementInfo.Text, Search)
SubScore = math.max(SubScore, ElementScore)
BestScore = math.max(BestScore, ElementScore)
else
ElementInfo.Base.Visible = false
end
if MatchesSearch(ElementInfo.SubButton, Search, SubTabMatches) and ElementInfo.SubButton.Visible then
Visible = true
local ElementScore = FuzzyMatchScore(ElementInfo.SubButton.Text, Search)
SubScore = math.max(SubScore, ElementScore)
BestScore = math.max(BestScore, ElementScore)
else
ElementInfo.SubButton.Base.Visible = false
end
ElementInfo.Holder.Visible = Visible
if Visible then
VisibleElements[SubTab] += 1
end
continue
end
if ElementInfo.Text and MatchesSearch(ElementInfo, Search, SubTabMatches) and ElementInfo.Visible then
ElementInfo.Holder.Visible = true
VisibleElements[SubTab] += 1
local ElementScore = FuzzyMatchScore(ElementInfo.Text, Search)
SubScore = math.max(SubScore, ElementScore)
BestScore = math.max(BestScore, ElementScore)
else
ElementInfo.Holder.Visible = false
end
end
for _, Depbox in SubTab.DependencyBoxes do
if not Depbox.Visible then
continue
end
local DepVisible, DepScore = CheckDepbox(Depbox, Search, SubTabMatches)
VisibleElements[SubTab] += DepVisible
SubScore = math.max(SubScore, DepScore)
BestScore = math.max(BestScore, DepScore)
end
SubTabScores[SubTab] = SubScore
end
local BestSubTab = nil
local BestSubScore = -1
for SubTab, Visible in VisibleElements do
SubTab.ButtonHolder.Visible = Visible > 0
if Visible > 0 then
VisibleTabs += 1
HasVisible = true
local SubScore = SubTabScores[SubTab] or 0
if SubScore > BestSubScore then
BestSubScore = SubScore
BestSubTab = SubTab
end
end
end
local ActiveSubTab = Tabbox.ActiveTab
local ActiveSubVisible = ActiveSubTab and (VisibleElements[ActiveSubTab] or 0) > 0
local ActiveSubScore = ActiveSubTab and (SubTabScores[ActiveSubTab] or -1) or -1
if ActiveSubVisible and ActiveSubScore >= BestSubScore then
ActiveSubTab:Resize()
elseif BestSubTab then
BestSubTab:Show()
end
Tabbox.BoxHolder.Visible = VisibleTabs > 0
SyncPopOutVisibility(Tabbox)
end
return HasVisible, BestScore
end
local function ResetTab(Tab)
if not Tab then
return
end
for _, Groupbox in Tab.Groupboxes do
for _, ElementInfo in Groupbox.Elements do
ElementInfo.Holder.Visible = ElementInfo.Visible ~= false
if ElementInfo.SubButton then
ElementInfo.Base.Visible = ElementInfo.Visible
ElementInfo.SubButton.Base.Visible = ElementInfo.SubButton.Visible
end
end
for _, Depbox in Groupbox.DependencyBoxes do
if not Depbox.Visible then
continue
end
RestoreDepbox(Depbox)
end
Groupbox:Resize()
Groupbox.BoxHolder.Visible = Groupbox.Visible ~= false
SyncPopOutVisibility(Groupbox)
end
for _, Tabbox in Tab.Tabboxes do
for _, SubTab in Tabbox.Tabs do
for _, ElementInfo in SubTab.Elements do
ElementInfo.Holder.Visible = ElementInfo.Visible ~= false
if ElementInfo.SubButton then
ElementInfo.Base.Visible = ElementInfo.Visible
ElementInfo.SubButton.Base.Visible = ElementInfo.SubButton.Visible
end
end
for _, Depbox in SubTab.DependencyBoxes do
if not Depbox.Visible then
continue
end
RestoreDepbox(Depbox)
end
SubTab.ButtonHolder.Visible = true
end
if Tabbox.ActiveTab then
Tabbox.ActiveTab:Resize()
end
Tabbox.BoxHolder.Visible = true
SyncPopOutVisibility(Tabbox)
end
end
function Library:UpdateSearch(SearchText)
Library.SearchText = SearchText
local TabsToSearch = {}
for _, Tab in Library.Tabs do
if typeof(Tab) == "table" and not Tab.IsKeyTab then
table.insert(TabsToSearch, Tab)
end
end
for _, Tab in TabsToSearch do
ResetTab(Tab)
end
local Search = NormalizeSearch(SearchText:lower())
if Trim(Search) == "" then
Library.Searching = false
Library.LastSearchTab = nil
return
end
if not Library.GlobalSearch and Library.ActiveTab and Library.ActiveTab.IsKeyTab then
Library.Searching = false
Library.LastSearchTab = nil
return
end
Library.Searching = true
local BestTab = nil
local BestScore = -1
local ActiveScore = -1
local ActiveHasVisible = false
for _, Tab in TabsToSearch do
local HasVisible, Score = ApplySearchToTab(Tab, Search)
if not HasVisible then
continue
end
if Tab == Library.ActiveTab then
ActiveHasVisible = true
ActiveScore = Score
end
if Score > BestScore then
BestScore = Score
BestTab = Tab
end
end
if not Library.GlobalSearch then
for _, Tab in TabsToSearch do
if Tab ~= BestTab then
ResetTab(Tab)
end
end
end
local StayOnActive = ActiveHasVisible and ActiveScore >= BestScore
if StayOnActive and Library.ActiveTab then
Library.ActiveTab:RefreshSides()
elseif BestTab then
local SearchMarker = SearchText
task.defer(function()
if Library.SearchText ~= SearchMarker then
return
end
if Library.ActiveTab ~= BestTab then
BestTab:Show()
elseif Library.ActiveTab then
Library.ActiveTab:RefreshSides()
end
end)
end
Library.LastSearchTab = nil
end
function Library:AddToRegistry(Instance, Properties)
Library.Registry[Instance] = Properties
end
function Library:RemoveFromRegistry(Instance)
Library.Registry[Instance] = nil
end
function Library:UpdateColorsUsingRegistry()
for Instance, Properties in Library.Registry do
for Property, Index in Properties do
local SchemeValue = GetSchemeValue(Index)
if SchemeValue or typeof(Index) == "function" then
Instance[Property] = SchemeValue or Index()
end
end
end
end
function Library:SetDPIScale(DPIScale: number)
Library.DPIScale = DPIScale / 100
Library.MinSize = Library.OriginalMinSize * Library.DPIScale
for _, UIScale in Library.Scales do
UIScale.Scale = Library.DPIScale - (tonumber(Library.ScalesOffset[UIScale]) or 0)
end
for _, Option in Options do
if Option.Type == "Dropdown" then
Option:RecalculateListSize()
Option:RefreshPool()
end
end
for _, Notification in Library.Notifications do
Notification:Resize()
end
end
function Library:GiveSignal(Connection: RBXScriptConnection | RBXScriptSignal)
local ConnectionType = typeof(Connection)
if Connection and (ConnectionType == "RBXScriptConnection" or ConnectionType == "RBXScriptSignal") then
table.insert(Library.Signals, Connection)
end
return Connection
end
function IsValidCustomIcon(Icon: string)
return typeof(Icon) == "string" and (Icon:match("^rbxasset://textures/") or Icon:match("roblox%.com/asset/%?id=") or Icon:match("rbxthumb://type="))
end
local function IsCustomAssetIcon(Icon: string, IncludeAssetId: boolean)
return typeof(Icon) == "string" and (Icon:match("^content://") or (Icon:match("^rbxasset://%x+/") or Icon:match("^rbxasset://[^/]+/")) or (IncludeAssetId == true and Icon:match("^rbxassetid://")))
end
type Icon = {
Url: string,
Id: number,
IconName: string,
ImageRectOffset: Vector2,
ImageRectSize: Vector2,
}
type IconModule = {
Icons: { string },
GetAsset: (Name: string) -> Icon?,
}
local FetchIcons = false
local Icons: IconModule | nil = nil
function Library:GetIcon(IconName: string)
if not FetchIcons or not Icons then
return
end
local Success, Icon = pcall(Icons.GetAsset, IconName)
if not Success then
return
end
return Icon
end
function Library:GetCustomIcon(IconName: string): any
if not IconName then
return nil
end
if tonumber(IconName) then
IconName = string.format("rbxassetid://%s", tostring(IconName))
end
if IsCustomAssetIcon(IconName, true) then
return {
Url = IconName,
ImageRectOffset = Vector2.zero,
ImageRectSize = Vector2.zero,
}
elseif IsValidCustomIcon(IconName) then
return {
Url = IconName,
ImageRectOffset = Vector2.zero,
ImageRectSize = Vector2.zero,
Custom = true,
}
end
local LucideIcon = Library:GetIcon(IconName)
if LucideIcon then
return LucideIcon
end
return nil
end
function Library:ApplyLucideIcon(ImageGui: any, Icon: any, Rotation: number?)
if not ImageGui or not Icon then
return
end
if not (ImageGui:IsA("ImageLabel") or ImageGui:IsA("ImageButton")) then
return
end
ImageGui.Image = Icon.Url or ImageGui.Image
ImageGui.ImageRectOffset = Icon.ImageRectOffset or ImageGui.ImageRectOffset
ImageGui.ImageRectSize = Icon.ImageRectSize or ImageGui.ImageRectSize
ImageGui.Rotation = Rotation or ImageGui.Rotation
end
function Library:Validate(Table: { [string]: any }, Template: { [string]: any }): { [string]: any }
if typeof(Table) ~= "table" then
return Template
end
for k, v in Template do
if typeof(k) == "number" then
continue
end
if typeof(v) == "table" then
Table[k] = Library:Validate(Table[k], v)
elseif Table[k] == nil then
Table[k] = v
end
end
return Table
end
local function FillInstance(Table: { [string]: any }, Instance: GuiObject)
local ThemeProperties = Library.Registry[Instance] or {}
for key, value in Table do
if key ~= "Text" then
local SchemeValue = GetSchemeValue(value)
if SchemeValue or typeof(value) == "function" then
ThemeProperties[key] = value
value = SchemeValue or value()
else
ThemeProperties[key] = nil
end
end
Instance[key] = value
end
if GetTableSize(ThemeProperties) > 0 then
Library.Registry[Instance] = ThemeProperties
end
end
local function New(ClassName: string, Properties: { [string]: any }): any
local Instance = Instance.new(ClassName)
if Templates[ClassName] then
FillInstance(Templates[ClassName], Instance)
end
FillInstance(Properties, Instance)
if Properties["Parent"] and not Properties["ZIndex"] then
pcall(function()
Instance.ZIndex = Properties.Parent.ZIndex
end)
end
return Instance
end
local function SafeParentUI(Instance: Instance, Parent: Instance | () -> Instance)
local success, _error = pcall(function()
if not Parent then
Parent = CoreGui
end
local DestinationParent
if typeof(Parent) == "function" then
DestinationParent = Parent()
else
DestinationParent = Parent
end
Instance.Parent = DestinationParent
end)
if not (success and Instance.Parent) then
Instance.Parent = Library.LocalPlayer:WaitForChild("PlayerGui", math.huge)
end
end
local function ParentUI(UI: Instance, SkipHiddenUI: boolean?)
if SkipHiddenUI then
SafeParentUI(UI, CoreGui)
return
end
pcall(protectgui, UI)
SafeParentUI(UI, gethui)
end
local function SetAlwaysOnTop(Gui: ScreenGui, Enabled: boolean)
if not Gui then
return
end
pcall(function()
if sethiddenproperty then
sethiddenproperty(Gui, "OnTopOfCoreBlur", Enabled)
elseif setscriptable then
setscriptable(Gui, "OnTopOfCoreBlur", true)
Gui.OnTopOfCoreBlur = Enabled
setscriptable(Gui, "OnTopOfCoreBlur", false)
end
end)
end
local ScreenGui = New("ScreenGui", {
Name = "Obsidian",
DisplayOrder = 998,
ResetOnSpawn = false,
ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
})
ParentUI(ScreenGui)
Library.ScreenGui = ScreenGui
ScreenGui.DescendantRemoving:Connect(function(Instance)
task.defer(function()
if Instance.Parent and Instance:IsDescendantOf(ScreenGui) then
return
end
Library:RemoveFromRegistry(Instance)
end)
end)
local ModalElement = New("TextButton", {
BackgroundTransparency = 1,
Modal = false,
Size = UDim2.fromScale(0, 0),
AnchorPoint = Vector2.zero,
Text = "",
ZIndex = -999,
Parent = ScreenGui,
})
local Floats = New("Frame", {
BackgroundTransparency = 1,
Size = UDim2.fromScale(1, 1),
ZIndex = 10,
Active = false,
Parent = ScreenGui,
})
local Overlay = New("Frame", {
BackgroundTransparency = 1,
Size = UDim2.fromScale(1, 1),
ZIndex = 20,
Active = false,
Parent = ScreenGui,
})
Library.Floats = Floats
Library.Overlay = Overlay
local Cursor
local CursorCross
local InnerCross = {}
local CursorCustomImage
do
Cursor = New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Size = UDim2.fromOffset(1, 1),
Visible = false,
ZIndex = 11000,
Parent = ScreenGui,
})
CursorCross = New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.fromOffset(11, 11),
Parent = Cursor,
})
New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = "DarkColor",
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.new(1, 0, 0, 3),
ZIndex = 1,
Parent = CursorCross,
})
table.insert(InnerCross, New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = "WhiteColor",
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.new(1, -2, 0, 1),
ZIndex = 2,
Parent = CursorCross,
}))
New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = "DarkColor",
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.new(0, 3, 1, 0),
ZIndex = 1,
Parent = CursorCross,
})
table.insert(InnerCross, New("Frame", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundColor3 = "WhiteColor",
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.new(0, 1, 1, -2),
ZIndex = 2,
Parent = CursorCross,
}))
CursorCustomImage = New("ImageLabel", {
AnchorPoint = Vector2.new(0.5, 0.5),
BackgroundTransparency = 1,
Position = UDim2.fromScale(0.5, 0.5),
Size = UDim2.fromOffset(20, 20),
ZIndex = 3,
Visible = false,
Parent = Cursor,
})
end
local function RestoreMouseIcon()
pcall(function()
RunService:UnbindFromRenderStep(Library.ShowCursorBinding)
RunService.RenderStepped:Wait()
end)
UserInputService.MouseIconEnabled = Library.OriginalMouseIconEnabled
if Cursor then Cursor.Visible = false end
end
local NotificationArea
local NotifyOrder = {}
do
NotificationArea = New("Frame", {
AnchorPoint = Vector2.new(1, 0),
BackgroundTransparency = 1,
Position = UDim2.new(1, -6, 0, 6),
Size = UDim2.new(0, 300, 1, -6),
ZIndex = 200,
Parent = ScreenGui,
})
table.insert(
Library.Scales,
New("UIScale", {
Parent = NotificationArea,
})
)
end
local CheckIcon, ArrowIcon, ResizeIcon, KeyIcon, MoveIcon, PopOutIcon, CloseIcon
function Library:SetIconModule(module: IconModule)
FetchIcons = true
Icons = module
CheckIcon = Library:GetIcon("check")
ArrowIcon = Library:GetIcon("chevron-up")
ResizeIcon = Library:GetIcon("move-diagonal-2")
KeyIcon = Library:GetIcon("key")
MoveIcon = Library:GetIcon("move")
PopOutIcon = Library:GetIcon("square-arrow-down-left")
CloseIcon = Library:GetIcon("x")
end
local OnlineFetchIcons, OnlineIcons = pcall(function() return requireModule("Icons") end)
if OnlineFetchIcons and OnlineIcons then
Library:SetIconModule(OnlineIcons)
end
Library.Cursor = {}
function Library.Cursor:ResetCross()
for _, Inner in InnerCross do
Library.Registry[Inner].BackgroundColor3 = "WhiteColor"
Inner.BackgroundColor3 = Library.Scheme.WhiteColor
end
end
function Library.Cursor:ResetIcon()
CursorCross.Visible = true
CursorCustomImage.Visible = false
CursorCustomImage.ImageColor3 = Color3.new(1, 1, 1)
CursorCustomImage.Size = UDim2.fromOffset(20, 20)
end
function Library.Cursor:ResetCursor()
Library.Cursor:ResetCross()
Library.Cursor:ResetIcon()
end
function Library.Cursor:ChangeCrossColor(Color: Color3)
assert(typeof(Color) == "Color3", "Color3 expected.")
for _, Inner in InnerCross do
Inner.BackgroundColor3 = Color
Library.Registry[Inner].BackgroundColor3 = nil
end
end
function Library.Cursor:ChangeIcon(ImageId: string)
if not ImageId or ImageId == "" then
Library.Cursor:ResetIcon()
return
end
local Icon = Library:GetCustomIcon(ImageId)
assert(Icon, "Image must be a valid Roblox asset or a valid URL or a valid lucide icon.")
CursorCross.Visible = false
CursorCustomImage.Visible = true
Library:ApplyLucideIcon(CursorCustomImage, Icon)
end
function Library.Cursor:ChangeIconColor(Color: Color3)
assert(typeof(Color) == "Color3", "Color3 expected.")
CursorCustomImage.ImageColor3 = Color
end
function Library.Cursor:ChangeIconSize(Size: UDim2)
assert(typeof(Size) == "UDim2", "UDim2 expected.")
CursorCustomImage.Size = Size
end
function Library:ChangeCursorCrossColor(Color: Color3)
warn("Obsidian:ChangeCursorCrossColor is deprecated, please use Obsidian.Cursor:ChangeCrossColor instead.")
Library.Cursor:ChangeCrossColor(Color)
end
function Library:ResetCursorCross()
warn("Obsidian:ResetCursorCross is deprecated, please use Obsidian.Cursor:ResetCross instead.")
Library.Cursor:ResetCross()
end
function Library:ChangeCursorIcon(ImageId: string)
warn("Obsidian:ChangeCursorIcon is deprecated, please use Obsidian.Cursor:ChangeIcon instead.")
Library.Cursor:ChangeIcon(ImageId)
end
function Library:ChangeCursorIconColor(Color: Color3)
warn("Obsidian:ChangeCursorIconColor is deprecated, please use Obsidian.Cursor:ChangeIconColor instead.")
Library.Cursor:ChangeIconColor(Color)
end
function Library:ChangeCursorIconSize(Size: UDim2)
warn("Obsidian:ChangeCursorIconSize is deprecated, please use Obsidian.Cursor:ChangeIconSize instead.")
Library.Cursor:ChangeIconSize(Size)
end
function Library:ResetCursorIcon()
warn("Obsidian:ResetCursorIcon is deprecated, please use Obsidian.Cursor:ResetIcon instead.")
Library.Cursor:ResetIcon()
end
function Library:GetBetterColor(Color: Color3, Add: number): Color3
Add = Add * (Library.IsLightTheme and -4 or 2)
return Color3.fromRGB(
math.clamp(Color.R * 255 + Add, 0, 255),
math.clamp(Color.G * 255 + Add, 0, 255),
math.clamp(Color.B * 255 + Add, 0, 255)
)
end
function Library:GetLighterColor(Color: Color3): Color3
local H, S, V = Color:ToHSV()
return Color3.fromHSV(H, math.max(0, S - 0.1), math.min(1, V + 0.1))
end
function Library:GetDarkerColor(Color: Color3): Color3
local H, S, V = Color:ToHSV()
return Color3.fromHSV(H, S, V / 2)
end
function Library:GetSwatchBorder(Color: Color3): Color3
if typeof(Color) ~= "Color3" then
return Color3.fromRGB(190, 190, 190)
end
local Brightness = (Color.R + Color.G + Color.B) / 3
if Brightness < 0.35 then
return Color3.fromRGB(190, 190, 190)
end
return Library:GetDarkerColor(Color)
end
function Library:GetKeyString(KeyCode: Enum.KeyCode)
if KeyCode.EnumType == Enum.KeyCode and KeyCode.Value > 33 and KeyCode.Value < 127 then
return string.char(KeyCode.Value)
end
return KeyCode.Name
end
function Library:GetTextBounds(Text: string, Font: Font, Size: number, Width: number?): (number, number)
local Scale = Library.DPIScale
local Params = Instance.new("GetTextBoundsParams")
Params.Text = Text
Params.RichText = true
Params.Font = Font
Params.Size = Size * Scale
if Width then
Params.Width = Width * Scale
else
Params.Width = workspace.CurrentCamera.ViewportSize.X - 32
end
local Bounds = TextService:GetTextBoundsAsync(Params)
return math.ceil(Bounds.X / Scale), math.ceil(Bounds.Y / Scale)
end
function Library:MouseIsOverFrame(Frame: GuiObject, Mouse: Vector2): boolean
local AbsPos, AbsSize = Frame.AbsolutePosition, Frame.AbsoluteSize
return Mouse.X >= AbsPos.X
and Mouse.X <= AbsPos.X + AbsSize.X
and Mouse.Y >= AbsPos.Y
and Mouse.Y <= AbsPos.Y + AbsSize.Y
end
function Library:IsInsideFrame(ParentFrame: GuiObject, Frame: GuiObject)
local GuiPos = Frame.AbsolutePosition
local GuiSize = Frame.AbsoluteSize
local FramePos = ParentFrame.AbsolutePosition
local FrameSize = ParentFrame.AbsoluteSize
return GuiPos.X >= FramePos.X
and GuiPos.X + GuiSize.X <= FramePos.X + FrameSize.X
and GuiPos.Y >= FramePos.Y
and GuiPos.Y + GuiSize.Y <= FramePos.Y + FrameSize.Y
end
function Library:SafeCallback(Func: (...any) -> ...any, ...: any)
if not (Func and typeof(Func) == "function") then
return
end
local Result = table.pack(xpcall(Func, function(Error)
task.defer(error, debug.traceback(Error, 2))
if Library.NotifyOnError and Library.Notify then
Library:Notify(Error)
end
return Error
end, ...))
if not Result[1] then
return nil
end
return table.unpack(Result, 2, Result.n)
end
function GetOverlappingDraggable(UI: GuiObject, TargetPos: Vector2?)
local Pos1 = TargetPos or UI.AbsolutePosition
local Size1 = UI.AbsoluteSize
for _, Other in ipairs(Library.DraggableElements) do
if Other == UI or not Other.Visible or not Other.Parent then
continue
end
local Pos2 = Other.AbsolutePosition
local Size2 = Other.AbsoluteSize
if Pos1.X < Pos2.X + Size2.X and
Pos1.X + Size1.X > Pos2.X and
Pos1.Y < Pos2.Y + Size2.Y and
Pos1.Y + Size1.Y > Pos2.Y then
return Other
end
end
return nil
end
function GetNonOverlappingPosition(UI: GuiObject, StartPos: UDim2?)
local ScreenSize = (workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize or Vector2.new(1920, 1080)) - Vector2.new(100, 100)
local Start = StartPos and Vector2.new(StartPos.X.Offset, StartPos.Y.Offset) or Vector2.new(6, 6)
local Padding = 6
local CurrentX = Start.X
local CurrentY = Start.Y
local Size = UI.AbsoluteSize
if Size.X == 0 and Size.Y == 0 then
RunService.RenderStepped:Wait()
Size = UI.AbsoluteSize
end
if Size.X == 0 then Size = Vector2.new(150, 40) end
local MaxXInColumn = Size.X
while true do
local Obstacle = GetOverlappingDraggable(UI, Vector2.new(CurrentX, CurrentY))
if not Obstacle then
break
end
if Obstacle.AbsoluteSize.X > MaxXInColumn then
MaxXInColumn = Obstacle.AbsoluteSize.X
end
local NextY = Obstacle.AbsolutePosition.Y + Obstacle.AbsoluteSize.Y + Padding
if NextY + Size.Y > ScreenSize.Y - Padding then
local NextX = CurrentX + MaxXInColumn + Padding
if NextX + Size.X > ScreenSize.X - Padding then
break
end
CurrentY = Start.Y
CurrentX = NextX
MaxXInColumn = Size.X
else
CurrentY = NextY
end
end
return UDim2.fromOffset(CurrentX, CurrentY)
end
function PositionDraggable(UI: GuiObject, StartPos: UDim2?)
UI.Position = GetNonOverlappingPosition(UI, StartPos)
end
local function GetCoreGuiInset(): (Vector2, Vector2)
local Success, TopLeft, BottomRight = pcall(function()
return GuiService:GetGuiInset()
end)
if Success and TopLeft and BottomRight then
return TopLeft, BottomRight
end
return Vector2.zero, Vector2.zero
end
local function GetSnapEdges(ElemSize: Vector2, ViewportSize: Vector2, Margin: number, AvoidCoreGui: boolean)
local SafeMin, SafeMax = Vector2.zero, ViewportSize
if AvoidCoreGui then
local TopLeftInset, BottomRightInset = GetCoreGuiInset()
SafeMin = TopLeftInset
SafeMax = ViewportSize - BottomRightInset
end
local TargetsX = {
LeftEdge = SafeMin.X + Margin,
Center = SafeMin.X + (SafeMax.X - SafeMin.X - ElemSize.X) / 2,
RightEdge = SafeMax.X - ElemSize.X - Margin,
}
local TargetsY = {
TopEdge = SafeMin.Y + Margin,
Center = SafeMin.Y + (SafeMax.Y - SafeMin.Y - ElemSize.Y) / 2,
BottomEdge = SafeMax.Y - ElemSize.Y - Margin,
}
return TargetsX, TargetsY
end
local function GetClosestSnapTarget(Value: number, Targets: { [string]: number }, Distance: number): (number?, string?)
local ClosestName, ClosestValue, ClosestDist = nil, nil, Distance
for Name, Target in Targets do
local Dist = math.abs(Value - Target)
if Dist <= ClosestDist then
ClosestDist = Dist
ClosestName = Name
ClosestValue = Target
end
end
return ClosestValue, ClosestName
end
local function GetSnapGuideOffset(Name: string, SnappedValue: number, ElemDimension: number): number
if Name == "RightEdge" or Name == "BottomEdge" then
return SnappedValue + ElemDimension
elseif Name == "Center" then
return SnappedValue + ElemDimension / 2
end
return SnappedValue
end
function Library:MakeDraggable(
UI: GuiObject,
DragFrame: GuiObject,
IgnoreToggled: boolean?,
IsMainWindow: boolean?,
SnapConfig: { Enabled: boolean, Distance: number?, Margin: number?, AvoidCoreGui: boolean? }?
)
local StartPos
local FramePos
local Dragging = false
local Changed
local InputBegan
local InputChanged
local SnapGuideX, SnapGuideY
local function GetSnapGuides()
if not SnapGuideX then
SnapGuideX = New("Frame", {
BackgroundColor3 = "AccentColor",
BackgroundTransparency = 0.25,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0.5, 0),
Size = UDim2.new(0, 2, 1, 0),
Visible = false,
ZIndex = 10000,
Parent = ScreenGui,
})
end
if not SnapGuideY then
SnapGuideY = New("Frame", {
BackgroundColor3 = "AccentColor",
BackgroundTransparency = 0.25,
BorderSizePixel = 0,
AnchorPoint = Vector2.new(0, 0.5),
Size = UDim2.new(1, 0, 0, 2),
Visible = false,
ZIndex = 10000,
Parent = ScreenGui,
})
end
return SnapGuideX, SnapGuideY
end
local function HideSnapGuides()
if SnapGuideX then
SnapGuideX.Visible = false
end
if SnapGuideY then
SnapGuideY.Visible = false
end
end
InputBegan = DragFrame.InputBegan:Connect(function(Input: InputObject)
if not IsClickInput(Input) or IsMainWindow and Library.CantDragForced then
return
end
StartPos = Input.Position
FramePos = UI.Position
Dragging = true
Changed = Input.Changed:Connect(function()
if Input.UserInputState ~= Enum.UserInputState.End then
return
end
Dragging = false
HideSnapGuides()
if Changed and Changed.Connected then
Changed:Disconnect()
Changed = nil
end
end)
end)
InputChanged = UserInputService.InputChanged:Connect(function(Input: InputObject)
if
(not IgnoreToggled and not Library.Toggled)
or (IsMainWindow and Library.CantDragForced)
or not (ScreenGui and ScreenGui.Parent)
then
Dragging = false
HideSnapGuides()
if Changed and Changed.Connected then
Changed:Disconnect()
Changed = nil
end
return
end
if Dragging and IsHoverInput(Input) then
local Delta = Input.Position - StartPos
local NewX = FramePos.X.Offset + Delta.X
local NewY = FramePos.Y.Offset + Delta.Y
if SnapConfig and SnapConfig.Enabled then
local ViewportSize = workspace.CurrentCamera and workspace.CurrentCamera.ViewportSize or Vector2.new(1920, 1080)
local Distance = SnapConfig.Distance or 28
local Margin = SnapConfig.Margin or 8
local AbsX = FramePos.X.Scale * ViewportSize.X + NewX
local AbsY = FramePos.Y.Scale * ViewportSize.Y + NewY
local ElemSize = UI.AbsoluteSize
local TargetsX, TargetsY = GetSnapEdges(ElemSize, ViewportSize, Margin, SnapConfig.AvoidCoreGui ~= false)
local SnappedX, SnappedXName = GetClosestSnapTarget(AbsX, TargetsX, Distance)
local SnappedY, SnappedYName = GetClosestSnapTarget(AbsY, TargetsY, Distance)
if SnappedX then
NewX = SnappedX - FramePos.X.Scale * ViewportSize.X
end
if SnappedY then
NewY = SnappedY - FramePos.Y.Scale * ViewportSize.Y
end
local GuideX, GuideY = GetSnapGuides()
GuideX.Visible = SnappedX ~= nil
if SnappedX then
GuideX.Position = UDim2.fromOffset(GetSnapGuideOffset(SnappedXName, SnappedX, ElemSize.X), 0)
end
GuideY.Visible = SnappedY ~= nil
if SnappedY then
GuideY.Position = UDim2.fromOffset(0, GetSnapGuideOffset(SnappedYName, SnappedY, ElemSize.Y))
end
end
UI.Position = UDim2.new(FramePos.X.Scale, NewX, FramePos.Y.Scale, NewY)
end
end)
Library:GiveSignal(InputChanged)
Library:GiveSignal(InputBegan)
UI.Destroying:Once(function()
if InputChanged and InputChanged.Connected then
InputChanged:Disconnect()
end
if InputBegan and InputBegan.Connected then
InputBegan:Disconnect()
end
if Changed and Changed.Connected then
Changed:Disconnect()
end
if SnapGuideX then
SnapGuideX:Destroy()
end
if SnapGuideY then
SnapGuideY:Destroy()
end
local IdxChanged = table.find(Library.Signals, InputChanged)
if IdxChanged then
table.remove(Library.Signals, IdxChanged)
end
local IdxBegan = table.find(Library.Signals, InputBegan)
if IdxBegan then
table.remove(Library.Signals, IdxBegan)
end
end)
end
function Library:MakeResizable(UI: GuiObject, DragFrame: GuiObject, Callback: () -> ()?)
local StartPos
local FrameSize
local Dragging = false
local Changed
local InputBegan
local InputChanged
InputBegan = DragFrame.InputBegan:Connect(function(Input: InputObject)
if not IsClickInput(Input) then
return
end
StartPos = Input.Position
FrameSize = UI.Size
Dragging = true
Changed = Input.Changed:Connect(function()
if Input.UserInputState ~= Enum.UserInputState.End then
return
end
Dragging = false
if Changed and Changed.Connected then
Changed:Disconnect()
Changed = nil
end
end)
end)
InputChanged = UserInputService.InputChanged:Connect(function(Input: InputObject)
if not UI.Visible or not (ScreenGui and ScreenGui.Parent) then
Dragging = false
if Changed and Changed.Connected then
Changed:Disconnect()
Changed = nil
end
return
end
if Dragging and IsHoverInput(Input) then
local Delta = Input.Position - StartPos
UI.Size = UDim2.new(
FrameSize.X.Scale,
math.clamp(FrameSize.X.Offset + Delta.X, Library.MinSize.X, math.huge),
FrameSize.Y.Scale,
math.clamp(FrameSize.Y.Offset + Delta.Y, Library.MinSize.Y, math.huge)
)
if Callback then
Library:SafeCallback(Callback)
end
end
end)
Library:GiveSignal(InputChanged)
Library:GiveSignal(InputBegan)
UI.Destroying:Once(function()
if InputChanged and InputChanged.Connected then
InputChanged:Disconnect()
end
if InputBegan and InputBegan.Connected then
InputBegan:Disconnect()
end
if Changed and Changed.Connected then
Changed:Disconnect()
end
local IdxChanged = table.find(Library.Signals, InputChanged)
if IdxChanged then
table.remove(Library.Signals, IdxChanged)
end
local IdxBegan = table.find(Library.Signals, InputBegan)
if IdxBegan then
table.remove(Library.Signals, IdxBegan)
end
end)
end
function Library:MakeCover(Holder: GuiObject, Place: string)
local Pos = Places[Place] or { 0, 0 }
local Size = Sizes[Place] or { 1, 0.5 }
local Cover = New("Frame", {
AnchorPoint = Vector2.new(Pos[1], Pos[2]),
BackgroundColor3 = Holder.BackgroundColor3,
Position = UDim2.fromScale(Pos[1], Pos[2]),
Size = UDim2.fromScale(Size[1], Size[2]),
Parent = Holder,
})
return Cover
end
function Library:MakeLine(Frame: GuiObject, Info)
local Line = New("Frame", {
AnchorPoint = Info.AnchorPoint or Vector2.zero,
BackgroundColor3 = "OutlineColor",
LayoutOrder = Info.LayoutOrder or 0,
Position = Info.Position,
Size = Info.Size,
ZIndex = Info.ZIndex or Frame.ZIndex,
Parent = Frame,
})
return Line
end
function Library:AddOutline(Frame: GuiObject)
local OutlineStroke = New("UIStroke", {
Color = "OutlineColor",
Thickness = 1,
ZIndex = 2,
Parent = Frame,
})
local ShadowStroke = New("UIStroke", {
Color = "DarkColor",
Thickness = 1.5,
ZIndex = 1,
Parent = Frame,
})
return OutlineStroke, ShadowStroke
end
function Library:AddBlank(Frame: GuiObject, Size: UDim2)
return New("Frame", {
BackgroundTransparency = 1,
Size = Size or UDim2.fromScale(0, 0),
Parent = Frame,
})
end
local TransparencyCache = {}
local ActiveTabTweens = setmetatable({}, { __mode = "k" })
function Library:PlayTabAnimation(Tab, Showing: boolean, OnComplete: (() -> ())?)
if type(Tab) ~= "table" or not Tab.Container then
if OnComplete then
OnComplete()
end
return
end
local TabContainer = Tab.Container :: Frame
local Existing = ActiveTabTweens[TabContainer]
if Existing then
StopTween(Existing, true)
ActiveTabTweens[TabContainer] = nil
end
local BaseZIndex = TabContainer.ZIndex
if not (Library.Animations and Library.Animations.TabSwitch) then
TabContainer.Visible = Showing
TabContainer.Position = UDim2.fromScale(0, 0)
TabContainer.ZIndex = BaseZIndex
if OnComplete then
OnComplete()
end
return
end
if Showing then
local TweenInfo = Library.TabTransitionInfo or TweenInfo.new(0.22, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
local Offset = Library.TabSwipeOffset or 26
local SwipeFrom = string.lower(Library.TabSwipeFrom or "bottom")
local StartPosition
local StartingPositions = {
Left = UDim2.fromOffset(-Offset, 0),
Right = UDim2.fromOffset(Offset, 0),
Top = UDim2.fromOffset(0, -Offset),
Bottom = UDim2.fromOffset(0, Offset),
}
if SwipeFrom == "auto" and Library.PreviousTab then
local CurrentOrder = Tab.Button.LayoutOrder
local PreviousOrder = Library.PreviousTab.Button.LayoutOrder
if CurrentOrder and PreviousOrder then
StartPosition = CurrentOrder > PreviousOrder and StartingPositions.Top or StartingPositions.Bottom
else
StartPosition = StartingPositions.Bottom
end
elseif SwipeFrom == "left" then
StartPosition = StartingPositions.Left
elseif SwipeFrom == "top" then
StartPosition = StartingPositions.Top
elseif SwipeFrom == "right" then
StartPosition = StartingPositions.Right
else
StartPosition = StartingPositions.Bottom
end
TabContainer.ZIndex = BaseZIndex + 1
TabContainer.Position = StartPosition
TabContainer.Visible = true
local Tween = TweenService:Create(TabContainer, TweenInfo, {
Position = UDim2.fromScale(0, 0)
})
ActiveTabTweens[TabContainer] = Tween
Tween:Play()
local Connection; Connection = Tween.Completed:Connect(function(PlaybackState)
if Connection then
Connection:Disconnect()
end
if ActiveTabTweens[TabContainer] == Tween then
ActiveTabTweens[TabContainer] = nil
end
if PlaybackState == Enum.PlaybackState.Cancelled then
return
end
TabContainer.ZIndex = BaseZIndex
if OnComplete then
OnComplete()
end
end)
else
TabContainer.Visible = false
TabContainer.Position = UDim2.fromScale(0, 0)
TabContainer.ZIndex = BaseZIndex
if OnComplete then
OnComplete()
end
end
end
function Library:MakeBoxPopOut(Box: any, Options: {
Enabled: boolean?,
Header: GuiObject?,
Children: (() -> { GuiObject })?,
Before: (() -> ())?,
After: (() -> ())?,
MaxPopOutHeight: number?,
PopOutWidth: number?,
})
Box.PoppedOut = false
Box.PopOutEnabled = Options.Enabled ~= false
Box.PopOutFloat = nil
Box.PopOutPlaceholder = nil
Box.PopOutMaxHeight = if typeof(Options.MaxPopOutHeight) == "number" then Options.MaxPopOutHeight else nil
Box.PopOutWidth = if typeof(Options.PopOutWidth) == "number" then Options.PopOutWidth else nil
if not Box.PopOutEnabled then
function Box:SetPoppedOut(_Value: boolean, _SetPoppedOut: UDim2) end
function Box:TogglePoppedOut() end
function Box:RefreshPopOutPlaceholder() end
function Box:SetMaxPopOutHeight(_Height: number?) end
function Box:SetPopOutWidth(_Width: number?) end
return
end
local BoxHolder = Box.BoxHolder
local Holder = Box.Holder
local Header = Options.Header
local Placeholder
local PlaceholderHeader
local Float
local FloatScale
local HandledChildren: { GuiObject } = {}
local OriginalParents: { [GuiObject]: Instance? } = {}
local OriginalLayoutOrders: { [GuiObject]: number } = {}
local DragState: "Idle" | "Holding" | "Dragging" = "Idle"
local DragInput: InputObject?
local PressMouse: Vector2?
local DragStartPos: UDim2?
local DragChanged: RBXScriptConnection?
local DragDidMove = false
local function GetPopOutWidth(): number
if typeof(Box.PopOutWidth) == "number" then
return math.max(50, math.floor(Box.PopOutWidth + 0.5))
end
if typeof(Box.PopOutDockedWidth) == "number" then
return math.max(50, math.floor(Box.PopOutDockedWidth + 0.5))
end
local Width = Holder.AbsoluteSize.X / Library.DPIScale
if Width < 50 then
Width = 200
end
return math.max(50, math.floor(Width + 0.5))
end
local function ApplyPopOutWidth()
if not (Box.PoppedOut and Float) then
return
end
Float.Size = UDim2.fromOffset(GetPopOutWidth(), Float.Size.Y.Offset)
if Box.Resize then
Box:Resize()
end
end
local function RaiseFloat()
if not Float or not Floats then
return
end
local MaxZ = Float.ZIndex
for _, Child in Floats:GetChildren() do
if Child:IsA("GuiObject") and Child ~= Float then
MaxZ = math.max(MaxZ, Child.ZIndex)
end
end
Float.ZIndex = MaxZ + 1
if Float.Parent == Floats then
Float.Parent = Overlay
end
Float.Parent = Floats
end
local function CreatePlaceholder()
local Frame = New("Frame", {
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundColor3 = "BackgroundColor",
BackgroundTransparency = 0.12,
ClipsDescendants = true,
Size = UDim2.new(1, 0, 0, 0),
Parent = BoxHolder,
})
table.insert(
Library.Corners,
New("UICorner", {
CornerRadius = UDim.new(0, Library.CornerRadius),
Parent = Frame,
})
)
Library:AddOutline(Frame)
PlaceholderHeader = Header:Clone()
PlaceholderHeader.Parent = Frame
DimPopOutClone(PlaceholderHeader)
if PopOutIcon then
local PlaceholderDockIcon = New("ImageButton", {
AutoButtonColor = false,
AnchorPoint = Vector2.new(1, 0.5),
BackgroundTransparency = 1,
ImageColor3 = "WhiteColor",
Position = UDim2.new(1, -8, 0.5, 0),
Size = UDim2.fromOffset(22, 22),
ZIndex = PlaceholderHeader.ZIndex + 1,
Parent = Frame,
})
Library:ApplyLucideIcon(PlaceholderDockIcon, PopOutIcon)
PlaceholderDockIcon.MouseButton1Click:Connect(function()
Box:SetPoppedOut(false)
end)
end
return Frame
end
function Box:RefreshPopOutPlaceholder()
if not Box.PoppedOut or not Placeholder or not Header then
return
end
if PlaceholderHeader then
PlaceholderHeader:Destroy()
PlaceholderHeader = nil
end
PlaceholderHeader = Header:Clone()
PlaceholderHeader.Parent = Placeholder
DimPopOutClone(PlaceholderHeader)
end
function Box:SetPoppedOut(Value: boolean, FloatPosition: UDim2?)
if not Box.PopOutEnabled or Box.Destroyed then
return
end
Value = Value == true
if Box.PoppedOut == Value then
if Value and FloatPosition and Float then
Float.Position = FloatPosition
end
return
end
if Value then
if Options.Before then
Options.Before()
end
local BoxChildren = if Options.Children then Options.Children() else { Holder }
HandledChildren = {}
table.clear(OriginalParents)
table.clear(OriginalLayoutOrders)
for _, Child in BoxChildren do
if not Child or not Child.Parent then
continue
end
table.insert(HandledChildren, Child)
OriginalParents[Child] = Child.Parent
OriginalLayoutOrders[Child] = Child.LayoutOrder
end
if #HandledChildren == 0 then
return
end
local DockedWidth = Holder.AbsoluteSize.X / Library.DPIScale
if DockedWidth < 50 then
DockedWidth = 200
end
Box.PopOutDockedWidth = math.max(50, math.floor(DockedWidth + 0.5))
local Width = GetPopOutWidth()
local AbsolutePosition = Holder.AbsolutePosition
Placeholder = CreatePlaceholder()
Box.PopOutPlaceholder = Placeholder
Float = New("Frame", {
Active = true,
AutomaticSize = Enum.AutomaticSize.Y,
BackgroundTransparency = 1,
Position = FloatPosition or UDim2.fromOffset(
AbsolutePosition.X / Library.DPIScale,
AbsolutePosition.Y / Library.DPIScale
),
Size = UDim2.fromOffset(Width, 0),
ZIndex = 1,
Parent = Floats,
})
FloatScale = New("UIScale", {
Parent = Float,
})
table.insert(Library.Scales, FloatScale)
FloatScale.Scale = Library.DPIScale - (tonumber(Library.ScalesOffset[FloatScale]) or 0)
New("UIListLayout", {
Padding = UDim.new(0, 6),
Parent = Float,
})
for _, Child in HandledChildren do
Child.Parent = Float
end
if not table.find(Library.DraggableElements, Float) then
table.insert(Library.DraggableElements, Float)
end
Box.PopOutFloat = Float
Box.PoppedOut = true
SyncPopOutVisibility(Box)
RaiseFloat()
Float:GetPropertyChangedSignal("AbsolutePosition"):Connect(function()
Box:Resize()
end)
if Options.After then
Options.After()
end
return
end
if Float then
local DraggableIndex = table.find(Library.DraggableElements, Float)
if DraggableIndex then
table.remove(Library.DraggableElements, DraggableIndex)
end
end
if FloatScale then
local ScaleIndex = table.find(Library.Scales, FloatScale)
if ScaleIndex then
table.remove(Library.Scales, ScaleIndex)
end
FloatScale = nil
end
for _, Child in HandledChildren do
if not Child or not Child.Parent then
continue
end
Child.Parent = OriginalParents[Child] or BoxHolder
Child.LayoutOrder = OriginalLayoutOrders[Child] or 0
end
if Placeholder then
Placeholder:Destroy()
Placeholder = nil
end
PlaceholderHeader = nil
if Float then
Float:Destroy()
Float = nil
end
Box.PopOutFloat = nil
Box.PopOutPlaceholder = nil
Box.PopOutDockedWidth = nil
Box.PoppedOut = false
table.clear(HandledChildren)
table.clear(OriginalParents)
table.clear(OriginalLayoutOrders)
if Options.After then
Options.After()
end
end
function Box:TogglePoppedOut()
Box:SetPoppedOut(not Box.PoppedOut)
end
function Box:SetMaxPopOutHeight(Height: number?)
if Height ~= nil then
assert(typeof(Height) == "number", "Height must be a number or nil")
assert(Height >= 0, "Height must be higher than 0")
end
Box.PopOutMaxHeight = Height
if Box.PoppedOut and Box.Resize then
Box:Resize()
end
end
function Box:SetPopOutWidth(Width: number?)
if Width ~= nil then
assert(typeof(Width) == "number", "Width must be a number or nil")
assert(Width >= 0, "Width must be higher than 0")
end
Box.PopOutWidth = Width
ApplyPopOutWidth()
end
local function StopDrag()
if DragState == "Idle" then
return
end
local WasDragging = DragState == "Dragging"
local DidMove = DragDidMove
DragState = "Idle"
DragInput = nil
PressMouse = nil
DragStartPos = nil
DragDidMove = false
if DragChanged and DragChanged.Connected then
DragChanged:Disconnect()
DragChanged = nil
end
if not WasDragging or not Box.PoppedOut or not Float then
return
end
local FloatCenter = Float.AbsolutePosition + (Float.AbsoluteSize * 0.5)
local NearPlaceholder = false
if Library.Toggled and Placeholder and Placeholder.Parent then
local PlaceholderCenter = Placeholder.AbsolutePosition + (Placeholder.AbsoluteSize * 0.5)
NearPlaceholder = (FloatCenter - PlaceholderCenter).Magnitude <= Library.PopOutSnapDistance
end
if NearPlaceholder or (DidMove and not IsScreenPointOutsideMain(FloatCenter)) then
Box:SetPoppedOut(false)
end
end
local function BeginDrag(Input: InputObject)
if DragState ~= "Idle" or Box.Destroyed or not (ScreenGui and ScreenGui.Parent) then
return
end
local Point = Vector2.new(Input.Position.X, Input.Position.Y)
local Top = GetTopFloatAt(Point)
if Box.PoppedOut then
if not Float or Top ~= Float then
return
end
elseif Top ~= nil and not Header:IsDescendantOf(Top) then
return
end
DragState = "Holding"
DragInput = Input
PressMouse = Vector2.new(Input.Position.X, Input.Position.Y)
DragStartPos = nil
DragDidMove = false
if Box.PoppedOut and Float then
RaiseFloat()
end
DragChanged = Input.Changed:Connect(function()
if Input.UserInputState == Enum.UserInputState.End then
StopDrag()
end
end)
task.delay(Library.PopOutHoldTime, function()
if (DragState :: any) ~= "Holding" or DragInput ~= Input then
return
end
DragState = "Dragging"
if Box.PoppedOut and Float then
RaiseFloat()
DragStartPos = Float.Position
end
end)
end
local function UpdateDrag(Input: InputObject)
if DragState ~= "Dragging" or not PressMouse then
return
end
if not (ScreenGui and ScreenGui.Parent) then
StopDrag()
return
end
local MousePosition = Vector2.new(Input.Position.X, Input.Position.Y)
local Delta = MousePosition - PressMouse
if not Box.PoppedOut then
if Delta.Magnitude < Library.PopOutDragThreshold then
return
end
Box:SetPoppedOut(true)
if not Float then
return
end
RaiseFloat()
DragStartPos = Float.Position
DragDidMove = true
elseif Delta.Magnitude >= Library.PopOutDragThreshold then
DragDidMove = true
end
if Float and DragStartPos then
Float.Position = UDim2.new(
DragStartPos.X.Scale,
DragStartPos.X.Offset + Delta.X,
DragStartPos.Y.Scale,
DragStartPos.Y.Offset + Delta.Y
)
end
end
local function BindDragSource(Gui: GuiObject)
Library:GiveSignal(Gui.InputBegan:Connect(function(Input: InputObject)
if IsClickInput(Input) then
BeginDrag(Input)
end
end))
end
BindDragSource(Header)
for _, Descendant in Header:QueryDescendants("GuiObject:not(ImageButton)") do
BindDragSource(Descendant)
end
Library:GiveSignal(Header.DescendantAdded:Connect(function(Descendant)
if Descendant:IsA("GuiObject") and not Descendant:IsA("ImageButton") then
BindDragSource(Descendant)
end
end))
Library:GiveSignal(UserInputService.InputChanged:Connect(function(Input: InputObject)
if IsHoverInput(Input) then
UpdateDrag(Input)
end
end))
end
function Library:MakeOutline(Frame: GuiObject, Corner: number?, ZIndex: number?)
warn("Obsidian:MakeOutline is deprecated, please use Obsidian:AddOutline instead.")
local Holder = New("Frame", {
BackgroundColor3 = "DarkColor",
Position = UDim2.fromOffset(-2, -2),
Size = UDim2.new(1, 4, 1, 4),
ZIndex = ZIndex,
Parent = Frame,
})
local Outline = New("Frame", {
BackgroundColor3 = "OutlineColor",
Position = UDim2.fromOffset(1, 1),
Size = UDim2.new(1, -2, 1, -2),
ZIndex = ZIndex,
Parent = Holder,
})
if Corner and Corner > 0 then
New("UICorner", {
CornerRadius = UDim.new(0, Corner + 1),
Parent = Holder,
})
New("UICorner", {
CornerRadius = UDim.new(0, Corner),
Parent = Outline,
})
end
return Holder, Outline
end
function Library:AddDraggableLabel(...)
local Params = select(1, ...)
local Text
local Icon
local IconPosition = "left"
if typeof(Params) == "table" then
Text = Params.Text
Icon = Params.Icon
IconPosition = Params.IconPosition or "left"
elseif typeof(Params) == "string" then
Text = Params
Icon = select(2, ...)
IconPosition = select(3, ...) or "left"
end
if typeof(IconPosition) ~= "string" then
IconPosition = "left"
end
IconPosition = string.lower(IconPosition)
assert(IconPosition == "left" or IconPosition == "right", "Icon Position needs to be either 'left' or 'right'.")
local DraggableLabel = {
Connections = {},
Destroyed = false
}
local IconImage
local Label = New("TextLabel", {
AutomaticSize = Enum.AutomaticSize.XY,
BackgroundColor3 = "BackgroundColor",
Size = UDim2.fromOffset(0, 0),
Position = UDim2.fromOffset(6, 6),
Text = Text,
TextSize = 15,
ZIndex = 1,
Parent = Floats,
})
table.insert(
Library.Corners,
New("UICorner", {
CornerRadius = UDim.new(0, Library.CornerRadius),
Parent = Label,
})
)
local Padding = New("UIPadding", {
PaddingBottom = UDim.new(0, 6),
PaddingLeft = UDim.new(0, 12),
PaddingRight = UDim.new(0, 12),
PaddingTop = UDim.new(0, 6),
Parent = Label,
})
table.insert(
Library.Scales,
New("UIScale", {
Parent = Label,
})
)
Library:AddOutline(Label)
Library:MakeDraggable(Label, Label, true)
function DraggableLabel:SetText(Text: string)
Label.Text = Text
end
function DraggableLabel:SetIcon(NewIcon: string)
Icon = NewIcon
local IsNotEmpty = Icon and Trim(tostring(Icon)) ~= ""
if IsNotEmpty then
local CustomIcon = Library:GetCustomIcon(Icon)
assert(CustomIcon, "Icon must be a valid Roblox asset or a valid URL or a valid lucide icon.")
IconImage = IconImage or New("ImageLabel", {
BackgroundTransparency = 1,
ImageColor3 = "FontColor",
Size = UDim2.fromOffset(16, 16),
ZIndex = 2,
Parent = Label,
})
Library:ApplyLucideIcon(IconImage, CustomIcon)
end
if IconImage then IconImage.Visible = IsNotEmpty end
DraggableLabel:SetIconPosition(IconPosition)
end
function DraggableLabel:SetIconPosition(NewPosition: string)
IconPosition = string.lower(NewPosition)
assert(IconPosition == "left" or IconPosition == "right", "Icon Position needs to be either 'left' or 'right'.")
local IsNotEmpty = Icon and Trim(tostring(Icon)) ~= ""
Padding.PaddingLeft = UDim.new(0, (IsNotEmpty and IconPosition == "left") and 34 or 12)
Padding.PaddingRight = UDim.new(0, (IsNotEmpty and IconPosition == "right") and 34 or 12)
if IconImage then
if IconPosition == "left" then
IconImage.AnchorPoint = Vector2.new(0, 0.5)
IconImage.Position = UDim2.new(0, -22, 0.5, 0)
else
IconImage.AnchorPoint = Vector2.new(1, 0.5)
IconImage.Position = UDim2.new(1, 22, 0.5, 0)
end
end
end
function DraggableLabel:SetVisible(Visible: boolean)
Label.Visible = Visible
end
DraggableLabel:SetIcon(Icon)
DraggableLabel.Label = Label
if not table.find(Library.DraggableElements, Label) then
table.insert(Library.DraggableElements, Label)
end
PositionDraggable(Label, Label.Position)
function DraggableLabel:Destroy()
DraggableLabel.Destroyed = true
if DraggableLabel.Connections then
for _, connection in DraggableLabel.Connections do
connection:Disconnect()
end
end
local ElemIdx = table.find(Library.DraggableElements, Label)
if ElemIdx then
table.remove(Library.DraggableElements, ElemIdx)
end
if Label then
Label:Destroy()
end
end
return DraggableLabel
end
function Library:AddDraggableButton(...)
local Params = select(1, ...)
local Text
local Func
local ExcludeScaling
local ExcludeDragging
if typeof(Params) == "table" then
Text = Params.Text
Func = Params.Callback or Params.Func
ExcludeScaling = Params.ExcludeScaling
ExcludeDragging = Params.ExcludeDragging
elseif typeof(Params) == "string" then
Text = Params
Func = select(2, ...)
ExcludeScaling = select(3, ...)
ExcludeDragging = select(4, ...)
end
local DraggableButton = {
Connections = {},
Destroyed = false
}
local Button = New("TextButton", {
BackgroundColor3 = "BackgroundColor",
Position = UDim2.fromOffset(6, 6),
TextSize = 16,
ZIndex = 1,
Parent = Floats,
})
table.insert(
Library.Corners,
New("UICorner", {
CornerRadius = UDim.new(0, Library.CornerRadius),
Parent = Button,
})
)
if not ExcludeScaling then
table.insert(
Library.Scales,
New("UIScale", {
Parent = Button,
})
)
end
Library:AddOutline(Button)
local MaxClickDistance = ExcludeDragging and 12 or math.huge
Button.InputBegan:Connect(function(Input: InputObject)
if not IsClickInput(Input) then
return
end
local StartPos = Input.Position
local Changed
Changed = Input.Changed:Connect(function()
if Input.UserInputState ~= Enum.UserInputState.End then
return
end
if (Input.Position - StartPos).Magnitude <= MaxClickDistance then
Library:SafeCallback(Func, DraggableButton)
end
if Changed and Changed.Connected then
Changed:Disconnect()
Changed = nil
end
end)
end)
function DraggableButton:SetText(Text: string)
local X, Y = Library:GetTextBounds(Text, Library.Scheme.Font, 16)
Button.Text = Text
Button.Size = UDim2.fromOffset(X * 2, Y * 2)
end
Library:MakeDraggable(Button, Button, true)
DraggableButton:SetText(Text)
DraggableButton.Button = Button
if not table.find(Library.DraggableElements, Button) then
table.insert(Library.DraggableElements, Button)
end
PositionDraggable(Button, Button.Position)
function DraggableButton:Destroy()
DraggableButton.Destroyed = true
if DraggableButton.Connections then
for _, connection in DraggableButton.Connections do
connection:Disconnect()
endReview the source before using it. Executions count successful copies, capped at 10 per user and network per script in 24 hours.
Community discussion
0Start the conversation.


Your community, in one place