-- [[ Rscripts Risk Notice ]] -- This script is not verified by rscripts.net. 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 Rscripts 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() end