do local genv = getgenv and getgenv() or nil if not (genv and genv["LPH_NO_VIRTUALIZE"]) and not _G["LPH_NO_VIRTUALIZE"] then loadstring([[ function LPH_NO_VIRTUALIZE(f) return f end ]])() end end local function PP_SCRAMBLE_STR(v) return v end local function PP_SCRAMBLE_NUM(v) return v end local function PP_SCRAMBLE_RE_NUM(v) return v end _G.PP_SCRAMBLE_STR = _G.PP_SCRAMBLE_STR or PP_SCRAMBLE_STR _G.PP_SCRAMBLE_NUM = _G.PP_SCRAMBLE_NUM or PP_SCRAMBLE_NUM _G.PP_SCRAMBLE_RE_NUM = _G.PP_SCRAMBLE_RE_NUM or PP_SCRAMBLE_RE_NUM shared = shared or {} shared.Lycoris = shared.Lycoris or { silent = false } local NO_HITBOX_ONLY = true local NO_HITBOX_REACTION_BIAS_SECONDS = -0.045 local NO_HITBOX_SKILL_POLL_SECONDS = 0.015 local NO_HITBOX_PART_POLL_SECONDS = 0.03 local NO_HITBOX_MIN_BLOCK_SECONDS = 0.35 local NO_HITBOX_ACTIVE_GUARD_PULSE_SECONDS = 0.18 local activeSkillDef = nil local executeDefensiveAction = function(_, _, actionFn) if type(actionFn) == "function" then return actionFn() end return nil end local handleAutoCounterTiming = function() end local Modules = {} local function defineModule(name, factory) Modules[name] = factory end local function require(name) local module = Modules[name] if module == nil then error('Missing module: ' .. tostring(name)) end if type(module) == 'function' then local result = module() Modules[name] = result return result end return module end -- MODULE: Features/Combat/AttributeListener defineModule("Features/Combat/AttributeListener", function() -- AttributeListener module. local AttributeListener = {} ---@modules Utility.Maid local Maid = require("Utility/Maid") ---@module Utility.Signal local Signal = require("Utility/Signal") -- Services. local players = game:GetService("Players") local function soundPredictionSeconds() return 0.15 + Defender.rdelay() end -- Attribute maid. local attributeMaid = Maid.new() ---On character added. ---@param character Model local function onCharacterAdded(character) end ---On character removing. ---@param character Model local function onCharacterRemoving(character) end ---Is block currently held? (FHeld) ---@return boolean function AttributeListener.fheld() local localPlayer = players.LocalPlayer local character = localPlayer and localPlayer.Character if not character then return false end local fheld = character:FindFirstChild("FHeld") if not fheld or not fheld:IsA("BoolValue") then local live = workspace:FindFirstChild("Live") local liveChar = live and localPlayer and live:FindFirstChild(localPlayer.Name) fheld = liveChar and liveChar:FindFirstChild("FHeld") if not fheld or not fheld:IsA("BoolValue") then return false end end return fheld.Value == true end ---Initialize AttributeListener module. function AttributeListener.init() local localPlayer = players.LocalPlayer local characterAddedSignal = Signal.new(localPlayer.CharacterAdded) local characterRemovingSignal = Signal.new(localPlayer.CharacterRemoving) attributeMaid:add(characterAddedSignal:connect("AttributeListener_OnCharacterAdded", function(character) onCharacterAdded(character) end)) attributeMaid:add(characterRemovingSignal:connect("AttributeListener_OnCharacterRemoving", function(character) onCharacterRemoving(character) end)) if localPlayer.Character then onCharacterAdded(localPlayer.Character) end end ---Detach AttributeListener module. function AttributeListener.detach() attributeMaid:clean() end -- Return AttributeListener module. return AttributeListener end) -- MODULE: Features/Combat/Defense defineModule("Features/Combat/Defense", function() ---@module Utility.Signal local Signal = require('Utility/Signal') ---@module Utility.Maid local Maid = require('Utility/Maid') ---@module Features.Combat.Objects.AnimatorDefender local AnimatorDefender = require('Features/Combat/Objects/AnimatorDefender') ---@module Features.Combat.Objects.SoundDefender local SoundDefender = require('Features/Combat/Objects/SoundDefender') ---@module Features.Combat.Objects.PartDefender local PartDefender = require('Features/Combat/Objects/PartDefender') ---@module Features.Combat.PositionHistory local PositionHistory = require('Features/Combat/PositionHistory') ---@module Utility.Logger local Logger = require('Utility/Logger') ---@module Utility.TaskSpawner local TaskSpawner = require('Utility/TaskSpawner') local Defense = {} local players = game:GetService('Players') local runService = game:GetService('RunService') local workspaceService = game:GetService('Workspace') local defenseMaid = Maid.new() local liveMaid = Maid.new() local defenderObjects = {} local defenderAnimationObjects = {} local defenderSoundObjects = {} local defenderPartObjects = {} local lastHistoryUpdate = os.clock() local addAnimatorDefender = LPH_NO_VIRTUALIZE(function(animator) local animationDefender = AnimatorDefender.new(animator) defenderObjects[animator] = animationDefender defenderAnimationObjects[animator] = animationDefender end) local addSoundDefender = LPH_NO_VIRTUALIZE(function(sound) local part = sound:FindFirstAncestorWhichIsA('BasePart') if not part then return end local soundDefender = SoundDefender.new(sound, part) defenderObjects[sound] = soundDefender defenderSoundObjects[sound] = soundDefender end) local addPartDefender = LPH_NO_VIRTUALIZE(function(part) local partDefender = PartDefender.new(part) if not partDefender then return end defenderObjects[part] = partDefender defenderPartObjects[part] = partDefender end) local onLiveDescendantAdded = LPH_NO_VIRTUALIZE(function(descendant) if descendant:IsA('Animator') then return addAnimatorDefender(descendant) end if descendant:IsA('Sound') then return addSoundDefender(descendant) end end) local function isStandModel(model) if not model or not model:IsA("Model") then return false end local stands = workspaceService:FindFirstChild("Stands") if stands and model:IsDescendantOf(stands) then return true end local live = workspaceService:FindFirstChild("Live") if not live then return false end for _, entity in next, live:GetChildren() do local standValue = entity:FindFirstChild("STANDOBJ") if standValue and standValue.Value and standValue.Value:IsA("Model") then if model == standValue.Value or model:IsDescendantOf(standValue.Value) then return true end end end return false end local onWorldDescendantAdded = LPH_NO_VIRTUALIZE(function(descendant) if descendant:IsA('Animator') then local model = descendant:FindFirstAncestorWhichIsA("Model") if isStandModel(model) then return addAnimatorDefender(descendant) end return end if descendant:IsA("AnimationController") then local model = descendant:FindFirstAncestorWhichIsA("Model") if isStandModel(model) then local animator = descendant:FindFirstChildOfClass("Animator") if not animator then animator = Instance.new("Animator") animator.Parent = descendant end return addAnimatorDefender(animator) end return end if descendant:IsA("Model") and isStandModel(descendant) then for _, child in next, descendant:GetDescendants() do if child:IsA("Animator") then addAnimatorDefender(child) elseif child:IsA("AnimationController") then local animator = child:FindFirstChildOfClass("Animator") if not animator then animator = Instance.new("Animator") animator.Parent = child end addAnimatorDefender(animator) end end return end if descendant:IsA('BasePart') then return addPartDefender(descendant) end end) local onLiveDescendantRemoved = LPH_NO_VIRTUALIZE(function(descendant) local object = defenderObjects[descendant] if not object then return end object:detach() defenderObjects[descendant] = nil defenderAnimationObjects[descendant] = nil defenderSoundObjects[descendant] = nil defenderPartObjects[descendant] = nil end) local liveInstance = nil local function clearLiveState() for _, object in next, defenderObjects do object:detach() end defenderObjects = {} defenderAnimationObjects = {} defenderSoundObjects = {} defenderPartObjects = {} end local function attachLive(live) if liveInstance == live then return end liveInstance = live liveMaid:clean() clearLiveState() if not live then return end liveMaid:mark(live.DescendantAdded:Connect(onLiveDescendantAdded)) liveMaid:mark(live.DescendantRemoving:Connect(onLiveDescendantRemoved)) for _, descendant in next, live:GetDescendants() do onLiveDescendantAdded(descendant) end end local updateHistory = LPH_NO_VIRTUALIZE(function() lastHistoryUpdate = os.clock() local character = players.LocalPlayer and players.LocalPlayer.Character if not character then return end local humanoidRootPart = character:FindFirstChild('HumanoidRootPart') if not humanoidRootPart then return end PositionHistory.add(players.LocalPlayer, humanoidRootPart.CFrame, tick()) for _, player in next, players:GetPlayers() do if player == players.LocalPlayer then continue end local pcharacter = player.Character if not pcharacter then continue end local proot = pcharacter:FindFirstChild('HumanoidRootPart') if not proot then continue end PositionHistory.add(pcharacter, proot.CFrame, tick()) end end) local updateDefenders = LPH_NO_VIRTUALIZE(function() for _, object in next, defenderAnimationObjects do if object and object.update then object:update() end end for _, object in next, defenderPartObjects do if object and object.update then object:update() end end end) function Defense.init() local renderStepped = Signal.new(runService.RenderStepped) local postSimulation = Signal.new(runService.PostSimulation) defenseMaid:mark(renderStepped:connect('Defense_UpdateHistory', updateHistory)) defenseMaid:mark(postSimulation:connect('Defense_UpdateDefenders', updateDefenders)) defenseMaid:mark(workspaceService.DescendantAdded:Connect(onWorldDescendantAdded)) defenseMaid:mark(workspaceService.DescendantRemoving:Connect(onLiveDescendantRemoved)) defenseMaid:mark(workspaceService.ChildAdded:Connect(function(child) if child.Name == "Live" then attachLive(child) end end)) defenseMaid:mark(workspaceService.ChildRemoved:Connect(function(child) if child == liveInstance then attachLive(nil) end end)) attachLive(workspaceService:FindFirstChild("Live")) for _, descendant in next, workspaceService:GetDescendants() do onWorldDescendantAdded(descendant) end Logger.warn('Defense initialized (animation + sound).') end function Defense.detach() liveMaid:clean() clearLiveState() defenseMaid:clean() liveInstance = nil Logger.warn('Defense detached.') end function Defense.blocking() for _, object in next, defenderAnimationObjects do if object and object.blocking and object:blocking() then return true end end return false end return Defense end) -- MODULE: Features/Combat/Objects/AnimatorDefender defineModule("Features/Combat/Objects/AnimatorDefender", function() ---@module Features.Combat.Objects.Defender local Defender = require("Features/Combat/Objects/Defender") ---@module Utility.Signal local Signal = require("Utility/Signal") ---@module Game.Timings.SaveManager local SaveManager = require("Game/Timings/SaveManager") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.InputClient local InputClient = require("Game/InputClient") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Game.Timings.PlaybackData local PlaybackData = require("Game/Timings/PlaybackData") local M1AutoTrade = require("Features/Combat/M1AutoTrade") ---@module GUI.Library local Library = require("GUI/Library") local workspaceService = game:GetService("Workspace") local function isBlacklistedM1(aid) if not Library or not Library.InfoLoggerData or not Library.InfoLoggerData.KeyBlacklistList then return false end return Library.InfoLoggerData.KeyBlacklistList[aid] == true end _G.isBlacklistedM1 = isBlacklistedM1 local function isStandModel(model) if not model or not model:IsA("Model") then return false end local stands = workspaceService:FindFirstChild("Stands") if stands and model:IsDescendantOf(stands) then return true end local live = workspaceService:FindFirstChild("Live") if not live then return false end for _, entity in next, live:GetChildren() do local standValue = entity:FindFirstChild("STANDOBJ") if standValue and standValue.Value and standValue.Value:IsA("Model") then if model == standValue.Value or model:IsDescendantOf(standValue.Value) then return true end end end return false end ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module Features.Combat.Objects.RepeatInfo local RepeatInfo = require("Features/Combat/Objects/RepeatInfo") ---@module Features.Combat.Objects.HitboxOptions local HitboxOptions = require("Features/Combat/Objects/HitboxOptions") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Game.Timings.PartTiming local PartTiming = require("Game/Timings/PartTiming") ---@module Game.Timings.Action local Action = require("Game/Timings/Action") ---@module Features.Combat.Objects.Task local Task = require("Features/Combat/Objects/Task") ---@module Utility.TaskSpawner local TaskSpawner = require("Utility/TaskSpawner") ---@module Features.Combat.PositionHistory local PositionHistory = require("Features/Combat/PositionHistory") ---@module Utility.OriginalStore local OriginalStore = require("Utility/OriginalStore") local runService = game:GetService("RunService") local hcCache = {} local hcCacheFrame = 0 runService.RenderStepped:Connect(LPH_NO_VIRTUALIZE(function() hcCacheFrame = hcCacheFrame + 1 hcCache = {} end)) local function hcKey(prefix, options, position, hitbox, shape, timing, action) local ent = options.entity or options.part or options.cframe or "" local filter = options.filter or "" local timingId = timing and (timing._id or timing.name) or "" local actionId = action and (action._type or action.name) or "" local actionWhen = action and action._when or "" local pos = position or Vector3.zero local ptime = options.ptime or 0 local spredict = options.spredict and 1 or 0 local fhb = timing and timing.fhb or false local duih = timing and timing.duih or false return string.format( "%s|%s|%s|%s|%s|%s|%s|%s|%s|%s|%.4f|%.2f,%.2f,%.2f|%.2f,%.2f,%.2f", prefix or "base", tostring(ent), tostring(filter), tostring(shape), tostring(timingId), tostring(actionId), tostring(actionWhen), tostring(spredict), tostring(fhb), tostring(duih), ptime, hitbox.X, hitbox.Y, hitbox.Z, pos.X, pos.Y, pos.Z ) end ---@module Features.Combat.AttributeListener local AttributeListener = require("Features/Combat/AttributeListener") ---@class AnimatorDefender: Defender ---@field animator Animator ---@field entity Model ---@field kfmaid Maid ---@field heffects Instance[] ---@field keyframes Action[] ---@field offset number? ---@field timing AnimationTiming? ---@field pbdata table Playback data to be recorded. ---@field rpbdata table Recorded playback data. Optimization so we don't have to constantly reiterate over recorded data. ---@field manimations table ---@field track AnimationTrack? Don't be confused. This is the **valid && last** animation track played. ---@field maid Maid This maid is cleaned up after every new animation track. Safe to use for on-animation-track setup. local AnimatorDefender = setmetatable({}, { __index = Defender }) AnimatorDefender.__index = AnimatorDefender AnimatorDefender.__type = "Animation" -- Services. local players = game:GetService("Players") -- Constants. local MAX_REPEAT_TIME = 5.0 local HISTORY_STEPS = 5.0 local PREDICT_FACING_DELTA = 0.3 ---Is animation stopped? Made into a function for de-duplication. ---@param self AnimatorDefender ---@param track AnimationTrack ---@param timing AnimationTiming ---@return boolean AnimatorDefender.stopped = LPH_NO_VIRTUALIZE(function(self, track, timing) if not NO_HITBOX_ONLY and Configuration.expectToggleValue("AllowFailure") and not timing.rpue and Random.new():NextNumber(1.0, 100.0) <= (Configuration.expectOptionValue("IgnoreAnimationEndRate") or 0.0) then return false, self:notify(timing, "Intentionally ignoring animation end to simulate human error.") end if not timing.iae and not track.IsPlaying then return true, self:notify(timing, "Animation stopped playing.") end if timing.iae and not timing.ieae and not track.IsPlaying and track.TimePosition < track.Length then return true, self:notify(timing, "Animation stopped playing early.") end end) ---Repeat conditional. Extra parameter 'track' added on. ---@param self AnimatorDefender ---@param info RepeatInfo ---@return boolean AnimatorDefender.rc = LPH_NO_VIRTUALIZE(function(self, info) ---@note: There are cases where we might not have a track. If it's not handled properly, it will throw an error. -- Perhaps, the animation can end and we're handling a different repeat conditional. if not info.track then return Logger.warn( "(%s) Did you forget to pass the track? Or perhaps you forgot to place a hook before using this function.", PP_SCRAMBLE_STR(info.timing.name) ) end if self:stopped(info.track, info.timing) then return false end if info.timing.iae and os.clock() - info.start >= ((info.timing.mat / 1000) or MAX_REPEAT_TIME) then return self:notify(info.timing, "Max animation timeout exceeded.") end return true end) ---Run predict facing hitbox check. ---@param self AnimatorDefender ---@param options HitboxOptions ---@return boolean AnimatorDefender.pfh = LPH_NO_VIRTUALIZE(function(self, options) local yrate = PositionHistory.yrate(self.entity) if not yrate then return false end local root = self.entity:FindFirstChild("HumanoidRootPart") if not root then return false end local localRoot = players.LocalPlayer.Character and players.LocalPlayer.Character:FindFirstChild("HumanoidRootPart") if not localRoot then return false end if math.abs(yrate) < PREDICT_FACING_DELTA then return end local clone = options:clone() clone.spredict = false clone.hcolor = Color3.new(0, 1, 1) clone.mcolor = Color3.new(1, 1, 0) local result = false local store = OriginalStore.new() store:run(root, "CFrame", CFrame.lookAt(root.Position, localRoot.Position), function() result = self:hc(clone, nil) end) return result end) ---Run past hitbox check. ---@param timing Timing ---@param options HitboxOptions ---@return boolean AnimatorDefender.phd = LPH_NO_VIRTUALIZE(function(self, timing, options) for _, cframe in next, PositionHistory.stepped(self.entity, HISTORY_STEPS, timing.phds) or {} do local clone = options:clone() clone.spredict = false clone.cframe = cframe clone.hcolor = Color3.new(0.839215, 0.976470, 0.537254) clone.mcolor = Color3.new(0.564705, 0, 1) if not self:hc(clone, nil) then continue end return true end end) ---Get extrapolated seconds. ---@param self Defender ---@param timing AnimationTiming ---@return number AnimatorDefender.fsecs = LPH_NO_VIRTUALIZE(function(self, timing) local player = players:GetPlayerFromCharacter(self.entity) local sd = (player and player:GetAttribute("AveragePing") or 50.0) / 2000 return (timing.pfht or 0.15) + (sd + Defender.rdelay()) end) ---Run our facing extrapolation / interpolation. AnimatorDefender.fpc = LPH_NO_VIRTUALIZE(function(self, timing, options) if timing.duih then return false end if timing.pfh and self:pfh(options) then return true end if timing.phd and self:phd(timing, options) then return true end end) ---Check if we're in a valid state to proceed with the action. ---@param self AnimatorDefender ---@param timing AnimationTiming ---@param action Action ---@return boolean AnimatorDefender.valid = LPH_NO_VIRTUALIZE(function(self, timing, action) if not Defender.valid(self, timing, action) then return false end if not self.track then return self:notify(timing, "No current track.") end if not self.entity then return self:notify(timing, "No entity found.") end local target = self:target(self.entity) if not target then return self:notify(timing, "Not a viable target.") end local root = self.entity:FindFirstChild("HumanoidRootPart") if not root then return self:notify(timing, "No humanoid root part found.") end if self:stopped(self.track, timing) then return false end local options = HitboxOptions.new(root, timing) options.spredict = not timing.duih and not timing.dp options.ptime = self:fsecs(timing) options.action = action options.entity = self.entity local info = RepeatInfo.new(timing) info.track = self.track local hc = self:hc(options, timing.duih and info or nil) if hc then return true end local pc = self:fpc(timing, options) if pc then return true end return self:notify(timing, "Not in hitbox.") end) ---Add a new Keyframe action. ---@param self AnimatorDefender ---@param action Action ---@param tp number function AnimatorDefender:akeyframe(action, tp) -- Set time position. action.tp = tp ---@note: These have to be sent in by a module, so the hitbox and the name also have to get fixed. action["_type"] = PP_SCRAMBLE_STR(action["_type"]) action["name"] = PP_SCRAMBLE_STR(action["name"]) action["hitbox"] = Vector3.new( PP_SCRAMBLE_RE_NUM(action["hitbox"].X), PP_SCRAMBLE_RE_NUM(action["hitbox"].Y), PP_SCRAMBLE_RE_NUM(action["hitbox"].Z) ) if action["shape"] ~= nil then action["shape"] = PP_SCRAMBLE_STR(action["shape"]) end if action["offset"] then action["offset"] = Vector3.new( PP_SCRAMBLE_RE_NUM(action["offset"].X), PP_SCRAMBLE_RE_NUM(action["offset"].Y), PP_SCRAMBLE_RE_NUM(action["offset"].Z) ) end -- Insert in list. table.insert(self.keyframes, action) end ---Get time position of current track. ---@return number? function AnimatorDefender:tp() if not self.track or self.offset == nil then return nil end ---@note: Compensate for ping. Convert seconds to time position by adjusting for speed. --- Higher speed means it will delay earlier. --- Smaller speed means it will delay later. return self.track.TimePosition + ((self.offset + self.sdelay()) / self.track.Speed) end ---Get latest keyframe action that we've exceeded. ---@return Action? AnimatorDefender.latest = LPH_NO_VIRTUALIZE(function(self) local latestKeyframe = nil local latestTimePosition = nil for _, keyframe in next, self.keyframes do if (self:tp() or 0.0) <= keyframe.tp then continue end if latestTimePosition and keyframe.tp <= latestTimePosition then continue end latestTimePosition = keyframe.tp latestKeyframe = keyframe end return latestKeyframe end) ---Update handling. ---@param self AnimatorDefender AnimatorDefender.update = LPH_NO_VIRTUALIZE(function(self) for track, data in next, self.pbdata do -- Don't process tracks. if not Configuration.expectToggleValue("ShowAnimationVisualizer") then self.pbdata[track] = nil continue end -- Check if the track is playing. if not track.IsPlaying then -- Remove out of 'pbdata' and put it in to the recorded table. self.pbdata[track] = nil self.rpbdata[tostring(track.Animation.AnimationId)] = data -- Continue to next playback data. continue end -- Start tracking the animation's speed. data:astrack(track.Speed) end -- Run on validated track & timing. if not self.track or not self.timing then return end if not self.track.IsPlaying then return end -- Find the latest keyframe that we have exceeded, if there is even any. local latest = self:latest() if not latest then return end -- Clear the keyframes that we have exceeded. local tp = self:tp() or 0.0 for idx, keyframe in next, self.keyframes do if tp <= keyframe.tp then continue end self.keyframes[idx] = nil end -- Log. self:notify( self.timing, "(%.2f) (really %.2f) Keyframe action '%s' with type '%s' is being executed.", tp, self.track.TimePosition, PP_SCRAMBLE_STR(latest.name), PP_SCRAMBLE_STR(latest._type) ) -- Ok, run action of this keyframe. self.maid:mark( TaskSpawner.spawn( string.format("KeyframeAction_%s", PP_SCRAMBLE_STR(latest._type)), self.handle, self, self.timing, latest, false ) ) end) ---Virtualized processing checks. ---@param track AnimationTrack ---@return boolean function AnimatorDefender:pvalidate(track) if track.Priority == Enum.AnimationPriority.Core then return false end return true end ---Process animation track. ---@todo: AP telemetry - aswell as tracking effects that are added with timestamps and current ping to that list. ---@param self AnimatorDefender ---@param track AnimationTrack AnimatorDefender.process = LPH_NO_VIRTUALIZE(function(self, track) local localCharacter = players.LocalPlayer.Character local isLocal = localCharacter and self.entity == localCharacter if not self:pvalidate(track) then return end -- Clean up Keyframe maid. self.kfmaid:clean() -- Add to playback data list. if Configuration.expectToggleValue("ShowAnimationVisualizer") then self.pbdata[track] = PlaybackData.new(self.entity) end -- Animation ID. local aid = tostring(track.Animation.AnimationId) -- In logging range? local distance = self:distance(self.entity) local logAll = Configuration.expectToggleValue("LogAllAnimations") if logAll and not distance then distance = 0 end local ilr = logAll or ( distance and distance >= (Configuration.expectOptionValue("MinimumLoggerDistance") or 0) and distance <= (Configuration.expectOptionValue("MaximumLoggerDistance") or 0) ) -- Keyframe logging. local keyframeReached = Signal.new(track.KeyframeReached) self.kfmaid:add(keyframeReached:connect("AnimationDefender_OnKeyFrameReached", function(kfname) if not ilr then return end Library:AddKeyFrameEntry(distance, aid, kfname, track.TimePosition, false) end)) ---@type AnimationTiming? local timing = self:initial(self.entity, SaveManager.as, self.entity.Name, aid) if not timing then if isLocal then M1AutoTrade.handleLocalM1(nil, track) end return end if ilr then Library:AddExistAnimEntry(self.entity.Name, distance, timing) end if isStandModel(self.entity) then if Configuration.expectToggleValue("EnableAutoDefense") and isBlacklistedM1(aid) then InputClient.blockHoldSeconds(0.3) end return end if isLocal then M1AutoTrade.handleLocalM1(timing, track) return end if type(handleAutoCounterTiming) == "function" then handleAutoCounterTiming(timing, "Animation", distance) end M1AutoTrade.handle(self, timing, distance) if not Configuration.expectToggleValue("EnableAutoDefense") then return end local humanoidRootPart = self.entity:FindFirstChild("HumanoidRootPart") if not humanoidRootPart then return end ---@note: Clean up previous tasks that are still waiting or suspended because they're in a different track. self:clean() -- Set current data. self.timing = timing self.track = track self.offset = self.rdelay() if timing._id == "rbxassetid://116007627948983" then local dummyAction = { ihbc = true } if not self:valid(timing, dummyAction) then return end local dist = distance or self:distance(self.entity) or math.huge if dist <= 40 then InputClient.dash("forward") InputClient.m1Hold(0.8) else InputClient.dash("back") task.delay(1.2, function() InputClient.dash("back") end) end return end -- Fake mistime rate. ---@type Action? local _, faction = next(timing.actions._data) -- Obviously, we don't want any modules where we don't know how many actions there are. -- We don't want any actions that have a count that is not equal to 1. -- We need to check if we can atleast dash, because we will be going to are fallback. -- We must also check if our action isn't too short or is not a parry type, defeating the purpose. if not NO_HITBOX_ONLY and Configuration.expectToggleValue("AllowFailure") and not timing.rpue and timing.actions:count() == 1 and Random.new():NextNumber(1.0, 100.0) <= (Configuration.expectOptionValue("FakeMistimeRate") or 0.0) and faction and PP_SCRAMBLE_STR(faction._type) == "Parry" and faction:when() > (self.rtt() + 0.6) then InputClient.deflect() self:notify(timing, "Intentionally mistimed to simulate human error.") end ---@note: Start processing the timing. Add the actions if we're not RPUE. if not timing.rpue then return self:actions(timing) end if not self:cooldownAllow(timing) then return end -- Start RPUE. local info = RepeatInfo.new(timing, self.rdelay()) info.track = track self:mark(Task.new(string.format("RPUE_%s_%i", timing.name, 0), function() return timing:rsd() - info.irdelay - self.sdelay() end, timing.punishable, timing.after, self.rpue, self, self.entity, timing, info)) -- Notify. if not LRM_UserNote or LRM_UserNote == "tester" then self:notify( timing, "Added RPUE '%s' (%.2fs, then every %.2fs) with ping '%.2f' (changing) subtracted.", PP_SCRAMBLE_STR(timing.name), timing:rsd(), timing:rpd(), self.rtt() ) else self:notify( timing, "Added RPUE '%s' ([redacted], then every [redacted]) with ping '%.2f' (changing) subtracted.", PP_SCRAMBLE_STR(timing.name), self.rtt() ) end end) ---Clean up the defender. function AnimatorDefender:clean() -- Empty data. self.keyframes = {} self.heffects = {} -- Empty Keyframe maid. self.kfmaid:clean() -- Clean through base method. Defender.clean(self) end ---Create new AnimatorDefender object. ---@param animator Animator ---@return AnimatorDefender function AnimatorDefender.new(animator) local entity = animator:FindFirstAncestorWhichIsA("Model") if not entity then return error(string.format("AnimatorDefender.new(%s) - no entity.", animator:GetFullName())) end local self = setmetatable(Defender.new(), AnimatorDefender) local animationPlayed = Signal.new(animator.AnimationPlayed) self.animator = animator self.entity = entity self.kfmaid = Maid.new() self.track = nil self.timing = nil self.rdelay = nil self.heffects = {} self.keyframes = {} self.pbdata = {} self.rpbdata = {} self.maid:mark(animationPlayed:connect( "AnimatorDefender_OnAnimationPlayed", LPH_NO_VIRTUALIZE(function(track) self:process(track) end) )) return self end -- Return AnimatorDefender module. return AnimatorDefender end) -- MODULE: Features/Combat/Objects/Defender defineModule("Features/Combat/Objects/Defender", function() ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Features.Combat.Objects.Task local Task = require("Features/Combat/Objects/Task") ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module GUI.Library local Library = require("GUI/Library") ---@module Utility.TaskSpawner local TaskSpawner = require("Utility/TaskSpawner") ---@module Features.Combat.Targeting local Targeting = require("Features/Combat/Targeting") ---@module Features.Combat.PositionHistory local PositionHistory = require("Features/Combat/PositionHistory") ---@module Features.Combat.Objects.HitboxOptions local HitboxOptions = require("Features/Combat/Objects/HitboxOptions") ---@module Game.InputClient local InputClient = require("Game/InputClient") ---@module Features.Combat.AttributeListener local AttributeListener = require("Features/Combat/AttributeListener") ---@module Game.Keybinding local Keybinding = require("Game/Keybinding") ---@module Utility.OriginalStore local OriginalStore = require("Utility/OriginalStore") ---@class Defender ---@field tasks Task[] ---@field tmaid Maid Cleaned up every clean cycle. ---@field rhook table Hooked functions that we can restore on clean-up. ---@field markers table Blocking markers for unknown length timings. If the entry exists and is true, then we're blocking. ---@field maid Maid ---@field hmaid Maid local Defender = {} Defender.__index = Defender Defender.__type = "Defender" -- Services. local stats = game:GetService("Stats") local userInputService = game:GetService("UserInputService") local players = game:GetService("Players") local textChatService = game:GetService("TextChatService") local debrisService = game:GetService("Debris") -- Constants. local MAX_VISUALIZATION_TIME = 5.0 local MAX_REPEAT_WAIT = 10.0 local PREDICTION_LENIENCY_MULTI = 5.0 local PREDICTION_EXTRA_SECONDS = 0 local PREDICTION_MAX_SECONDS = 0.5 local PREDICTION_SPEED_REF = 16 local PREDICTION_MIN_SCALE = 0.6 local PREDICTION_MAX_SCALE = 1.4 local blockTagCounter = 0 local defenseCooldowns = setmetatable({}, { __mode = "k" }) local function timingKey(timing) if timing and type(timing.id) == "function" then local ok, key = pcall(timing.id, timing) if ok and key then return tostring(key) end end return tostring(timing and timing.name or "Unknown") end local function nextBlockTagId() blockTagCounter += 1 return string.format("Defender_StartBlock_%d", blockTagCounter) end ---Log a miss to the UI library with distance check. ---@param type string ---@param key string ---@param name string? ---@param distance number ---@param parent string? If provided, will be shown in the log. ---@return boolean function Defender:miss(type, key, name, distance, parent) if not Configuration.expectToggleValue("ShowLoggerWindow") then return false end if distance < (Configuration.expectOptionValue("MinimumLoggerDistance") or 0) or distance > (Configuration.expectOptionValue("MaximumLoggerDistance") or 0) then return false end Library:AddMissEntry(type, key, name, distance, parent) return true end ---Fetch distance. ---@param from Model? | BasePart? ---@return number? function Defender:distance(from) if not from then return end local entRootPart = from if from:IsA("Model") then entRootPart = from:FindFirstChild("HumanoidRootPart") end if not entRootPart then return end local localCharacter = players.LocalPlayer.Character if not localCharacter then return end local localRootPart = localCharacter:FindFirstChild("HumanoidRootPart") if not localRootPart then return end return (entRootPart.Position - localRootPart.Position).Magnitude end ---Find target - hookable function. ---@param self Defender ---@param entity Model ---@return Target? Defender.target = LPH_NO_VIRTUALIZE(function(self, entity) return Targeting.find(entity) end) ---Repeat until parry end. ---@param self Defender ---@param entity Model ---@param timing AnimationTiming ---@param info RepeatInfo Defender.rpue = LPH_NO_VIRTUALIZE(function(self, entity, timing, info) local distance = self:distance(entity) if not distance then return Logger.warn("Stopping RPUE '%s' because the distance is not valid.", PP_SCRAMBLE_STR(timing.name)) end if timing and (distance < PP_SCRAMBLE_NUM(timing.imdd) or distance > PP_SCRAMBLE_NUM(timing.imxd)) then return self:notify(timing, "Distance is out of range.") end if not self:rc(info) then return Logger.warn( "Stopping RPUE '%s' because the repeat condition is not valid.", PP_SCRAMBLE_STR(timing.name) ) end local target = self:target(entity) local options = HitboxOptions.new(CFrame.new(), timing) options.spredict = true options.part = target and target.root options.entity = entity local success = target and self:hc(options, timing.duih and info or nil) info.index = info.index + 1 self:mark(Task.new(string.format("RPUE_%s_%i", PP_SCRAMBLE_STR(timing.name), info.index), function() return timing:rpd() - info.irdelay - self.sdelay() end, timing.punishable, timing.after, self.rpue, self, self.entity, timing, info)) if not target then return Logger.warn("Skipping RPUE '%s' because the target is not valid.", PP_SCRAMBLE_STR(timing.name)) end if not success then return Logger.warn("Skipping RPUE '%s' because we are not in the hitbox.", PP_SCRAMBLE_STR(timing.name)) end if not timing.srpn then self:notify(timing, "(%i) Action 'RPUE Parry' is being executed.", info.index) end InputClient.block(true) InputClient.block(false) end) ---Check if we're in a valid state to proceed with action handling. Extend me. ---@param self Defender ---@param timing Timing ---@param action Action ---@return boolean Defender.valid = LPH_NO_VIRTUALIZE(function(self, timing, action) if not Configuration.expectToggleValue("EnableAutoDefense") then if Configuration.expectToggleValue("EnableM1AutoTrade") then return self:notify(timing, "Auto Defense disabled (M1 Trade only).") end return self:notify(timing, "Auto Defense disabled.") end local character = players.LocalPlayer.Character if not character then return self:notify(timing, "No character found.") end local integer = Random.new():NextNumber(1.0, 100.0) local rate = Configuration.expectOptionValue("FailureRate") or 0.0 if not NO_HITBOX_ONLY and Configuration.expectToggleValue("AllowFailure") and integer <= rate then return self:notify(timing, "(%i <= %i) Intentionally did not run.", integer, rate) end local selectedFilters = Configuration.expectOptionValue("AutoDefenseFilters") or {} local function filterEnabled(name, legacy) return selectedFilters[name] or (legacy and selectedFilters[legacy]) end local currentState = character:GetAttribute("CurrentState") if filterEnabled("Disable When Knocked Recently") and AttributeListener.krecently and AttributeListener.krecently() then return self:notify(timing, "User was knocked recently.") end if filterEnabled("Disable When In Dash") and currentState == "Dashing" then return self:notify(timing, "User is dashing.") end if filterEnabled("Disable When In Flashstep") and currentState == "Flashstep" then return self:notify(timing, "User is flashstepping.") end if currentState == "Attacking" or currentState == "Skill" then return self:notify(timing, "Currently attacking.") end local chatInputBarConfiguration = textChatService:FindFirstChildOfClass("ChatInputBarConfiguration") if filterEnabled("Disable When Textbox Focused") and (userInputService:GetFocusedTextBox() or (chatInputBarConfiguration and chatInputBarConfiguration.IsFocused)) then return self:notify(timing, "User is typing in a text box.") end if filterEnabled("Disable When Window Not Active") and iswindowactive and not iswindowactive() then return self:notify(timing, "Window is not active.") end if filterEnabled("Disable When Holding Block") and userInputService:IsKeyDown((Keybinding.info and Keybinding.info["Block / Parry"]) or Enum.KeyCode.F) then return self:notify(timing, "User is holding block.") end if timing.tag == "M1" and filterEnabled("Filter Out M1s") then return self:notify(timing, "Attacker is using a 'M1' attack.") end if (timing.tag == "Skill" or timing.tag == "Mantra") and filterEnabled("Filter Out Skills", "Filter Out Mantras") then return self:notify(timing, "Attacker is using a 'Skill' attack.") end if (timing.tag == "Counter" or timing.tag == "Critical") and filterEnabled("Filter Out Counters", "Filter Out Criticals") then return self:notify(timing, "Attacker is using a 'Counter' attack.") end if (timing.tag == "Other" or timing.tag == "Undefined") and filterEnabled("Filter Out Others", "Filter Out Undefined") then return self:notify(timing, "Attacker is using an 'Other' attack.") end return true end) function Defender:cooldownAllow(timing) local ms = Configuration.expectOptionValue("DefenseCooldownMs") or 0 if NO_HITBOX_ONLY and ms <= 0 then ms = 45 end if ms <= 0 then return true end local entity = self.entity or self.owner or self.part if not entity then return true end local key = timingKey(timing) local now = os.clock() local bucket = defenseCooldowns[entity] if not bucket then bucket = {} defenseCooldowns[entity] = bucket end local last = bucket[key] if last and (now - last) < (ms / 1000) then return false end bucket[key] = now return true end ---Check if any parts that are in our filter were hit. ---@note: Solara fallback. local function checkParts(parts, filter) for _, part in next, parts do for _, fpart in next, filter do if part ~= fpart and not part:IsDescendantOf(fpart) then continue end return true end end return false end ---Visualize a position and size. ---@param self Defender ---@param identifier number? If the identifier is nil, then we will auto-generate one for each visualization. ---@param cframe CFrame ---@param size Vector3 ---@param color Color3 ---@param shape string? Defender.visualize = LPH_NO_VIRTUALIZE(function(self, identifier, cframe, size, color, shape) local id = identifier or self.hmaid:uid() local vpart = self.hmaid[id] local okParent, parent = true, nil if vpart then okParent, parent = pcall(function() return vpart.Parent end) end if vpart and (not okParent or parent == nil) then pcall(function() vpart:Destroy() end) self.hmaid[id] = nil vpart = nil end vpart = vpart or Instance.new("Part") local hitboxShape = shape == "Ball" and "Ball" or "Box" local renderSize = size if hitboxShape == "Ball" then local radius = math.max(size.X, size.Y, size.Z) / 2 renderSize = Vector3.new(radius * 2, radius * 2, radius * 2) vpart.Shape = Enum.PartType.Ball else vpart.Shape = Enum.PartType.Block end local ok = pcall(function() vpart.Parent = workspace end) if not ok then self.hmaid[id] = nil return end vpart.Anchored = true vpart.CanCollide = false vpart.CanQuery = false vpart.CanTouch = false vpart.Material = Enum.Material.ForceField vpart.CastShadow = false vpart.Size = renderSize vpart.CFrame = cframe vpart.Color = color vpart.Name = string.format("RW_Visualization_%i", id) vpart.Transparency = Configuration.expectToggleValue("EnableVisualizations") and 0.2 or 1.0 if self.hmaid[id] then return end self.hmaid[id] = vpart debrisService:AddItem(vpart, MAX_VISUALIZATION_TIME) end) ---Run hitbox check. Returns wheter if the hitbox is being touched. ---@todo: An issue is that the player's current look vector will not be the same as when they attack due to a parry timing being seperate from the attack causing this check to fail. ---@param self Defender ---@param cframe CFrame ---@param fd boolean ---@param size Vector3 ---@param filter Instance[] ---@param shape string? ---@return boolean?, CFrame? Defender.hitbox = LPH_NO_VIRTUALIZE(function(self, cframe, fd, size, filter, shape) local shouldManualFilter = getexecutorname and (getexecutorname():match("Solara") or getexecutorname():match("Xeno")) local overlapParams = OverlapParams.new() overlapParams.FilterDescendantsInstances = shouldManualFilter and {} or filter overlapParams.FilterType = shouldManualFilter and Enum.RaycastFilterType.Exclude or Enum.RaycastFilterType.Include local character = players.LocalPlayer.Character if not character then return nil, nil end local root = character:FindFirstChild("HumanoidRootPart") if not root then return nil, nil end -- Used CFrame. local usedCFrame = cframe if fd then usedCFrame = usedCFrame * CFrame.new(0, 0, -(size.Z / 2)) end local parts = nil local hitboxShape = shape == "Ball" and "Ball" or "Box" if hitboxShape == "Ball" then local radius = math.max(size.X, size.Y, size.Z) / 2 parts = workspace:GetPartBoundsInRadius(usedCFrame.Position, radius, overlapParams) else -- Parts in bounds. parts = workspace:GetPartBoundsInBox(usedCFrame, size, overlapParams) end -- Return result. return shouldManualFilter and checkParts(parts, filter) or #parts > 0, usedCFrame end) ---Check initial state. ---@param self Defender ---@param from Model? | BasePart? ---@param pair TimingContainerPair ---@param name string ---@param key string ---@return Timing? Defender.initial = LPH_NO_VIRTUALIZE(function(self, from, pair, name, key) -- Find timing. local timing = pair:index(key) -- Fetch distance. local distance = self:distance(from) if not distance then return nil end -- Check for distance; if we have a timing. if timing and (distance < PP_SCRAMBLE_NUM(timing.imdd) or distance > PP_SCRAMBLE_NUM(timing.imxd)) then return nil end -- Check for no timing. If so, let's log a miss. ---@note: Ignore return value. if not timing then self:miss(self.__type, key, name, distance, from and tostring(from.Parent) or nil) return nil end -- Return timing. return timing end) ---Logger notify. ---@param self Defender ---@param timing Timing ---@param str string Defender.notify = LPH_NO_VIRTUALIZE(function(self, timing, str, ...) if not Configuration.expectToggleValue("EnableNotifications") then return end Logger.notify("[%s] (%s) %s", PP_SCRAMBLE_STR(timing.name), self.__type, string.format(str, ...)) end) ---@note: Perhaps one day, we can get better approximations for these. --- These used to rely on GetNetworkPing which we assumed would be sending or atleast receiving delay. --- That is incorrect, it is RakNet ping thereby being RTT. local function safePingSeconds() local network = stats:FindFirstChild("Network") if not network then return 0 end local serverStatsItem = network:FindFirstChild("ServerStatsItem") if not serverStatsItem then return 0 end local dataPingItem = serverStatsItem:FindFirstChild("Data Ping") if not dataPingItem then return 0 end local ok, value = pcall(function() return dataPingItem:GetValue() end) if not ok or type(value) ~= "number" then return 0 end return value / 1000 end ---Get receiving delay. ---@return number function Defender.rdelay() return math.max(safePingSeconds() / 2, 0.0) end ---Get sending delay. ---@return number function Defender.sdelay() return math.max(safePingSeconds() / 2, 0.0) end ---Get data ping. ---@note: https://devforum.roblox.com/t/in-depth-information-about-robloxs-remoteevents-instance-replication-and-physics-replication-w-sources/1847340 ---@note: The forum post above is misleading, not only is it the RTT time, please note that this also takes into account all delays like frame time. ---@note: This is our round-trip time (e.g double the ping) since we have a receiving delay (replication) and a sending delay when we send the input to the server. ---@todo: For every usage, the sending delay needs to be continously updated. The receiving one must be calculated once at initial send for AP ping compensation. ---@return number function Defender.rtt() return safePingSeconds() end ---Repeat conditional. ---@param self Defender ---@param info RepeatInfo ---@return boolean Defender.rc = LPH_NO_VIRTUALIZE(function(self, info) if os.clock() - info.start >= MAX_REPEAT_WAIT then return false end return true end) ---Handle delay until in hitbox. ---@param self Defender ---@param options HitboxOptions ---@param info RepeatInfo ---@return boolean Defender.duih = LPH_NO_VIRTUALIZE(function(self, options, info) local clone = options:clone() clone.hmid = self.hmaid:uid() while task.wait() do if not self:rc(info) then return false end if not self:hc(clone, nil) then continue end return true end end) ---Handle hitbox check options. ---@param self Defender ---@param options HitboxOptions ---@param info RepeatInfo? Pass this in if you want to use the delay until in hitbox. ---@return boolean Defender.hc = LPH_NO_VIRTUALIZE(function(self, options, info) local action = options.action local timing = options.timing -- Run basic validation. local character = players.LocalPlayer.Character if not character then return false end if NO_HITBOX_ONLY then return true end local root = character:FindFirstChild("HumanoidRootPart") if not root then return false end if action and action.ihbc then return true end -- If we have info, then we want to delay until in hitbox. if info then return self:duih(options, info) end -- Fetch the data that we need. local hitbox = options:hitbox() local shape = options:shape() local position = options:pos() -- Run hitbox check (cached per frame). local cacheKey = hcKey and hcKey("base", options, position, hitbox, shape, timing, action) or nil local cached = cacheKey and hcCache[cacheKey] or nil local result, usedCFrame if cached and cached.frame == hcCacheFrame then result = cached.result usedCFrame = cached.usedCFrame else result, usedCFrame = self:hitbox(position, timing.fhb, hitbox, options.filter, shape) if cacheKey then hcCache[cacheKey] = { frame = hcCacheFrame, result = result, usedCFrame = usedCFrame } end end if usedCFrame then self:visualize(options.hmid, usedCFrame, hitbox, options:ghcolor(result), shape) self:visualize(options.hmid and options.hmid + 1 or nil, root.CFrame, root.Size, options:ghcolor(result), "Box") end if not options.spredict or result then return result end -- Run prediction check. local closest = PositionHistory.closest(players.LocalPlayer, tick() - (self.sdelay() * PREDICTION_LENIENCY_MULTI)) if not closest then return false end local basePtime = options.ptime or 0 if basePtime <= 0 then return false end local store = OriginalStore.new() local extraSeconds = Configuration.expectOptionValue("PredictionExtraSeconds") if type(extraSeconds) ~= "number" then extraSeconds = PREDICTION_EXTRA_SECONDS end local speed = options.part and options.part.AssemblyLinearVelocity and options.part.AssemblyLinearVelocity.Magnitude or 0 local scale = math.clamp(speed / PREDICTION_SPEED_REF, PREDICTION_MIN_SCALE, PREDICTION_MAX_SCALE) options.ptime = basePtime + (extraSeconds * scale) if options.ptime > PREDICTION_MAX_SECONDS then options.ptime = PREDICTION_MAX_SECONDS end local eposition = options:extrapolate() -- Run check. local predKey = hcKey and hcKey("pred", options, eposition, hitbox, shape, timing, action) or nil local predCached = predKey and hcCache[predKey] or nil if predCached and predCached.frame == hcCacheFrame then result = predCached.result usedCFrame = predCached.usedCFrame else store:run(root, "CFrame", closest, function() result, usedCFrame = self:hitbox(eposition, timing.fhb, hitbox, options.filter, shape) end) if predKey then hcCache[predKey] = { frame = hcCacheFrame, result = result, usedCFrame = usedCFrame } end end options.ptime = basePtime -- Visualize predicted hitbox. if usedCFrame then self:visualize(options.hmid and options.hmid + 1 or nil, usedCFrame, hitbox, options:gphcolor(result), shape) self:visualize(options.hmid and options.hmid + 1 or nil, root.CFrame, root.Size, options:gphcolor(result), "Box") end -- Return result. return result end) ---Handle end block. ---@param self Defender Defender.bend = LPH_NO_VIRTUALIZE(function(self) local tagId = self.blockTagId or nextBlockTagId() self.blockTagId = tagId InputClient.blockTag(tagId, false) end) ---Handle action. ---@param self Defender ---@param timing Timing ---@param action Action ---@param notify boolean Defender.handle = LPH_NO_VIRTUALIZE(function(self, timing, action, notify) if not Configuration.expectToggleValue("EnableAutoDefense") then return end if not self:valid(timing, action) then return end if not notify then self:notify(timing, "Action type '%s' is being executed.", PP_SCRAMBLE_STR(action._type)) end local actionType = PP_SCRAMBLE_STR(action._type) local reactionDelay = NO_HITBOX_ONLY and 0 or (Configuration.expectOptionValue("ReactionDelaySeconds") or 0) if reactionDelay > 0 and actionType ~= "Start Block" and actionType ~= "End Block" then task.wait(reactionDelay) end -- Dash instead of parry. local dashReplacement = (not NO_HITBOX_ONLY) and (Random.new():NextNumber(1.0, 100.0) <= (Configuration.expectOptionValue("DashInsteadOfParryRate") or 0.0)) if actionType ~= "Parry" then dashReplacement = false end if not Configuration.expectToggleValue("AllowFailure") then dashReplacement = false end if timing.actions:count() ~= 1 then dashReplacement = false end if actionType == "Start Block" then local tagId = self.blockTagId or nextBlockTagId() self.blockTagId = tagId return InputClient.blockTag(tagId, true, 20.0) end if actionType == "End Block" then local tagId = self.blockTagId or nextBlockTagId() self.blockTagId = tagId return InputClient.blockTag(tagId, false) end if actionType == "Dash" then local dashDir = nil if action.name then local lname = string.lower(action.name) if lname:find("right", 1, true) then dashDir = "right" elseif lname:find("left", 1, true) then dashDir = "left" elseif lname:find("forward", 1, true) then dashDir = "forward" elseif lname:find("back", 1, true) then dashDir = "back" end end return InputClient.dash(dashDir) end if actionType == "Jump" then return InputClient.jump() end if dashReplacement and not timing.nfdb then self:notify(timing, "Action type 'Parry' replaced to 'Dash' type.") return InputClient.dash() end return InputClient.deflect() end) ---Check if we have input blocking tasks. ---@param self Defender ---@return boolean Defender.blocking = LPH_NO_VIRTUALIZE(function(self) for _, marker in next, self.markers do if not marker then continue end return true end for _, task in next, self.tasks do if not task:blocking() then continue end return true end end) ---Mark task. ---@param task Task function Defender:mark(task) self.tasks[#self.tasks + 1] = task end ---Clean up hooks. function Defender:clhook() for key, old in next, self.rhook do if not self[key] then continue end self[key] = old end self.rhook = {} end ---Clean up all tasks. ---@param self Defender Defender.clean = LPH_NO_VIRTUALIZE(function(self) -- Clean-up hooks. self:clhook() -- Clear temporary maid. self.tmaid:clean() -- Clear markers. self.markers = {} -- Clean up hitboxes. self.hmaid:clean() local stopBlock = false for idx, task in next, self.tasks do -- Cancel task. task:cancel() -- Clear in table. self.tasks[idx] = nil if task.identifier == "Start Block" or task.identifier == "End Block" then stopBlock = true end end if stopBlock then local tagId = self.blockTagId or nextBlockTagId() self.blockTagId = tagId InputClient.blockTag(tagId, false) end end) ---Process module. ---@param self Defender ---@param timing Timing ---@varargs any Defender.module = LPH_NO_VIRTUALIZE(function(self, timing, ...) return self:notify(timing, "Module manager is disabled.") end) ---Add a action to the defender object. ---@param self Defender ---@param timing Timing ---@param action Action Defender.action = LPH_NO_VIRTUALIZE(function(self, timing, action) if timing.umoa then action["_type"] = PP_SCRAMBLE_STR(action["_type"]) action["name"] = PP_SCRAMBLE_STR(action["name"]) action["_when"] = PP_SCRAMBLE_RE_NUM(action["_when"]) action["hitbox"] = Vector3.new( PP_SCRAMBLE_RE_NUM(action["hitbox"].X), PP_SCRAMBLE_RE_NUM(action["hitbox"].Y), PP_SCRAMBLE_RE_NUM(action["hitbox"].Z) ) if action["shape"] ~= nil then action["shape"] = PP_SCRAMBLE_STR(action["shape"]) end if action["offset"] then action["offset"] = Vector3.new( PP_SCRAMBLE_RE_NUM(action["offset"].X), PP_SCRAMBLE_RE_NUM(action["offset"].Y), PP_SCRAMBLE_RE_NUM(action["offset"].Z) ) end end -- Get initial receive delay. local rdelay = self.rdelay() -- Add action. self:mark(Task.new(PP_SCRAMBLE_STR(action._type), function() return action:when() - rdelay - self.sdelay() end, timing.punishable, timing.after, self.handle, self, timing, action)) -- Log. if not LRM_UserNote or LRM_UserNote == "tester" then self:notify( timing, "Added action '%s' (%.2fs) with ping '%.2f' (changing) subtracted.", PP_SCRAMBLE_STR(action.name), action:when(), self.rtt() ) else self:notify( timing, "Added action '%s' ([redacted]) with ping '%.2f' (changing) subtracted.", PP_SCRAMBLE_STR(action.name), self.rtt() ) end end) ---Add actions from timing to defender object. ---@param self Defender ---@param timing Timing Defender.actions = LPH_NO_VIRTUALIZE(function(self, timing) if not Configuration.expectToggleValue("EnableAutoDefense") then return end if not self:cooldownAllow(timing) then return end for _, action in next, timing.actions:get() do self:action(timing, action) end end) ---Safely replace a function in the defender object. ---@param key string ---@param new function ---@return boolean, function function Defender:hook(key, new) -- Check if we're already hooked. if self.rhook[key] then Logger.warn("Cannot hook '%s' because it is already hooked.", key) return false, nil end -- Get our assumed old / target function. local old = self[key] -- Check if function. if typeof(old) ~= "function" then Logger.warn("Cannot hook '%s' because it is not a function.", key) return false, nil end -- Create hook. self[key] = new -- Add to hook table with the old function so we can restore it on clean-up. self.rhook[key] = old -- Log. Logger.warn("Hooked '%s' with new function.", key) return true, old end ---Detach defender object. function Defender:detach() -- Clean self. self:clean() self.maid:clean() -- Set object nil. self = nil end ---Create new Defender object. ---@return Defender function Defender.new() local self = setmetatable({}, Defender) self.tasks = {} self.rhook = {} self.tmaid = Maid.new() self.maid = Maid.new() self.hmaid = Maid.new() self.markers = {} self.lvisualization = os.clock() self.blockTagId = nextBlockTagId() return self end -- Return Defender module. return Defender end) -- MODULE: Features/Combat/Objects/SoundDefender defineModule("Features/Combat/Objects/SoundDefender", function() ---@module Features.Combat.Objects.Defender local Defender = require("Features/Combat/Objects/Defender") ---@module Game.Timings.SaveManager local SaveManager = require("Game/Timings/SaveManager") ---@module Utility.Signal local Signal = require("Utility/Signal") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Features.Combat.Objects.RepeatInfo local RepeatInfo = require("Features/Combat/Objects/RepeatInfo") ---@module Features.Combat.Objects.HitboxOptions local HitboxOptions = require("Features/Combat/Objects/HitboxOptions") ---@class SoundDefender: Defender ---@field owner Model? The owner of the part. ---@field sound Sound The sound that we're defending. ---@field part BasePart A part that we can base the position off of. local SoundDefender = setmetatable({}, { __index = Defender }) SoundDefender.__index = SoundDefender SoundDefender.__type = "Sound" -- Services. local players = game:GetService("Players") ---Check if we're in a valid state to proceed with the action. ---@param self SoundDefender ---@param timing PartTiming ---@param action Action ---@return boolean SoundDefender.valid = LPH_NO_VIRTUALIZE(function(self, timing, action) if not Defender.valid(self, timing, action) then return false end if self.owner and not self:target(self.owner) then return self:notify(timing, "Not a viable target.") end local character = players.LocalPlayer.Character if not character then return self:notify(timing, "No character found.") end local options = HitboxOptions.new(self.part, timing, { character }) options.spredict = true options.ptime = soundPredictionSeconds() options.entity = self.owner or self.part options.action = action if not self:hc(options, timing.duih and RepeatInfo.new(timing) or nil) then return self:notify(timing, "Not in hitbox.") end return true end) ---Repeat conditional. ---@param self SoundDefender ---@param _ RepeatInfo ---@return boolean SoundDefender.rc = LPH_NO_VIRTUALIZE(function(self, _) if not self.sound.IsPlaying then return false end return true end) ---Process sound playing. ---@param self SoundDefender SoundDefender.process = LPH_NO_VIRTUALIZE(function(self) ---@type SoundTiming? local timing = self:initial( self.owner or self.part, SaveManager.ss, self.owner and self.owner.Name or self.part.Name, tostring(self.sound.SoundId) ) if not timing then return end local distance = self:distance(self.owner or self.part) if type(handleAutoCounterTiming) == "function" then handleAutoCounterTiming(timing, "Sound", distance) end if not Configuration.expectToggleValue("EnableAutoDefense") then return end if players.LocalPlayer.Character and self.owner == players.LocalPlayer.Character then return end ---@note: Clean up previous tasks that are still waiting or suspended because they're in a different track. self:clean() -- Add actions. return self:actions(timing) end) ---Create new SoundDefender object. ---@param sound Sound ---@param part BasePart ---@return SoundDefender function SoundDefender.new(sound, part) local self = setmetatable(Defender.new(), SoundDefender) local soundPlayed = Signal.new(sound.Played) self.sound = sound self.part = part self.owner = sound:FindFirstAncestorWhichIsA("Model") self.maid:mark(soundPlayed:connect( "SoundDefender_OnSoundPlayed", LPH_NO_VIRTUALIZE(function() self:process() end) )) if sound.Playing then self:process() end return self end -- Return SoundDefender module. return SoundDefender end) -- MODULE: Features/Combat/Objects/PartDefender defineModule("Features/Combat/Objects/PartDefender", function() ---@module Features.Combat.Objects.Defender local Defender = require("Features/Combat/Objects/Defender") ---@module Game.Timings.SaveManager local SaveManager = require("Game/Timings/SaveManager") ---@module Features.Combat.Objects.RepeatInfo local RepeatInfo = require("Features/Combat/Objects/RepeatInfo") ---@module Features.Combat.Objects.HitboxOptions local HitboxOptions = require("Features/Combat/Objects/HitboxOptions") ---@class PartDefender: Defender ---@field part BasePart ---@field timing PartTiming ---@field touched boolean Determines whether if we touched the timing in the past. local PartDefender = setmetatable({}, { __index = Defender }) PartDefender.__index = PartDefender PartDefender.__type = "Part" -- Services. local players = game:GetService("Players") local HIT_COLOR = Color3.new(0, 1, 0) local MISS_COLOR = Color3.new(1, 0, 0) local exclusiveUntil = {} local function partPredictionSeconds() return 0.15 + Defender.rdelay() end local function applyWorldOffset(cframe, offset) if not offset or offset == Vector3.zero then return cframe end local rotation = cframe - cframe.Position local applied = (cframe.RightVector * offset.X) + (cframe.UpVector * offset.Y) + (cframe.LookVector * offset.Z) return CFrame.new(cframe.Position + applied) * rotation end local function normalizeCharName(value) return string.lower(tostring(value or "")):gsub("[^%w]", "") end local function getLastLoadedChar(player) if not player then return nil end local raw = player:GetAttribute("LastLoadedChar") if raw ~= nil then return raw end local character = player.Character if character then return character:GetAttribute("LastLoadedChar") end return nil end local function matchesCharRequirement(part, timing) if not timing.pchar or timing.pchar == "" then return true end local directChar = part:GetAttribute("LastLoadedChar") if not directChar then local parent = part.Parent if parent then directChar = parent:GetAttribute("LastLoadedChar") end end if not directChar then local modelAttr = part:FindFirstAncestorWhichIsA("Model") if modelAttr then directChar = modelAttr:GetAttribute("LastLoadedChar") end end if directChar then return normalizeCharName(directChar) == normalizeCharName(timing.pchar) end local owner = nil local model = part:FindFirstAncestorWhichIsA("Model") if model then owner = players:GetPlayerFromCharacter(model) end local creator = part:FindFirstChild("creator") or part:FindFirstChild("Creator") if not owner and creator and creator.Value and creator.Value:IsA("Player") then owner = creator.Value end local ownerAttr = part:GetAttribute("Owner") if not owner and typeof(ownerAttr) == "Instance" and ownerAttr:IsA("Player") then owner = ownerAttr end local ownerId = part:GetAttribute("OwnerUserId") if not owner and type(ownerId) == "number" then owner = players:GetPlayerByUserId(ownerId) end if not owner then local closest = nil local closestDist = math.huge local targetNorm = normalizeCharName(timing.pchar) for _, plr in ipairs(players:GetPlayers()) do if plr == players.LocalPlayer then continue end local raw = getLastLoadedChar(plr) if raw and normalizeCharName(raw) == targetNorm then local char = plr.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if root then local dist = (root.Position - part.Position).Magnitude if dist < closestDist then closestDist = dist closest = plr end end end end owner = closest end if owner == players.LocalPlayer then return false end local raw = getLastLoadedChar(owner) if not raw then return false end if raw == timing.pchar then return true end return normalizeCharName(raw) == normalizeCharName(timing.pchar) end local function matchesColorRequirement(part, timing) if not timing.pbrick and not timing.prgb then return true end if timing.pbrick then local partBrick = part.BrickColor and part.BrickColor.Name or nil if not partBrick or string.lower(partBrick) ~= string.lower(timing.pbrick) then return false end end if timing.prgb then local r = timing.prgb.r or timing.prgb.R or timing.prgb[1] local g = timing.prgb.g or timing.prgb.G or timing.prgb[2] local b = timing.prgb.b or timing.prgb.B or timing.prgb[3] if type(r) ~= "number" or type(g) ~= "number" or type(b) ~= "number" then return false end local color = part.Color if not color then return false end local pr = math.floor(color.R * 255 + 0.5) local pg = math.floor(color.G * 255 + 0.5) local pb = math.floor(color.B * 255 + 0.5) if math.abs(pr - r) > 1 or math.abs(pg - g) > 1 or math.abs(pb - b) > 1 then return false end end return true end local function timingSpecificity(timing) local score = 0 if timing.pbrick then score += 1 end if timing.prgb then score += 2 end if timing.pchar then score += 3 end return score end local function matchesTiming(part, timing) local pname = timing.pname local partName = string.lower(part.Name) if pname and pname ~= "" then if string.lower(pname) ~= partName then return false end elseif string.lower(timing.name) ~= partName then return false end if not matchesColorRequirement(part, timing) then return false end if not matchesCharRequirement(part, timing) then return false end return true end local function resolveTiming(part) local best = nil local bestScore = -1 for _, timing in ipairs(SaveManager.ps:list()) do if not matchesTiming(part, timing) then continue end local score = timingSpecificity(timing) if score > bestScore then bestScore = score best = timing end end return best end local function exclusiveKey(timing) if timing.pname and timing.pname ~= "" then return timing.pname end return timing.name or "" end local function exclusiveActive(timing) local seconds = timing.exclusiveSeconds or 0 if seconds <= 0 then return false end local key = exclusiveKey(timing) local untilTime = exclusiveUntil[key] if not untilTime then return false end if os.clock() >= untilTime then exclusiveUntil[key] = nil return false end return true end local function markExclusive(timing) local seconds = timing.exclusiveSeconds or 0 if seconds <= 0 then return end local key = exclusiveKey(timing) exclusiveUntil[key] = os.clock() + seconds end ---Get CFrame. ---@param self PartDefender ---@return CFrame PartDefender.cframe = LPH_NO_VIRTUALIZE(function(self) return self.timing.uhc and self.part.CFrame or CFrame.new(self.part.Position) end) ---Check if we're in a valid state to proceed with the action. ---@param self PartDefender ---@param timing PartTiming ---@param action Action ---@return boolean PartDefender.valid = LPH_NO_VIRTUALIZE(function(self, timing, action) if not Defender.valid(self, timing, action) then return false end if exclusiveActive(timing) then return false end local character = players.LocalPlayer.Character if not character then return self:notify(timing, "No character found.") end local options = HitboxOptions.new(self.part, timing, { character }) options.spredict = true options.ptime = partPredictionSeconds() options.entity = self.entity or self.part options.action = action if self.hmid then options.hmid = self.hmid end if not self.timing.duih then if not self:hc(options, timing.duih and RepeatInfo.new(timing) or nil) then return self:notify(timing, "Not in hitbox.") end end return true end) ---Update PartDefender object. ---@param self PartDefender PartDefender.update = LPH_NO_VIRTUALIZE(function(self) if not self.timing.duih then return end if exclusiveActive(self.timing) then return end if #self.tasks > 0 then return end local localPlayer = players.LocalPlayer if not localPlayer then return end local character = localPlayer.Character if not character then return end local hb = self.timing.hitbox hb = Vector3.new(PP_SCRAMBLE_NUM(hb.X), PP_SCRAMBLE_NUM(hb.Y), PP_SCRAMBLE_NUM(hb.Z)) local shape = self.timing.shape and PP_SCRAMBLE_STR(self.timing.shape) or "Box" local options = HitboxOptions.new(self.part, self.timing, { character }) options.spredict = true options.ptime = partPredictionSeconds() options.entity = self.entity or self.part if not self.hmid then self.hmid = self.hmaid:uid() end options.hmid = self.hmid local touching = self:hc(options, nil) if not touching then return end if self.touched and touching then return end self.touched = touching self:clean() markExclusive(self.timing) if type(handleAutoCounterTiming) == "function" then handleAutoCounterTiming(self.timing, "Part", self:distance(self.part)) end return self:actions(self.timing) end) function PartDefender:detach() Defender.detach(self) end ---Create new PartDefender object. ---@param part BasePart ---@param timing PartTiming? ---@return PartDefender? function PartDefender.new(part, timing) local self = setmetatable(Defender.new(), PartDefender) self.part = part self.entity = part:FindFirstAncestorWhichIsA("Model") local selected = timing or resolveTiming(part) if not selected then self:initial(part, SaveManager.ps, nil, part.Name) return nil end local distance = self:distance(part) if not distance then return nil end if distance < PP_SCRAMBLE_NUM(selected.imdd) or distance > PP_SCRAMBLE_NUM(selected.imxd) then return nil end self.timing = selected self.touched = false if not self.timing then return nil end if self.timing.umoa then self:module(self.timing) end if type(handleAutoCounterTiming) == "function" then handleAutoCounterTiming(self.timing, "Part", distance) end if not self.timing.umoa and not self.timing.duih then self:actions(self.timing) end return self end -- Return PartDefender module. return PartDefender end) -- MODULE: Features/Combat/Objects/HitboxOptions defineModule("Features/Combat/Objects/HitboxOptions", function() ---@class HitboxOptions ---@note: Options for the hitbox check. ---@field part BasePart? If this is specified and it exists, it will be used for the position. ---@field cframe CFrame? Else, the part's CFrame will be used. ---@field timing Timing|AnimationTiming|SoundTiming ---@field action Action? ---@field filter Instance[] ---@field spredict boolean If true, a check will run for predicted positions. ---@field ptime number? The predicted time in seconds for extrapolation. ---@field entity Model? The entity for extrapolation. ---@field phcolor Color3 The color for predicted hitboxes. ---@field pmcolor Color3 The color for predicted missed hitboxes. ---@field hcolor Color3 The color for hitboxes. ---@field mcolor Color3 The color for missed hitboxes. ---@field hmid number? Hitbox visualization ID for normal hitbox check. local HitboxOptions = {} HitboxOptions.__index = HitboxOptions -- Services. local players = game:GetService("Players") local function applyHitboxWorldOffset(cframe, offset) if not offset or offset == Vector3.zero then return cframe end local rotation = cframe - cframe.Position local applied = (cframe.RightVector * offset.X) + (cframe.UpVector * offset.Y) + (cframe.LookVector * offset.Z) return CFrame.new(cframe.Position + applied) * rotation end ---Hit color. ---@return Color3 function HitboxOptions:ghcolor(result) return result and self.hcolor or self.mcolor end ---Predicted hit color. ---@return Color3 function HitboxOptions:gphcolor(result) return result and self.phcolor or self.pmcolor end ---Cloned hitbox options. ---@return HitboxOptions function HitboxOptions:clone() local options = setmetatable({}, HitboxOptions) options.action = self.action options.spredict = self.spredict options.entity = self.entity options.ptime = self.ptime options.entity = self.entity options.phcolor = self.phcolor options.pmcolor = self.pmcolor options.hcolor = self.hcolor options.mcolor = self.mcolor options.hmid = self.hmid options.filter = self.filter options.part = self.part options.cframe = self.cframe options.timing = self.timing return options end ---Get the hitbox shape. ---@return string function HitboxOptions:shape() local shape = self.action and self.action.shape or self.timing.shape if self.timing.duih then shape = self.timing.shape end if typeof(shape) == "string" then shape = PP_SCRAMBLE_STR(shape) end return shape == "Ball" and "Ball" or "Box" end ---Get the hitbox size. ---@return Vector3 function HitboxOptions:hitbox() local hitbox = self.action and self.action.hitbox or self.timing.hitbox if self.timing.duih then hitbox = self.timing.hitbox end hitbox = Vector3.new(PP_SCRAMBLE_NUM(hitbox.X), PP_SCRAMBLE_NUM(hitbox.Y), PP_SCRAMBLE_NUM(hitbox.Z)) return hitbox end ---Get the hitbox offset. ---@return Vector3 function HitboxOptions:offset() local offset = self.action and self.action.offset or self.timing.offset if self.timing.duih then offset = self.timing.offset end if typeof(offset) ~= "Vector3" then return Vector3.zero end return Vector3.new(PP_SCRAMBLE_NUM(offset.X), PP_SCRAMBLE_NUM(offset.Y), PP_SCRAMBLE_NUM(offset.Z)) end ---Get extrapolated position. ---@return CFrame HitboxOptions.extrapolate = LPH_NO_VIRTUALIZE(function(self) if not self.part then return error("HitboxOptions.extrapolate - unimplemented for CFrame") end if not self.entity then return error("HitboxOptions.extrapolate - no entity specified") end if not self.ptime then return error("HitboxOptions.extrapolate - no predicted time specified") end -- Return the extrapolated position. local cframe = self.part.CFrame + (self.part.AssemblyLinearVelocity * self.ptime) return applyHitboxWorldOffset(cframe, self:offset()) end) ---Get position. ---@return CFrame HitboxOptions.pos = LPH_NO_VIRTUALIZE(function(self) local cframe = nil if self.cframe then cframe = self.cframe end if self.part then cframe = self.part.CFrame end if not cframe then return error("HitboxOptions.pos - impossible condition") end return applyHitboxWorldOffset(cframe, self:offset()) end) ---Create new HitboxOptions object. ---@param target Instance|CFrame ---@param timing Timing|AnimationTiming|SoundTiming ---@param filter Instance[]? ---@return HitboxOptions HitboxOptions.new = LPH_NO_VIRTUALIZE(function(target, timing, filter) local self = setmetatable({}, HitboxOptions) self.part = typeof(target) == "Instance" and target:IsA("BasePart") and target self.cframe = typeof(target) == "CFrame" and target self.timing = timing self.action = nil self.filter = filter or {} self.spredict = false self.hmid = nil self.entity = nil self.phcolor = Color3.new(1, 0, 1) self.pmcolor = Color3.new(0.349019, 0.345098, 0.345098) self.hcolor = Color3.new(0, 1, 0) self.mcolor = Color3.new(1, 0, 0) self.ptime = nil if not self.part and not self.cframe then return error("HitboxOptions: No part or CFrame specified.") end if filter then return self end local character = players.LocalPlayer.Character if not character then return self end self.filter = { character } return self end) -- Return HitboxOptions module. return HitboxOptions end) -- MODULE: Features/Combat/Objects/RepeatInfo defineModule("Features/Combat/Objects/RepeatInfo", function() ---@note: Typed object that represents information. It's not really a true class but just needs to store the correct data. ---@class RepeatInfo ---@field track AnimationTrack? ---@field timing Timing ---@field start number ---@field index number ---@field irdelay number Initial receive delay. local RepeatInfo = {} RepeatInfo.__index = RepeatInfo ---Create new RepeatInfo object. ---@param timing Timing ---@param irdelay number ---@return RepeatInfo function RepeatInfo.new(timing, irdelay) local self = setmetatable({}, RepeatInfo) self.track = nil self.timing = timing self.start = os.clock() self.index = 0 self.irdelay = irdelay return self end -- Return RepeatInfo module. return RepeatInfo end) -- MODULE: Features/Combat/Objects/Target defineModule("Features/Combat/Objects/Target", function() ---@note: Typed object that represents a target. It's not really a true class but just needs to store the correct data. ---@class Target ---@field character Model ---@field humanoid Humanoid ---@field root BasePart ---@field dc number Distance to crosshair. ---@field fov number Field of view to target. ---@field du number Distance to us. local Target = {} ---Create new Target object. ---@param character Model ---@param humanoid Humanoid ---@param root BasePart ---@param dc number ---@param fov number ---@param du number ---@return Target function Target.new(character, humanoid, root, dc, fov, du) local self = setmetatable({}, Target) self.character = character self.humanoid = humanoid self.root = root self.dc = dc self.fov = fov self.du = du return self end -- Return Target module. return Target end) -- MODULE: Features/Combat/Objects/Task defineModule("Features/Combat/Objects/Task", function() ---@module Utility.TaskSpawner local TaskSpawner = require("Utility/TaskSpawner") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@class Task ---@field thread thread ---@field identifier string ---@field _when number A timestamp when the task will be executed. ---@field punishable number A window in seconds where the task can be punished. ---@field after number A window in seconds where the task can be executed. ---@field delay function local Task = {} Task.__index = Task ---Check if task should block the input. ---@return boolean function Task:blocking() if not (coroutine.status(self.thread) ~= "dead") then return false end -- We've exceeded the execution time. Block if we're within the after window. local whenAt = Task.when(self) if os.clock() >= whenAt then return os.clock() <= whenAt + self.after end ---@note: Allow us to do inputs up until a certain amount of time before the task happens. return os.clock() >= whenAt - self.punishable end ---Cancel task. function Task:cancel() if coroutine.status(self.thread) ~= "suspended" then return end task.cancel(self.thread) end ---Get when approximately the task will be executed. ---@return number function Task:when() local base = self._when if type(base) ~= "number" then local legacy = self.when if type(legacy) == "number" then base = legacy else base = 0 end end local delay = self.delay if type(delay) == "function" then return base + delay() end return base + (delay or 0) end ---Create new Task object. ---@param identifier string ---@param delay function ---@param punishable number ---@param after number ---@param callback function ---@vararg any ---@return Task function Task.new(identifier, delay, punishable, after, callback, ...) local self = setmetatable({}, Task) self.identifier = identifier self._when = os.clock() self.delay = delay self.punishable = punishable self.after = after self.thread = TaskSpawner.delay("Action_" .. identifier, delay, callback, ...) if not self.punishable or self.punishable <= 0 then self.punishable = Configuration.expectOptionValue("DefaultPunishableWindow") or 0.7 end if not self.after or self.after <= 0 then self.after = Configuration.expectOptionValue("DefaultAfterWindow") or 0.1 end return self end -- Return Task module. return Task end) -- MODULE: Features/Combat/PositionHistory defineModule("Features/Combat/PositionHistory", function() -- PositionHistory module. local PositionHistory = {} -- Histories table. local histories = {} -- Max history seconds. local MAX_HISTORY_SECS = 3.0 ---Add an entry to the history list. ---@param idx any ---@param position CFrame ---@param timestamp number function PositionHistory.add(idx, position, timestamp) local history = histories[idx] or {} if not histories[idx] then histories[idx] = history end history[#history + 1] = { position = position, timestamp = timestamp, } while true do local tail = history[1] if not tail then break end if tick() - tail.timestamp <= MAX_HISTORY_SECS then break end table.remove(history, 1) end end ---Get the horizontal angular velocity (yaw rate) for a current index. ---@param index any ---@return number? function PositionHistory.yrate(index) local history = histories[index] if not history or #history < 2 then return nil end local latest = history[#history] local previous = history[#history - 1] local dt = latest.timestamp - previous.timestamp if dt <= 1e-4 then return nil end local prevLook = Vector3.new(previous.position.LookVector.X, 0, previous.position.LookVector.Z).Unit local latestLook = Vector3.new(latest.position.LookVector.X, 0, latest.position.LookVector.Z).Unit local dot = prevLook:Dot(latestLook) local crossY = prevLook:Cross(latestLook).Y local angle = math.atan2(crossY, dot) return angle / dt end ---Divides the history into a number of equal steps and returns the position at each step. ---@param idx any ---@param steps number ---@param phds number History second limit for past hitbox detection. ---@return CFrame[]? function PositionHistory.stepped(idx, steps, phds) local history = histories[idx] if not history or #history == 0 then return nil end if not steps or steps <= 0 then return nil end local vhistory = {} local vhtime = history[#history].timestamp for _, data in next, history do if vhtime - data.timestamp > phds then continue end vhistory[#vhistory + 1] = data.position end if #vhistory == 0 then return {} end local count = math.min(steps, #vhistory) local out = table.create(count) for cidx = 1, count do out[cidx] = vhistory[math.max(math.floor((cidx * #vhistory) / count), 1)] end return out end ---Get closest position (in time) to a timestamp. ---@param idx any ---@param timestamp number ---@return CFrame? function PositionHistory.closest(idx, timestamp) if not histories[idx] then return nil end local closestDelta = nil local closestPosition = nil for _, data in next, histories[idx] do local delta = math.abs(timestamp - data.timestamp) if closestDelta and delta >= closestDelta then continue end closestPosition = data.position closestDelta = delta end return closestPosition end -- Return PositionHistory module. return PositionHistory end) -- MODULE: Features/Combat/Targeting defineModule("Features/Combat/Targeting", function() -- Targeting module. ---@note: Glorified extended non-utility Entities file. local Targeting = {} ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Features.Combat.Objects.Target local Target = require("Features/Combat/Objects/Target") ---@module Utility.Table local Table = require("Utility/Table") -- Services. local players = game:GetService("Players") local userInputService = game:GetService("UserInputService") local targetCache = { lastAt = 0, lastMouse = nil, lastLocalPos = nil, lastCamCFrame = nil, targets = nil, sortType = nil, maxTargets = nil, } local function shouldRebuildTargets(localRootPart, currentCamera, sortType, maxTargets) if not targetCache.targets then return true end local now = os.clock() if now - (targetCache.lastAt or 0) > 0.05 then return true end if targetCache.sortType ~= sortType or targetCache.maxTargets ~= maxTargets then return true end local mousePos = userInputService:GetMouseLocation() local lastMouse = targetCache.lastMouse if not lastMouse then return true end local dx = mousePos.X - lastMouse.X local dy = mousePos.Y - lastMouse.Y if (dx * dx + dy * dy) > 4 then return true end if not localRootPart then return true end local lastPos = targetCache.lastLocalPos if not lastPos or (localRootPart.Position - lastPos).Magnitude > 2 then return true end if not currentCamera then return true end local lastCam = targetCache.lastCamCFrame if not lastCam then return true end local camCFrame = currentCamera.CFrame if (camCFrame.Position - lastCam.Position).Magnitude > 0.5 then return true end if camCFrame.LookVector:Dot(lastCam.LookVector) < 0.999 then return true end return false end local function isAlly(player) local localPlayer = players.LocalPlayer if not localPlayer or not player then return false end local ok, status = pcall(localPlayer.GetFriendStatus, localPlayer, player) if not ok then return false end return status == Enum.FriendStatus.Friend end ---Get a list of all viable targets. ---@return Target[] Targeting.viable = LPH_NO_VIRTUALIZE(function() local ents = workspace:FindFirstChild("Live") if not ents then return {} end local localCharacter = players.LocalPlayer.Character if not localCharacter then return {} end local localRootPart = localCharacter and localCharacter:FindFirstChild("HumanoidRootPart") if not localRootPart then return {} end local currentCamera = workspace.CurrentCamera if not currentCamera then return {} end local targets = {} for _, entity in next, ents:GetChildren() do if entity == localCharacter then continue end local playerFromCharacter = players:GetPlayerFromCharacter(entity) if not playerFromCharacter then continue end if Configuration.expectToggleValue("IgnorePlayers") then continue end local humanoid = entity:FindFirstChildWhichIsA("Humanoid") if not humanoid then continue end local rootPart = entity:FindFirstChild("HumanoidRootPart") if not rootPart then continue end if humanoid.Health <= 0 then continue end if playerFromCharacter then local usernameList = Options["UsernameList"] local displayNameFound = table.find(usernameList.Values, playerFromCharacter.DisplayName) local usernameFound = table.find(usernameList.Values, playerFromCharacter.Name) if displayNameFound or usernameFound then continue end end local fieldOfViewToEntity = currentCamera.CFrame.LookVector:Dot((localRootPart.Position - rootPart.Position).Unit) local currentDistance = (rootPart.Position - localRootPart.Position).Magnitude if currentDistance > Configuration.expectOptionValue("DistanceLimit") then continue end if playerFromCharacter and isAlly(playerFromCharacter) and Configuration.expectToggleValue("IgnoreAllies") then continue end local mousePosition = userInputService:GetMouseLocation() local unitRay = workspace.CurrentCamera:ScreenPointToRay(mousePosition.X, mousePosition.Y) local distanceToCrosshair = unitRay:Distance(rootPart.Position) targets[#targets + 1] = Target.new(entity, humanoid, rootPart, distanceToCrosshair, fieldOfViewToEntity, currentDistance) end return targets end) ---Get the best targets through sorting. ---@return Target[] Targeting.best = LPH_NO_VIRTUALIZE(function() local sortType = Configuration.expectOptionValue("PlayerSelectionType") local maxTargets = Configuration.expectOptionValue("MaxTargets") local localCharacter = players.LocalPlayer and players.LocalPlayer.Character local localRootPart = localCharacter and localCharacter:FindFirstChild("HumanoidRootPart") local currentCamera = workspace.CurrentCamera if localRootPart and currentCamera and not shouldRebuildTargets(localRootPart, currentCamera, sortType, maxTargets) then return targetCache.targets end local targets = Targeting.viable() local sortFunction = nil if sortType == "Closest To Crosshair" then sortFunction = function(first, second) return first.dc < second.dc end end if sortType == "Closest In Distance" then sortFunction = function(first, second) return first.du < second.du end end if sortType == "Least Health" then sortFunction = function(first, second) return first.humanoid.Health < second.humanoid.Health end end table.sort(targets, sortFunction) local sliced = Table.slice(targets, 1, maxTargets) targetCache.targets = sliced targetCache.lastAt = os.clock() targetCache.lastMouse = userInputService:GetMouseLocation() targetCache.lastLocalPos = localRootPart and localRootPart.Position or nil targetCache.lastCamCFrame = currentCamera and currentCamera.CFrame or nil targetCache.sortType = sortType targetCache.maxTargets = maxTargets return sliced end) ---Find our model from a list of best targets. ---@param model Model ---@return Target? Targeting.find = LPH_NO_VIRTUALIZE(function(model) for _, target in next, Targeting.best() do if target.character ~= model then continue end return target end end) -- Return Targeting module. return Targeting end) -- MODULE: Game/InputClient defineModule("Game/InputClient", function() local InputClient = {} local players = game:GetService('Players') local workspaceService = game:GetService('Workspace') local runService = game:GetService('RunService') local virtualInput = game:GetService('VirtualInputManager') local userInputService = game:GetService('UserInputService') local Configuration = require('Utility/Configuration') local Keybinding = require('Game/Keybinding') local setThreadIdentity = setthreadidentity or set_thread_identity or (syn and syn.set_thread_identity) local getThreadIdentity = getthreadidentity or get_thread_identity or (syn and syn.get_thread_identity) local ROBLOX_IDENTITY = 8 local getgc = getgc local getinfo = debug.getinfo local hookmetamethod = hookmetamethod local newcclosure = newcclosure local clock = os.clock local DODGE_CAMERA_SPOOF_SECONDS = 0.30 local JUMP_HOLD_SECONDS = 0.05 local M1_MIN_HOLD_SECONDS = 0.02 local BLOCK_TYPE_DEFLECT = 1 local BLOCK_TYPE_NORMAL = 2 local BLOCK_RESEND_COOLDOWN = 0.05 local blockQueue = {} local blockStack = {} local blockId = 0 local blockEvent = nil local fHeldValue = nil local lastBlockSendAt = 0 local lastUnblockSendAt = 0 local dodgeFunc = nil local dodgeResolveCooldownUntil = 0 local cameraSpoofActive = false local cameraSpoofCFrame = nil local cameraSpoofEpoch = 0 local cameraSpoofUntil = 0 local cameraInstance = nil local cameraHooked = false local cameraIndexOld = nil local cachedLogger = nil local lastDodgeErrorAt = 0 local blockOff local function isManualBlockHeld() local key = Keybinding and Keybinding.info and Keybinding.info.Block if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then return false end return userInputService:IsKeyDown(key) end local function resetDashState() blockQueue = {} blockStack = {} blockId = 0 fHeldValue = nil blockEvent = nil lastBlockSendAt = 0 lastUnblockSendAt = 0 dodgeFunc = nil dodgeResolveCooldownUntil = 0 cameraSpoofActive = false cameraSpoofCFrame = nil cameraSpoofEpoch = 0 cameraSpoofUntil = 0 cameraInstance = nil if blockOff then task.defer(blockOff) end end do local lp = players.LocalPlayer if not lp then players:GetPropertyChangedSignal("LocalPlayer"):Wait() lp = players.LocalPlayer end if lp then lp.CharacterAdded:Connect(function() resetDashState() end) lp.CharacterRemoving:Connect(function() resetDashState() end) end end local function getLogger() if cachedLogger then return cachedLogger end local ok, logger = pcall(require, 'Utility/Logger') if ok then cachedLogger = logger return cachedLogger end return nil end local function withRobloxIdentity(action) local ok, err if setThreadIdentity and getThreadIdentity then local prev = getThreadIdentity() if prev ~= ROBLOX_IDENTITY then setThreadIdentity(ROBLOX_IDENTITY) end ok, err = pcall(action) if prev ~= ROBLOX_IDENTITY then setThreadIdentity(prev) end else ok, err = pcall(action) end if not ok then local logger = getLogger() if logger and logger.warn then logger.warn("VirtualInputManager failed: %s", tostring(err)) else warn(string.format("[Cottom hub AB] VirtualInputManager failed: %s", tostring(err))) end end return ok end local VIM_SendKeyEvent = virtualInput and virtualInput.SendKeyEvent or nil local function sendKeyEvent(isDown, keyCode) if not virtualInput or not VIM_SendKeyEvent then return false end return withRobloxIdentity(function() VIM_SendKeyEvent(virtualInput, isDown == true, keyCode, false, game) end) == true end local function sendMouseButtonEvent(isDown) if not virtualInput then return false end local pos = userInputService:GetMouseLocation() return withRobloxIdentity(function() virtualInput:SendMouseButtonEvent(pos.X, pos.Y, 0, isDown == true, game, 0) end) == true end local function useControllerM1() return Configuration.expectToggleValue("UseControllerM1") == true end local function sendControllerM1(isDown) if not useControllerM1() then return false end return sendKeyEvent(isDown == true, Enum.KeyCode.ButtonR2) end local function safeGetInfo(...) if not getinfo then return nil end local ok, info = pcall(getinfo, ...) if not ok then return nil end return info end local function resolveBlockEvent() if blockEvent and blockEvent.Parent ~= nil and players.LocalPlayer and players.LocalPlayer.Backpack and blockEvent:IsDescendantOf(players.LocalPlayer.Backpack) then return blockEvent end local lp = players.LocalPlayer if not lp then return nil end local backpack = lp:FindFirstChild("Backpack") local traits = backpack and backpack:FindFirstChild("ServerTraits") local input = traits and traits:FindFirstChild("Input") if input and input:IsA("RemoteEvent") then blockEvent = input return input end return nil end local function resolveToolRange(tool) if not tool then return nil end local config = tool:FindFirstChild("Config") local rangeValue = config and config:FindFirstChild("range") if not rangeValue then rangeValue = tool:FindFirstChild("range") or tool:FindFirstChild("Range") end if rangeValue and rangeValue.Value ~= nil then return tostring(rangeValue.Value) end local attr = tool:GetAttribute("range") or tool:GetAttribute("Range") if attr ~= nil then return tostring(attr) end return nil end local function buildFirePayload(toolName, rangeValue) if not toolName or toolName == "" then return nil end local cam = workspaceService.CurrentCamera if not cam then return nil end local mousePos = userInputService:GetMouseLocation() local ray = cam:ViewportPointToRay(mousePos.X, mousePos.Y) local dir = ray.Direction if dir.Magnitude <= 0 then return nil end local origin = ray.Origin local hitPos = origin + dir * 500 local mouseHit = CFrame.new(hitPos, hitPos + dir) local humanoid = players.LocalPlayer and players.LocalPlayer.Character and players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid") local air = humanoid and humanoid.FloorMaterial == Enum.Material.Air or false return { air = air, mousehit = mouseHit, camdir = dir.Unit, neutral = true, campos = origin, range = rangeValue or "2", ToolName = toolName, } end local function resolveFHeld() local lp = players.LocalPlayer if not lp then return nil end local char = lp.Character local flag = char and char:FindFirstChild("FHeld") if flag and flag:IsA("BoolValue") then fHeldValue = flag return flag end local live = workspaceService:FindFirstChild("Live") local liveChar = live and live:FindFirstChild(lp.Name) flag = liveChar and liveChar:FindFirstChild("FHeld") if flag and flag:IsA("BoolValue") then fHeldValue = flag return flag end return nil end local function getBlockingState() local flag = fHeldValue local char = players.LocalPlayer and players.LocalPlayer.Character or nil if flag then if flag.Parent == nil then flag = nil elseif char and not flag:IsDescendantOf(char) then flag = nil end end if not flag then flag = resolveFHeld() end if flag then return flag.Value == true, true end return false, false end local function blockOn() local ev = resolveBlockEvent() if ev then ev:FireServer("f") return true end return false end blockOff = function() local ev = resolveBlockEvent() if ev then ev:FireServer("foff") return true end return false end local function nextBlockId(prefix) blockId += 1 return string.format("%s_%d", prefix or "Block", blockId) end local function queueBlock(kind, duration) local wasEmpty = next(blockQueue) == nil local id = nextBlockId(kind == BLOCK_TYPE_DEFLECT and "Deflect" or "Block") blockQueue[id] = { start = clock(), type = kind, dt = duration } if wasEmpty then local blocking = getBlockingState() if not blocking then blockOn() lastBlockSendAt = clock() end end return id end local function queueBlockWithId(id, kind, duration) if not id or id == "" then return queueBlock(kind, duration) end local wasEmpty = next(blockQueue) == nil blockQueue[id] = { start = clock(), type = kind, dt = duration } if wasEmpty then local blocking = getBlockingState() if not blocking then blockOn() lastBlockSendAt = clock() end end return id end local function unqueueBlock(id) if not id or not blockQueue[id] then return end if timing._id == "rbxassetid://13631792671" then local dummyAction = { ihbc = true } if not self:valid(timing, dummyAction) then return end local dist = distance or self:distance(self.entity) or math.huge local holdSeconds = dist <= 40 and 1.2 or 1.6 InputClient.blockHoldSeconds(holdSeconds) return end blockQueue[id] = nil end local function stopBlockId(id) if not id or not blockQueue[id] then return end blockQueue[id] = nil end local function blockAcquire() local id = queueBlock(BLOCK_TYPE_NORMAL, nil) blockStack[#blockStack + 1] = id end local function blockRelease() local idx = #blockStack if idx <= 0 then return end local id = blockStack[idx] blockStack[idx] = nil unqueueBlock(id) end local function cleanupBlockQueue(blocking) local now = clock() for id, data in next, blockQueue do if data.dt and now - data.start >= data.dt then blockQueue[id] = nil elseif data.type == BLOCK_TYPE_DEFLECT and blocking then blockQueue[id] = nil end end end local blockSyncConn = nil local lastWantsBlock = false local function ensureBlockSync() if blockSyncConn then return end blockSyncConn = runService.RenderStepped:Connect(LPH_NO_VIRTUALIZE(function() local blocking, hasFlag = getBlockingState() cleanupBlockQueue(blocking) local wantsBlock = next(blockQueue) ~= nil if wantsBlock then if not blocking then local now = clock() if now - lastBlockSendAt >= BLOCK_RESEND_COOLDOWN then blockOn() lastBlockSendAt = now end end lastWantsBlock = true return end local autoDefenseEnabled = Configuration.expectToggleValue("EnableAutoDefense") local m1TradeEnabled = Configuration.expectToggleValue("EnableM1AutoTrade") if not autoDefenseEnabled and not m1TradeEnabled then lastWantsBlock = false return end if m1TradeEnabled and not autoDefenseEnabled then if isManualBlockHeld() then lastWantsBlock = false return end if not lastWantsBlock then return end end if blocking or not hasFlag then local now = clock() if now - lastUnblockSendAt >= BLOCK_RESEND_COOLDOWN then blockOff() lastUnblockSendAt = now end end lastWantsBlock = false end)) end ensureBlockSync() function InputClient.block(state) if state == true then blockAcquire() else blockRelease() end end function InputClient.deflect() local holdSeconds = Configuration.expectOptionValue("BlockHoldSeconds") if holdSeconds == nil then holdSeconds = (Configuration.expectOptionValue("DeflectHoldTime") or 0) / 1000 end return InputClient.blockHoldSeconds(math.max(holdSeconds or 0, 0)) end function InputClient.blockHoldSeconds(seconds) local duration = seconds or 0 if duration <= 0 then return false end queueBlock(BLOCK_TYPE_NORMAL, duration) return true end function InputClient.fireTool(toolName, rangeValue) local ev = resolveBlockEvent() if not ev then return false end local payload = buildFirePayload(toolName, rangeValue) if not payload then return false end local ok = pcall(ev.FireServer, ev, "fire", payload) return ok == true end function InputClient.fireToolObject(tool) if not tool then return false end return InputClient.fireTool(tool.Name, resolveToolRange(tool)) end function InputClient.blockTap(seconds) local duration = seconds or 0 if duration <= 0 then return false end blockOn() task.delay(0.02, blockOn) task.delay(duration, function() blockOff() end) return true end function InputClient.blockTag(id, state, duration, deflect) if not id or id == "" then return InputClient.block(state) end if state then local kind = deflect and BLOCK_TYPE_DEFLECT or BLOCK_TYPE_NORMAL queueBlockWithId(id, kind, duration) return true end stopBlockId(id) return true end local function normalizeKeyCode(key) if typeof(key) == "EnumItem" and key.EnumType == Enum.KeyCode then return key end return nil end function InputClient.keyHold(keyCode, seconds) local duration = seconds or 0 if duration <= 0 then return false end local key = normalizeKeyCode(keyCode) if not key then return false end if not sendKeyEvent(true, key) then return false end task.delay(duration, function() sendKeyEvent(false, key) end) return true end local function resolveJumpKey() local key = Keybinding and Keybinding.info and Keybinding.info.Jump or Enum.KeyCode.Space if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then key = Enum.KeyCode.Space end return key end function InputClient.jumpHold(seconds) local duration = seconds or JUMP_HOLD_SECONDS return InputClient.keyHold(resolveJumpKey(), duration) end local function resolveSprintKey() local key = Keybinding and Keybinding.info and (Keybinding.info.Sprint or Keybinding.info.Run) if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then key = Enum.KeyCode.LeftShift end return key end function InputClient.sprintHold(seconds) local duration = seconds or 0 if duration <= 0 then return false end return InputClient.keyHold(resolveSprintKey(), duration) end function InputClient.jump() local key = Keybinding and Keybinding.info and Keybinding.info.Jump or Enum.KeyCode.Space if typeof(key) ~= "EnumItem" or key == Enum.KeyCode.Unknown then key = Enum.KeyCode.Space end if not sendKeyEvent(true, key) then return false end task.delay(JUMP_HOLD_SECONDS, function() sendKeyEvent(false, key) end) return true end function InputClient.tapKey(keyCode, holdSeconds) if typeof(keyCode) ~= "EnumItem" then return false end local duration = holdSeconds or 0.02 if not sendKeyEvent(true, keyCode) then return false end task.delay(duration, function() sendKeyEvent(false, keyCode) end) return true end function InputClient.m1Hold(seconds) local duration = seconds or 0 if duration <= 0 then return false end InputClient._m1AutoUntil = (InputClient._m1AutoUntil or 0) InputClient._m1AutoUntil = math.max(InputClient._m1AutoUntil, clock() + duration + 0.06) if useControllerM1() then if InputClient._m1KeyDown then sendControllerM1(false) InputClient._m1KeyDown = false end else if InputClient._m1Down then sendMouseButtonEvent(false) InputClient._m1Down = false end end InputClient._m1Token = (InputClient._m1Token or 0) + 1 local token = InputClient._m1Token if useControllerM1() then if not sendControllerM1(true) then return false end InputClient._m1KeyDown = true else if not sendMouseButtonEvent(true) then return false end InputClient._m1Down = true end local function releaseIfStill(token) if InputClient._m1Token ~= token then return end if useControllerM1() then sendControllerM1(false) InputClient._m1KeyDown = false else sendMouseButtonEvent(false) InputClient._m1Down = false end end task.delay(duration, function() releaseIfStill(token) end) task.delay(duration + 0.03, function() releaseIfStill(token) end) task.delay(duration + 0.06, function() releaseIfStill(token) end) return true end function InputClient.m1Release() InputClient._m1Token = (InputClient._m1Token or 0) + 1 if useControllerM1() then if InputClient._m1KeyDown then sendControllerM1(false) InputClient._m1KeyDown = false end else if InputClient._m1Down then sendMouseButtonEvent(false) InputClient._m1Down = false end end InputClient._m1AutoUntil = 0 return true end function InputClient.m1Tap(seconds) local duration = seconds or M1_MIN_HOLD_SECONDS return InputClient.m1Hold(duration) end local m1ReleaseConn = nil local function ensureM1Release() if m1ReleaseConn then return end m1ReleaseConn = runService.RenderStepped:Connect(LPH_NO_VIRTUALIZE(function() if useControllerM1() then if InputClient._m1KeyDown and InputClient._m1AutoUntil and clock() > InputClient._m1AutoUntil then InputClient._m1ReleaseTries = (InputClient._m1ReleaseTries or 0) + 1 sendControllerM1(false) if InputClient._m1ReleaseTries >= 6 then InputClient._m1KeyDown = false InputClient._m1AutoUntil = 0 InputClient._m1ReleaseTries = 0 end else InputClient._m1ReleaseTries = 0 end return end if InputClient._m1Down and InputClient._m1AutoUntil and clock() > InputClient._m1AutoUntil then -- Retry release for a few frames if VIM misses the up event. InputClient._m1ReleaseTries = (InputClient._m1ReleaseTries or 0) + 1 sendMouseButtonEvent(false) if InputClient._m1ReleaseTries >= 6 then InputClient._m1Down = false InputClient._m1AutoUntil = 0 InputClient._m1ReleaseTries = 0 end else InputClient._m1ReleaseTries = 0 end end)) end ensureM1Release() local function normalizeDirection(direction) if not direction or direction == "" then direction = Configuration.expectOptionValue("DefaultDashDirection") or "S" end if direction == "Random" then local options = { "W", "A", "S", "D" } direction = options[math.random(1, #options)] end if typeof(direction) == "EnumItem" then direction = tostring(direction.Name) end direction = tostring(direction) if direction == "W" or direction == "w" then direction = "forward" elseif direction == "A" or direction == "a" then direction = "left" elseif direction == "D" or direction == "d" then direction = "right" elseif direction == "S" or direction == "s" then direction = "back" else direction = direction:lower() end if direction == "forward" or direction == "back" or direction == "left" or direction == "right" then return direction end return "back" end local function resolveDodgeFunction() if dodgeFunc then return dodgeFunc end if dodgeResolveCooldownUntil > 0 and clock() < dodgeResolveCooldownUntil then return nil end local gcList = getgc(true) for i = 1, #gcList do local fn = gcList[i] if type(fn) == "function" then local info = getinfo(fn, "nS") if info and info.name == "dodge" and info.source and info.source:find("LocalCharacterScript", 1, true) then dodgeFunc = fn break end end end if not dodgeFunc then dodgeResolveCooldownUntil = clock() + 1 end return dodgeFunc end local function getDashLookVector(direction, hrpCFrame) if not hrpCFrame then return nil end local d = direction or "forward" if d == "back" then return -hrpCFrame.LookVector elseif d == "left" then return -hrpCFrame.RightVector elseif d == "right" then return hrpCFrame.RightVector end return hrpCFrame.LookVector end local function computeSpoofCameraCFrame(direction) local char = players.LocalPlayer and players.LocalPlayer.Character or nil if not char then return nil end local hrp = char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso") if not hrp then return nil end local lookVec = getDashLookVector(direction, hrp.CFrame) if not lookVec then return nil end local pos = hrp.Position return CFrame.new(pos, pos + lookVec) end local function ensureCameraHook() if cameraHooked then return end local function isDodgeOnStack() if not dodgeFunc then return false end for level = 2, 7 do local info = safeGetInfo(level, 'f') if info and info.func == dodgeFunc then return true end end return false end local handler = LPH_NO_VIRTUALIZE(function(self, key) if cameraSpoofActive and cameraInstance and self == cameraInstance then if cameraSpoofUntil > 0 and clock() > cameraSpoofUntil then cameraSpoofActive = false cameraSpoofCFrame = nil cameraSpoofUntil = 0 return cameraIndexOld(self, key) end if not isDodgeOnStack() then return cameraIndexOld(self, key) end if key == 'CFrame' or key == 'CoordinateFrame' or key == 'Focus' then return cameraSpoofCFrame end end return cameraIndexOld(self, key) end) local wrapper = handler if type(newcclosure) == 'function' then wrapper = newcclosure(handler) end local old = hookmetamethod(game, '__index', wrapper) if type(old) ~= 'function' then return end cameraIndexOld = old cameraHooked = true end local function normalizeDashDirection(direction) if direction == "left" then return "right" end return direction end local function callDodge() local fn = resolveDodgeFunction() if not fn then return false end local ok, err = pcall(fn, "dodge") if not ok then dodgeFunc = nil dodgeResolveCooldownUntil = clock() + 0.5 local now = clock() if now - lastDodgeErrorAt > 2 then lastDodgeErrorAt = now local logger = getLogger() if logger and logger.warn then logger.warn("Dash failed: %s", tostring(err)) else warn(string.format("[Cottom hub AB] Dash failed: %s", tostring(err))) end end return false end return true end local function dodgeDash(direction) if not resolveDodgeFunction() then return false end direction = normalizeDashDirection(direction) local spoof = computeSpoofCameraCFrame(direction) local epoch = cameraSpoofEpoch + 1 if spoof ~= nil then cameraInstance = workspaceService.CurrentCamera cameraSpoofEpoch = epoch cameraSpoofCFrame = spoof cameraSpoofActive = true cameraSpoofUntil = clock() + DODGE_CAMERA_SPOOF_SECONDS ensureCameraHook() else cameraSpoofActive = false cameraSpoofCFrame = nil cameraSpoofUntil = 0 cameraInstance = nil end local ok = callDodge() if spoof ~= nil then local restore = function() if cameraSpoofEpoch ~= epoch then return end cameraSpoofActive = false cameraSpoofCFrame = nil cameraSpoofUntil = 0 end if DODGE_CAMERA_SPOOF_SECONDS > 0 then task.delay(DODGE_CAMERA_SPOOF_SECONDS, restore) else restore() end end return ok end function InputClient.dash(direction) return dodgeDash(normalizeDirection(direction)) end return InputClient end) -- MODULE: Features/Combat/UsingSkillWatcher defineModule("Features/Combat/UsingSkillWatcher", function() local UsingSkillWatcher = { rules = {} } local Players = game:GetService("Players") local workspaceService = game:GetService("Workspace") local userInputService = game:GetService("UserInputService") local textChatService = game:GetService("TextChatService") local InputClient = require("Game/InputClient") local Configuration = require("Utility/Configuration") local Library = require("GUI/Library") local AttributeListener = require("Features/Combat/AttributeListener") local Defender = require("Features/Combat/Objects/Defender") local Logger = require("Utility/Logger") local LocalPlayer = Players.LocalPlayer if not LocalPlayer then Players:GetPropertyChangedSignal("LocalPlayer"):Wait() LocalPlayer = Players.LocalPlayer end local lower = string.lower local hitboxDefender = Defender.new() local clock = os.clock local pollActive = false local pollTask = nil local pollInterval = NO_HITBOX_SKILL_POLL_SECONDS local NO_HITBOX_COOLDOWN = 0.03 local MAX_NO_HITBOX_DELAY = 0.06 local skillContext = {} local SKILL_LOCK_BASE = 0.08 local runSkillAction local skillDelaySeconds local fireNoHitbox local function noHitboxReactionBiasSeconds() return NO_HITBOX_REACTION_BIAS_SECONDS end local function noHitboxSkillPollSeconds() return NO_HITBOX_SKILL_POLL_SECONDS end local function normalizeShape(shape) return shape == "Ball" and "Ball" or "Box" end local function passesDistance(def, rule) if rule.imdd == nil and rule.imxd == nil then return true end local distance = hitboxDefender:distance(def.character) if not distance then return false end local minDistance = rule.imdd or 0 local maxDistance = rule.imxd or math.huge return distance >= minDistance and distance <= maxDistance end local function passesHitbox(def, rule) local _ = def local _rule = rule return true end local function passesHitboxRaw(def, rule) local _ = def local _rule = rule return true end local function blockAcquire() return InputClient.block(true) end local function blockRelease() return InputClient.block(false) end local function blockHoldSeconds(seconds) return InputClient.blockHoldSeconds(seconds) end local function jumpNow() return InputClient.jump() end local function allowActions(tag) if not Configuration.expectToggleValue("EnableAutoDefense") then return false end local character = Players.LocalPlayer and Players.LocalPlayer.Character if not character then return false end local selectedFilters = Configuration.expectOptionValue("AutoDefenseFilters") or {} local function filterEnabled(name, legacy) return selectedFilters[name] or (legacy and selectedFilters[legacy]) end local currentState = character:GetAttribute("CurrentState") if filterEnabled("Disable When Knocked Recently") and AttributeListener.krecently and AttributeListener.krecently() then return false end if filterEnabled("Disable When In Dash") and currentState == "Dashing" then return false end if filterEnabled("Disable When In Flashstep") and currentState == "Flashstep" then return false end if currentState == "Attacking" or currentState == "Skill" then return false end local chatInputBarConfiguration = textChatService:FindFirstChildOfClass("ChatInputBarConfiguration") if filterEnabled("Disable When Textbox Focused") and (userInputService:GetFocusedTextBox() or (chatInputBarConfiguration and chatInputBarConfiguration.IsFocused)) then return false end if filterEnabled("Disable When Window Not Active") and iswindowactive and not iswindowactive() then return false end if filterEnabled("Disable When Holding Block") and userInputService:IsKeyDown(Enum.KeyCode.F) then return false end if tag == "Skill" and filterEnabled("Filter Out Skills", "Filter Out Mantras") then return false end return true end local function normalizeSkillValue(raw) local lowerValue = lower(raw) if lowerValue:find("%s") then return lowerValue, lowerValue:gsub("%s+", "") end return lowerValue, lowerValue end local function canonicalSkillValue(raw) return tostring(raw or ""):gsub("[^%w]", "") end local function isWordBoundaryChar(ch) return ch == nil or ch == "" or not string.match(ch, "[%w]") end local function boundedContains(haystack, needle) if type(haystack) ~= "string" or type(needle) ~= "string" or needle == "" then return false end local from = 1 while true do local s, e = haystack:find(needle, from, true) if not s then return false end local prev = (s > 1) and haystack:sub(s - 1, s - 1) or nil local nextc = (e < #haystack) and haystack:sub(e + 1, e + 1) or nil if isWordBoundaryChar(prev) and isWordBoundaryChar(nextc) then return true end from = s + 1 end end local function getSkillLogIdentity(def) local plr = def.player if not plr and def.character then plr = Players:GetPlayerFromCharacter(def.character) end local name = (plr and plr.Name) or (def.character and def.character.Name) or "Unknown" return name, plr == LocalPlayer end local function updateSkillEntryLabel(entry, tag, who, state, value, seconds, secondsLabel) if not entry or not entry.Label then return end local displayValue = value if displayValue == "" then displayValue = "(cleared)" end local timeText = os.date("%H:%M:%S") local suffix = string.format(" | at=%s", timeText) if type(seconds) == "number" then local label = secondsLabel or "elapsed" suffix = suffix .. string.format(" | %s=%.3fs", label, seconds) end entry.Label.Text = string.format("(%s) %s UsingSkill %s: %s%s", tag, who, state, displayValue, suffix) end local function matchSkill(lowerValue, compactValue, token, spacedToken) local tokenLower = lower(tostring(token or "")) if tokenLower == "" then return false end local spacedLower = spacedToken and lower(tostring(spacedToken)) or nil if spacedLower and (lowerValue == spacedLower or boundedContains(lowerValue, spacedLower)) then return true end if lowerValue == tokenLower or boundedContains(lowerValue, tokenLower) then return true end local canonicalValue = canonicalSkillValue(lowerValue) local canonicalCompact = canonicalSkillValue(compactValue) local canonicalToken = canonicalSkillValue(tokenLower) if canonicalToken ~= "" and (canonicalValue == canonicalToken or canonicalCompact == canonicalToken) then return true end if spacedLower then local canonicalSpaced = canonicalSkillValue(spacedLower) if canonicalSpaced ~= "" and (canonicalValue == canonicalSpaced or canonicalCompact == canonicalSpaced) then return true end end return false end local function jumpTwice() InputClient.jump() task.delay(0.08, function() InputClient.jump() end) end local function blockFor(seconds) local hold = seconds or 0 if hold <= 0 and NO_HITBOX_ONLY then hold = NO_HITBOX_MIN_BLOCK_SECONDS end if hold > 0 then InputClient.blockHoldSeconds(hold) end end local function dash(dir) InputClient.dash(dir) end local function dashAfter(seconds, dir, withJump) local delaySeconds = seconds or 0 if delaySeconds <= 0 then if withJump then jumpTwice() end return dash(dir) end task.delay(delaySeconds, function() if withJump then jumpTwice() end dash(dir) end) end local function blockAfter(seconds, duration) local delaySeconds = seconds or 0 if delaySeconds <= 0 then return blockFor(duration) end task.delay(delaySeconds, function() blockFor(duration) end) end local localSkillEndRules = {} local function addSkillRule(token, spacedToken, onEnd) localSkillEndRules[#localSkillEndRules + 1] = { token = token, spacedToken = spacedToken, onEnd = onEnd, } end local function addAssetRule(id, onEnd) local token = "rbxassetid://" .. tostring(id) addSkillRule(token, nil, onEnd) addSkillRule(tostring(id), nil, onEnd) end addSkillRule("uzumakibarrage", "uzumaki barrage", function() blockFor(0.3) end) addSkillRule("shadowclone", "shadow clone", function() blockFor(0.8) end) addSkillRule("clonethrow", "clone throw", function() blockFor(1.2) end) addSkillRule("fireballjutsu", "fireball jutsu", function(_, _, _, duration) if duration and duration >= 2.0 then blockFor(3) else dashAfter(0.65, "right") end end) addSkillRule("chidori", nil, function() dash("back") end) addSkillRule("fumashuriken", "fuma shuriken", function() blockFor(1.2) end) addSkillRule("lionsbarrage", "lion's barrage", function() blockFor(0.8) end) addSkillRule("headhunterjutsu", "headhunter jutsu", function() blockFor(0.8) end) addSkillRule("kunai", nil, function() blockFor(1.1) end) addSkillRule("primarylotus", "primary lotus", function() blockFor(0.6) end) addSkillRule("shuriken", nil, function() blockFor(0.5) end) addSkillRule("leafhurricane", "leaf hurricane", function() blockFor(0.8) end) addSkillRule("risingwind", "rising wind", function() dash("back") end) addSkillRule("rotation", nil, function() dash("back") end) addSkillRule("explosivekunai", "explosive kunai", function() blockFor(0.8) end) addSkillRule("64palms", "64 palms", function() blockFor(6) end) addSkillRule("airpalm", "air palm", function() blockFor(0.7) end) addSkillRule("waterclone", "water clone", function() blockFor(1.2) end) addSkillRule("decapitate", nil, function() task.delay(0.6, function() jumpTwice() dash("back") end) end) addSkillRule("swordthrow", "sword throw", function() blockFor(0.5) end) addSkillRule("sandcoffin", "sand coffin", function() blockFor(0.3) end) addSkillRule("sandtsunami", "sand tsunami", function() jumpTwice() dash("right") end) addSkillRule("sandspikes", "sand spikes", function() blockFor(1.2) end) addSkillRule("chidoristream", "chidori stream", function() blockFor(1.1) end) addSkillRule("chidorisenbon", "chidori senbon", function() blockFor(0.75) end) addSkillRule("chidoritruespear", "chidori true spear", function() jumpTwice() dash("right") end) addSkillRule("rasengancombo", "rasengan combo", function() blockFor(0.3) end) addSkillRule("shadowclones", "shadow clones", function() blockFor(0.9) end) addSkillRule("rasenganthrow", "rasengan throw", function() task.delay(0.7, function() jumpTwice() dash("right") end) end) addSkillRule("shurikenassualt", "shuriken assualt", function() blockFor(0.8) end) addSkillRule("demonicillusion", "demonic illusion", function() blockFor(0.65) end) addSkillRule("asurapath", "asura path", function() blockAfter(0.3, 0.75) end) addSkillRule("sixpathscombo", "six paths combo", function() blockFor(1.1) end) addSkillRule("almightypush", "almighty push", function(_, _, _, duration) if duration and duration > 1.5 then dash("back") else blockFor(1.2) end end) addSkillRule("universalpull", "universal pull", function() blockFor(0.8) end) addSkillRule("nunchaku", nil, function() blockFor(6) end) addSkillRule("severehurricane", "severe hurricane", function() task.delay(0.19, function() jumpTwice() dash("right") end) end) addSkillRule("dynamicentry", "dynamic entry", function() blockFor(1.5) end) addSkillRule("flipkick", "flip kick", function() blockAfter(0.4, 0.9) end) addSkillRule("vicegrip", "vice grip", function() blockFor(1.1) end) addSkillRule("ligerbomb", "liger bomb", function() blockFor(0.4) end) addSkillRule("sharkbombjutsu", "shark bomb jutsu", function() task.delay(0.5, function() dash("right") end) end) addSkillRule("waterprison", "water prison", function() blockAfter(0.7, 0.4) end) addSkillRule("samehadafeast", "samehada feast", function() jumpTwice() dash("back") end) addAssetRule(3240243801, function() blockFor(0.3) end) addAssetRule(3240242879, function() blockFor(0.3) end) addAssetRule(3240241715, function() blockFor(0.3) end) addAssetRule(3240240417, function() blockFor(0.3) end) addAssetRule(3240245051, function() dash("back") end) addAssetRule(3263871265, function() blockFor(0.3) end) addAssetRule(3263870104, function() blockFor(0.3) end) addAssetRule(3263925841, function() dash("back") end) addAssetRule(12831969898, function() dashAfter(0.2, "right") end) addAssetRule(14437150122, function() blockFor(0.3) end) addAssetRule(14437148019, function() blockFor(0.3) end) addAssetRule(14437145085, function() blockFor(0.3) end) addAssetRule(14436312737, function() blockFor(0.3) end) addAssetRule(14437152064, function() dash("back") end) local function handleLocalSkillEnd(rawValue, durationSeconds) if type(rawValue) ~= "string" or rawValue == "" then return end local lowerValue, compactValue = normalizeSkillValue(rawValue) local bestRule = nil local bestLen = -1 for _, rule in ipairs(localSkillEndRules) do if rule and rule.token and matchSkill(lowerValue, compactValue, rule.token, rule.spacedToken) then local len = #tostring(rule.token) if rule.spacedToken and #rule.spacedToken > len then len = #rule.spacedToken end if len > bestLen then bestLen = len bestRule = rule end end end if bestRule and type(bestRule.onEnd) == "function" then bestRule.onEnd(nil, lowerValue, compactValue, durationSeconds) end end local function deferHitboxAction(def, key, rule, delaySeconds, callback) local delay = delaySeconds or 0 local info = def.stateInfo and def.stateInfo[key] if not info then return false end info.delayToken = (info.delayToken or 0) + 1 local token = info.delayToken task.delay(delay, function() if not def.states or def.states[key] ~= true then return end if info.delayToken ~= token then return end if not allowActions("Skill") then return end if not passesDistance(def, rule) then return end if type(callback) == "function" then local ok, err = pcall(function() executeDefensiveAction(def, rule, callback) end) if not ok then Logger.warn("UsingSkill deferred callback failed: %s", tostring(err)) end end end) return true end local function logUsingSkill(def, rawValue, isLocal) local toggleKey = isLocal and "LogUsingSkillLocal" or "LogUsingSkillOthers" if not Configuration.expectToggleValue(toggleKey) then def.lastLoggedValue = nil def.lastSkillValue = nil def.lastSkillStart = nil def.lastSkillEntry = nil def.lastSkillUpdateAt = nil def.lastSkillToken = (def.lastSkillToken or 0) + 1 return end local value = type(rawValue) == "string" and rawValue or "" local now = clock() local playerName = (def.player and def.player.Name) or (def.character and def.character.Name) or "Unknown" local tag = isLocal and "Local" or "Other" if def.lastLoggedValue == value then if value ~= "" and def.lastSkillEntry and def.lastSkillStart then if not def.lastSkillUpdateAt or (now - def.lastSkillUpdateAt) >= 0.1 then def.lastSkillUpdateAt = now local elapsed = now - def.lastSkillStart updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "Running", value, elapsed, "elapsed") end end return end local previousValue = def.lastSkillValue or "" if value ~= previousValue then if previousValue ~= "" then local duration = def.lastSkillStart and (now - def.lastSkillStart) or nil if def.lastSkillEntry then updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "End", previousValue, duration, "lasted") end if Library and Library.AddUsingSkillEntry then Library:AddUsingSkillEntry(playerName, previousValue, isLocal, "End", duration) end if isLocal then handleLocalSkillEnd(previousValue, duration) end def.lastSkillEntry = nil def.lastSkillUpdateAt = nil def.lastSkillToken = (def.lastSkillToken or 0) + 1 end if value ~= "" then def.lastSkillValue = value def.lastSkillStart = now if Library and Library.AddUsingSkillEntry then def.lastSkillToken = (def.lastSkillToken or 0) + 1 local token = def.lastSkillToken Library:AddUsingSkillEntry(playerName, value, isLocal, "Start", nil, function(entry) if def.lastSkillToken ~= token or def.lastSkillValue ~= value then return end def.lastSkillEntry = entry def.lastSkillUpdateAt = now end) end else def.lastSkillValue = "" def.lastSkillStart = nil def.lastSkillEntry = nil def.lastSkillUpdateAt = nil def.lastSkillToken = (def.lastSkillToken or 0) + 1 end end def.lastLoggedValue = value end local usingSkillDefs = {} function UsingSkillWatcher.setRules(rules) UsingSkillWatcher.rules = rules or {} for _, rule in pairs(UsingSkillWatcher.rules) do if typeof(rule.hitbox) == "table" and typeof(rule.hitbox.X) == "number" then rule.hitbox = Vector3.new(rule.hitbox.X, rule.hitbox.Y, rule.hitbox.Z) end if typeof(rule.offset) == "table" and typeof(rule.offset.X) == "number" then rule.offset = Vector3.new(rule.offset.X, rule.offset.Y, rule.offset.Z) end if rule.shape then rule.shape = normalizeShape(rule.shape) end if rule.fhb == nil then rule.fhb = true end end end function UsingSkillWatcher.blockAcquire() blockAcquire() end function UsingSkillWatcher.blockRelease() blockRelease() end function UsingSkillWatcher.blockHoldSeconds(seconds) return blockHoldSeconds(seconds) end function UsingSkillWatcher.jump() return jumpNow() end function UsingSkillWatcher.deferHitbox(def, key, delaySeconds, callback) local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if not rule or not def then return false end return deferHitboxAction(def, key, rule, delaySeconds, callback) end local function updateOwner(def) local obj = def.obj local character = obj and obj:FindFirstAncestorWhichIsA("Model") def.character = character def.player = character and Players:GetPlayerFromCharacter(character) or nil end local function safeRuleStart(def, rule, lowerValue, compactValue) local ok, err = pcall(function() executeDefensiveAction(def, rule, function() return rule.onStart(def, lowerValue, compactValue) end) end) if not ok then Logger.warn("UsingSkill onStart failed: %s", tostring(err)) end end local function executeRuleOnStart(def, key, rule, lowerValue, compactValue) if not def or not rule or type(rule.onStart) ~= "function" then return end if rule.ihbc then if fireNoHitbox then fireNoHitbox(def, key, rule, lowerValue, compactValue) else safeRuleStart(def, rule, lowerValue, compactValue) end return end if runSkillAction then runSkillAction(def, key, rule, lowerValue, compactValue, function() safeRuleStart(def, rule, lowerValue, compactValue) end) else safeRuleStart(def, rule, lowerValue, compactValue) end end function UsingSkillWatcher.update(def) if not Configuration.expectToggleValue("EnableAutoDefense") then return end updateOwner(def) local plr = def.player local skill = def.obj local rawValue = skill and skill.Value local now = clock() if plr == LocalPlayer then logUsingSkill(def, rawValue, true) if def and def.states then for key, active in pairs(def.states) do if active then def.states[key] = false local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if rule and type(rule.onStop) == "function" then rule.onStop(def) end end end end return end if plr then logUsingSkill(def, rawValue, false) end if not plr then if def and def.states then for key, active in pairs(def.states) do if active then def.states[key] = false local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if rule and type(rule.onStop) == "function" then rule.onStop(def) end end end end return end if not skill or skill.Parent == nil then if def and def.states then for key, active in pairs(def.states) do if active then def.states[key] = false local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if rule and type(rule.onStop) == "function" then rule.onStop(def) end end end end return end local value = skill.Value if type(value) ~= "string" then if def and def.states then for key, active in pairs(def.states) do if active then def.states[key] = false local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if rule and type(rule.onStop) == "function" then rule.onStop(def) end end end end return end local lowerValue, compactValue = normalizeSkillValue(value) def.lastValue = lowerValue local canAct = allowActions("Skill") def.states = def.states or {} def.stateInfo = def.stateInfo or {} for key, rule in pairs(UsingSkillWatcher.rules) do if not rule or not rule.token then continue end local info = def.stateInfo[key] if not info then info = { holdUntil = 0 } def.stateInfo[key] = info end local tokenMatched = canAct and matchSkill(lowerValue, compactValue, rule.token, rule.spacedToken) if tokenMatched and type(rule.match) == "function" and not rule.match(def, lowerValue, compactValue) then tokenMatched = false end local isDeferred = rule.deferHitboxSeconds and rule.deferHitboxSeconds > 0 local distanceMatched = tokenMatched and passesDistance(def, rule) local wasActive = def.states[key] == true if NO_HITBOX_ONLY then if distanceMatched then if not wasActive then def.states[key] = true info.hitboxActive = true info.guardPulseAt = now if handleAutoCounterTiming then handleAutoCounterTiming({ tag = "Skill" }, "SkillWatcher", hitboxDefender:distance(def.character)) end blockFor((rule and rule.minBlockSeconds) or NO_HITBOX_MIN_BLOCK_SECONDS) if type(rule.onStart) == "function" then executeRuleOnStart(def, key, rule, lowerValue, compactValue) end else if type(rule.onTick) == "function" then rule.onTick(def, lowerValue, compactValue) else local pulseAt = info.guardPulseAt or 0 if now - pulseAt >= NO_HITBOX_ACTIVE_GUARD_PULSE_SECONDS then info.guardPulseAt = now blockFor((rule and rule.minBlockSeconds) or NO_HITBOX_MIN_BLOCK_SECONDS) end end end elseif wasActive then def.states[key] = false info.hitboxActive = false if info.trackToken then info.trackToken = info.trackToken + 1 end if type(rule.onStop) == "function" then rule.onStop(def, lowerValue, compactValue) end end else local ruleMatched = distanceMatched and passesHitbox(def, rule) local hitboxWasActive = info.hitboxActive == true if tokenMatched and not wasActive then def.states[key] = true info.hitboxActive = false if handleAutoCounterTiming then handleAutoCounterTiming({ tag = "Skill" }, "SkillWatcher", hitboxDefender:distance(def.character)) end if isDeferred and type(rule.onStart) == "function" then executeRuleOnStart(def, key, rule, lowerValue, compactValue) end elseif not tokenMatched and wasActive then def.states[key] = false info.hitboxActive = false if info.trackToken then info.trackToken = info.trackToken + 1 end if type(rule.onStop) == "function" then rule.onStop(def, lowerValue, compactValue) end end if tokenMatched and not isDeferred then if ruleMatched and not hitboxWasActive then info.hitboxActive = true executeRuleOnStart(def, key, rule, lowerValue, compactValue) elseif ruleMatched and hitboxWasActive then if type(rule.onTick) == "function" then rule.onTick(def, lowerValue, compactValue) end elseif not ruleMatched and hitboxWasActive then info.hitboxActive = false end end end end end function UsingSkillWatcher.queueUpdate(def) if not def then return end UsingSkillWatcher.update(def) end local function detachUsingSkill(obj) local def = usingSkillDefs[obj] if not def then return end local lastSkillValue = def.lastSkillValue or "" if lastSkillValue ~= "" then local playerName, isLocal = getSkillLogIdentity(def) local tag = isLocal and "Local" or "Other" local duration = def.lastSkillStart and (clock() - def.lastSkillStart) or nil if def.lastSkillEntry then updateSkillEntryLabel(def.lastSkillEntry, tag, playerName, "End", lastSkillValue, duration, "lasted") end if Library and Library.AddUsingSkillEntry then Library:AddUsingSkillEntry(playerName, lastSkillValue, isLocal, "End", duration) end def.lastSkillValue = "" def.lastSkillStart = nil def.lastSkillEntry = nil def.lastSkillUpdateAt = nil def.lastSkillToken = (def.lastSkillToken or 0) + 1 end if def.states then for key, active in pairs(def.states) do if active then def.states[key] = false local rule = UsingSkillWatcher.rules and UsingSkillWatcher.rules[key] if rule and type(rule.onStop) == "function" then rule.onStop(def) end end end end usingSkillDefs[obj] = nil if def.valueConn then def.valueConn:Disconnect() end if def.ancestryConn then def.ancestryConn:Disconnect() end end local function attachUsingSkill(obj) if usingSkillDefs[obj] ~= nil then return false end local def = { obj = obj, player = nil, character = nil, lastValue = nil, states = {} } usingSkillDefs[obj] = def if obj.GetPropertyChangedSignal then def.valueConn = obj:GetPropertyChangedSignal("Value"):Connect(function() UsingSkillWatcher.queueUpdate(def) end) else def.valueConn = obj.Changed:Connect(function() UsingSkillWatcher.queueUpdate(def) end) end def.ancestryConn = obj.AncestryChanged:Connect(function() if not obj:IsDescendantOf(game) then detachUsingSkill(obj) return end UsingSkillWatcher.queueUpdate(def) end) UsingSkillWatcher.queueUpdate(def) return true end local function onDescendantAdded(descendant) if descendant.Name == "UsingSkill" and descendant:IsA("ValueBase") then attachUsingSkill(descendant) end end local function onDescendantRemoving(descendant) if usingSkillDefs[descendant] ~= nil then detachUsingSkill(descendant) end end local liveInstance = nil local liveAddedConn = nil local liveRemovingConn = nil local liveChildAddedConn = nil local liveChildRemovedConn = nil local function clearLiveState() for obj in pairs(usingSkillDefs) do detachUsingSkill(obj) end end local function attachLive(live) if liveInstance == live then return end if liveAddedConn then liveAddedConn:Disconnect() liveAddedConn = nil end if liveRemovingConn then liveRemovingConn:Disconnect() liveRemovingConn = nil end liveInstance = live if not live then clearLiveState() return end liveAddedConn = live.DescendantAdded:Connect(onDescendantAdded) liveRemovingConn = live.DescendantRemoving:Connect(onDescendantRemoving) for _, descendant in ipairs(live:GetDescendants()) do onDescendantAdded(descendant) end end function UsingSkillWatcher.init() attachLive(workspaceService:FindFirstChild("Live")) if not pollActive then pollActive = true pollTask = task.spawn(LPH_NO_VIRTUALIZE(function() while pollActive do local interval = NO_HITBOX_ONLY and noHitboxSkillPollSeconds() or pollInterval task.wait(interval) for _, def in pairs(usingSkillDefs) do UsingSkillWatcher.update(def) end end end)) end liveChildAddedConn = workspaceService.ChildAdded:Connect(function(child) if child.Name == "Live" then attachLive(child) end end) liveChildRemovedConn = workspaceService.ChildRemoved:Connect(function(child) if child == liveInstance then attachLive(nil) end end) end skillDelaySeconds = function() if NO_HITBOX_ONLY then return 0 end local biased = Defender.rdelay() + noHitboxReactionBiasSeconds() return math.clamp(math.max(0, biased), 0, MAX_NO_HITBOX_DELAY) end local function getSkillContext(def) if not def or not def.character then return nil end local ctx = skillContext[def.character] if not ctx then ctx = { lockUntil = 0, lockPriority = 0, lastSkillAt = {} } skillContext[def.character] = ctx end return ctx end runSkillAction = function(def, key, rule, lowerValue, compactValue, actionFn) local _ = compactValue if not Configuration.expectToggleValue("EnableAutoDefense") then return end local ctx = getSkillContext(def) if not ctx then return end local now = clock() local distance = hitboxDefender:distance(def.character) or math.huge local value = tostring(lowerValue or "") local threat = (rule and tonumber(rule.priority)) or 1 if distance <= 16 then threat = threat + 2.6 elseif distance <= 30 then threat = threat + 1.8 elseif distance <= 50 then threat = threat + 1.1 elseif distance <= 75 then threat = threat + 0.6 else threat = threat + 0.2 end if value:find("grab", 1, true) or value:find("rush", 1, true) or value:find("combo", 1, true) then threat = threat + 1.4 elseif value:find("barrage", 1, true) or value:find("gatling", 1, true) or value:find("onslaught", 1, true) then threat = threat + 1.1 elseif value:find("meteor", 1, true) or value:find("final", 1, true) or value:find("ult", 1, true) then threat = threat + 1.2 end local prio = 1 local cdMs = 75 local lockSeconds = (rule and rule.lockSeconds) or SKILL_LOCK_BASE if threat >= 4.8 then prio = 3 cdMs = 35 lockSeconds = math.max(lockSeconds, 0.10) elseif threat >= 3.2 then prio = 2 cdMs = 52 lockSeconds = math.max(lockSeconds, 0.08) else prio = 1 cdMs = 75 lockSeconds = math.max(lockSeconds, 0.06) end if rule and type(rule.priority) == "number" then prio = math.max(prio, rule.priority) end if rule and type(rule.cooldownMs) == "number" then cdMs = math.max(0, rule.cooldownMs) end if not NO_HITBOX_ONLY and ctx.lockUntil and now < ctx.lockUntil and prio < (ctx.lockPriority or 0) then return end local last = ctx.lastSkillAt[key] if last and now - last < (cdMs / 1000) then return end ctx.lastSkillAt[key] = now ctx.lockPriority = prio ctx.lockUntil = now + (NO_HITBOX_ONLY and 0.03 or lockSeconds) local delay = 0 if not NO_HITBOX_ONLY then delay = rule.adaptiveDelay == false and 0 or skillDelaySeconds() end if delay > 0 then task.delay(delay, function() if not Configuration.expectToggleValue("EnableAutoDefense") then return end local prev = activeSkillDef activeSkillDef = def actionFn() activeSkillDef = prev end) else local prev = activeSkillDef activeSkillDef = def actionFn() activeSkillDef = prev end end fireNoHitbox = function(def, key, rule, lowerValue, compactValue) if not def or not rule or type(rule.onStart) ~= "function" then return end def.stateInfo = def.stateInfo or {} local info = def.stateInfo[key] if not info then info = { holdUntil = 0 } def.stateInfo[key] = info end local now = clock() local cooldown = rule.nhcd or NO_HITBOX_COOLDOWN if info.nhLast and now - info.nhLast < cooldown then return end info.nhLast = now local function safeStart() local ok, err = pcall(function() executeDefensiveAction(def, rule, function() return rule.onStart(def, lowerValue, compactValue) end) end) if not ok then Logger.warn("UsingSkill onStart failed: %s", tostring(err)) end end if runSkillAction then runSkillAction(def, key, rule, lowerValue, compactValue, safeStart) else safeStart() end end function UsingSkillWatcher.detach() if liveAddedConn then liveAddedConn:Disconnect() liveAddedConn = nil end if liveRemovingConn then liveRemovingConn:Disconnect() liveRemovingConn = nil end if liveChildAddedConn then liveChildAddedConn:Disconnect() liveChildAddedConn = nil end if liveChildRemovedConn then liveChildRemovedConn:Disconnect() liveChildRemovedConn = nil end clearLiveState() liveInstance = nil InputClient.block(false) pollActive = false pollTask = nil end UsingSkillWatcher._allowActions = allowActions UsingSkillWatcher._passesDistance = passesDistance UsingSkillWatcher._passesHitboxRaw = passesHitboxRaw return UsingSkillWatcher end) -- MODULE: Features/Combat/M1AutoTrade defineModule("Features/Combat/M1AutoTrade", function() local M1AutoTrade = {} local Players = game:GetService("Players") local userInputService = game:GetService("UserInputService") local workspaceService = game:GetService("Workspace") local Configuration = require("Utility/Configuration") local InputClient = require("Game/InputClient") local Table = require("Utility/Table") local AutoCombo = require("Features/Combat/AutoCombo") local Logger = require("Utility/Logger") local lastTradeAt = {} local TRADE_COOLDOWN = 0.15 local METERS_TO_STUDS = 3.57 local lastHitObj = nil local lastHitConn = nil local lastHitDescConn = nil local lastHitAt = 0 local lastHitTarget = nil local localM1WindowStart = 0 local localM1WindowUntil = 0 local localM1Token = 0 local LOCAL_M1_RANGE_STUDS = 15 local localInitDone = false local clearingLastHit = false local function safeLower(value) local lowerFn = string and string.lower if lowerFn then return lowerFn(tostring(value or "")) end return tostring(value or "") end local function isLikelyM1Name(name) local lowered = safeLower(name) if lowered == "" then return false end if lowered == "m1" then return true end if lowered:find("m1_") or lowered:find("_m1") or lowered:find(" m1") or lowered:find("m1 ") or lowered:find("-m1") or lowered:find("m1-") then return true end if lowered:find("light") or lowered:find("basic") or lowered:find("jab") then return true end return false end local function isLocalUsingSkill() local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character if not localCharacter then return false end local state = localCharacter:GetAttribute("CurrentState") if state == "Skill" then return true end local skillValue = localCharacter:FindFirstChild("UsingSkill", true) if skillValue and skillValue:IsA("ValueBase") then local raw = skillValue.Value if type(raw) == "string" then local value = safeLower(raw) if value ~= "" and value ~= "none" and value ~= "idle" then return true end end end return false end local function resolveLastHit(character) if not character then return nil end local direct = character:FindFirstChild("LastHit") if direct and direct:IsA("ValueBase") then return direct end local descendant = character:FindFirstChild("LastHit", true) if descendant and descendant:IsA("ValueBase") then return descendant end return nil end local clearValueBase local listCloseTargets clearValueBase = function(valueObj) if not valueObj or not valueObj:IsA("ValueBase") then return end pcall(function() if valueObj:IsA("ObjectValue") then valueObj.Value = nil elseif valueObj:IsA("StringValue") then valueObj.Value = "" elseif valueObj:IsA("BoolValue") then valueObj.Value = false elseif valueObj:IsA("NumberValue") or valueObj:IsA("IntValue") then valueObj.Value = 0 else valueObj.Value = nil end end) end local function startComboIfReady() if not Configuration.expectToggleValue("EnableAutoCombo") then return false end if not Configuration.expectToggleValue("AutoComboOnM1Hit") then return false end if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then return false end if isLocalUsingSkill() then return false end local profileName = Configuration.expectOptionValue("AutoComboProfile") or "" if profileName == "" then if Logger and Logger.longNotify then Logger.longNotify("Please select an Auto-Combo profile.") end return false end AutoCombo.run(profileName) return true end local function openLocalM1Window() if not Configuration.expectToggleValue("EnableAutoCombo") then return end if not Configuration.expectToggleValue("AutoComboOnM1Hit") then return end if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then return end if isLocalUsingSkill() then return end local holdMs = Configuration.expectOptionValue("AutoComboOnM1HoldMs") or 250 local holdSeconds = math.max(holdMs, 10) / 1000 local now = os.clock() localM1WindowStart = now localM1WindowUntil = now + holdSeconds local targets = listCloseTargets(LOCAL_M1_RANGE_STUDS) if #targets == 0 then return end local snapshot = {} for _, target in ipairs(targets) do snapshot[target.character] = target.humanoid.Health end localM1Token = localM1Token + 1 local token = localM1Token local deadline = localM1WindowUntil task.spawn(function() while os.clock() <= deadline do if token ~= localM1Token then return end if AutoCombo and AutoCombo.isRunning and AutoCombo.isRunning() then return end if isLocalUsingSkill() then return end for _, target in ipairs(listCloseTargets(LOCAL_M1_RANGE_STUDS)) do local prev = snapshot[target.character] if prev and target.humanoid.Health < prev then startComboIfReady() return end end task.wait(0.03) end end) end local function handleLastHitChanged() if clearingLastHit then return end local obj = lastHitObj if not obj then return end local raw = nil local ok = pcall(function() raw = obj.Value end) if not ok then return end if raw == nil or raw == "" then return end lastHitTarget = raw lastHitAt = os.clock() clearingLastHit = true clearValueBase(obj) clearingLastHit = false end local function isM1Block03(timing) if not timing or timing.tag ~= "M1" then return false end local startBlock = false local endBlock03 = false for _, action in next, timing.actions._data do local atype = tostring(action._type or "") if atype == "Start Block" then startBlock = true elseif atype == "End Block" then local whenMs = tonumber(action._when) or 0 if whenMs == 300 then endBlock03 = true end end end return startBlock and endBlock03 end local function isAlly(player) local localPlayer = Players.LocalPlayer if not localPlayer or not player then return false end local ok, status = pcall(localPlayer.GetFriendStatus, localPlayer, player) if not ok then return false end return status == Enum.FriendStatus.Friend end listCloseTargets = function(maxDistanceStuds) local ents = workspaceService:FindFirstChild("Live") if not ents then return {} end local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character if not localCharacter then return {} end local localRootPart = localCharacter:FindFirstChild("HumanoidRootPart") or localCharacter:FindFirstChild("Torso") if not localRootPart then return {} end local targets = {} for _, entity in next, ents:GetChildren() do if entity == localCharacter then continue end local playerFromCharacter = Players:GetPlayerFromCharacter(entity) if playerFromCharacter and Configuration.expectToggleValue("IgnorePlayers") then continue end if playerFromCharacter and isAlly(playerFromCharacter) and Configuration.expectToggleValue("IgnoreAllies") then continue end local humanoid = entity:FindFirstChildWhichIsA("Humanoid") if not humanoid or humanoid.Health <= 0 then continue end local rootPart = entity:FindFirstChild("HumanoidRootPart") or entity:FindFirstChild("Torso") if not rootPart then continue end local distance = (rootPart.Position - localRootPart.Position).Magnitude if distance <= maxDistanceStuds then targets[#targets + 1] = { character = entity, humanoid = humanoid, root = rootPart, distance = distance, } end end return targets end local function bestTargets() local ents = workspaceService:FindFirstChild("Live") if not ents then return {} end local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character if not localCharacter then return {} end local localRootPart = localCharacter:FindFirstChild("HumanoidRootPart") if not localRootPart then return {} end local currentCamera = workspaceService.CurrentCamera if not currentCamera then return {} end local targets = {} local maxDistance = Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1 maxDistance = maxDistance * METERS_TO_STUDS for _, entity in next, ents:GetChildren() do if entity == localCharacter then continue end local playerFromCharacter = Players:GetPlayerFromCharacter(entity) if not playerFromCharacter then continue end if playerFromCharacter and Configuration.expectToggleValue("IgnorePlayers") then continue end local humanoid = entity:FindFirstChildWhichIsA("Humanoid") if not humanoid or humanoid.Health <= 0 then continue end local rootPart = entity:FindFirstChild("HumanoidRootPart") if not rootPart then continue end local distance = (rootPart.Position - localRootPart.Position).Magnitude if distance > maxDistance then continue end if playerFromCharacter and isAlly(playerFromCharacter) and Configuration.expectToggleValue("IgnoreAllies") then continue end local mousePosition = userInputService:GetMouseLocation() local unitRay = workspaceService.CurrentCamera:ScreenPointToRay(mousePosition.X, mousePosition.Y) local distanceToCrosshair = unitRay:Distance(rootPart.Position) local fieldOfViewToEntity = currentCamera.CFrame.LookVector:Dot((localRootPart.Position - rootPart.Position).Unit) targets[#targets + 1] = { character = entity, root = rootPart, humanoid = humanoid, dc = distanceToCrosshair, fov = fieldOfViewToEntity, du = distance, } end local sortType = Configuration.expectOptionValue("M1TradeSelectionType") or "Closest In Distance" if sortType == "Closest To Crosshair" then table.sort(targets, function(a, b) return a.dc < b.dc end) elseif sortType == "Least Health" then table.sort(targets, function(a, b) return a.humanoid.Health < b.humanoid.Health end) else table.sort(targets, function(a, b) return a.du < b.du end) end local maxTargets = Configuration.expectOptionValue("M1TradeMaxTargets") or 1 return Table.slice(targets, 1, maxTargets) end local function isAllowedTarget(entity) for _, target in next, bestTargets() do if target.character == entity then return true end end return false end local function doM1Trade() local holdMs = Configuration.expectOptionValue("M1TradeBlockHoldMs") or 160 local waitMs = Configuration.expectOptionValue("M1TradeBlockWaitMs") or 0 local holdSeconds = math.max(holdMs, 10) / 1000 local waitSeconds = math.max(waitMs, 0) / 1000 if waitSeconds > 0 then task.wait(waitSeconds) end InputClient.blockTap(holdSeconds) if Configuration.expectToggleValue("M1TradeManualOnly") then return end task.delay(holdSeconds + 0.005, function() local oneTap = Configuration.expectToggleValue("M1TradeOneAutoM1") local totalClicks = oneTap and 1 or 5 local gapMs = Configuration.expectOptionValue("M1TradeClickGapMs") or 40 local holdMs = Configuration.expectOptionValue("M1TradeClickHoldMs") or 30 local gapSeconds = math.max(gapMs, 10) / 1000 local holdSecondsClick = math.max(holdMs, 10) / 1000 if totalClicks == 1 then if InputClient._m1Down or InputClient._m1KeyDown then InputClient.m1Release() task.wait(0.02) end InputClient.m1Tap(holdSecondsClick) InputClient.m1Release() return end for i = 1, totalClicks do InputClient.m1Tap(holdSecondsClick) if i < totalClicks then task.wait(gapSeconds) end end InputClient.m1Release() end) end function M1AutoTrade.handle(defender, timing, distance) if not Configuration.expectToggleValue("EnableM1AutoTrade") then return end if not isM1Block03(timing) then return end if not defender or not defender.entity then return end local dist = distance or defender:distance(defender.entity) or math.huge local maxDistance = (Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1) * METERS_TO_STUDS if dist > maxDistance then return end if not isAllowedTarget(defender.entity) then return end local last = lastTradeAt[defender.entity] local now = os.clock() if last and now - last < TRADE_COOLDOWN then return end lastTradeAt[defender.entity] = now doM1Trade() end function M1AutoTrade.handleEntity(timing, entity, distance) if not Configuration.expectToggleValue("EnableM1AutoTrade") then return end if not isM1Block03(timing) then return end if not entity then return end local dist = distance or math.huge local maxDistance = (Configuration.expectOptionValue("M1TradeDistanceHit") or 11.1) * METERS_TO_STUDS if dist > maxDistance then return end if not isAllowedTarget(entity) then return end local last = lastTradeAt[entity] local now = os.clock() if last and now - last < TRADE_COOLDOWN then return end lastTradeAt[entity] = now doM1Trade() end function M1AutoTrade.handleLocalM1(timing, track) local isM1 = timing and timing.tag == "M1" if not isM1 and not timing and track then if isLikelyM1Name(track.Name) then isM1 = true else local anim = track.Animation if anim and isLikelyM1Name(anim.Name) then isM1 = true else local aid = (anim and anim.AnimationId) or track.AnimationId if isLikelyM1Name(aid) then isM1 = true end end end end if not isM1 then return end openLocalM1Window() end function M1AutoTrade.init() if localInitDone then return end localInitDone = true local localPlayer = Players.LocalPlayer if not localPlayer then Players:GetPropertyChangedSignal("LocalPlayer"):Wait() localPlayer = Players.LocalPlayer end local liveChildConn = nil local function detachLastHit() if lastHitConn then lastHitConn:Disconnect() lastHitConn = nil end if lastHitDescConn then lastHitDescConn:Disconnect() lastHitDescConn = nil end lastHitObj = nil lastHitAt = 0 lastHitTarget = nil localM1WindowStart = 0 localM1WindowUntil = 0 end local function attachLastHit(character) detachLastHit() if not character then return end local hit = resolveLastHit(character) if not hit then local live = workspaceService:FindFirstChild("Live") local liveChar = live and localPlayer and live:FindFirstChild(localPlayer.Name) if liveChar and liveChar ~= character then hit = resolveLastHit(liveChar) if hit then character = liveChar end end end if hit then lastHitObj = hit if hit.GetPropertyChangedSignal then lastHitConn = hit:GetPropertyChangedSignal("Value"):Connect(handleLastHitChanged) else lastHitConn = hit.Changed:Connect(handleLastHitChanged) end end lastHitDescConn = character.DescendantAdded:Connect(function(desc) if lastHitObj then return end if desc.Name == "LastHit" and desc:IsA("ValueBase") then lastHitObj = desc if desc.GetPropertyChangedSignal then lastHitConn = desc:GetPropertyChangedSignal("Value"):Connect(handleLastHitChanged) else lastHitConn = desc.Changed:Connect(handleLastHitChanged) end end end) end local function watchLive(live) if liveChildConn then liveChildConn:Disconnect() liveChildConn = nil end if not live then return end liveChildConn = live.ChildAdded:Connect(function(child) if not localPlayer or not child then return end if child.Name == localPlayer.Name then attachLastHit(child) end end) local existing = live:FindFirstChild(localPlayer.Name) if existing and not lastHitObj then attachLastHit(existing) end end localPlayer.CharacterAdded:Connect(attachLastHit) localPlayer.CharacterRemoving:Connect(function() detachLastHit() end) if localPlayer.Character then attachLastHit(localPlayer.Character) end watchLive(workspaceService:FindFirstChild("Live")) workspaceService.ChildAdded:Connect(function(child) if child and child.Name == "Live" then watchLive(child) end end) workspaceService.ChildRemoved:Connect(function(child) if child and child.Name == "Live" then if liveChildConn then liveChildConn:Disconnect() liveChildConn = nil end end end) userInputService.InputBegan:Connect(function(input, gameProcessed) if gameProcessed then return end if input.UserInputType == Enum.UserInputType.MouseButton1 then openLocalM1Window() return end if input.UserInputType == Enum.UserInputType.Gamepad1 and input.KeyCode == Enum.KeyCode.ButtonR2 then openLocalM1Window() end end) end return M1AutoTrade end) -- MODULE: Features/Combat/AutoCombo defineModule("Features/Combat/AutoCombo", function() local AutoCombo = {} local Players = game:GetService("Players") local TweenService = game:GetService("TweenService") local RunService = game:GetService("RunService") local ContextActionService = game:GetService("ContextActionService") local Configuration = require("Utility/Configuration") local Logger = require("Utility/Logger") local Filesystem = require("Utility/Filesystem") local Serializer = require("Utility/Serializer") local Deserializer = require("Utility/Deserializer") local InputClient = require("Game/InputClient") local Targeting = require("Features/Combat/Targeting") local TaskSpawner = require("Utility/TaskSpawner") local MOVE_BLOCK_ACTION = "AutoCombo_BlockMove" local moveBlockActive = false local function setMoveBlock(enabled) if not ContextActionService then return end if enabled then if moveBlockActive then return end moveBlockActive = true pcall(function() ContextActionService:BindActionAtPriority( MOVE_BLOCK_ACTION, function() return Enum.ContextActionResult.Sink end, false, Enum.ContextActionPriority.High.Value, Enum.KeyCode.W, Enum.KeyCode.A, Enum.KeyCode.S, Enum.KeyCode.D, Enum.KeyCode.Up, Enum.KeyCode.Down, Enum.KeyCode.Left, Enum.KeyCode.Right ) end) else if not moveBlockActive then return end moveBlockActive = false pcall(function() ContextActionService:UnbindAction(MOVE_BLOCK_ACTION) end) end end local fs = Filesystem.new("CottomHub-AutoCombos") local comboState = { token = 0, running = false, currentStep = nil, lockedTarget = nil, lastTarget = nil, } local function sanitizeName(name) name = tostring(name or "") name = name:gsub("[\\/%:%*%?\"<>|]", "") name = name:gsub("^%s+", ""):gsub("%s+$", "") return name end local function safeNumber(value, fallback) local num = tonumber(value) if num == nil then return fallback end return num end local function optionValue(key, fallback) local val = Configuration.expectOptionValue(key) if type(val) == "number" then return val end return fallback end local function normalizeOffset(value) if typeof(value) == "Vector3" then return { x = value.X, y = value.Y, z = value.Z } end if type(value) == "table" then local x = tonumber(value.x or value.X or value[1]) or 0 local y = tonumber(value.y or value.Y or value[2]) or 0 local z = tonumber(value.z or value.Z or value[3]) or 0 return { x = x, y = y, z = z } end return { x = 0, y = 0, z = 0 } end local function vectorFromOffset(value) local offset = normalizeOffset(value) return Vector3.new(offset.x or 0, offset.y or 0, offset.z or 0) end local function coerceStep(step) if type(step) ~= "table" then return nil end local stype = tostring(step.type or "") if stype == "" then return nil end local out = { type = stype } if stype == "Skill" then out.slot = math.clamp(safeNumber(step.slot, 1), 1, 4) if type(step.toolName) == "string" and step.toolName ~= "" then out.toolName = step.toolName end local delayAfter = tonumber(step.delayAfterMs) if delayAfter ~= nil then out.delayAfterMs = delayAfter end elseif stype == "Wait" then out.durationMs = safeNumber(step.durationMs, 0) elseif stype == "M1" then out.holdMs = safeNumber(step.holdMs, 60) out["repeat"] = math.max(1, safeNumber(step["repeat"], 1)) out.gapMs = safeNumber(step.gapMs, 60) elseif stype == "Uptilt" then out.jumpHoldMs = safeNumber(step.jumpHoldMs, optionValue("AutoComboUptiltJumpHoldMs", 120)) out.m1HoldMs = safeNumber(step.m1HoldMs, optionValue("AutoComboUptiltM1HoldMs", 60)) out.delayBeforeM1Ms = safeNumber(step.delayBeforeM1Ms, 30) elseif stype == "Upfling" then out.jumpHoldMs = math.max(0, safeNumber(step.jumpHoldMs, 400)) out.m1HoldMs = math.max(10, safeNumber(step.m1HoldMs, 230)) elseif stype == "JumpHold" then out.holdMs = math.max(0, safeNumber(step.holdMs, 400)) elseif stype == "Jump" then out.count = math.max(1, safeNumber(step.count, 1)) out.holdMs = math.max(10, safeNumber(step.holdMs, 50)) out.gapMs = math.max(0, safeNumber(step.gapMs, 120)) elseif stype == "TweenToHead" then out.maxDistance = safeNumber(step.maxDistance, optionValue("AutoComboTweenMaxDistance", 15)) local durationSeconds = tonumber(step.durationSeconds or step.duration or step.tweenDuration) if durationSeconds and durationSeconds > 0 then out.durationSeconds = durationSeconds end out.alignFeet = step.alignFeet == true local speed = tonumber(step.speed) if not speed then local legacyTime = tonumber(step.tweenTime) if legacyTime and legacyTime > 0 and out.maxDistance and out.maxDistance > 0 then speed = out.maxDistance / legacyTime end end out.speed = safeNumber(speed, optionValue("AutoComboTweenSpeed", 60)) out.offset = normalizeOffset(step.offset) elseif stype == "MoveToTarget" then local duration = tonumber(step.durationSeconds) if not duration then local legacyTimeout = tonumber(step.timeoutMs) if legacyTimeout then duration = legacyTimeout / 1000 end end out.durationSeconds = safeNumber(duration, optionValue("AutoComboMoveToDurationSeconds", 1.2)) out.stopDistance = safeNumber(step.stopDistance, optionValue("AutoComboMoveToStopDistance", 4)) out.useSprint = step.useSprint == true elseif stype == "Dash" then local dir = tostring(step.dir or "right") if dir ~= "left" and dir ~= "right" and dir ~= "forward" and dir ~= "back" then dir = "right" end out.dir = dir elseif stype == "CameraPitch" then out.pitchDegrees = safeNumber(step.pitchDegrees, -45) out.durationMs = math.max(0, safeNumber(step.durationMs, 0)) out.holdMs = math.max(0, safeNumber(step.holdMs, 0)) out.resetMs = math.max(0, safeNumber(step.resetMs, 0)) end out.parallel = step.parallel == true if step.delayAfterMs ~= nil then out.delayAfterMs = safeNumber(step.delayAfterMs, 0) end return out end local function coerceProfile(data) if type(data) ~= "table" then return nil end local profile = { name = tostring(data.name or "Unnamed"), steps = {}, meta = type(data.meta) == "table" and data.meta or {}, } if type(data.steps) == "table" then for _, step in ipairs(data.steps) do local normalized = coerceStep(step) if normalized then table.insert(profile.steps, normalized) end end end return profile end function AutoCombo.listProfiles() local ok, files = pcall(fs.list, fs) if not ok or type(files) ~= "table" then return {} end local list = {} for _, file in ipairs(files) do local name = tostring(file) if name:sub(-5) == ".json" then table.insert(list, name:sub(1, -6)) end end table.sort(list) return list end function AutoCombo.loadProfile(name) local clean = sanitizeName(name) if clean == "" then return nil, "empty" end local ok, content = pcall(fs.read, fs, clean .. ".json") if not ok then return nil, "missing" end local ok2, parsed = pcall(Deserializer.unmarshal_one, content) if not ok2 then return nil, "parse" end local profile = coerceProfile(parsed) if not profile then return nil, "invalid" end profile.name = clean return profile end function AutoCombo.saveProfile(name, data) local clean = sanitizeName(name) if clean == "" then return false, "empty" end local profile = coerceProfile(data or {}) if not profile then return false, "invalid" end profile.name = clean profile.meta = profile.meta or {} profile.meta.updatedAt = os.time() if not profile.meta.createdAt then profile.meta.createdAt = os.time() end local ok, payload = pcall(Serializer.marshal, profile) if not ok then return false, "serialize" end local ok2 = pcall(fs.write, fs, clean .. ".json", payload) if not ok2 then return false, "write" end return true end function AutoCombo.deleteProfile(name) local clean = sanitizeName(name) if clean == "" then return false, "empty" end local ok = pcall(fs.delete, fs, clean .. ".json") if not ok then return false, "delete" end return true end function AutoCombo.stop() comboState.token = (comboState.token or 0) + 1 comboState.running = false comboState.currentStep = nil comboState.lockedTarget = nil comboState.lastTarget = nil end function AutoCombo.isRunning() return comboState.running == true end local function isRunning(token) return comboState.running and token == comboState.token end local function isTargetValid(target) if not target or not target.character or not target.root or not target.humanoid then return false end if target.character.Parent == nil or target.root.Parent == nil then return false end if target.humanoid.Health <= 0 then return false end return true end local function selectTarget() local targets = Targeting.best() return targets and targets[1] or nil end local function resolveTarget(state) if Configuration.expectToggleValue("AutoComboLockTarget") then if isTargetValid(state.lockedTarget) then return state.lockedTarget end if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then state.lockedTarget = selectTarget() return state.lockedTarget end return nil end if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then state.lastTarget = selectTarget() return state.lastTarget end return state.lastTarget end local function ensureTargetAlive(state) if not Configuration.expectToggleValue("AutoComboLockTarget") then return true end if not state.lockedTarget then if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then state.lockedTarget = selectTarget() end return true end if not isTargetValid(state.lockedTarget) then if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") then state.lockedTarget = selectTarget() end if state.lockedTarget and not isTargetValid(state.lockedTarget) then AutoCombo.stop() return false end end return true end local function getLocalRoot() local character = Players.LocalPlayer and Players.LocalPlayer.Character if not character then return nil, nil, nil end local root = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso") local humanoid = character:FindFirstChildOfClass("Humanoid") return character, root, humanoid end local function moveHumanoid(humanoid, direction) if not humanoid or not humanoid.Parent then return false end local moveDirection = direction if typeof(moveDirection) ~= "Vector3" then moveDirection = Vector3.zero end if moveDirection.Magnitude > 1 then moveDirection = moveDirection.Unit end local ok = pcall(function() humanoid:Move(moveDirection, false) end) return ok end local function moveHumanoidTo(humanoid, position) if not humanoid or not humanoid.Parent then return false end if typeof(position) ~= "Vector3" then return false end local ok = pcall(function() humanoid:MoveTo(position) end) return ok end local waitWithCancel = LPH_NO_VIRTUALIZE(function(seconds, token) if seconds <= 0 then return true end local endTime = os.clock() + seconds while true do if not isRunning(token) then return false end local remaining = endTime - os.clock() if remaining <= 0 then return true end task.wait(math.min(remaining, 0.03)) end end) local autoComboHotbarRef = nil local function resolveAutoComboHotbar() if autoComboHotbarRef and autoComboHotbarRef.Parent then return autoComboHotbarRef end local playerGui = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("PlayerGui") if not playerGui then return nil end local hud = playerGui:FindFirstChild("HUD") local backpackGui = hud and hud:FindFirstChild("Backpack") local hotbar = backpackGui and backpackGui:FindFirstChild("Hotbar") if not hotbar then for _, inst in ipairs(playerGui:GetDescendants()) do if inst.Name == "Hotbar" then hotbar = inst break end end end autoComboHotbarRef = hotbar return hotbar end local function resolveAutoComboToolName(slot) local hotbar = resolveAutoComboHotbar() if not hotbar then return nil end local slotFrame = hotbar:FindFirstChild(tostring(slot)) if not slotFrame then return nil end local toolNameObj = slotFrame:FindFirstChild("ToolName") or slotFrame:FindFirstChild("Name") if not toolNameObj then return nil end if toolNameObj:IsA("TextLabel") or toolNameObj:IsA("TextButton") then return toolNameObj.Text end if toolNameObj:IsA("StringValue") then return toolNameObj.Value end return nil end local function resolveAutoComboTool(slot) local backpack = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("Backpack") if backpack then for _, tool in ipairs(backpack:GetChildren()) do if tool:IsA("Tool") then local attrSlot = tool:GetAttribute("Slot") or tool:GetAttribute("HotbarSlot") or tool:GetAttribute("Index") if attrSlot and tonumber(attrSlot) == slot then return tool end local slotValue = tool:FindFirstChild("Slot") or tool:FindFirstChild("Index") if slotValue and tonumber(slotValue.Value) == slot then return tool end end end end local character = Players.LocalPlayer and Players.LocalPlayer.Character return character and character:FindFirstChildOfClass("Tool") or nil end local function fireSkill(step) if not InputClient then return false end local slot = math.clamp(safeNumber(step.slot, 1), 1, 4) local keyMap = { [1] = Enum.KeyCode.One, [2] = Enum.KeyCode.Two, [3] = Enum.KeyCode.Three, [4] = Enum.KeyCode.Four, } local key = keyMap[slot] if InputClient.tapKey and key then return InputClient.tapKey(key) end return false end local doM1 = LPH_NO_VIRTUALIZE(function(step, token) local repeatCount = math.max(1, safeNumber(step["repeat"], 1)) local holdSeconds = math.max(10, safeNumber(step.holdMs, 60)) / 1000 local gapSeconds = math.max(0, safeNumber(step.gapMs, 60)) / 1000 for i = 1, repeatCount do if not isRunning(token) then return end InputClient.m1Hold(holdSeconds) if not waitWithCancel(holdSeconds + (i < repeatCount and gapSeconds or 0), token) then return end end end) local doUptilt = LPH_NO_VIRTUALIZE(function(step, token) local jumpHoldSeconds = math.max(10, safeNumber(step.jumpHoldMs, optionValue("AutoComboUptiltJumpHoldMs", 120))) / 1000 local m1HoldSeconds = math.max(10, safeNumber(step.m1HoldMs, optionValue("AutoComboUptiltM1HoldMs", 60))) / 1000 local delayBeforeM1 = math.max(0, safeNumber(step.delayBeforeM1Ms, 30)) / 1000 InputClient.jumpHold(jumpHoldSeconds) if delayBeforeM1 > 0 then if not waitWithCancel(delayBeforeM1, token) then return end end InputClient.m1Hold(m1HoldSeconds) waitWithCancel(m1HoldSeconds, token) end) local doUpfling = LPH_NO_VIRTUALIZE(function(step, token) local jumpHoldSeconds = math.max(10, safeNumber(step.jumpHoldMs, 400)) / 1000 local m1HoldSeconds = math.max(10, safeNumber(step.m1HoldMs, 230)) / 1000 InputClient.jumpHold(jumpHoldSeconds) InputClient.m1Hold(m1HoldSeconds) waitWithCancel(math.max(jumpHoldSeconds, m1HoldSeconds), token) end) local doJumpHold = LPH_NO_VIRTUALIZE(function(step, token) local holdSeconds = math.max(0, safeNumber(step.holdMs, 400)) / 1000 if holdSeconds <= 0 then return end InputClient.jumpHold(holdSeconds) waitWithCancel(holdSeconds, token) end) local doJump = LPH_NO_VIRTUALIZE(function(step, token) local count = math.max(1, safeNumber(step.count, 1)) local holdSeconds = math.max(0, safeNumber(step.holdMs, 50)) / 1000 local gapSeconds = math.max(0, safeNumber(step.gapMs, 120)) / 1000 for i = 1, count do if not isRunning(token) then return end if holdSeconds > 0 then InputClient.jumpHold(holdSeconds) if not waitWithCancel(holdSeconds, token) then return end else InputClient.jump() end if i < count and gapSeconds > 0 then if not waitWithCancel(gapSeconds, token) then return end end end end) local doTween = LPH_NO_VIRTUALIZE(function(step, target, token, state) if not target or not target.root then return end local character, localRoot, humanoid = getLocalRoot() if not localRoot then return end local maxDistance = safeNumber(step.maxDistance, optionValue("AutoComboTweenMaxDistance", 15)) local offset = vectorFromOffset(step.offset) local durationSeconds = tonumber(step.durationSeconds) local speed = math.max(0, safeNumber(step.speed, optionValue("AutoComboTweenSpeed", 60))) local function resolveHead() return target.character and target.character:FindFirstChild("Head") or target.root end local head = resolveHead() if not head then return end local dist = (head.Position - localRoot.Position).Magnitude if not durationSeconds then if maxDistance > 0 and dist > maxDistance then return end end local totalTime = 0 if durationSeconds and durationSeconds > 0 then totalTime = durationSeconds elseif speed > 0 then totalTime = dist / speed end if totalTime <= 0 then return end local function computeRelativeOffset(vec) if not step.relativeOffset then return vec end local fallbackBasis = (localRoot and localRoot.CFrame) or (target and target.root and target.root.CFrame) or CFrame.new() local basis = nil if step.relativeToLocal then basis = (localRoot and localRoot.CFrame) or fallbackBasis else basis = (target and target.root and target.root.CFrame) or fallbackBasis end local forward = Vector3.new(basis.LookVector.X, 0, basis.LookVector.Z) if forward.Magnitude > 1e-3 then forward = forward.Unit else forward = Vector3.new(0, 0, -1) end local right = Vector3.new(basis.RightVector.X, 0, basis.RightVector.Z) if right.Magnitude > 1e-3 then right = right.Unit else right = Vector3.new(1, 0, 0) end return right * vec.X + Vector3.new(0, 1, 0) * vec.Y + forward * vec.Z end local function computeOffset(headInstance) if not step.alignFeet then return computeRelativeOffset(offset) end local character = Players.LocalPlayer and Players.LocalPlayer.Character local humanoid = character and character:FindFirstChildOfClass("Humanoid") local hip = humanoid and humanoid.HipHeight or (localRoot.Size.Y / 2) local headHalf = headInstance and headInstance.Size and headInstance.Size.Y / 2 or 1.5 local baseOffset = offset + Vector3.new(0, headHalf + hip, 0) return computeRelativeOffset(baseOffset) end local start = os.clock() while os.clock() - start < totalTime do if not isRunning(token) then return end local liveCharacter, liveRoot, liveHumanoid = getLocalRoot() if not liveCharacter or not liveRoot then return end character = liveCharacter localRoot = liveRoot humanoid = liveHumanoid or humanoid if not isTargetValid(target) then if Configuration.expectToggleValue("AutoComboRetargetOnInvalid") and state then target = resolveTarget(state) end if not isTargetValid(target) then if Configuration.expectToggleValue("AutoComboLockTarget") and state and state.lockedTarget then AutoCombo.stop() end return end end head = resolveHead() if not head then return end local dest = head.Position + computeOffset(head) local current = localRoot.Position local toDest = dest - current local distance = toDest.Magnitude if humanoid then moveHumanoid(humanoid, localRoot.CFrame.LookVector) end if distance > 0.001 then local dt = RunService.RenderStepped:Wait() local stepDist = speed > 0 and speed * dt or distance if stepDist > distance then stepDist = distance end local newPos = current + toDest.Unit * stepDist local rotation = localRoot.CFrame - localRoot.CFrame.Position localRoot.CFrame = CFrame.new(newPos) * rotation else RunService.RenderStepped:Wait() end end if humanoid then moveHumanoid(humanoid, Vector3.zero) end end) local doMoveTo = LPH_NO_VIRTUALIZE(function(step, target, token, state) local character, localRoot, humanoid = getLocalRoot() if not character or not localRoot then return end if not humanoid then return end local duration = math.max(0, safeNumber(step.durationSeconds, optionValue("AutoComboMoveToDurationSeconds", 1.2))) local stopDistance = math.max(0, safeNumber(step.stopDistance, optionValue("AutoComboMoveToStopDistance", 4))) local useSprint = step.useSprint == true local sprintHold = math.max(0, safeNumber(optionValue("AutoComboSprintHoldMs", 350), 350)) / 1000 local repathSeconds = math.max(0.03, safeNumber(step.repathSeconds, 0.06)) if duration <= 0 then return end local movementLocked = false local function stopMoveTo() local _, liveRoot, liveHumanoid = getLocalRoot() if liveHumanoid and liveRoot then moveHumanoidTo(liveHumanoid, liveRoot.Position) elseif humanoid and localRoot then moveHumanoidTo(humanoid, localRoot.Position) end end local function lockMovement() if movementLocked then return end movementLocked = true setMoveBlock(true) end local function unlockMovement() if not movementLocked then return end movementLocked = false setMoveBlock(false) end lockMovement() local start = os.clock() local lastSprint = 0 local lastPos = target and target.root and target.root.Position or nil local nextMoveToAt = 0 local lastMoveToPos = nil while os.clock() - start <= duration do if not isRunning(token) then stopMoveTo() unlockMovement() return end local liveCharacter, liveRoot, liveHumanoid = getLocalRoot() if not liveCharacter or not liveRoot or not liveHumanoid then stopMoveTo() unlockMovement() return end character = liveCharacter localRoot = liveRoot humanoid = liveHumanoid if target and isTargetValid(target) then state.lastTarget = target lastPos = target.root.Position else target = resolveTarget(state) if target and isTargetValid(target) then state.lastTarget = target lastPos = target.root.Position else if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then AutoCombo.stop() end stopMoveTo() unlockMovement() return end end if lastPos then local toDest = lastPos - localRoot.Position local flat = Vector3.new(toDest.X, 0, toDest.Z) local dist = flat.Magnitude if stopDistance > 0 and dist <= stopDistance then break end if dist > 1e-3 then local now = os.clock() local shouldUpdate = (not lastMoveToPos) or ((lastMoveToPos - lastPos).Magnitude > 1) or (now >= nextMoveToAt) if shouldUpdate then moveHumanoidTo(humanoid, lastPos) lastMoveToPos = lastPos nextMoveToAt = now + repathSeconds end end end if useSprint and sprintHold > 0 then local now = os.clock() if now - lastSprint >= sprintHold * 0.8 then InputClient.sprintHold(sprintHold) lastSprint = now end end RunService.Heartbeat:Wait() end stopMoveTo() unlockMovement() end) local function executeStep(step, token, state) if step.type == "Wait" then waitWithCancel((step.durationMs or 0) / 1000, token) elseif step.type == "Skill" then fireSkill(step) elseif step.type == "M1" then doM1(step, token) elseif step.type == "Uptilt" then doUptilt(step, token) elseif step.type == "Upfling" then doUpfling(step, token) elseif step.type == "JumpHold" then doJumpHold(step, token) elseif step.type == "Jump" then doJump(step, token) elseif step.type == "TweenToHead" then local target = resolveTarget(state) if not target or not isTargetValid(target) then if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then AutoCombo.stop() end return end doTween(step, target, token, state) elseif step.type == "MoveToTarget" then local target = resolveTarget(state) if not target or not isTargetValid(target) then if Configuration.expectToggleValue("AutoComboLockTarget") and state.lockedTarget then AutoCombo.stop() end return end doMoveTo(step, target, token, state) elseif step.type == "Dash" then local dir = step.dir or "right" InputClient.dash(dir) elseif step.type == "CameraPitch" then local cam = workspace.CurrentCamera if not cam then return end local pitch0, yaw0, roll0 = cam.CFrame:ToOrientation() local targetPitch = math.rad(step.pitchDegrees or -45) local duration = math.max(0, (step.durationMs or 0) / 1000) local hold = math.max(0, (step.holdMs or 0) / 1000) local reset = math.max(0, (step.resetMs or 0) / 1000) local function lerpPitch(fromPitch, toPitch, seconds) if seconds <= 0 then cam.CFrame = CFrame.new(cam.CFrame.Position) * CFrame.fromOrientation(toPitch, yaw0, roll0) return end local start = os.clock() while os.clock() - start < seconds do if not isRunning(token) then return end local alpha = math.clamp((os.clock() - start) / seconds, 0, 1) local p = fromPitch + (toPitch - fromPitch) * alpha cam.CFrame = CFrame.new(cam.CFrame.Position) * CFrame.fromOrientation(p, yaw0, roll0) RunService.RenderStepped:Wait() end end lerpPitch(pitch0, targetPitch, duration) if hold > 0 then if not waitWithCancel(hold, token) then return end end if reset > 0 then lerpPitch(targetPitch, pitch0, reset) end end end local runProfile = LPH_NO_VIRTUALIZE(function(profile, token) if Configuration.expectToggleValue("AutoComboLockTarget") then comboState.lockedTarget = selectTarget() end for index, step in ipairs(profile.steps or {}) do if not isRunning(token) then break end if not ensureTargetAlive(comboState) then break end comboState.currentStep = index local normalized = coerceStep(step) if normalized then if normalized.parallel == true then TaskSpawner.spawn("AutoCombo_Step_" .. tostring(normalized.type), LPH_NO_VIRTUALIZE(function() if not isRunning(token) then return end executeStep(normalized, token, comboState) end)) else executeStep(normalized, token, comboState) end end if not isRunning(token) then break end local delayMs = normalized and normalized.delayAfterMs if delayMs == nil then delayMs = optionValue("AutoComboDefaultStepDelayMs", 100) end if delayMs and delayMs > 0 then if not waitWithCancel(delayMs / 1000, token) then break end end end if token == comboState.token then comboState.running = false comboState.currentStep = nil comboState.lockedTarget = nil comboState.lastTarget = nil if Logger and Logger.notify then Logger.notify("Auto-Combo finished: %s", profile.name or "Profile") end end end) function AutoCombo.run(profileName) if not Configuration.expectToggleValue("EnableAutoCombo") then return end local profile = nil if type(profileName) == "table" then profile = coerceProfile(profileName) else profile = AutoCombo.loadProfile(profileName) end if not profile then Logger.longNotify("Auto-Combo profile '%s' could not be loaded.", tostring(profileName)) return end AutoCombo.stop() comboState.running = true comboState.token = (comboState.token or 0) + 1 local token = comboState.token TaskSpawner.spawn("AutoCombo_Run", LPH_NO_VIRTUALIZE(function() runProfile(profile, token) end)) end return AutoCombo end) -- MODULE: Game/Keybinding defineModule("Game/Keybinding", function() -- Keybinding module. local Keybinding = { info = {} } ---@module Utility.Signal local Signal = require("Utility/Signal") ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module Utility.TaskSpawner local TaskSpawner = require("Utility/TaskSpawner") -- Services. local players = game:GetService("Players") local replicatedStorage = game:GetService("ReplicatedStorage") -- Maids. local keybindingMaid = Maid.new() ---Refresh keybind data. local function refreshKeybindData() local character = players.LocalPlayer.Character or players.LocalPlayer.CharacterAdded:Wait() repeat task.wait() until character:GetAttribute("KeybindsLoaded") local requests = replicatedStorage:WaitForChild("Requests") local getKeybindInfo = requests:WaitForChild("GetKeybindsInfo") for _, keybindGroup in next, getKeybindInfo:InvokeServer() or {} do for keybindType, keybindCode in next, keybindGroup do local success, result = pcall(function() return Enum.KeyCode[keybindCode] end) Keybinding.info[keybindType] = success and result or Enum.KeyCode.Unknown end end end ---Initialize Keybinding module. function Keybinding.init() local remotes = replicatedStorage:WaitForChild("Remotes") local sendKeybindInfo = remotes:WaitForChild("SendKeybindInfo") local sendKeybindInfoEvent = Signal.new(sendKeybindInfo.OnClientEvent) keybindingMaid:add(sendKeybindInfoEvent:connect("Keybinding_SkiOnClientEvent", refreshKeybindData)) keybindingMaid:add(TaskSpawner.spawn("Keybinding_UpdateKeybinds", refreshKeybindData)) end ---Detach Keybinding module. function Keybinding.detach() keybindingMaid:clean() end -- Return Keybinding module. return Keybinding end) -- MODULE: Game/PlayerScanning defineModule("Game/PlayerScanning", function() -- Player scanning is handled here. local PlayerScanning = { scanQueue = {}, scanDataCache = {}, friendCache = {}, waitingForLoad = {}, readyList = {}, scanning = false, } ---@module Utility.CoreGuiManager local CoreGuiManager = require("Utility/CoreGuiManager") ---@module Utility.Signal local Signal = require("Utility/Signal") ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") -- Services. local players = game:GetService("Players") local httpService = game:GetService("HttpService") local collectionService = game:GetService("CollectionService") local runService = game:GetService("RunService") -- Instances. local moderatorSound = CoreGuiManager.imark(Instance.new("Sound")) -- Maid. local playerScanningMaid = Maid.new() -- Timestamp. local lastRateLimit = nil ---Fetch name. local function fetchName(player) local spoofName = Configuration.expectToggleValue("InfoSpoofing") and Configuration.expectToggleValue("SpoofOtherPlayers") return spoofName and "[REDACTED]" or string.format("(%s) %s", player:GetAttribute("CharacterName") or "Unknown Character Name", player.Name) end ---Run player scans. local runPlayerScans = LPH_NO_VIRTUALIZE(function() local localPlayer = players.LocalPlayer if not localPlayer then return end for player, _ in next, PlayerScanning.scanQueue do if shared.Lycoris.dpscanning then continue end if not PlayerScanning.scanDataCache[player] then local handledSuccess, handledResult = nil, nil local unhandledSuccess, unhandledResult = pcall(function() handledSuccess, handledResult = PlayerScanning.getStaffRank(player) end) if not unhandledSuccess then Logger.warn( "Scan player %s ran into error '%s' while getting staff rank.", player.Name, unhandledResult ) Logger.longNotify("Failed to scan player %s for moderator status.", fetchName(player), unhandledResult) PlayerScanning.scanQueue[player] = nil continue end if not handledSuccess then continue end if Configuration.expectToggleValue("NotifyMod") and handledResult then Logger.longNotify("%s is a staff member with the rank '%s' in group.", fetchName(player), handledResult) if Configuration.expectToggleValue("NotifyModSound") then moderatorSound.SoundId = "rbxassetid://6045346303" moderatorSound.PlaybackSpeed = 1 moderatorSound.Volume = Configuration.expectOptionValue("NotifyModSoundVolume") or 10 moderatorSound:Play() end end PlayerScanning.scanDataCache[player] = { staffRank = handledResult } end PlayerScanning.scanQueue[player] = nil PlayerScanning.friendCache[player] = localPlayer:GetFriendStatus(player) == Enum.FriendStatus.Friend Logger.warn("Player scanning finished scanning %s in queue.", fetchName(player)) end end) ---Are there moderators in the server? ---@return table function PlayerScanning.hasModerators() for _, scanData in next, PlayerScanning.scanDataCache do if not scanData.staffRank then continue end return true end return false end ---Is a player an ally? ---@param player Player ---@return boolean function PlayerScanning.isAlly(player) return PlayerScanning.friendCache[player] end ---Fetch roblox data. ---@param url string ---@return boolean, string? local function fetchRobloxData(url) if lastRateLimit and os.clock() - lastRateLimit <= 30 then return false, "On rate-limit cooldown." end local response = request({ Url = url, Method = "GET", Headers = { ["Content-Type"] = "application/json", }, }) if response.StatusCode == 429 then Logger.longNotify("Player scanning is being rate-limited and results will be delayed.") Logger.longNotify("Please stay in the server with caution.") lastRateLimit = os.clock() return false, "Rate-limited." end if not response then return error("Failed to fetch Roblox data.") end if not response.Success then return error( string.format("Failed to successfully fetch Roblox data with status code %i.", response.StatusCode) ) end if not response.Body then return error("Failed to find Roblox data.") end return true, httpService:JSONDecode(response.Body) end ---Get staff rank - nil if they're not a staff. ---@param player Player ---@return boolean, string? function PlayerScanning.getStaffRank(player) local responseSuccess, responseData = fetchRobloxData(("https://groups.roblox.com/v2/users/%i/groups/roles?includeLocked=true"):format(player.UserId)) if not responseSuccess then return false, responseData end local character = player.Character if character and character:GetAttribute("ContentCreator") then return true, "Content Creator" end for _, groupData in next, responseData.data do if groupData.group.id ~= 32740991 and groupData.group.id ~= 13077028 then continue end if groupData.role.rank <= 0 then continue end return true, groupData.role.name end return true, nil end ---Update player scanning. ---@note: Request will yield - so we need a debounce to prevent multiple scan loops. ---@note: We must defer the error back to the caller and reset the scanning debounce so errors will not break the scanning loop. function PlayerScanning.update() if PlayerScanning.scanning then return end PlayerScanning.scanning = true local success, result = pcall(runPlayerScans) PlayerScanning.scanning = false if success then return end return error(result) end ---On friend status changed. ---@param player Player ---@param status Enum.FriendStatus function PlayerScanning.friend(player, status) PlayerScanning.friendCache[player] = status == Enum.FriendStatus.Friend end ---On player added. ---@param player Player function PlayerScanning.onPlayerAdded(player) if player == players.LocalPlayer then return end PlayerScanning.scanQueue[player] = true end ---On player removing. ---@param player Player function PlayerScanning.onPlayerRemoving(player) PlayerScanning.scanQueue[player] = nil PlayerScanning.scanDataCache[player] = nil PlayerScanning.friendCache[player] = nil PlayerScanning.waitingForLoad[player] = nil end ---Initialize PlayerScanning. function PlayerScanning.init() -- Signals. local playerAddedSignal = Signal.new(players.PlayerAdded) local playerRemovingSignal = Signal.new(players.PlayerRemoving) local renderSteppedSignal = Signal.new(runService.RenderStepped) local friendStatusChanged = Signal.new(players.LocalPlayer.FriendStatusChanged) -- Connect events. playerScanningMaid:add(friendStatusChanged:connect("PlayerScanning_OnFriendStatusChanged", PlayerScanning.friend)) playerScanningMaid:add(renderSteppedSignal:connect("PlayerScanning_Update", PlayerScanning.update)) playerScanningMaid:add(playerAddedSignal:connect("PlayerScanning_OnPlayerAdded", PlayerScanning.onPlayerAdded)) playerScanningMaid:add( playerRemovingSignal:connect("PlayerScanning_OnPlayerRemoving", PlayerScanning.onPlayerRemoving) ) -- Run event(s) for existing players. for _, player in next, players:GetPlayers() do PlayerScanning.onPlayerAdded(player) end end ---Detach PlayerScanning. function PlayerScanning.detach() playerScanningMaid:clean() end -- Return PlayerScanning module. return PlayerScanning end) -- MODULE: Game/Timings/Action defineModule("Game/Timings/Action", function() ---@class Action ---@field _type string ---@field _when number When the action will occur in miliseconds. Never access directly. ---@field hitbox Vector3 The hitbox of the action. ---@field shape string The hitbox shape ("Box" or "Ball"). ---@field offset Vector3 The hitbox offset. ---@field ihbc boolean Ignore hitbox check. ---@field name string The name of the action. ---@field tp number Time position. Never accessible unless inside of a module or inside of real code. This is never serialized. local Action = {} Action.__index = Action ---Getter for when in seconds. ---@return number function Action:when() return PP_SCRAMBLE_NUM(self._when) / 1000 end ---Load from partial values. ---@param values table function Action:load(values) if typeof(values._type) == "string" then self._type = values._type end if typeof(values.when) == "number" then self._when = values.when end if typeof(values.name) == "string" then self.name = values.name end if typeof(values.hitbox) == "table" then self.hitbox = Vector3.new(values.hitbox.X or 0, values.hitbox.Y or 0, values.hitbox.Z or 0) elseif typeof(values.hitbox) == "Vector3" then self.hitbox = values.hitbox end if typeof(values.ihbc) == "boolean" then self.ihbc = values.ihbc end if typeof(values.shape) == "string" then self.shape = values.shape end if typeof(values.offset) == "table" then self.offset = Vector3.new(values.offset.X or 0, values.offset.Y or 0, values.offset.Z or 0) elseif typeof(values.offset) == "Vector3" then self.offset = values.offset end end ---Equals check. ---@param other Action ---@return boolean function Action:equals(other) if self._type ~= other._type then return false end if self._when ~= other._when then return false end if self.name ~= other.name then return false end if self.hitbox ~= other.hitbox then return false end if self.ihbc ~= other.ihbc then return false end if self.shape ~= other.shape then return false end if self.offset ~= other.offset then return false end return true end ---Clone action. ---@return Action function Action:clone() local clone = Action.new() clone._type = self._type clone._when = self._when clone.name = self.name clone.hitbox = self.hitbox clone.ihbc = self.ihbc clone.shape = self.shape clone.offset = self.offset return clone end ---Return a serializable table. ---@return table function Action:serialize() return { _type = self._type, when = self._when, name = self.name, hitbox = { X = self.hitbox.X, Y = self.hitbox.Y, Z = self.hitbox.Z, }, shape = self.shape, offset = { X = self.offset.X, Y = self.offset.Y, Z = self.offset.Z, }, ihbc = self.ihbc, } end ---Create new Action object. ---@param values table? ---@return Action function Action.new(values) local self = setmetatable({}, Action) self._type = "N/A" self._when = 0 self.name = "" self.hitbox = Vector3.zero self.ihbc = false self.shape = "Box" self.offset = Vector3.zero self.tp = 0 if values then self:load(values) end return self end -- Return Action module. return Action end) -- MODULE: Game/Timings/ActionContainer defineModule("Game/Timings/ActionContainer", function() ---@module Game.Timings.Action local Action = require("Game/Timings/Action") ---@class ActionContainer ---@field _data table local ActionContainer = {} ActionContainer.__index = ActionContainer ---Clone action container. ---@return ActionContainer function ActionContainer:clone() local clone = ActionContainer.new() for _, action in next, self._data do clone:push(action:clone()) end return clone end ---Equal check. ---@param other ActionContainer ---@return boolean function ActionContainer:equals(other) if self:count() ~= other:count() then return false end for name, action in next, self._data do local otherAction = other:find(name) if not otherAction then return false end if not action:equals(otherAction) then return false end end return true end ---Find a action from name. ---@param name string ---@return Action? function ActionContainer:find(name) return self._data[name] end ---Remove a action from the list. ---@param action Action function ActionContainer:remove(action) self._data[action.name] = nil self._count = self._count - 1 end ---Push a action to the list. ---@param action Action function ActionContainer:push(action) local name = action.name ---@note: Action array keys must all be unique. if self._data[name] then return error(string.format("Action name '%s' already exists in container.", name)) end self._data[name] = action self._count = self._count + 1 end ---Load from partial values. ---@param values table function ActionContainer:load(values) for _, data in next, values do self:push(Action.new(data)) end end ---List all action names. ---@return string[] function ActionContainer:names() local names = {} for name, _ in next, self._data do table.insert(names, name) end return names end ---Get action count. ---@return number function ActionContainer:count() return self._count end ---Clear actions. function ActionContainer:clear() self._data = {} end ---Get action data. ---@return table function ActionContainer:get() return self._data end ---Return a serializable table. ---@return table function ActionContainer:serialize() local data = {} for _, action in next, self._data do table.insert(data, action:serialize()) end return data end ---Create new ActionContainer object. ---@param values table? ---@return ActionContainer function ActionContainer.new(values) local self = setmetatable({}, ActionContainer) self._data = {} self._count = 0 if values then self:load(values) end return self end -- Return ActionContainer module. return ActionContainer end) -- MODULE: Game/Timings/AnimationTiming defineModule("Game/Timings/AnimationTiming", function() ---@module Game.Timings.Timing local Timing = require("Game/Timings/Timing") ---@class AnimationTiming: Timing ---@field id string Animation ID. ---@field rpue boolean Repeat parry until end. ---@field _rsd number Repeat start delay in miliseconds. Never access directly. ---@field _rpd number Delay between each repeat parry in miliseconds. Never access directly. ---@field iae boolean Flag to see whether or not this timing should ignore animation end. ---@field phd boolean Past hitbox detection. ---@field pfh boolean Predict hitboxes facing. ---@field phds number History seconds for past hitbox detection. ---@field pfht number Extrapolation time for hitbox prediction. ---@field ieae boolean Flag to see whether or not this timing should ignore early animation end. ---@field mat number Max animation timeout in milliseconds. ---@field dp boolean Disable prediction. local AnimationTiming = setmetatable({}, { __index = Timing }) AnimationTiming.__index = AnimationTiming ---Timing ID. ---@return string function AnimationTiming:id() return self._id end ---Getter for repeat start delay in seconds ---@return number function AnimationTiming:rsd() return PP_SCRAMBLE_NUM(self._rsd) / 1000 end ---Getter for repeat parry delay in seconds. ---@return number function AnimationTiming:rpd() return PP_SCRAMBLE_NUM(self._rpd) / 1000 end ---Load from partial values. ---@param values table function AnimationTiming:load(values) Timing.load(self, values) if typeof(values._id) == "string" then self._id = values._id end if typeof(values.rsd) == "string" then self._rsd = tonumber(values.rsd) or 0.0 end if typeof(values.rpd) == "string" then self._rpd = tonumber(values.rpd) or 0.0 end if typeof(values.rsd) == "number" then self._rsd = values.rsd end if typeof(values.rpd) == "number" then self._rpd = values.rpd end if typeof(values.rpue) == "boolean" then self.rpue = values.rpue end if typeof(values.iae) == "boolean" then self.iae = values.iae end if typeof(values.ieae) == "boolean" then self.ieae = values.ieae end if typeof(values.mat) == "number" then self.mat = values.mat end if typeof(values.phd) == "boolean" then self.phd = values.phd end if typeof(values.pfh) == "boolean" then self.pfh = values.pfh end if typeof(values.phds) == "number" then self.phds = values.phds end if typeof(values.pfht) == "number" then self.pfht = values.pfht end if typeof(values.dp) == "boolean" then self.dp = values.dp end end ---Clone timing. ---@return AnimationTiming function AnimationTiming:clone() local clone = setmetatable(Timing.clone(self), AnimationTiming) clone._rsd = self._rsd clone._rpd = self._rpd clone._id = self._id clone.rpue = self.rpue clone.iae = self.iae clone.ieae = self.ieae clone.mat = self.mat clone.phd = self.phd clone.pfh = self.pfh clone.phds = self.phds clone.pfht = self.pfht clone.dp = self.dp return clone end ---Return a serializable table. ---@return AnimationTiming function AnimationTiming:serialize() local serializable = Timing.serialize(self) serializable._id = self._id serializable.rsd = self._rsd serializable.rpd = self._rpd serializable.rpue = self.rpue serializable.iae = self.iae serializable.ieae = self.ieae serializable.mat = self.mat serializable.phd = self.phd serializable.pfh = self.pfh serializable.phds = self.phds serializable.pfht = self.pfht serializable.dp = self.dp return serializable end ---Create a new animation timing. ---@param values table? ---@return AnimationTiming function AnimationTiming.new(values) local self = setmetatable(Timing.new(), AnimationTiming) self.dp = false self._id = "" self._rsd = 0 self._rpd = 0 self.rpue = false self.iae = false self.ieae = false self.mat = 2000 self.phd = false self.pfh = false self.phds = 0 self.pfht = 0.15 if values then self:load(values) end return self end -- Return AnimationTiming module. return AnimationTiming end) -- MODULE: Game/Timings/ModuleManager defineModule("Game/Timings/ModuleManager", function() -- Internal modules if they exist, provided by to by preprocessor. local INTERNAL_MODULES = {} local INTERNAL_GLOBALS = {} -- Module manager. ---@note: All globals get executed first but never ran. This gets set in the global environment of every future module after. local ModuleManager = { modules = {}, globals = {} } ---@module Utility.Filesystem local Filesystem = require("Utility/Filesystem") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.Timings.Action local Action = require("Game/Timings/Action") ---@module Features.Combat.Objects.Task local Task = require("Features/Combat/Objects/Task") ---@module Game.Timings.Timing local Timing = require("Game/Timings/Timing") ---@module Utility.TaskSpawner local TaskSpawner = require("Utility/TaskSpawner") ---@module Features.Combat.Targeting local Targeting = require("Features/Combat/Targeting") ---@module Game.Timings.PartTiming local PartTiming = require("Game/Timings/PartTiming") ---@module Features.Combat.Objects.HitboxOptions local HitboxOptions = require("Features/Combat/Objects/HitboxOptions") ---@module Features.Combat.Objects.RepeatInfo local RepeatInfo = require("Features/Combat/Objects/RepeatInfo") ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module Utility.Signal local Signal = require("Utility/Signal") -- Module filesystem. local fs = Filesystem.new("Lycoris-Rewrite-TypeSoul-Modules") local gfs = Filesystem.new(fs:append("Globals")) -- Detach table. local tdetach = {} ---Execute module function. ---@param lf function ---@param id string ---@param file string? ---@param global boolean function ModuleManager.execute(lf, id, file, global) ---@module Features.Combat.Defense ---@note: For some reason, it broke lol. Returned nil. -- Has to do with loadingPlaceholder issue. A very wide cyclic dependency where depdendencies rely on each other can break the bundler. local Defense = require("Features/Combat/Defense") -- Set function environment to allow for internal modules. getfenv(lf).Timing = Timing getfenv(lf).PartTiming = PartTiming getfenv(lf).Defense = Defense getfenv(lf).Action = Action getfenv(lf).Task = Task getfenv(lf).Maid = Maid getfenv(lf).Signal = Signal getfenv(lf).TaskSpawner = TaskSpawner getfenv(lf).Targeting = Targeting getfenv(lf).Logger = Logger getfenv(lf).HitboxOptions = HitboxOptions getfenv(lf).RepeatInfo = RepeatInfo -- Load globals if we should. for name, entry in next, (not global) and ModuleManager.globals or {} do getfenv(lf)[name] = entry end -- Run executable function to initialize it. local success, result = pcall(lf) if not success then return Logger.warn("Module '%s' failed to load due to error '%s' while executing.", file or id, result) end if global and typeof(result) ~= "table" then return Logger.warn("Global module '%s' is invalid because it does not return a table.", file or id) end -- Output table. local output = global and ModuleManager.globals or ModuleManager.modules -- Get the result as a function. output[id] = result -- If this is a global, the result is a table, and it has a detach function, store it for later. if typeof(result) == "table" and typeof(result.detach) == "function" then tdetach[#tdetach + 1] = result.detach end end ---Load file modules from filesystem. ---@param tfs Filesystem The filesystem to load from. ---@param global boolean Whether we're loading global modules or not. function ModuleManager.load(tfs, global) for _, file in next, tfs:list(false) do -- Check if it is .lua. if string.sub(file, #file - 3, #file) ~= ".lua" then continue end -- Get string to load. local ls = tfs:read(file) -- Get function that we can execute. local lf, lr = loadstring(ls) if not lf then Logger.warn("Module file '%s' failed to load due to error '%s' while loading.", file, lr) continue end ModuleManager.execute(lf, string.sub(file, 1, #file - 4), file, global) end end ---List loaded modules. ---@return string[] function ModuleManager.loaded() local out = {} for file, _ in next, ModuleManager.modules do table.insert(out, file) end return out end ---Detach functions. function ModuleManager.detach() for _, detach in next, tdetach do detach() end -- Clear detach table. tdetach = {} end ---Refresh ModuleManager. function ModuleManager.refresh() -- Detach all modules. ModuleManager.detach() -- Reset current list. ModuleManager.modules = {} ModuleManager.globals = {} for id, lf in next, INTERNAL_GLOBALS do ModuleManager.execute(lf, id, nil, true) end for id, lf in next, INTERNAL_MODULES do ModuleManager.execute(lf, id, nil, false) end -- Load all globals in our filesystem. ModuleManager.load(gfs, true) -- Load all modules in our filesystem. ModuleManager.load(fs, false) end -- Return ModuleManager module. return ModuleManager end) -- MODULE: Game/Timings/PartTiming defineModule("Game/Timings/PartTiming", function() ---@module Game.Timings.Timing local Timing = require("Game/Timings/Timing") ---@class PartTiming: Timing ---@field pname string Part name. ---@field uhc boolean Use hitbox CFrame. ---@field pbrick string? Required BrickColor name. ---@field prgb table? Required color (RGB table). ---@field pchar string? Required LastLoadedChar value. ---@field exclusiveSeconds number? Ignore other matches for this timing for a duration. local PartTiming = setmetatable({}, { __index = Timing }) PartTiming.__index = PartTiming local function normalizePartKey(value) return string.lower(tostring(value or "")):gsub("[^%w]", "") end local function rgbKey(prgb) if type(prgb) ~= "table" then return nil end local r = prgb.r or prgb.R or prgb[1] local g = prgb.g or prgb.G or prgb[2] local b = prgb.b or prgb.B or prgb[3] if type(r) ~= "number" or type(g) ~= "number" or type(b) ~= "number" then return nil end return string.format("%d,%d,%d", r, g, b) end ---Timing ID. ---@return string function PartTiming:id() local base = self.pname or self.name or "" local key = normalizePartKey(base) if self.pbrick and self.pbrick ~= "" then key = key .. "|brick:" .. normalizePartKey(self.pbrick) end local rgb = rgbKey(self.prgb) if rgb then key = key .. "|rgb:" .. rgb end if self.pchar and self.pchar ~= "" then key = key .. "|char:" .. normalizePartKey(self.pchar) end return key end ---Load from partial values. ---@param values table function PartTiming:load(values) Timing.load(self, values) if typeof(values.pname) == "string" then self.pname = values.pname end if typeof(values.uhc) == "boolean" then self.uhc = values.uhc end if typeof(values.pbrick) == "string" then self.pbrick = values.pbrick end if typeof(values.prgb) == "table" then self.prgb = values.prgb end if typeof(values.pchar) == "string" then self.pchar = values.pchar end if typeof(values.exclusiveSeconds) == "number" then self.exclusiveSeconds = values.exclusiveSeconds end end ---Clone timing. ---@return PartTiming function PartTiming:clone() local clone = setmetatable(Timing.clone(self), PartTiming) clone.pname = self.pname clone.uhc = self.uhc clone.pbrick = self.pbrick clone.prgb = self.prgb clone.pchar = self.pchar clone.exclusiveSeconds = self.exclusiveSeconds return clone end ---Return a serializable table. ---@return PartTiming function PartTiming:serialize() local serializable = Timing.serialize(self) serializable.pname = self.pname serializable.uhc = self.uhc serializable.pbrick = self.pbrick serializable.prgb = self.prgb serializable.pchar = self.pchar serializable.exclusiveSeconds = self.exclusiveSeconds return serializable end ---Create a new part timing. ---@param values table? ---@return PartTiming function PartTiming.new(values) local self = setmetatable(Timing.new(), PartTiming) self.pname = "" self.uhc = false self.pbrick = nil self.prgb = nil self.pchar = nil self.exclusiveSeconds = 0 if values then self:load(values) end return self end -- Return PartTiming module. return PartTiming end) -- MODULE: Game/Timings/PlaybackData defineModule("Game/Timings/PlaybackData", function() ---@class PlaybackData ---@field base number Timestamp of when the object was created. ---@field ash table Animation speed history. The key is the timestamp delta and the value is the speed at that point. ---@field entity Model Entity to playback. local PlaybackData = {} PlaybackData.__index = PlaybackData ---Get last exceeded speed difference from a timestamp delta. ---@param from number ---@return number?, number? function PlaybackData:last(from) local latestExceededSpeed = nil local latestExceededDelta = nil for delta, speed in next, self.ash do if from <= delta then continue end if latestExceededDelta and delta <= latestExceededDelta then continue end latestExceededSpeed = speed latestExceededDelta = delta end return latestExceededSpeed, latestExceededDelta end ---Track animation speed. ---@param speed number function PlaybackData:astrack(speed) local delta = os.clock() - self.base if self:last(delta) == speed then return end self.ash[delta] = speed end ---Create new PlaybackData object. ---@param entity Model ---@return PlaybackData function PlaybackData.new(entity) local self = setmetatable({}, PlaybackData) self.base = os.clock() self.entity = entity ---@note: Timestamp delta is how many seconds need to pass before being able to reach this speed. self.ash = {} return self end -- Return PlaybackData module. return PlaybackData end) -- MODULE: Game/Timings/SaveManager defineModule("Game/Timings/SaveManager", function() ---@module Game.Timings.TimingSave local TimingSave = require("Game/Timings/TimingSave") ---@module Game.Timings.TimingContainerPair local TimingContainerPair = require("Game/Timings/TimingContainerPair") ---@module Game.Timings.TimingContainer local TimingContainer = require("Game/Timings/TimingContainer") ---@module Game.Timings.AnimationTiming local AnimationTiming = require("Game/Timings/AnimationTiming") ---@module Game.Timings.PartTiming local PartTiming = require("Game/Timings/PartTiming") ---@module Game.Timings.SoundTiming local SoundTiming = require("Game/Timings/SoundTiming") ---@module Utility.Maid local Maid = require("Utility/Maid") ---@module Utility.Signal local Signal = require("Utility/Signal") -- SaveManager module. local SaveManager = { llc = nil, llcn = nil, lct = nil } ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Utility.Filesystem local Filesystem = require("Utility/Filesystem") ---@module Utility.Deserializer local Deserializer = require("Utility/Deserializer") ---@module Utility.Serializer local Serializer = require("Utility/Serializer") ---@module Utility.Logger local Logger = require("Utility/Logger") -- Manager filesystem. local fs = Filesystem.new("CottomHub-Timings") -- Current timing save. local config = TimingSave.new() local EMBEDDED_TIMINGS_TEXT = [=[ { "part": [ { "name": "Genki", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1200, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Genki", "uhc": false }, { "name": "Genki_GohanTeen", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1400, "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Genki", "uhc": false, "pbrick": null, "prgb": null, "pchar": "Gohan [Teen]", "exclusiveSeconds": 1.4 }, { "name": "Genki_GokuBuuSaga", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Genki", "uhc": false, "pbrick": null, "prgb": null, "pchar": "Goku [Buu Saga]", "exclusiveSeconds": 1.6 }, { "name": "Genki_MediumBlue", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Jump", "name": "Jump_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Jump", "name": "Jump_2", "when": 200, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Genki", "uhc": false, "pbrick": "Medium blue", "prgb": null, "pchar": null }, { "name": "GodFlash", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Jump", "name": "Jump_1", "when": 0, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Jump", "name": "Jump_2", "when": 200, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "GodFlash", "uhc": false, "pbrick": null, "prgb": null, "pchar": null }, { "name": "Blast", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 44, "Y": 46, "Z": 46 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 10 }, "ihbc": false } ], "hitbox": { "X": 44, "Y": 46, "Z": 46 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 10 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Blast", "uhc": false }, { "name": "Blast_HotPink", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Blast", "uhc": false, "pbrick": "Hot pink", "prgb": null, "pchar": null }, { "name": "Coin", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1200, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Coin", "uhc": false, "pbrick": null, "prgb": null, "pchar": null }, { "name": "Javelin", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Javelin", "uhc": false, "pbrick": "Toothpaste", "prgb": null, "pchar": null }, { "name": "EraserBlow", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 60, "Y": 60, "Z": 60 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "EraserBlow", "uhc": false, "pbrick": "Really black", "prgb": null, "pchar": null }, { "name": "Ball_Black", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 0, "hitbox": { "X": 100, "Y": 100, "Z": 100 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 100, "Y": 100, "Z": 100 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Ball", "uhc": false, "pbrick": "Really black", "prgb": null, "pchar": null }, { "name": "KiBlast_Black", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 40, "Y": 40, "Z": 40 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "pbrick": "Really black", "prgb": null, "pchar": null }, { "name": "KiBlast", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "exclusiveSeconds": 1.6 }, { "name": "KiBlast_Persimmon", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "pbrick": "Persimmon", "prgb": null, "pchar": null, "exclusiveSeconds": 1.6 }, { "name": "KiBlast_Majin", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "pbrick": null, "prgb": null, "pchar": "Vegeta [Majin]", "exclusiveSeconds": 1.6 }, { "name": "KiBlast_Piccolo", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Dash", "name": "DashBack_2", "when": 1400, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "pbrick": null, "prgb": null, "pchar": "Piccolo", "exclusiveSeconds": 1.2 }, { "name": "KiBlast_Krillin", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 30, "Y": 30, "Z": 30 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "KiBlast", "uhc": false, "pbrick": null, "prgb": null, "pchar": "Krillin", "exclusiveSeconds": 1.6 }, { "name": "Ring_Majin", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1200, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Ring", "uhc": false, "pbrick": null, "prgb": { "r": 203, "g": 151, "b": 61 }, "pchar": "Vegeta [Majin]", "exclusiveSeconds": 1.4 }, { "name": "Ring_Color", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1400, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "Ring", "uhc": false, "pbrick": null, "prgb": { "r": 203, "g": 151, "b": 61 }, "pchar": null, "exclusiveSeconds": 1.4 }, { "name": "laysiawhiteLawRock", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": true, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 45, "Y": 45, "Z": 45 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 45, "Y": 45, "Z": 45 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 2000, "hitbox": { "X": 45, "Y": 45, "Z": 45 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 45, "Y": 45, "Z": 45 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "pname": "laysiawhiteLawRock", "uhc": false, "pbrick": null, "prgb": null, "pchar": null } ], "sound": [], "version": 1, "animation": [ { "name": "M1_1947243130", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1947243130", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1885684657", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1885684657", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470472673", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470472673", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470482438", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470482438", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1947173251", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1947173251", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1947196236", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1947196236", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1947219719", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1947219719", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1947230024", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1947230024", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1885679702", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1885679702", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1885690040", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1885690040", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1885693880", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1885693880", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1885667765", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1885667765", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1730640014", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1730640014", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1730629532", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1730629532", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1730618971", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1730618971", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1730606513", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1730606513", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1730596371", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1730596371", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_13631792671", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0.85, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 15, "Y": 15, "Z": 100 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 15, "Y": 15, "Z": 100 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 15, "Y": 15, "Z": 100 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://13631792671", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_8343484557", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 16, "Y": 10, "Z": 12 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1000, "hitbox": { "X": 16, "Y": 10, "Z": 12 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 16, "Y": 10, "Z": 12 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8343484557", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_13801810717", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 350, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "Dash", "name": "DashBack_1", "when": 350, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 2050, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://13801810717", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_72535824544679", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0.8, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 15, "Y": 15, "Z": 85 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1100, "hitbox": { "X": 15, "Y": 15, "Z": 85 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 15, "Y": 15, "Z": 85 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://72535824544679", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_80128331690638", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 1.2, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 218, "hitbox": { "X": 24, "Y": 24, "Z": 27 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1418, "hitbox": { "X": 24, "Y": 24, "Z": 27 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 27 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://80128331690638", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_73498149701712", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 1.5, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 15, "Y": 15, "Z": 70 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1500, "hitbox": { "X": 15, "Y": 15, "Z": 70 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 15, "Y": 15, "Z": 70 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://73498149701712", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_132237726679338", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Jump", "name": "Jump_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://132237726679338", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_17421950447", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0.4, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 328, "hitbox": { "X": 17, "Y": 17, "Z": 120 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 17, "Y": 17, "Z": 120 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17421950447", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": false, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17423591514", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17423591514", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17423592816", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17423592816", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17423594220", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 600, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17423594220", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17442363923", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 600, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17442363923", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17423597575", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 600, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17423597575", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8442976984", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8442976984", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8442979908", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8442979908", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8442981940", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8442981940", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461128166", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461128166", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461128859", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461128859", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461136273", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461136273", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461136875", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461136875", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461137417", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461137417", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461277837", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 37, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 37, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 37, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461277837", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461145506", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 18, "Y": 19, "Z": 23 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 18, "Y": 19, "Z": 23 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 18, "Y": 19, "Z": 23 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461145506", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1461127258", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 34, "Y": 33, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1461127258", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_12927983921", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 388, "hitbox": { "X": 20, "Y": 40, "Z": 0 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 20, "Y": 40, "Z": 0 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://12927983921", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_12927306244", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashRight_1", "when": 405, "hitbox": { "X": 15, "Y": 17, "Z": 170 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 15, "Y": 17, "Z": 170 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://12927306244", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17306010161", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17306010161", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17306012933", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17306012933", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17306015782", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17306015782", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17306019069", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17306019069", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_17306027379", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Ball", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://17306027379", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_116007627948983", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [], "hitbox": { "X": 0, "Y": 0, "Z": 0 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://116007627948983", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_112322786810921", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 1600, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 10 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 10 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://112322786810921", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_92901308072582", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://92901308072582", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8320258247", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8320258247", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8319862463", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8319862463", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8321532463", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8321532463", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_8321564926", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://8321564926", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_99173747030368", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": false } ], "hitbox": { "X": 24, "Y": 24, "Z": 24 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://99173747030368", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470422387", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470422387", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470439852", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470439852", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470449816", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470449816", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470447472", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470447472", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_1470454728", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://1470454728", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_3242973133", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 489, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 1689, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://3242973133", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "Skill_3242845717", "tag": "Skill", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 196, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "Start Block", "name": "StartBlock_1", "when": 196, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 1396, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://3242845717", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_10005397081", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://10005397081", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_10005430027", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://10005430027", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_10005462854", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://10005462854", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_10005508646", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://10005508646", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_129856070992547", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://129856070992547", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_106548325298957", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://106548325298957", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_108198644371262", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://108198644371262", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_84395905295585", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://84395905295585", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_100538053697796", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://100538053697796", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_71830104436314", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://71830104436314", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_111366686942798", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://111366686942798", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_123396863449599", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Start Block", "name": "StartBlock_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true }, { "_type": "End Block", "name": "EndBlock_1", "when": 300, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://123396863449599", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_10005583738", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://10005583738", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false }, { "name": "M1_139831323330863", "tag": "M1", "imdd": 0, "imxd": 1000, "duih": false, "punishable": 0, "after": 0, "actions": [ { "_type": "Dash", "name": "DashBack_1", "when": 0, "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "ihbc": true } ], "hitbox": { "X": 1, "Y": 1, "Z": 1 }, "shape": "Box", "offset": { "X": 0, "Y": 0, "Z": 0 }, "umoa": false, "smod": "N/A", "smn": false, "srpn": false, "aatk": false, "fhb": true, "ndfb": false, "scrambled": false, "_id": "rbxassetid://139831323330863", "rpue": false, "rpd": 0, "rsd": 0, "ha": false, "iae": false, "ieae": false, "mat": 2000, "phd": false, "pfh": true, "phds": 0, "pfht": 0.15, "dp": false } ] } ]=] -- Services. local runService = game:GetService("RunService") -- Maids. local saveMaid = Maid.new() ---Get save files list. ---@return table function SaveManager.list() local list = fs:list(true) local out = {} for idx = 1, #list do local file = list[idx] if file:sub(-4) ~= ".txt" then continue end local pos = file:find(".txt", 1, true) local char = file:sub(pos, pos) local start = pos while char ~= "/" and char ~= "\\" and char ~= "" do pos = pos - 1 char = file:sub(pos, pos) end if char == "/" or char == "\\" then table.insert(out, file:sub(pos + 1, start - 1)) end end return out end ---Merge with current config. ---@param name string ---@param type MergeType function SaveManager.merge(name, type) if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end local success, result = pcall(fs.read, fs, name .. ".txt") if not success then Logger.longNotify("Failed to read config file %s.", name) return Logger.warn("Timing manager ran into the error '%s' while attempting to read config %s.", result, name) end success, result = pcall(Deserializer.unmarshal_one, result) if not success then Logger.longNotify("Failed to deserialize config file %s.", name) return Logger.warn( "Timing manager ran into the error '%s' while attempting to deserialize config %s.", result, name ) end if typeof(result) ~= "table" then Logger.longNotify("Failed to load config file %s.", name) return Logger.warn("Timing manager failed to load config %s with result %s.", name, tostring(result)) end config:merge(TimingSave.new(result), type) Logger.notify("Config file %s has merged with the loaded one.", name) end ---Refresh dropdown values with timing data. ---@param dropdown table function SaveManager.refresh(dropdown) dropdown:SetValues(SaveManager.list()) end ---Set config name as auto-load. ---@param name string function SaveManager.autoload(name) if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end local success, result = pcall(fs.write, fs, "autoload.txt", name) if not success then Logger.longNotify("Failed to write autoload file %s.", name) return Logger.warn( "Timing manager ran into the error '%s' while attempting to write autoload file %s.", result, name ) end Logger.notify("Config file %s has set to auto-load.", name) end ---Create timing as config name. ---@param name string function SaveManager.create(name) if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end if fs:file(name .. ".txt") then return Logger.longNotify("Config file %s already exists.", name) end SaveManager.write(name) end ---Save timing as config name. ---@param name string function SaveManager.save(name) if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end if not fs:file(name .. ".txt") then return Logger.longNotify("Config file %s does not exist.", name) end SaveManager.write(name) end ---Write timing as config name. ---@param name string ---@return number function SaveManager.write(name) if not name or #name <= 0 then return -1, Logger.longNotify("Config name cannot be empty.") end local success, result = pcall(Serializer.marshal, config:serialize()) if not success then Logger.longNotify("Failed to serialize config file %s.", name) return -2, Logger.warn("Timing manager ran into the error '%s' while attempting to serialize config %s.", result, name) end success, result = pcall(fs.write, fs, name .. ".txt", result) if not success then Logger.longNotify("Failed to write config file %s.", name) return -3, Logger.warn("Timing manager ran into the error '%s' while attempting to write config %s.", result, name) end Logger.notify("Config file %s has written to.", name) return 0 end ---Clear config from config name. ---@param name string function SaveManager.clear(name) if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end local success, result = pcall(Serializer.marshal, TimingSave.new():serialize()) if not success then Logger.longNotify("Failed to serialize config file %s.", name) return Logger.warn( "Timing manager ran into the error '%s' while attempting to serialize config %s.", result, name ) end success, result = pcall(fs.write, fs, name .. ".txt", result) if not success then Logger.longNotify("Failed to write config file %s.", name) return Logger.warn("Timing manager ran into the error '%s' while attempting to write config %s.", result, name) end Logger.notify("Config file %s has cleared.", name) end ---Load timing from config name. ---@param name string function SaveManager.load(name) local timestamp = os.clock() if not name or #name <= 0 then return Logger.longNotify("Config name cannot be empty.") end if SaveManager.llcn and SaveManager.llcn == name and SaveManager.llc then return Logger.notify("Config file %s is already loaded.", name) end local success, result = pcall(fs.read, fs, name .. ".txt") if not success then Logger.longNotify("Failed to read config file %s.", name) return Logger.warn("Timing manager ran into the error '%s' while attempting to read config %s.", result, name) end success, result = pcall(Deserializer.unmarshal_one, result) if not success then Logger.longNotify("Failed to deserialize config file %s.", name) return Logger.warn( "Timing manager ran into the error '%s' while attempting to deserialize config %s.", result, name ) end if typeof(result) ~= "table" then Logger.longNotify("Failed to process config file %s.", name) return Logger.warn("Timing manager failed to process config %s with result %s.", name, tostring(result)) end config:clear() success, result = pcall(config.load, config, result) if not success then Logger.longNotify("Failed to load config file %s.", name) return Logger.warn("Timing manager ran into the error '%s' while attempting to load config %s.", result, name) end Logger.notify( "Config file %s has loaded with %i timings in %.2f seconds.", name, config:count(), os.clock() - timestamp ) SaveManager.llc = config:clone() SaveManager.llcn = name end local function loadEmbeddedConfig() if not EMBEDDED_TIMINGS_TEXT or #EMBEDDED_TIMINGS_TEXT == 0 then return false end local success, result = pcall(Deserializer.unmarshal_one, EMBEDDED_TIMINGS_TEXT) if not success or typeof(result) ~= "table" then Logger.warn("Embedded config failed to deserialize: %s", tostring(result)) return false end config:clear() local ok, err = pcall(config.load, config, result) if not ok then Logger.warn("Embedded config failed to load: %s", tostring(err)) return false end SaveManager.llc = config:clone() SaveManager.llcn = "embedded" Logger.notify("Embedded config loaded with %i timings.", config:count()) return true end ---Initialize SaveManager. function SaveManager.init() local timestamp = os.clock() local preRenderSignal = Signal.new(runService.PreRender) -- Create internal timing containers. local internalAnimationContainer = TimingContainer.new(AnimationTiming.new()) local internalPartContainer = TimingContainer.new(PartTiming.new()) local internalSoundContainer = TimingContainer.new(SoundTiming.new()) internalAnimationContainer:load({}) internalPartContainer:load({}) internalSoundContainer:load({}) -- Count up internal timings. local internalCount = internalAnimationContainer:count() + internalPartContainer:count() + internalSoundContainer:count() Logger.notify( "Internal timings have loaded with %i timings in %.2f seconds.", internalCount, os.clock() - timestamp ) -- Load embedded config first (prevents duplicate autoload). local embeddedLoaded = loadEmbeddedConfig() -- Attempt to read auto-load config if no embedded config was loaded. if not embeddedLoaded then local success, result = pcall(fs.read, fs, "autoload.txt") if success and result then SaveManager.load(result) end end -- Animation stack. SaveManager.as = TimingContainerPair.new(internalAnimationContainer, config:get().animation) -- Part stack. SaveManager.ps = TimingContainerPair.new(internalPartContainer, config:get().part) -- Sound stack. SaveManager.ss = TimingContainerPair.new(internalSoundContainer, config:get().sound) -- Run auto save. saveMaid:add(preRenderSignal:connect("SaveManager_AutoSave", function() local llc = SaveManager.llc if not llc then return end local llcn = SaveManager.llcn if not llcn then return end if not Configuration.expectToggleValue("PeriodicAutoSave") then return end if SaveManager.lct and os.clock() - SaveManager.lct < (Configuration.expectOptionValue("PeriodicAutoSaveInterval") or 60) then return end SaveManager.lct = os.clock() if config:equals(llc) then return end Logger.warn("Auto-saving timings to '%s' config file.", SaveManager.llcn) SaveManager.write(SaveManager.llcn) SaveManager.llc = config:clone() Logger.notify("Timing auto-save has completed successfully.") end)) end ---Detach SaveManager. function SaveManager.detach() saveMaid:clean() end -- Return SaveManager module. return SaveManager end) -- MODULE: Game/Timings/SoundTiming defineModule("Game/Timings/SoundTiming", function() ---@module Game.Timings.Timing local Timing = require("Game/Timings/Timing") ---@class SoundTiming: Timing ---@field id string Sound ID. ---@field rpue boolean Repeat parry until end. ---@field _rsd number Repeat start delay in miliseconds. Never access directly. ---@field _rpd number Delay between each repeat parry in miliseconds. Never access directly. local SoundTiming = setmetatable({}, { __index = Timing }) SoundTiming.__index = SoundTiming ---Timing ID. ---@return string function SoundTiming:id() return self._id end -- Getter for repeat start delay in seconds. ---@return number function SoundTiming:rsd() return PP_SCRAMBLE_NUM(self._rsd) / 1000 end -- Getter for repeat start delay in seconds. ---@return number function SoundTiming:rpd() return PP_SCRAMBLE_NUM(self._rpd) / 1000 end ---Load from partial values. ---@param values table function SoundTiming:load(values) Timing.load(self, values) if typeof(values._id) == "string" then self._id = values._id end if type(values.rsd) == "number" then self._rsd = values.rsd end if typeof(values.rpue) == "boolean" then self.rpue = values.rpue end if typeof(values.rpd) == "number" then self._rpd = values.rpd end end ---Clone timing. ---@return SoundTiming function SoundTiming:clone() local clone = setmetatable(Timing.clone(self), SoundTiming) clone._rpd = self._rpd clone.rpue = self.rpue clone._rsd = self._rsd clone._id = self._id return clone end ---Return a serializable table. ---@return SoundTiming function SoundTiming:serialize() local serializable = Timing.serialize(self) serializable._id = self._id serializable.rpue = self.rpue serializable.rsd = self._rsd serializable.rpd = self._rpd return serializable end ---Create a new sound timing. ---@param values table? ---@return SoundTiming function SoundTiming.new(values) local self = setmetatable(Timing.new(), SoundTiming) self._id = "" self.rpue = false self._rsd = 0 self._rpd = 0 if values then self:load(values) end return self end -- Return SoundTiming module. return SoundTiming end) -- MODULE: Game/Timings/Timing defineModule("Game/Timings/Timing", function() ---@module Game.Timings.ActionContainer local ActionContainer = require("Game/Timings/ActionContainer") ---@class Timing ---@field name string ---@field tag string ---@field imdd number Initial minimum distance from position. ---@field imxd number Initial maximum distance from position. ---@field punishable number Punishable window in seconds. ---@field after number After window in seconds. ---@field duih boolean Delay until in hitbox. ---@field actions ActionContainer ---@field hitbox Vector3 ---@field shape string The hitbox shape ("Box" or "Ball"). ---@field offset Vector3 The hitbox offset. ---@field umoa boolean Use module over actions. ---@field smn boolean Skip module notification. ---@field srpn boolean Skip repeat notification. ---@field smod string Selected module string. ---@field aatk boolean Allow attacking. ---@field fhb boolean Hitbox facing offset. ---@field ndfb boolean No dash fallback. ---@field scrambled boolean Scrambled? local Timing = {} Timing.__index = Timing ---Timing ID. Override me. ---@return string function Timing:id() return self.name end ---Set timing ID. Override me. ---@param id string function Timing:set(id) self.name = id end ---Load from partial values. ---@param values table function Timing:load(values) if typeof(values.name) == "string" then self.name = values.name end if typeof(values.tag) == "string" then self.tag = values.tag end if typeof(values.imdd) == "number" then self.imdd = values.imdd end if typeof(values.imxd) == "number" then self.imxd = values.imxd end if typeof(values.duih) == "boolean" then self.duih = values.duih end if typeof(values.punishable) == "number" then self.punishable = values.punishable end if typeof(values.after) == "number" then self.after = values.after end if typeof(values.actions) == "table" then self.actions:load(values.actions) end if typeof(values.smn) == "boolean" then self.smn = values.smn end if typeof(values.hitbox) == "table" then self.hitbox = Vector3.new(values.hitbox.X or 0, values.hitbox.Y or 0, values.hitbox.Z or 0) elseif typeof(values.hitbox) == "Vector3" then self.hitbox = values.hitbox end if typeof(values.shape) == "string" then self.shape = values.shape end if typeof(values.offset) == "table" then self.offset = Vector3.new(values.offset.X or 0, values.offset.Y or 0, values.offset.Z or 0) elseif typeof(values.offset) == "Vector3" then self.offset = values.offset end if typeof(values.umoa) == "boolean" then self.umoa = values.umoa end if typeof(values.srpn) == "boolean" then self.srpn = values.srpn end if typeof(values.smod) == "string" then self.smod = values.smod end if typeof(values.aatk) == "boolean" then self.aatk = values.aatk end if typeof(values.fhb) == "boolean" then self.fhb = values.fhb end if typeof(values.ndfb) == "boolean" then self.ndfb = values.ndfb end if typeof(values.scrambled) == "boolean" then self.scrambled = values.scrambled end end ---Equals check. ---@param other Timing ---@return boolean function Timing:equals(other) if self.name ~= other.name then return false end if self.tag ~= other.tag then return false end if self.imdd ~= other.imdd then return false end if self.imxd ~= other.imxd then return false end if self.duih ~= other.duih then return false end if self.punishable ~= other.punishable then return false end if self.after ~= other.after then return false end if not self.actions:equals(other.actions) then return false end if self.smn ~= other.smn then return false end if self.hitbox ~= other.hitbox then return false end if self.shape ~= other.shape then return false end if self.offset ~= other.offset then return false end if self.umoa ~= other.umoa then return false end if self.srpn ~= other.srpn then return false end if self.smod ~= other.smod then return false end if self.aatk ~= other.aatk then return false end if self.fhb ~= other.fhb then return false end if self.ndfb ~= other.ndfb then return false end if self.scrambled ~= other.scrambled then return false end return true end ---Clone timing. ---@return Timing function Timing:clone() local clone = Timing.new() clone.name = self.name clone.tag = self.tag clone.duih = self.duih clone.imdd = self.imdd clone.imxd = self.imxd clone.smn = self.smn clone.punishable = self.punishable clone.after = self.after clone.actions = self.actions:clone() clone.hitbox = self.hitbox clone.shape = self.shape clone.offset = self.offset clone.umoa = self.umoa clone.srpn = self.srpn clone.smod = self.smod clone.aatk = self.aatk clone.fhb = self.fhb clone.ndfb = self.ndfb clone.scrambled = self.scrambled return clone end ---Return a serializable table. ---@return table function Timing:serialize() return { name = self.name, tag = self.tag, imdd = self.imdd, imxd = self.imxd, duih = self.duih, punishable = self.punishable, smn = self.smn, after = self.after, actions = self.actions:serialize(), hitbox = { X = self.hitbox.X, Y = self.hitbox.Y, Z = self.hitbox.Z, }, shape = self.shape, offset = { X = self.offset.X, Y = self.offset.Y, Z = self.offset.Z, }, srpn = self.srpn, umoa = self.umoa, smod = self.smod, aatk = self.aatk, fhb = self.fhb, ndfb = self.ndfb, scrambled = self.scrambled, phd = self.phd, pfh = self.pfh, } end ---Create new Timing object. ---@param values table? ---@return Timing function Timing.new(values) local self = setmetatable({}, Timing) self.tag = "Other" self.name = "N/A" self.imdd = 0 self.imxd = 0 self.smn = false self.punishable = 0 self.after = 0 self.duih = false self.actions = ActionContainer.new() self.hitbox = Vector3.zero self.shape = "Box" self.offset = Vector3.zero self.umoa = false self.srpn = false self.smod = "N/A" self.aatk = false self.fhb = true self.ndfb = false self.scrambled = false if values then self:load(values) end return self end -- Return Timing module. return Timing end) -- MODULE: Game/Timings/TimingContainer defineModule("Game/Timings/TimingContainer", function() ---@module Utility.Logger local Logger = require("Utility/Logger") ---@class TimingContainer ---@field timings table ---@field module Timing local TimingContainer = {} TimingContainer.__index = TimingContainer ---Merge timing container. ---@param other TimingContainer ---@param type MergeType function TimingContainer:merge(other, type) local mergeType = type if type == "Add New Timings" or type == "1" then mergeType = 1 end if type == "Overwrite and Add Everything" or type == "2" then mergeType = 2 end assert(mergeType == 1 or mergeType == 2, "Invalid timing table merge type") for idx, timing in next, other.timings do if mergeType == 1 and self.timings[idx] then continue end self.timings[idx] = timing end end ---Find a timing from name. ---@param name string ---@return Timing? function TimingContainer:find(name) for _, timing in next, self.timings do if timing.name ~= name then continue end return timing end end ---Clone timing container. ---@return TimingContainer function TimingContainer:clone() local container = TimingContainer.new(self.module) for _, timing in next, self.timings do container:push(timing:clone()) end return container end ---List all timings. ---@return Timing[] function TimingContainer:list() local timings = {} for _, timing in next, self.timings do timings[#timings + 1] = timing end return timings end ---Get names of all timings. ---@return string[] function TimingContainer:names() local names = {} for _, timing in next, self.timings do names[#names + 1] = timing.name end table.sort(names) return names end ---Remove a timing from the list. ---@param timing Timing function TimingContainer:remove(timing) local id = timing:id() if not id then return end self.timings[id] = nil end ---Push a timing to the list. ---@param timing Timing function TimingContainer:push(timing) local id = timing:id() if not id then return end ---@note: Timing array keys must all be unique. if self.timings[id] then return error(string.format("Timing identifier '%s' already exists in container.", id)) end ---@note: Every timing must have unique names. if self:find(timing.name) then return error(string.format("Timing name '%s' already exists in container.", timing.name)) end self.timings[id] = timing end ---Equals check. ---@param other TimingContainer ---@return boolean function TimingContainer:equals(other) if self:count() ~= other:count() then return false end for id, timing in next, self.timings do local otherTiming = other.timings[id] if not otherTiming then return false end if not timing:equals(otherTiming) then return false end end return true end ---Clear all timings. function TimingContainer:clear() self.timings = {} end ---Get timing count. ---@return number function TimingContainer:count() local count = 0 for _ in next, self.timings do count = count + 1 end return count end ---Load from partial values. ---@param values table function TimingContainer:load(values) for _, value in next, values do local timing = self.module.new(value) if not timing then continue end local id = timing:id() if not id then continue end ---@note: Timing array keys must all be unique. if self.timings[id] then return error(string.format("Timing identifier '%s' already exists in container.", id)) end ---@note: Every timing must have unique names. if self:find(timing.name) then return error(string.format("Timing name '%s' already exists in container.", timing.name)) end ---@note: Why are the stored timing keys different from what's loaded? --- Internally, all timings are stored by their identifiers. --- This helps to quickly find a timing by its identifier. Example - an animation ID. --- Although, this does not mean each identifier must have a meaning. It can be random. self.timings[id] = timing end end ---Return a serializable table. ---@return table function TimingContainer:serialize() local out = {} for _, timing in next, self.timings do out[#out + 1] = timing:serialize() end return out end ---Create new TimingContainer object. ---@param module Timing ---@return TimingContainer function TimingContainer.new(module) local self = setmetatable({}, TimingContainer) self.timings = {} self.module = module return self end -- Return TimingContainer module. return TimingContainer end) -- MODULE: Game/Timings/TimingContainerPair defineModule("Game/Timings/TimingContainerPair", function() ---@class TimingContainerPair ---@note The configs are always prioritized over the internal timings. ---@field internal TimingContainer ---@field config TimingContainer local TimingContainerPair = {} TimingContainerPair.__index = TimingContainerPair ---Create new TimingContainerPair object. ---@param internal TimingContainer ---@param config TimingContainer ---@return TimingContainerPair function TimingContainerPair.new(internal, config) local self = setmetatable({}, TimingContainerPair) self.internal = internal self.config = config return self end ---Index timing container. ---@param key any? ---@return Timing? function TimingContainerPair:index(key) key = PP_SCRAMBLE_STR(key) return self.config.timings[key] or self.internal.timings[key] end ---Find timing from name. ---@param name string ---@return Timing? function TimingContainerPair:find(name) return self.config:find(name) or self.internal:find(name) end ---List all timings. ---@return Timing[] function TimingContainerPair:list() local timings = {} for _, timing in next, self.config:list() do table.insert(timings, timing) end for _, timing in next, self.internal:list() do table.insert(timings, timing) end return timings end -- Return TimingContainerStack module. return TimingContainerPair end) -- MODULE: Game/Timings/TimingSave defineModule("Game/Timings/TimingSave", function() ---@module Game.Timings.TimingContainer local TimingContainer = require("Game/Timings/TimingContainer") ---@module Game.Timings.AnimationTiming local AnimationTiming = require("Game/Timings/AnimationTiming") ---@module Game.Timings.PartTiming local PartTiming = require("Game/Timings/PartTiming") ---@module Game.Timings.SoundTiming local SoundTiming = require("Game/Timings/SoundTiming") ---@class TimingSave ---@field _data TimingContainer[] local TimingSave = {} TimingSave.__index = TimingSave ---Timing save version constant. ---@note: Increment me when the data structure changes and we need to add backwards compatibility. local TIMING_SAVE_VERSION = 1 ---@alias MergeType ---| '1' # Only add new timings ---| '2' # Overwrite and add everything ---Get timing save. ---@return TimingContainer[] function TimingSave:get() return self._data end ---Clear timing containers. function TimingSave:clear() for _, container in next, self._data do container:clear() end end ---Merge with another TimingSave object. ---@param save TimingSave The other save. ---@param type MergeType function TimingSave:merge(save, type) for idx, other in next, save._data do local container = self._data[idx] if not container then continue end container:merge(other, type) end end ---Load from partial values. ---@param values table function TimingSave:load(values) local data = self._data if typeof(values.animation) == "table" then data.animation:load(values.animation) end if typeof(values.part) == "table" then data.part:load(values.part) end if typeof(values.sound) == "table" then data.sound:load(values.sound) end end ---Clone timing save. ---@return TimingSave function TimingSave:clone() local save = TimingSave.new() for idx, container in next, self._data do save._data[idx] = container:clone() end return save end ---Equal timing saves. ---@param other TimingSave ---@return boolean function TimingSave:equals(other) if not other or typeof(other) ~= "table" then return false end for idx, container in next, self._data do local otherContainer = other._data[idx] if not otherContainer then return false end if not container:equals(otherContainer) then return false end end return true end ---Get timing save count. ---@return number function TimingSave:count() local count = 0 for _, container in next, self._data do count = count + container:count() end return count end ---Return a serializable table. ---@return table function TimingSave:serialize() local data = self._data return { version = TIMING_SAVE_VERSION, animation = data.animation:serialize(), part = data.part:serialize(), sound = data.sound:serialize(), } end ---Create new TimingSave object. ---@param values table? ---@return TimingSave function TimingSave.new(values) local self = setmetatable({}, TimingSave) self._data = { animation = TimingContainer.new(AnimationTiming), part = TimingContainer.new(PartTiming), sound = TimingContainer.new(SoundTiming), } if values then self:load(values) end return self end -- Return TimingSave module. return TimingSave end) -- MODULE: Menu/Objects/BuilderSection defineModule("Menu/Objects/BuilderSection", function() ---@module Game.Timings.Action local Action = require("Game/Timings/Action") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.Timings.Timing local Timing = require("Game/Timings/Timing") ---@module Utility.Configuration local Configuration = require("Utility/Configuration") ---@module Features.Combat.PositionHistory local PositionHistory = require("Features/Combat/PositionHistory") ---@class BuilderSection ---@note: We assume that all elements will exist in callbacks. This is why they are not explicitly set in the constructor. ---@field tabbox table ---@field pair TimingContainerPair ---@field name string ---@field timingList table ---@field timingName table ---@field timingTag table ---@field hitboxLength table ---@field hitboxWidth table ---@field hitboxHeight table ---@field timingType table ---@field punishableWindow table ---@field afterWindow table ---@field delayUntilInHitbox table ---@field initialMinimumDistance table ---@field initialMaximumDistance table ---@field actionList table ---@field actionName table ---@field actionDelay table ---@field actionType table local BuilderSection = {} BuilderSection.__index = BuilderSection -- Services. local stats = game:GetService("Stats") local players = game:GetService("Players") local debrisService = game:GetService("Debris") local PREVIEW_LIFETIME = 5.0 local PREVIEW_HIT_COLOR = Color3.new(0, 1, 0) local PREVIEW_PREDICT_COLOR = Color3.new(1, 0, 1) local PREVIEW_PAST_COLOR = Color3.new(0.839215, 0.976470, 0.537254) local function mapTag(tag) if tag == "Mantra" then return "Skill" end if tag == "Critical" then return "Counter" end if tag == "Undefined" then return "Other" end return tag end local function resolvePreviewRoot() local localPlayer = players.LocalPlayer local character = localPlayer and localPlayer.Character if not character then return nil, "No character found." end local root = character:FindFirstChild("HumanoidRootPart") if not root then return nil, "No HumanoidRootPart found." end return root, nil end local function applyFacingOffset(cframe, size, useFacingOffset) if not useFacingOffset then return cframe end return cframe * CFrame.new(0, 0, -(size.Z / 2)) end local function applyWorldOffset(cframe, offset) if not offset or offset == Vector3.zero then return cframe end local rotation = cframe - cframe.Position local applied = (cframe.RightVector * offset.X) + (cframe.UpVector * offset.Y) + (cframe.LookVector * offset.Z) return CFrame.new(cframe.Position + applied) * rotation end local function buildPreviewSize(width, height, length) return Vector3.new(tonumber(width) or 0, tonumber(height) or 0, tonumber(length) or 0) end local function spawnPreviewPart(cframe, size, color, shape) local part = Instance.new("Part") local hitboxShape = shape == "Ball" and "Ball" or "Box" local renderSize = size if hitboxShape == "Ball" then local radius = math.max(size.X, size.Y, size.Z) / 2 renderSize = Vector3.new(radius * 2, radius * 2, radius * 2) part.Shape = Enum.PartType.Ball else part.Shape = Enum.PartType.Block end part.Parent = workspace part.Anchored = true part.CanCollide = false part.CanQuery = false part.CanTouch = false part.Material = Enum.Material.ForceField part.CastShadow = false part.Size = renderSize part.CFrame = cframe part.Color = color part.Name = "RW_PreviewHitbox" part.Transparency = Configuration.expectToggleValue("EnableVisualizations") == false and 1 or 0.2 debrisService:AddItem(part, PREVIEW_LIFETIME) end ---Create timing ID element. Override me. ---@param tab table function BuilderSection:tide(tab) end ---Create extra elements. Override me. ---@param tab table function BuilderSection:extra(tab) end ---Load the extra elements. Override me. ---@param timing Timing function BuilderSection:exload(timing) end ---Load the extra action elements. Override me. ---@param action Action function BuilderSection:exaload(action) end function BuilderSection:previewHitbox(kind) local root, reason = resolvePreviewRoot() if not root then return Logger.longNotify("Preview failed: %s", reason) end local size = nil local shape = "Box" local offset = Vector3.zero if kind == "action" then size = buildPreviewSize(self.hitboxWidth.Value, self.hitboxHeight.Value, self.hitboxLength.Value) shape = self.actionHitboxShape and self.actionHitboxShape.Value or "Box" offset = Vector3.new( tonumber(self.actionHitboxOffsetX and self.actionHitboxOffsetX.Value) or 0, tonumber(self.actionHitboxOffsetY and self.actionHitboxOffsetY.Value) or 0, tonumber(self.actionHitboxOffsetZ and self.actionHitboxOffsetZ.Value) or 0 ) if self.delayUntilInHitbox and self.delayUntilInHitbox.Value then Logger.longNotify("Delay Until In Hitbox is enabled; runtime uses timing hitbox.") end else size = buildPreviewSize(self.timingHitboxWidth.Value, self.timingHitboxHeight.Value, self.timingHitboxLength.Value) shape = self.timingHitboxShape and self.timingHitboxShape.Value or "Box" offset = Vector3.new( tonumber(self.timingHitboxOffsetX and self.timingHitboxOffsetX.Value) or 0, tonumber(self.timingHitboxOffsetY and self.timingHitboxOffsetY.Value) or 0, tonumber(self.timingHitboxOffsetZ and self.timingHitboxOffsetZ.Value) or 0 ) end if size.Magnitude <= 0 then return Logger.longNotify("Preview failed: hitbox size is zero.") end if Configuration.expectToggleValue("EnableVisualizations") == false then Logger.longNotify("Enable Visualizations is off; preview will be invisible.") end local useFacingOffset = self.hitboxFacingOffset and self.hitboxFacingOffset.Value local baseCFrame = applyFacingOffset(root.CFrame, size, useFacingOffset) baseCFrame = applyWorldOffset(baseCFrame, offset) spawnPreviewPart(baseCFrame, size, PREVIEW_HIT_COLOR, shape) if self.predictFacingHitboxes and self.predictFacingHitboxes.Value then local predictTime = tonumber(self.extrapolationTime and self.extrapolationTime.Value) or 0 local predicted = root.CFrame + (root.AssemblyLinearVelocity * predictTime) predicted = applyFacingOffset(predicted, size, useFacingOffset) predicted = applyWorldOffset(predicted, offset) spawnPreviewPart(predicted, size, PREVIEW_PREDICT_COLOR, shape) end if self.pastHitboxDetection and self.pastHitboxDetection.Value then local historySeconds = tonumber(self.historySeconds and self.historySeconds.Value) or 0 local history = PositionHistory.stepped(players.LocalPlayer, 5, historySeconds) or {} for _, cframe in next, history do local previewCFrame = applyFacingOffset(cframe, size, useFacingOffset) previewCFrame = applyWorldOffset(previewCFrame, offset) spawnPreviewPart(previewCFrame, size, PREVIEW_PAST_COLOR, shape) end end end ---Action delay. Override me. ---@param base table function BuilderSection:daction(base) -- The user can accidently click this input through the dropdown and override the delay. -- It has been moved and set to "Finished" to prevent this. self.actionDelay = base:AddInput(nil, { Text = "Action Delay", Numeric = true, Finished = true, Callback = self:anc(function(action, value) action._when = tonumber(value) end), }) end ---Reset elements. Extend me. function BuilderSection:reset() -- Reset timing elements. self.timingName:SetRawValue("") self.timingType:SetRawValue("Config") self.timingTag:SetRawValue("Other") self.initialMaximumDistance:SetRawValue(0) self.punishableWindow:SetRawValue(0) self.afterWindow:SetRawValue(0) self.initialMinimumDistance:SetRawValue(0) self.delayUntilInHitbox:SetRawValue(false) self.timingHitboxHeight:SetRawValue(0) self.timingHitboxLength:SetRawValue(0) self.timingHitboxWidth:SetRawValue(0) if self.timingHitboxLengthInput then self.timingHitboxLengthInput:SetRawValue(0) end if self.timingHitboxWidthInput then self.timingHitboxWidthInput:SetRawValue(0) end if self.timingHitboxHeightInput then self.timingHitboxHeightInput:SetRawValue(0) end if self.timingHitboxShape then self.timingHitboxShape:SetRawValue("Box") end if self.timingHitboxOffsetX then self.timingHitboxOffsetX:SetRawValue(0) end if self.timingHitboxOffsetY then self.timingHitboxOffsetY:SetRawValue(0) end if self.timingHitboxOffsetZ then self.timingHitboxOffsetZ:SetRawValue(0) end self.skipRepeatNotification:SetRawValue(false) self.noDashFallback:SetRawValue(false) self.hitboxFacingOffset:SetRawValue(true) -- Reset action list. self:arefresh(nil) -- Reset action elements. self:raction() end ---Check before creating new timing. Override me. ---@return boolean function BuilderSection:check() if not self.timingName.Value or #self.timingName.Value <= 0 then return Logger.longNotify("Please enter a valid timing name.") end if self.pair:find(self.timingName.Value) then return Logger.longNotify("The timing '%s' already exists in the list.", self.timingName.Value) end return true end ---Check before creating new action. Override me. ---@param timing Timing ---@return boolean function BuilderSection:acheck(timing) if not self.actionName.Value or #self.actionName.Value <= 0 then return Logger.longNotify("Please enter a valid action name.") end if timing.actions:find(self.actionName.Value) then return Logger.longNotify("The action '%s' already exists in the list.", self.actionName.Value) end return true end ---Set creation timing properties. Override me. ---@param timing Timing function BuilderSection:cset(timing) timing.name = self.timingName.Value end ---Create new timing. Override me. ---@return Timing function BuilderSection:create() local timing = Timing.new() self:cset(timing) return timing end ---Initialize action tab. Extend me. function BuilderSection:action() self:baction(self.tabbox:AddTab("Action")) end ---Reset action elements. function BuilderSection:raction() self.actionName:SetRawValue("") self.actionDelay:SetRawValue(0) self.actionType:SetRawValue("Parry") self.hitboxHeight:SetRawValue(0) self.hitboxLength:SetRawValue(0) self.hitboxWidth:SetRawValue(0) if self.hitboxLengthInput then self.hitboxLengthInput:SetRawValue(0) end if self.hitboxWidthInput then self.hitboxWidthInput:SetRawValue(0) end if self.hitboxHeightInput then self.hitboxHeightInput:SetRawValue(0) end if self.actionHitboxShape then self.actionHitboxShape:SetRawValue("Box") end if self.actionHitboxOffsetX then self.actionHitboxOffsetX:SetRawValue(0) end if self.actionHitboxOffsetY then self.actionHitboxOffsetY:SetRawValue(0) end if self.actionHitboxOffsetZ then self.actionHitboxOffsetZ:SetRawValue(0) end end ---Refresh timing list. function BuilderSection:refresh() local values = self.timingType.Value == "Internal" and self.pair.internal:names() or self.pair.config:names() self.timingList:SetValues(values) self.timingList:SetValue(nil) self.timingList:Display() end ---Refresh action list. ---@param timing Timing? function BuilderSection:arefresh(timing) self.actionList:SetValues(timing and timing.actions:names() or {}) self.actionList:SetValue(nil) self.actionList:Display() end ---Wrap a callback that needs a timing. This will check for internal timings. ---@param callback function(Timing, ...) ---@return function(...) function BuilderSection:tnc(callback) return function(...) -- If no value, return. if not self.timingList.Value then return Logger.warn("No timing selected.") end -- Find timing. local timing = self.pair:find(self.timingList.Value) if not timing then return Logger.longNotify("You must select a valid timing to perform this action.") end -- Check timing type. if self.timingType.Value == "Internal" then return Logger.longNotify("Internal timing. Changes not replicated. You must clone it to the config first.") end -- Fire callback. callback(timing, ...) end end ---Wrap a callback that needs both an action and a timing. ---@note: This will check for internal timings. ---@param callback function(Timing, Action, ...) ---@return function function BuilderSection:tanc(callback) return function(...) -- If no value, return. if not self.timingList.Value then return Logger.warn("No timing selected.") end -- Find timing. local timing = self.pair:find(self.timingList.Value) if not timing then return Logger.longNotify("You must select a valid timing to perform this action.") end -- If no value, return. if not self.actionList.Value then return Logger.warn("No action selected.") end -- Find action. local action = timing.actions:find(self.actionList.Value) if not action then return Logger.longNotify("You must select a valid action to perform this action.") end -- Check timing type. if self.timingType.Value == "Internal" then return Logger.longNotify("Internal timing. Changes not replicated. You must clone it to the config first.") end -- Fire callback. callback(timing, action, ...) end end ---Wrap a callback that needs a action. This will check for internal timings. ---@param callback function(Action, ...) ---@return function function BuilderSection:anc(callback) return function(...) -- If no value, return. if not self.timingList.Value then return Logger.warn("No timing selected.") end -- Find timing. local timing = self.pair:find(self.timingList.Value) if not timing then return Logger.longNotify("You must select a valid timing to perform this action.") end -- If no value, return. if not self.actionList.Value then return Logger.warn("No action selected.") end -- Find action. local action = timing.actions:find(self.actionList.Value) if not action then return Logger.longNotify("You must select a valid action to perform this action.") end -- Check timing type. if self.timingType.Value == "Internal" then return Logger.longNotify("Internal timing. Changes not replicated. You must clone it to the config first.") end -- Fire callback. callback(action, ...) end end ---Initialize action base. ---@param base table function BuilderSection:baction(base) self.actionList = base:AddDropdown(nil, { Text = "Action List", Values = {}, AllowNull = true, Callback = self:tnc(function(timing, value) -- Reset action elements. self:raction() -- Check if value exists. if not value then return Logger.warn("No action value.") end -- Find action. local action = timing.actions:find(value) if not action then return Logger.longNotify("The selected action '%s' does not exist in the list.", value) end -- Set action elements. self.actionName:SetRawValue(action.name) self.actionDelay:SetRawValue(action._when or 0) self.actionType:SetRawValue(action._type) self.hitboxWidth:SetRawValue(action.hitbox.X) self.hitboxHeight:SetRawValue(action.hitbox.Y) self.hitboxLength:SetRawValue(action.hitbox.Z) if self.hitboxLengthInput then self.hitboxLengthInput:SetRawValue(action.hitbox.Z) end if self.hitboxWidthInput then self.hitboxWidthInput:SetRawValue(action.hitbox.X) end if self.hitboxHeightInput then self.hitboxHeightInput:SetRawValue(action.hitbox.Y) end if self.actionHitboxShape then self.actionHitboxShape:SetRawValue(action.shape or "Box") end if self.actionHitboxOffsetX then local offset = action.offset or Vector3.zero self.actionHitboxOffsetX:SetRawValue(offset.X) self.actionHitboxOffsetY:SetRawValue(offset.Y) self.actionHitboxOffsetZ:SetRawValue(offset.Z) end -- Load extra action elements. self:exaload(action) end), }) self.actionType = base:AddDropdown(nil, { Text = "Action Type", Values = { "Parry", "Dash", "Jump", "Start Block", "End Block" }, Default = 1, Callback = self:anc(function(action, value) action._type = value end), }) self:daction(base) self._syncingActionHitbox = false self.actionHitboxShape = base:AddDropdown(nil, { Text = "Hitbox Shape", Values = { "Box", "Ball" }, Default = 1, Callback = self:anc(function(action, value) action.shape = value end), }) local function updateActionLength(value) local num = tonumber(value) or 0 if self._syncingActionHitbox then return end self._syncingActionHitbox = true self.hitboxLength:SetRawValue(num) if self.hitboxLengthInput then self.hitboxLengthInput:SetRawValue(num) end self._syncingActionHitbox = false self:anc(function(action, val) action.hitbox = Vector3.new(action.hitbox.X, action.hitbox.Y, val) end)(num) end local function updateActionWidth(value) local num = tonumber(value) or 0 if self._syncingActionHitbox then return end self._syncingActionHitbox = true self.hitboxWidth:SetRawValue(num) if self.hitboxWidthInput then self.hitboxWidthInput:SetRawValue(num) end self._syncingActionHitbox = false self:anc(function(action, val) action.hitbox = Vector3.new(val, action.hitbox.Y, action.hitbox.Z) end)(num) end local function updateActionHeight(value) local num = tonumber(value) or 0 if self._syncingActionHitbox then return end self._syncingActionHitbox = true self.hitboxHeight:SetRawValue(num) if self.hitboxHeightInput then self.hitboxHeightInput:SetRawValue(num) end self._syncingActionHitbox = false self:anc(function(action, val) action.hitbox = Vector3.new(action.hitbox.X, val, action.hitbox.Z) end)(num) end self.hitboxLength = base:AddSlider(nil, { Text = "Hitbox Length", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateActionLength, }) self.hitboxLengthInput = base:AddInput(nil, { Text = "Hitbox Length (Input)", Numeric = true, Finished = false, Callback = updateActionLength, }) self.hitboxWidth = base:AddSlider(nil, { Text = "Hitbox Width", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateActionWidth, }) self.hitboxWidthInput = base:AddInput(nil, { Text = "Hitbox Width (Input)", Numeric = true, Finished = false, Callback = updateActionWidth, }) self.hitboxHeight = base:AddSlider(nil, { Text = "Hitbox Height", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateActionHeight, }) self.hitboxHeightInput = base:AddInput(nil, { Text = "Hitbox Height (Input)", Numeric = true, Finished = false, Callback = updateActionHeight, }) self.actionHitboxOffsetX = base:AddInput(nil, { Text = "Hitbox Offset X", Numeric = true, Finished = true, Callback = self:anc(function(action, value) local x = tonumber(value) or 0 local y = tonumber(self.actionHitboxOffsetY and self.actionHitboxOffsetY.Value) or 0 local z = tonumber(self.actionHitboxOffsetZ and self.actionHitboxOffsetZ.Value) or 0 action.offset = Vector3.new(x, y, z) end), }) self.actionHitboxOffsetY = base:AddInput(nil, { Text = "Hitbox Offset Y", Numeric = true, Finished = true, Callback = self:anc(function(action, value) local x = tonumber(self.actionHitboxOffsetX and self.actionHitboxOffsetX.Value) or 0 local y = tonumber(value) or 0 local z = tonumber(self.actionHitboxOffsetZ and self.actionHitboxOffsetZ.Value) or 0 action.offset = Vector3.new(x, y, z) end), }) self.actionHitboxOffsetZ = base:AddInput(nil, { Text = "Hitbox Offset Z", Numeric = true, Finished = true, Callback = self:anc(function(action, value) local x = tonumber(self.actionHitboxOffsetX and self.actionHitboxOffsetX.Value) or 0 local y = tonumber(self.actionHitboxOffsetY and self.actionHitboxOffsetY.Value) or 0 local z = tonumber(value) or 0 action.offset = Vector3.new(x, y, z) end), }) base:AddButton("Preview Action Hitbox", function() self:previewHitbox("action") end) base:AddDivider() self.actionName = base:AddInput(nil, { Text = "Action Name", }) base:AddButton( "Create New Action", self:tnc(function(timing) -- Fetch actions. local actions = timing.actions -- Check. if not self:acheck(timing) then return end -- Create new action. local action = Action.new() action.name = self.actionName.Value action._type = "Parry" -- Record ping for telemetry. local network = stats:FindFirstChild("Network") local serverStatsItem = network and network:FindFirstChild("ServerStatsItem") local dataPingItem = serverStatsItem and serverStatsItem:FindFirstChild("Data Ping") if dataPingItem then local ok, value = pcall(function() return dataPingItem:GetValue() end) if ok and type(value) == "number" then action.ping = value end end -- Push action. actions:push(action) -- Refresh action list. self:arefresh(timing) -- Set action list value. self.actionList:SetValue(action.name) self.actionList:Display() end) ) base:AddButton( "Duplicate Selected Action", self:tanc(function(timing, action) -- Fetch actions. local actions = timing.actions -- Check. if not self:acheck(timing) then return end -- Create new action. local newAction = action:clone() newAction.name = self.actionName.Value -- Record ping for telemetry. local network = stats:FindFirstChild("Network") local serverStatsItem = network and network:FindFirstChild("ServerStatsItem") local dataPingItem = serverStatsItem and serverStatsItem:FindFirstChild("Data Ping") if dataPingItem then local ok, value = pcall(function() return dataPingItem:GetValue() end) if ok and type(value) == "number" then newAction.ping = value end end -- Push action. actions:push(newAction) -- Refresh action list. self:arefresh(timing) -- Set action list value. self.actionList:SetValue(newAction.name) self.actionList:Display() end) ) base:AddButton( "Remove Selected Action", self:tnc(function(timing) -- Get selected value. local selected = self.actionList.Value if not selected then return Logger.longNotify("Please select an action to remove.") end -- Fetch actions. local actions = timing.actions -- Find action. local action = actions:find(selected) if not action then return Logger.longNotify("The selected action '%s' does not exist in the list.", selected) end -- Remove action. actions:remove(action) -- Refresh action list. self:arefresh(timing) end) ) end ---Initialize timing tab. function BuilderSection:timing() local tab = self.tabbox:AddTab("Timings") self.timingType = tab:AddDropdown(nil, { Text = "Timing Type", Values = { "Config", "Internal" }, Default = 1, Callback = function() -- Refresh timing list. self:refresh() -- Reset elements. self:reset() end, }) self.timingList = tab:AddDropdown(nil, { Text = "Timing List", Values = self.timingType.Value == "Internal" and self.pair.internal:names() or self.pair.config:names(), AllowNull = true, Callback = function(value) -- Reset elements. self:reset() -- Check if value exists. if not value then return Logger.warn("No timing value.") end -- Fetch timing. local found = self.pair:find(value) if not found then return Logger.longNotify("The selected timing '%s' does not exist in the list.", value) end -- Set timing elements. self.timingName:SetRawValue(found.name) self.timingTag:SetRawValue(mapTag(found.tag)) self.initialMaximumDistance:SetRawValue(found.imxd) self.initialMinimumDistance:SetRawValue(found.imdd) self.delayUntilInHitbox:SetRawValue(found.duih) self.timingHitboxLength:SetRawValue(found.hitbox.Z) self.timingHitboxWidth:SetRawValue(found.hitbox.X) self.timingHitboxHeight:SetRawValue(found.hitbox.Y) if self.timingHitboxLengthInput then self.timingHitboxLengthInput:SetRawValue(found.hitbox.Z) end if self.timingHitboxWidthInput then self.timingHitboxWidthInput:SetRawValue(found.hitbox.X) end if self.timingHitboxHeightInput then self.timingHitboxHeightInput:SetRawValue(found.hitbox.Y) end if self.timingHitboxShape then self.timingHitboxShape:SetRawValue(found.shape or "Box") end if self.timingHitboxOffsetX then local offset = found.offset or Vector3.zero self.timingHitboxOffsetX:SetRawValue(offset.X) self.timingHitboxOffsetY:SetRawValue(offset.Y) self.timingHitboxOffsetZ:SetRawValue(offset.Z) end self.punishableWindow:SetRawValue(found.punishable) self.afterWindow:SetRawValue(found.after) self.skipRepeatNotification:SetRawValue(found.srpn) self.hitboxFacingOffset:SetRawValue(found.fhb) self.noDashFallback:SetRawValue(found.ndfb) -- Load extra elements. self:exload(found) -- Refresh action list. self:arefresh(found) end, }) tab:AddDivider() self.timingName = tab:AddInput(nil, { Text = "Timing Name", Finished = true, }) self:tide(tab) local configDepBox = tab:AddDependencyBox() configDepBox:AddButton("Create New Timing", function() -- Fetch config. local config = self.pair.config -- Check if we can successfully create a timing from the given data. if not self:check() then return end -- Create new timing. local timing = self:create() -- Push new timing. config:push(timing) -- Refresh timing list. self:refresh() -- Set timing list value. self.timingList:SetValue(timing.name) self.timingList:Display() end) configDepBox:AddButton( "Duplicate Selected Timing", self:tnc(function(found) -- Fetch config. local config = self.pair.config -- Check if we can successfully create a timing from the given data. if not self:check() then return end -- Clone new timing. local timing = found:clone() -- Set creation properties. self:cset(timing) -- Push new timing. config:push(timing) -- Refresh timing list. self:refresh() -- Set timing list value. self.timingList:SetValue(timing.name) self.timingList:Display() end) ) local internalDepBox = tab:AddDependencyBox() internalDepBox:AddButton("Clone To Config", function() -- Fetch name. local name = self.timingList.Value if not name then return Logger.longNotify("Please select a timing to clone.") end -- Fetch data. local internal = self.pair.internal local config = self.pair.config -- Fetch the currently selected timing. local found = internal:find(name) if not found then return Logger.longNotify("The selected timing '%s' does not exist in the list.", name) end -- Check for existing ID. if config.timings[found:id()] then return Logger.longNotify("The timing ID '%s' already exists in the config.", found:id()) end -- Check for existing timing. if config:find(found.name) then return Logger.longNotify("The timing name '%s' already exists in the config.", found.name) end -- Clone timing. ---@note: No need to refresh after this. It's in the other timing list! config:push(internal:clone(found)) end) tab:AddButton("Remove Selected Timing", function() -- Fetch name. local name = self.timingList.Value if not name then return Logger.longNotify("Please select a timing to remove.") end -- Fetch data. local internal = self.pair.internal local config = self.pair.config local found = config:find(name) -- Check if internal. ---@todo: Implement functionality to remove internal timings. if internal:find(name) then return Logger.longNotify("You cannot remove internal timings, only override them.") end -- Check if found. if not found then return Logger.longNotify("The selected timing '%s' does not exist in the list.", name) end -- Remove timing. config:remove(found) -- Refresh timing list. self:refresh() end) configDepBox:SetupDependencies({ { self.timingType, "Config" }, }) internalDepBox:SetupDependencies({ { self.timingType, "Internal" }, }) end ---Initialize builder tab. function BuilderSection:builder() local tab = self.tabbox:AddTab(string.format("%s", self.name)) self.timingTag = tab:AddDropdown(nil, { Text = "Timing Tag", Values = { "Other", "Counter", "Skill", "M1" }, Default = 1, Callback = self:tnc(function(timing, value) timing.tag = value end), }) self.initialMinimumDistance = tab:AddSlider(nil, { Text = "Initial Minimum Distance", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = self:tnc(function(timing, value) timing.imdd = value end), }) self.initialMaximumDistance = tab:AddSlider(nil, { Text = "Initial Maximum Distance", Min = 0, Max = 2500, Suffix = "s", Default = 1000, Rounding = 0, Callback = self:tnc(function(timing, value) timing.imxd = value end), }) self.punishableWindow = tab:AddSlider(nil, { Text = "Punishable Window", Min = 0, Max = 2, Default = 0.6, Suffix = "s", Rounding = 1, Callback = self:tnc(function(timing, value) timing.punishable = value end), }) self.afterWindow = tab:AddSlider(nil, { Text = "After Window", Min = 0, Max = 1, Default = 0.1, Suffix = "s", Rounding = 2, Callback = self:tnc(function(timing, value) timing.after = value end), }) self.delayUntilInHitbox = tab:AddToggle(nil, { Text = "Delay Until In Hitbox", Default = false, Callback = self:tnc(function(timing, value) timing.duih = value end), }) local duihDepBox = tab:AddDependencyBox() self._syncingTimingHitbox = false self.timingHitboxShape = duihDepBox:AddDropdown(nil, { Text = "Hitbox Shape", Values = { "Box", "Ball" }, Default = 1, Callback = self:tnc(function(timing, value) timing.shape = value end), }) local function updateTimingLength(value) local num = tonumber(value) or 0 if self._syncingTimingHitbox then return end self._syncingTimingHitbox = true self.timingHitboxLength:SetRawValue(num) if self.timingHitboxLengthInput then self.timingHitboxLengthInput:SetRawValue(num) end self._syncingTimingHitbox = false self:tnc(function(timing, val) timing.hitbox = Vector3.new(timing.hitbox.X, timing.hitbox.Y, val) end)(num) end local function updateTimingWidth(value) local num = tonumber(value) or 0 if self._syncingTimingHitbox then return end self._syncingTimingHitbox = true self.timingHitboxWidth:SetRawValue(num) if self.timingHitboxWidthInput then self.timingHitboxWidthInput:SetRawValue(num) end self._syncingTimingHitbox = false self:tnc(function(timing, val) timing.hitbox = Vector3.new(val, timing.hitbox.Y, timing.hitbox.Z) end)(num) end local function updateTimingHeight(value) local num = tonumber(value) or 0 if self._syncingTimingHitbox then return end self._syncingTimingHitbox = true self.timingHitboxHeight:SetRawValue(num) if self.timingHitboxHeightInput then self.timingHitboxHeightInput:SetRawValue(num) end self._syncingTimingHitbox = false self:tnc(function(timing, val) timing.hitbox = Vector3.new(timing.hitbox.X, val, timing.hitbox.Z) end)(num) end self.timingHitboxLength = duihDepBox:AddSlider(nil, { Text = "Hitbox Length", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateTimingLength, }) self.timingHitboxLengthInput = duihDepBox:AddInput(nil, { Text = "Hitbox Length (Input)", Numeric = true, Finished = false, Callback = updateTimingLength, }) self.timingHitboxWidth = duihDepBox:AddSlider(nil, { Text = "Hitbox Width", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateTimingWidth, }) self.timingHitboxWidthInput = duihDepBox:AddInput(nil, { Text = "Hitbox Width (Input)", Numeric = true, Finished = false, Callback = updateTimingWidth, }) self.timingHitboxHeight = duihDepBox:AddSlider(nil, { Text = "Hitbox Height", Min = 0, Max = 300, Suffix = "s", Default = 0, Rounding = 0, Callback = updateTimingHeight, }) self.timingHitboxHeightInput = duihDepBox:AddInput(nil, { Text = "Hitbox Height (Input)", Numeric = true, Finished = false, Callback = updateTimingHeight, }) self.timingHitboxOffsetX = duihDepBox:AddInput(nil, { Text = "Hitbox Offset X", Numeric = true, Finished = true, Callback = self:tnc(function(timing, value) local x = tonumber(value) or 0 local y = tonumber(self.timingHitboxOffsetY and self.timingHitboxOffsetY.Value) or 0 local z = tonumber(self.timingHitboxOffsetZ and self.timingHitboxOffsetZ.Value) or 0 timing.offset = Vector3.new(x, y, z) end), }) self.timingHitboxOffsetY = duihDepBox:AddInput(nil, { Text = "Hitbox Offset Y", Numeric = true, Finished = true, Callback = self:tnc(function(timing, value) local x = tonumber(self.timingHitboxOffsetX and self.timingHitboxOffsetX.Value) or 0 local y = tonumber(value) or 0 local z = tonumber(self.timingHitboxOffsetZ and self.timingHitboxOffsetZ.Value) or 0 timing.offset = Vector3.new(x, y, z) end), }) self.timingHitboxOffsetZ = duihDepBox:AddInput(nil, { Text = "Hitbox Offset Z", Numeric = true, Finished = true, Callback = self:tnc(function(timing, value) local x = tonumber(self.timingHitboxOffsetX and self.timingHitboxOffsetX.Value) or 0 local y = tonumber(self.timingHitboxOffsetY and self.timingHitboxOffsetY.Value) or 0 local z = tonumber(value) or 0 timing.offset = Vector3.new(x, y, z) end), }) duihDepBox:AddButton("Preview Timing Hitbox", function() self:previewHitbox("timing") end) duihDepBox:SetupDependencies({ { self.delayUntilInHitbox, true }, }) self.skipRepeatNotification = tab:AddToggle(nil, { Text = "Skip Repeat Notification", Default = false, Callback = self:tnc(function(timing, value) timing.srpn = value end), }) self.hitboxFacingOffset = tab:AddToggle(nil, { Text = "Hitbox Facing Offset", Tooltip = "Should the hitbox be offset towards the facing direction?", Default = true, Callback = self:tnc(function(timing, value) timing.fhb = value end), }) self.noDashFallback = tab:AddToggle(nil, { Text = "No Dash Fallback", Tooltip = "If enabled, the timing will not fallback to a dash if the parry action is not available.", Default = false, Callback = self:tnc(function(timing, value) timing.ndfb = value end), }) self:extra(tab) end ---Initialize BuilderSection object. function BuilderSection:init() self:timing() self:builder() self:action() end ---Create new BuilderSection object. ---@param name string ---@param tabbox table ---@param pair TimingContainerPair ---@param timing Timing ---@return BuilderSection function BuilderSection.new(name, tabbox, pair, timing) local self = setmetatable({}, BuilderSection) self.name = name self.tabbox = tabbox self.pair = pair return self end -- Return BuilderSection module. return BuilderSection end) -- MODULE: Menu/Objects/AnimationBuilderSection defineModule("Menu/Objects/AnimationBuilderSection", function() ---@module Menu.Objects.BuilderSection local BuilderSection = require("Menu/Objects/BuilderSection") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.Timings.AnimationTiming local AnimationTiming = require("Game/Timings/AnimationTiming") ---@class AnimationBuilderSection: BuilderSection ---@field animationId table ---@field repeatStartDelay table ---@field repeatUntilParryEnd table ---@field repeatParryDelay table ---@field timing AnimationTiming local AnimationBuilderSection = setmetatable({}, { __index = BuilderSection }) AnimationBuilderSection.__index = AnimationBuilderSection ---Create timing ID element. Override me. ---@param tab table function AnimationBuilderSection:tide(tab) self.animationId = tab:AddInput(nil, { Text = "Animation ID", }) end ---Load the extra elements. Override me. ---@param timing AnimationTiming function AnimationBuilderSection:exload(timing) self.animationId:SetRawValue(timing._id) self.repeatUntilParryEnd:SetRawValue(timing.rpue) self.repeatStartDelay:SetRawValue(timing._rsd) self.repeatParryDelay:SetRawValue(timing._rpd) self.ignoreAnimationEnd:SetRawValue(timing.iae) self.ignoreEarlyAnimationEnd:SetRawValue(timing.ieae) self.maxAnimationTimeout:SetRawValue(timing.mat) self.pastHitboxDetection:SetRawValue(timing.phd) self.predictFacingHitboxes:SetRawValue(timing.pfh) self.historySeconds:SetRawValue(timing.phds) self.extrapolationTime:SetRawValue(timing.pfht) end ---Reset the elements. Extend me. function AnimationBuilderSection:reset() BuilderSection.reset(self) self.animationId:SetRawValue("") self.repeatParryDelay:SetRawValue(0) self.repeatStartDelay:SetRawValue(0) self.repeatUntilParryEnd:SetRawValue(false) self.hitboxFacingOffset:SetRawValue(true) self.ignoreAnimationEnd:SetRawValue(false) self.ignoreEarlyAnimationEnd:SetRawValue(false) self.maxAnimationTimeout:SetRawValue(2000) self.pastHitboxDetection:SetRawValue(false) self.historySeconds:SetRawValue(0.5) self.predictFacingHitboxes:SetRawValue(false) self.extrapolationTime:SetRawValue(0.15) end ---Check before creating new timing. Override me. ---@return boolean function AnimationBuilderSection:check() if not BuilderSection.check(self) then return false end if not self.animationId.Value or #self.animationId.Value <= 0 then return Logger.longNotify("Please enter a valid animation ID.") end local timing = self.pair:index(self.animationId.Value) if timing then return Logger.longNotify("The timing ID '%s' (%s) is already in the list.", self.animationId.Value, timing.name) end return true end ---Set creation timing properties. Override me. ---@param timing AnimationTiming function AnimationBuilderSection:cset(timing) timing.name = self.timingName.Value timing._id = self.animationId.Value end ---Create new timing. Override me. ---@return Timing function AnimationBuilderSection:create() local timing = AnimationTiming.new() self:cset(timing) return timing end ---Initialize extra tab. ---@param tab table function AnimationBuilderSection:extra(tab) self.ignoreAnimationEnd = tab:AddToggle(nil, { Text = "Ignore Animation End", Tooltip = "Should the timing ignore the end of the animation?", Default = false, Callback = self:tnc(function(timing, value) timing.iae = value end), }) local depBoxEnd = tab:AddDependencyBox() self.maxAnimationTimeout = depBoxEnd:AddInput(nil, { Text = "Max Animation Timeout", Tooltip = "The maximum time (in milliseconds) that the animation is allowed to run with no end check.", Default = 2000, Numeric = true, Callback = self:tnc(function(timing, value) timing.mat = tonumber(value) end), }) depBoxEnd:SetupDependencies({ { self.ignoreAnimationEnd, true }, }) self.ignoreEarlyAnimationEnd = tab:AddToggle(nil, { Text = "Ignore Early Animation End", Tooltip = "Should the timing ignore the early end of the animation?", Default = false, Callback = self:tnc(function(timing, value) timing.ieae = value end), }) self.pastHitboxDetection = tab:AddToggle(nil, { Text = "Past Hitbox Detection", Default = false, Tooltip = "Should the hitbox detection track the past hitboxes too?", Callback = self:tnc(function(timing, value) timing.phd = value end), }) local pfdOffDepBox = tab:AddDependencyBox() self.historySeconds = pfdOffDepBox:AddSlider(nil, { Text = "History Seconds", Tooltip = "How far back in seconds should we fetch history?", Default = 0.5, Min = 0, Max = 3.0, Rounding = 2, Numeric = true, Callback = self:tnc(function(timing, value) timing.phds = tonumber(value) or 0 end), }) pfdOffDepBox:SetupDependencies({ { self.pastHitboxDetection, true }, }) self.predictFacingHitboxes = tab:AddToggle(nil, { Text = "Predict Facing Hitboxes", Default = false, Tooltip = "Should we make a prediction on the facing direction and make a hitbox on that?", Callback = self:tnc(function(timing, value) timing.pfh = value end), }) self.disablePrediction = tab:AddToggle(nil, { Text = "Disable Prediction", Default = false, Tooltip = "Should we disable prediction?", Callback = self:tnc(function(timing, value) timing.dp = value end), }) self.extrapolationTime = tab:AddSlider(nil, { Text = "Extrapolation Time", Tooltip = "The time (in seconds) to extrapolate by.", Default = 0.15, Min = 0, Max = 2.0, Rounding = 3, Numeric = true, Callback = self:tnc(function(timing, value) timing.pfht = tonumber(value) end), }) end ---Initialize action tab. function AnimationBuilderSection:action() local tab = self.tabbox:AddTab("Action") self.repeatUntilParryEnd = tab:AddToggle(nil, { Text = "Repeat Parry Until End", Default = false, Callback = self:tnc(function(timing, value) timing.rpue = value end), }) local depBoxOn = tab:AddDependencyBox() self.repeatStartDelay = depBoxOn:AddInput(nil, { Text = "Repeat Start Delay", Numeric = true, Finished = true, Callback = self:tnc(function(timing, value) timing._rsd = tonumber(value) or 0 end), }) self.repeatParryDelay = depBoxOn:AddInput(nil, { Text = "Repeat Parry Delay", Numeric = true, Finished = true, Callback = self:tnc(function(timing, value) timing._rpd = tonumber(value) or 0 end), }) local depBoxOff = tab:AddDependencyBox() self:baction(depBoxOff) depBoxOn:SetupDependencies({ { self.repeatUntilParryEnd, true }, }) depBoxOff:SetupDependencies({ { self.repeatUntilParryEnd, false }, }) end ---Create new AnimationBuilderSection object. ---@param name string ---@param tabbox table ---@param pair TimingContainerPair ---@param timing AnimationTiming ---@return AnimationBuilderSection function AnimationBuilderSection.new(name, tabbox, pair, timing) return setmetatable(BuilderSection.new(name, tabbox, pair, timing), AnimationBuilderSection) end -- Return AnimationBuilderSection module. return AnimationBuilderSection end) -- MODULE: Menu/Objects/PartBuilderSection defineModule("Menu/Objects/PartBuilderSection", function() ---@module Menu.Objects.BuilderSection local BuilderSection = require("Menu/Objects/BuilderSection") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.Timings.PartTiming local PartTiming = require("Game/Timings/PartTiming") ---@class PartBuilderSection: BuilderSection ---@field partName table ---@field timingDelay table ---@field initialMinimumDistance table ---@field initialMaximumDistance table ---@field timing PartTiming local PartBuilderSection = setmetatable({}, { __index = BuilderSection }) PartBuilderSection.__index = PartBuilderSection ---Check before writing. ---@return boolean function PartBuilderSection:check() if not BuilderSection.check(self) then return false end if not self.partName.Value or #self.partName.Value <= 0 then return Logger.longNotify("Please enter a valid part name.") end if self.pair:index(self.partName.Value) then return Logger.longNotify("The timing ID '%s' is already in the list.", self.partName.Value) end return true end ---Load the extra elements. Override me. ---@param timing Timing function PartBuilderSection:exload(timing) self.useHitboxCFrame:SetRawValue(timing.uhc) self.partName:SetRawValue(timing.pname) end ---Reset the elements. Extend me. function PartBuilderSection:reset() BuilderSection.reset(self) self.partName:SetRawValue("") end ---Set creation timing properties. Override me. ---@param timing PartTiming function PartBuilderSection:cset(timing) timing.name = self.timingName.Value timing.pname = self.partName.Value end ---Create new timing. Override me. ---@return PartTiming function PartBuilderSection:create() local timing = PartTiming.new() self:cset(timing) return timing end ---Create timing ID element. Override me. ---@param tab table function PartBuilderSection:tide(tab) self.partName = tab:AddInput(nil, { Text = "Part Name", }) end ---Initialize extra tab. ---@param tab table function PartBuilderSection:extra(tab) self.useHitboxCFrame = tab:AddToggle(nil, { Text = "Use Hitbox CFrame", Tooltip = "Should the hitbox face where it was originally supposed to?", Default = true, Callback = self:tnc(function(timing, value) timing.uhc = value end), }) end ---Initialize PartBuilderSection object. function PartBuilderSection:init() self:timing() self:builder() self:action() end ---Create new PartBuilderSection object. ---@param name string ---@param tabbox table ---@param pair TimingContainerPair ---@param timing PartTiming ---@return PartBuilderSection function PartBuilderSection.new(name, tabbox, pair, timing) return setmetatable(BuilderSection.new(name, tabbox, pair, timing), PartBuilderSection) end -- Return PartBuilderSection module. return PartBuilderSection end) -- MODULE: Menu/Objects/SoundBuilderSection defineModule("Menu/Objects/SoundBuilderSection", function() ---@module Menu.Objects.BuilderSection local BuilderSection = require("Menu/Objects/BuilderSection") ---@module Utility.Logger local Logger = require("Utility/Logger") ---@module Game.Timings.SoundTiming local SoundTiming = require("Game/Timings/SoundTiming") ---@class SoundBuilderSection: BuilderSection ---@field soundId table ---@field repeatStartDelay table ---@field repeatUntilParryEnd table ---@field repeatParryDelay table ---@field timing SoundTiming local SoundBuilderSection = setmetatable({}, { __index = BuilderSection }) SoundBuilderSection.__index = SoundBuilderSection ---Create timing ID element. Override me. ---@param tab table function SoundBuilderSection:tide(tab) self.soundId = tab:AddInput(nil, { Text = "Sound ID", }) end ---Load the extra elements. Override me. ---@param timing Timing function SoundBuilderSection:exload(timing) self.soundId:SetRawValue(timing._id) self.repeatStartDelay:SetRawValue(timing._rsd) self.repeatUntilParryEnd:SetRawValue(timing.rpue) self.repeatParryDelay:SetRawValue(timing._rpd) end ---Reset the elements. Extend me. function SoundBuilderSection:reset() BuilderSection.reset(self) self.soundId:SetRawValue("") self.repeatParryDelay:SetRawValue(0) self.repeatStartDelay:SetRawValue(0) self.repeatUntilParryEnd:SetRawValue(false) end ---Check before creating new timing. Override me. ---@return boolean function SoundBuilderSection:check() if not BuilderSection.check(self) then return false end if not self.soundId.Value or #self.soundId.Value <= 0 then return Logger.longNotify("Please enter a valid sound ID.") end if self.pair:index(self.soundId.Value) then return Logger.longNotify("The timing ID '%s' is already in the list.", self.soundId.Value) end return true end ---Set creation timing properties. Override me. ---@param timing SoundTiming function SoundBuilderSection:cset(timing) timing.name = self.timingName.Value timing._id = self.soundId.Value end ---Create new timing. Override me. ---@return Timing function SoundBuilderSection:create() local timing = SoundTiming.new() self:cset(timing) return timing end ---Initialize action tab. function SoundBuilderSection:action() local tab = self.tabbox:AddTab("Action") self.repeatUntilParryEnd = tab:AddToggle(nil, { Text = "Repeat Parry Until End", Default = false, Callback = self:tnc(function(timing, value) timing.rpue = value end), }) local depBoxOn = tab:AddDependencyBox() self.repeatStartDelay = depBoxOn:AddInput(nil, { Text = "Repeat Start Delay", Default = false, Callback = self:tnc(function(timing, value) timing._rsd = tonumber(value) or 0 end), }) self.repeatParryDelay = depBoxOn:AddInput(nil, { Text = "Repeat Parry Delay", Numeric = true, Callback = self:tnc(function(timing, value) timing._rpd = tonumber(value) or 0 end), }) local depBoxOff = tab:AddDependencyBox() self:baction(depBoxOff) depBoxOn:SetupDependencies({ { self.repeatUntilParryEnd, true }, }) depBoxOff:SetupDependencies({ { self.repeatUntilParryEnd, false }, }) end ---Create new SoundBuilderSection object. ---@param name string ---@param tabbox table ---@param pair TimingContainerPair ---@param timing SoundTiming ---@return SoundBuilderSection function SoundBuilderSection.new(name, tabbox, pair, timing) return setmetatable(BuilderSection.new(name, tabbox, pair, timing), SoundBuilderSection) end -- Return SoundBuilderSection module. return SoundBuilderSection end) -- MODULE: GUI/Library defineModule("GUI/Library", function() local Profiler = require("Utility/Profiler") local CoreGuiManager = require("Utility/CoreGuiManager") return LPH_NO_VIRTUALIZE(function() local InputService = game:GetService("UserInputService") local TextService = game:GetService("TextService") local Teams = game:GetService("Teams") local Players = game:GetService("Players") local RunService = game:GetService("RunService") local TweenService = game:GetService("TweenService") repeat task.wait() until Players.LocalPlayer local RenderStepped = RunService.RenderStepped local LocalPlayer = Players.LocalPlayer local Mouse = LocalPlayer:GetMouse() local ProtectGui = protectgui or (syn and syn.protect_gui) or function() end local ScreenGui = CoreGuiManager.imark(Instance.new("ScreenGui")) ProtectGui(ScreenGui) ScreenGui.ZIndexBehavior = Enum.ZIndexBehavior.Global local Toggles = {} local Options = {} local ColorPickers = {} local Entries = {} local ContextMenus = {} local Tooltips = {} local ModeSelectFrames = {} local UpdateTimestamp = os.clock() local Toggled = false local NeedsRefresh = false pcall(function() getgenv().Toggles = Toggles getgenv().Options = Options end) local Library = { Registry = {}, RegistryMap = {}, HudRegistry = {}, FontColor = Color3.fromRGB(255, 255, 255), MainColor = Color3.fromRGB(28, 28, 28), BackgroundColor = Color3.fromRGB(20, 20, 20), AccentColor = Color3.fromRGB(0, 85, 255), OutlineColor = Color3.fromRGB(50, 50, 50), RiskColor = Color3.fromRGB(255, 50, 50), Black = Color3.new(0, 0, 0), Font = Font.fromEnum(Enum.Font.RobotoMono), OpenedFrames = {}, DependencyBoxes = {}, Signals = {}, ScreenGui = ScreenGui, OwnerTag = nil, OwnerKeys = { toggles = {}, options = {} }, } function Library:RegisterOwnerKey(kind, key) if not self.OwnerTag or not key then return end local bucket = self.OwnerKeys[kind] if not bucket then return end bucket[key] = true end local RainbowStep = 0 local Hue = 0 table.insert( Library.Signals, RenderStepped:Connect(function(Delta) local showInfo = not (Toggles.ShowLoggerWindow and not Toggles.ShowLoggerWindow.Value) local showEffect = not (Toggles.ShowEffectLoggerWindow and not Toggles.ShowEffectLoggerWindow.Value) local showSkill = not (Toggles.ShowSkillLoggerWindow and not Toggles.ShowSkillLoggerWindow.Value) if not showInfo and not showEffect and not showSkill then Entries = {} end local NextIndex, NextEntry = next(Entries) if NextIndex and NextEntry then Entries[NextIndex] = nil NextEntry() end RainbowStep = RainbowStep + Delta if RainbowStep >= (1 / 60) then RainbowStep = 0 Hue = Hue + (1 / 400) if Hue > 1 then Hue = 0 end Library.CurrentRainbowHue = Hue Library.CurrentRainbowColor = Color3.fromHSV(Hue, 0.8, 1) for _, ColorPicker in next, ColorPickers do if ColorPicker.Rainbow then ColorPicker:Display() end end end local LocalPlayer = game:GetService("Players").LocalPlayer local PlayerGui = LocalPlayer and LocalPlayer.PlayerGui local CursorGui = PlayerGui and PlayerGui:FindFirstChild("CursorGui") local Cursor = CursorGui and CursorGui:FindFirstChild("Cursor") if Cursor then Cursor.Visible = false game:GetService("UserInputService").MouseIconEnabled = true end end) ) local function GetPlayersString() local PlayerList = Players:GetPlayers() for i = 1, #PlayerList do PlayerList[i] = PlayerList[i].Name end table.sort(PlayerList, function(str1, str2) return str1 < str2 end) return PlayerList end local function GetTeamsString() local TeamList = Teams:GetTeams() for i = 1, #TeamList do TeamList[i] = TeamList[i].Name end table.sort(TeamList, function(str1, str2) return str1 < str2 end) return TeamList end function Library:SafeCallback(label, f, ...) if not f then return end xpcall(Profiler.wrap(label, f), function(err) warn(string.format("Library:SafeCallback - failed on label %s - %s", label, err)) warn(debug.traceback()) end, ...) end function Library:AttemptSave() if Library.SaveManager then Library.SaveManager:Save() end end function Library:Create(Class, Properties) local _Instance = Class if type(Class) == "string" then _Instance = Instance.new(Class) end for Property, Value in next, Properties do _Instance[Property] = Value end return _Instance end function Library:KeyBlacklists() local tbl = {} for key, val in next, Library.InfoLoggerData.KeyBlacklistList do if not val then continue end tbl[#tbl + 1] = key end return tbl end function Library:RefreshInfoLogger() local CurrentTypeCycle = Library.InfoLoggerCycles[Library.InfoLoggerCycle] local Blacklist = Library.InfoLoggerData.KeyBlacklistList for Idx, Entry in next, Library.InfoLoggerData.MissingDataEntries do if not Blacklist[Entry.Key] then continue end table.remove(Library.InfoLoggerData.MissingDataEntries, Idx) pcall(Entry.Label.Destroy, Entry.Label) end for Idx, Entry in next, Library.InfoLoggerData.MissingDataEntries do Entry.Label.Parent = Entry.Type == CurrentTypeCycle and Library.InfoLoggerContainer or nil Entry.Label.LayoutOrder = Idx end Library.InfoLoggerLabel.Text = string.format("Info Logger (%s)", CurrentTypeCycle) local YSize = 0 local XSize = 0 for _, Entry in next, Library.InfoLoggerData.MissingDataEntries do if not Entry.Label.Parent then continue end YSize = YSize + Entry.Label.TextBounds.Y + 2 if Entry.Label.TextBounds.X <= XSize then continue end XSize = Entry.Label.TextBounds.X end XSize = XSize + 20 YSize = YSize + 22 Library.InfoLoggerFrame.Size = UDim2.new(0, math.clamp(XSize, 210, 800), 0, math.clamp(YSize, 24, 180)) end function Library:RefreshEffectLogger() local ed = Library.EffectLoggerData if not ed then return end local entries = ed.Entries local bl = Library.InfoLoggerData and Library.InfoLoggerData.KeyBlacklistList or {} for Idx, Entry in next, entries do if bl[Entry.Key] then table.remove(entries, Idx) pcall(Entry.Label.Destroy, Entry.Label) end end for Idx, Entry in next, entries do Entry.Label.Parent = Library.EffectLoggerContainer Entry.Label.LayoutOrder = Idx end local YSize = 0 local XSize = 0 for _, Entry in next, entries do if not Entry.Label.Parent then continue end YSize = YSize + Entry.Label.TextBounds.Y + 2 if Entry.Label.TextBounds.X > XSize then XSize = Entry.Label.TextBounds.X end end XSize = XSize + 20 YSize = YSize + 22 if Library.EffectLoggerFrame then Library.EffectLoggerFrame.Size = UDim2.new(0, math.clamp(XSize, 210, 800), 0, math.clamp(YSize, 24, 180)) end end function Library:RefreshSkillLogger() local sd = Library.SkillLoggerData if not sd then return end local entries = sd.Entries local bl = Library.InfoLoggerData and Library.InfoLoggerData.KeyBlacklistList or {} for Idx, Entry in next, entries do if bl[Entry.Key] then table.remove(entries, Idx) pcall(Entry.Label.Destroy, Entry.Label) end end for Idx, Entry in next, entries do Entry.Label.Parent = Library.SkillLoggerContainer Entry.Label.LayoutOrder = Idx end local YSize = 0 local XSize = 0 for _, Entry in next, entries do if not Entry.Label.Parent then continue end YSize = YSize + Entry.Label.TextBounds.Y + 2 if Entry.Label.TextBounds.X > XSize then XSize = Entry.Label.TextBounds.X end end XSize = XSize + 20 YSize = YSize + 22 if Library.SkillLoggerFrame then Library.SkillLoggerFrame.Size = UDim2.new(0, math.clamp(XSize, 210, 800), 0, math.clamp(YSize, 24, 180)) end end function Library:AddTelemetryEntry(str, ...) local type = "Telemetry" local lolll = string.format(str, ...) local ts = os.clock() local ifd = Library.InfoLoggerData local mde = ifd.MissingDataEntries table.insert(Entries, 1, function() debug.profilebegin("Library:AddTelemetryEntry") local function getEntriesForThisType() local entries = {} for Idx, Entry in next, mde do if Entry.Type == type then table.insert(entries, { [1] = Entry, [2] = Idx }) end end return entries end -- Pop the last element if we're under 30 entries for this type. -- Max of 30 entries per type; in total - 120 for all types. local entries = getEntriesForThisType() local last = entries[#entries] if #entries > 30 and last then last[1].Label:Destroy() table.remove(mde, last[2]) end -- Create a new label. ---@type TextLabel local label = Library:CreateLabel({ Text = lolll, TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) -- entry local entry = { Timestamp = ts, Label = label, Key = tostring(math.random()), Type = type } -- Copy & blacklist. label.InputBegan:Connect(function(Input) if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify("Copied timestamp to clipboard.") end end) -- Create a new entry for later destroying. table.insert(mde, 1, entry) -- Refresh. Library:RefreshInfoLogger() debug.profileend() end) end function Library:AddUsingSkillEntry(playerName, skillValue, isLocal, state, durationSeconds, onCreated) local entryType = "UsingSkill" local ts = tick() local who = tostring(playerName or "Unknown") local displayValue = tostring(skillValue or "") if displayValue == "" then displayValue = "(cleared)" end local tag = isLocal and "Local" or "Other" state = state or "Update" if type(durationSeconds) ~= "number" then durationSeconds = nil end local sd = Library.SkillLoggerData if not sd then return end local entries = sd.Entries local ifd = Library.InfoLoggerData local bl = ifd and ifd.KeyBlacklistList or {} local key = "Skill:" .. tostring(skillValue or who) if bl[key] then return end table.insert(Entries, 1, function() debug.profilebegin("Library:AddUsingSkillEntry") local last = entries[#entries] if #entries > 30 and last then last.Label:Destroy() table.remove(entries, #entries) end local timeText = os.date("%H:%M:%S") local durationText = string.format(" | at=%s", timeText) if durationSeconds ~= nil then durationText = durationText .. string.format(" | lasted=%.3fs", durationSeconds) end ---@type TextLabel local label = Library:CreateLabel({ Text = string.format("(%s) %s UsingSkill %s: %s%s", tag, who, state, displayValue, durationText), TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) local entry = { Timestamp = ts, Label = label, Key = key, Type = entryType } label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then setclipboard(label.Text) Library:Notify("Copied skill log to clipboard.") end if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify("Copied timestamp to clipboard.") end if Input.UserInputType == Enum.UserInputType.MouseButton2 then ifd.KeyBlacklistList[key] = true ifd.KeyBlacklistHistory[#ifd.KeyBlacklistHistory + 1] = key Library:RefreshInfoLogger() Library:RefreshEffectLogger() Library:RefreshSkillLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Blacklisted key '%s' from list.", key)) end end) if type(onCreated) == "function" then pcall(onCreated, entry) end table.insert(entries, 1, entry) Library:RefreshSkillLogger() debug.profileend() end) end function Library:AddEffectEntry(effectName, source, category, payloadText) local type = "Effect" local ts = tick() local key = tostring(effectName or "Unknown") local src = tostring(source or "Effect") local cat = tostring(category or "Other") local payload = tostring(payloadText or key) local ifd = Library.InfoLoggerData local bl = ifd and ifd.KeyBlacklistList or {} local ed = Library.EffectLoggerData if not ed then return end local entries = ed.Entries if bl[key] then return end table.insert(Entries, 1, function() debug.profilebegin("Library:AddEffectEntry") local last = entries[#entries] if #entries > 30 and last then last.Label:Destroy() table.remove(entries, #entries) end ---@type TextLabel local label = Library:CreateLabel({ Text = string.format("[%s][%s] %s", src, cat, payload), TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) local entry = { Timestamp = ts, Label = label, Key = key, Type = type } label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then setclipboard(payload) Library:Notify("Copied effect payload to clipboard.") end if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify("Copied timestamp to clipboard.") end if Input.UserInputType == Enum.UserInputType.MouseButton2 then ifd.KeyBlacklistList[key] = true ifd.KeyBlacklistHistory[#ifd.KeyBlacklistHistory + 1] = key Library:RefreshInfoLogger() Library:RefreshEffectLogger() Library:RefreshSkillLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Blacklisted key '%s' from list.", key)) end end) table.insert(entries, 1, entry) Library:RefreshEffectLogger() debug.profileend() end) end function Library:AddKeyFrameEntry(distance, key, name, position, flag) local ifd = Library.InfoLoggerData local mde = ifd.MissingDataEntries local bl = ifd.KeyBlacklistList local ts = tick() if bl[key] then return end local type = "Keyframe" table.insert(Entries, 1, function() debug.profilebegin("Library:AddKeyFrameEntry") local function getEntriesForThisType() local entries = {} for Idx, Entry in next, mde do if Entry.Type == type then table.insert(entries, { [1] = Entry, [2] = Idx }) end end return entries end -- Pop the last element if we're under 30 entries for this type. -- Max of 30 entries per type; in total - 120 for all types. local entries = getEntriesForThisType() local last = entries[#entries] if #entries > 30 and last then last[1].Label:Destroy() table.remove(mde, last[2]) end local SaveManager = require("Game/Timings/SaveManager") local asdf = SaveManager.as:index(key) -- Create a new label. ---@type TextLabel local label = Library:CreateLabel({ -- (52.4m away) (HitStart) Animation 'rbxassetid://124453535' reached keyframe at position 0.69. Text = string.format( "(%.2fm away) %s '%s' %s '%s' at '%.3f' time position.", distance, asdf and "Timing" or "Animation", asdf and PP_SCRAMBLE_STR(asdf.name) or key, flag and "will reach" or "reached", name, position ), TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) -- entry local entry = { Timestamp = ts, Label = label, Key = key, Type = type } -- Copy & blacklist. label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then setclipboard(key) Library:Notify(string.format("Copied key '%s' to clipboard.", key)) end if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify(string.format("Copied timestamp for '%s' to clipboard.", key)) end if Input.UserInputType == Enum.UserInputType.MouseButton2 then ifd.KeyBlacklistList[key] = true ifd.KeyBlacklistHistory[#ifd.KeyBlacklistHistory + 1] = key Library:RefreshInfoLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Blacklisted key '%s' from list.", key)) end end) -- Create a new entry for later destroying. table.insert(mde, 1, entry) -- Refresh. Library:RefreshInfoLogger() debug.profileend() end) end function Library:AddExistAnimEntry(name, distance, timing) local ifd = Library.InfoLoggerData local mde = ifd.MissingDataEntries local bl = ifd.KeyBlacklistList local ts = tick() local key = timing.name if bl[key] then return end local type = "Existing Anim" table.insert(Entries, 1, function() debug.profilebegin("Library:AddExistAnimEntry") local function getEntriesForThisType() local entries = {} for Idx, Entry in next, mde do if Entry.Type == type then table.insert(entries, { [1] = Entry, [2] = Idx }) end end return entries end -- Pop the last element if we're under 30 entries for this type. -- Max of 30 entries per type; in total - 120 for all types. local entries = getEntriesForThisType() local last = entries[#entries] if #entries > 30 and last then last[1].Label:Destroy() table.remove(mde, last[2]) end -- Create a new label. ---@type TextLabel local label = Library:CreateLabel({ Text = string.format("(%.2fm away) Animation timing '%s' from '%s' was played.", distance, key, name), TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) -- entry local entry = { Timestamp = ts, Label = label, Key = key, Type = type } -- Copy & blacklist. label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then setclipboard(key) Library:Notify(string.format("Copied key '%s' to clipboard.", key)) end if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify(string.format("Copied timestamp for '%s' to clipboard.", key)) end if Input.UserInputType == Enum.UserInputType.MouseButton2 then ifd.KeyBlacklistList[key] = true ifd.KeyBlacklistHistory[#ifd.KeyBlacklistHistory + 1] = key Library:RefreshInfoLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Blacklisted key '%s' from list.", key)) end end) -- Create a new entry for later destroying. table.insert(mde, 1, entry) -- Refresh. Library:RefreshInfoLogger() debug.profileend() end) end function Library:AddMissEntry(type, key, name, distance, parent) local ifd = Library.InfoLoggerData local mde = ifd.MissingDataEntries local bl = ifd.KeyBlacklistList local ts = tick() if bl[key] then return end table.insert(Entries, 1, function() debug.profilebegin("Library:AddMissEntry") local function getEntriesForThisType() local entries = {} for Idx, Entry in next, mde do if Entry.Type == type then table.insert(entries, { [1] = Entry, [2] = Idx }) end end return entries end -- Pop the last element if we're under 30 entries for this type. -- Max of 30 entries per type; in total - 120 for all types. local entries = getEntriesForThisType() local last = entries[#entries] if #entries > 30 and last then last[1].Label:Destroy() table.remove(mde, last[2]) end local asset = typeof(key) == "string" and tonumber(key:sub(14, 40)) or nil -- Create a new label. ---@type TextLabel local label = Library:CreateLabel({ Text = name and string.format("(%.2fm away) Key '%s' from '%s' is missing.", distance, key, name) or string.format("(%.2fm away) Key '%s' is missing.", distance, key), TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 14), LayoutOrder = 1, TextSize = 12, Visible = true, ZIndex = 306, Parent = nil, }, true) if parent then label.Text = string.format("(%s) %s", parent, label.Text) end Library:AddToRegistry(label, { TextColor3 = "FontColor", }, true) if asset then task.spawn(function() pcall(function() local lol = game:GetService("MarketplaceService"):GetProductInfo(asset) if not lol then return end label.Text = string.format("(%s) %s", lol.Name, label.Text) end) end) end -- entry local entry = { Timestamp = ts, Label = label, Key = key, Type = type } -- Copy & blacklist. label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then setclipboard(key) Library:Notify(string.format("Copied key '%s' to clipboard.", key)) end if Input.KeyCode == Enum.KeyCode.T then setclipboard(tostring(entry.Timestamp)) Library:Notify(string.format("Copied timestamp for '%s' to clipboard.", key)) end if Input.UserInputType == Enum.UserInputType.MouseButton2 then ifd.KeyBlacklistList[key] = true ifd.KeyBlacklistHistory[#ifd.KeyBlacklistHistory + 1] = key Library:RefreshInfoLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Blacklisted key '%s' from list.", key)) end end) -- Create a new entry for later destroying. table.insert(mde, 1, entry) -- Refresh. Library:RefreshInfoLogger() debug.profileend() end) end function Library:ApplyTextStroke(Inst) Inst.TextStrokeTransparency = 1 --[[ Library:Create("UIStroke", { Color = Color3.new(0, 0, 0), Thickness = 1, LineJoinMode = Enum.LineJoinMode.Miter, Parent = Inst, }) ]] -- end function Library:CreateLabel(Properties, IsHud) local _Instance = Library:Create("TextLabel", { BackgroundTransparency = 1, FontFace = Library.Font, TextColor3 = Library.FontColor, TextSize = 16, TextStrokeTransparency = 0, }) Library:ApplyTextStroke(_Instance) Library:AddToRegistry(_Instance, { TextColor3 = "FontColor", }, IsHud) if Properties.TextSize then Properties.TextSize = Properties.TextSize + 1 end return Library:Create(_Instance, Properties) end function Library:MakeDraggable(Instance, Cutoff) Instance.Active = true Instance.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch then local ObjPos = Vector2.new(Mouse.X - Instance.AbsolutePosition.X, Mouse.Y - Instance.AbsolutePosition.Y) if ObjPos.Y > (Cutoff or 40) then return end while InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) or InputService:IsMouseButtonPressed(Enum.UserInputType.Touch) do Instance.Position = UDim2.new( 0, Mouse.X - ObjPos.X + (Instance.Size.X.Offset * Instance.AnchorPoint.X), 0, Mouse.Y - ObjPos.Y + (Instance.Size.Y.Offset * Instance.AnchorPoint.Y) ) RenderStepped:Wait() end end end) end function Library:AddToolTip(InfoStr, HoverInstance) local X, Y = Library:GetTextBounds(InfoStr, Library.Font, 14) local Tooltip = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, Size = UDim2.fromOffset(X + 5, Y + 4), ZIndex = 100, Parent = Library.ScreenGui, Visible = false, }) local Label = Library:CreateLabel({ Position = UDim2.fromOffset(3, 1), Size = UDim2.fromOffset(X, Y), TextSize = 14, Text = InfoStr, TextColor3 = Library.FontColor, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = Tooltip.ZIndex + 1, Parent = Tooltip, }) Library:AddToRegistry(Tooltip, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) Library:AddToRegistry(Label, { TextColor3 = "FontColor", }) Tooltips[#Tooltips + 1] = Tooltip local IsHovering = false HoverInstance.MouseEnter:Connect(function() if Library:MouseIsOverOpenedFrame() then return end IsHovering = true Tooltip.Position = UDim2.fromOffset(Mouse.X + 15, Mouse.Y + 12) Tooltip.Visible = true while IsHovering do RunService.Heartbeat:Wait() Tooltip.Position = UDim2.fromOffset(Mouse.X + 15, Mouse.Y + 12) end end) HoverInstance.MouseLeave:Connect(function() IsHovering = false Tooltip.Visible = false end) end function Library:OnHighlight(HighlightInstance, Instance, Properties, PropertiesDefault) HighlightInstance.MouseEnter:Connect(function() local Reg = Library.RegistryMap[Instance] for Property, ColorIdx in next, Properties do Instance[Property] = Library[ColorIdx] or ColorIdx if Reg and Reg.Properties[Property] then Reg.Properties[Property] = ColorIdx end end end) HighlightInstance.MouseLeave:Connect(function() local Reg = Library.RegistryMap[Instance] for Property, ColorIdx in next, PropertiesDefault do Instance[Property] = Library[ColorIdx] or ColorIdx if Reg and Reg.Properties[Property] then Reg.Properties[Property] = ColorIdx end end end) end function Library:MouseIsOverOpenedFrame() for Frame, _ in next, Library.OpenedFrames do local AbsPos, AbsSize = Frame.AbsolutePosition, Frame.AbsoluteSize if Mouse.X >= AbsPos.X and Mouse.X <= AbsPos.X + AbsSize.X and Mouse.Y >= AbsPos.Y and Mouse.Y <= AbsPos.Y + AbsSize.Y then return true end end end function Library:IsMouseOverFrame(Frame) local AbsPos, AbsSize = Frame.AbsolutePosition, Frame.AbsoluteSize if Mouse.X >= AbsPos.X and Mouse.X <= AbsPos.X + AbsSize.X and Mouse.Y >= AbsPos.Y and Mouse.Y <= AbsPos.Y + AbsSize.Y then return true end end function Library:UpdateDependencyBoxes() for _, Depbox in next, Library.DependencyBoxes do Depbox:Update() end end function Library:MapValue(Value, MinA, MaxA, MinB, MaxB) return (1 - ((Value - MinA) / (MaxA - MinA))) * MinB + ((Value - MinA) / (MaxA - MinA)) * MaxB end function Library:GetTextBounds(Text, Font, Size, Resolution) local Bounds = TextService:GetTextSize(Text, Size, "RobotoMono", Resolution or Vector2.new(1920, 1080)) return Bounds.X, Bounds.Y end function Library:GetDarkerColor(Color) local H, S, V = Color3.toHSV(Color) return Color3.fromHSV(H, S, V / 1.5) end Library.AccentColorDark = Library:GetDarkerColor(Library.AccentColor) function Library:AddToRegistry(Instance, Properties, IsHud) local Idx = #Library.Registry + 1 local Data = { Instance = Instance, Properties = Properties, Idx = Idx, } table.insert(Library.Registry, Data) Library.RegistryMap[Instance] = Data if IsHud then table.insert(Library.HudRegistry, Data) end end function Library:RemoveFromRegistry(Instance) local Data = Library.RegistryMap[Instance] if Data then for Idx = #Library.Registry, 1, -1 do if Library.Registry[Idx] == Data then table.remove(Library.Registry, Idx) end end for Idx = #Library.HudRegistry, 1, -1 do if Library.HudRegistry[Idx] == Data then table.remove(Library.HudRegistry, Idx) end end Library.RegistryMap[Instance] = nil end end function Library:UpdateColorsUsingRegistry() -- TODO: Could have an 'active' list of objects -- where the active list only contains Visible objects. -- IMPL: Could setup .Changed events on the AddToRegistry function -- that listens for the 'Visible' propert being changed. -- Visible: true => Add to active list, and call UpdateColors function -- Visible: false => Remove from active list. -- The above would be especially efficient for a rainbow menu color or live color-changing. for Idx, Object in next, Library.Registry do for Property, ColorIdx in next, Object.Properties do if type(ColorIdx) == "string" then Object.Instance[Property] = Library[ColorIdx] elseif type(ColorIdx) == "function" then Object.Instance[Property] = ColorIdx() end end end end function Library:GiveSignal(Signal) -- Only used for signals not attached to library instances, as those should be cleaned up on object destruction by Roblox table.insert(Library.Signals, Signal) end function Library:Unload() -- Unload all of the signals for Idx = #Library.Signals, 1, -1 do local Connection = table.remove(Library.Signals, Idx) Connection:Disconnect() end -- Call our unload callback, maybe to undo some hooks etc if Library.OnUnload then Library.OnUnload() end ScreenGui:Destroy() end function Library:OnUnload(Callback) Library.OnUnload = Callback end Library:GiveSignal(ScreenGui.DescendantRemoving:Connect(function(Instance) if Library.RegistryMap[Instance] then Library:RemoveFromRegistry(Instance) end end)) local BaseAddons = {} do local Funcs = {} function Funcs:AddColorPicker(Idx, Info) local ToggleLabel = self.TextLabel -- local Container = self.Container; assert(Info.Default, "AddColorPicker: Missing default value.") local ColorPicker = { Value = Info.Default, Transparency = Info.Transparency or 0, Type = "ColorPicker", Title = type(Info.Title) == "string" and Info.Title or "Color picker", Callback = Info.Callback or function(Color) end, Rainbow = Info.Rainbow or false, } function ColorPicker:SetHSVFromRGB(Color) local H, S, V = Color3.toHSV(Color) ColorPicker.Hue = H ColorPicker.Sat = S ColorPicker.Vib = V end ColorPicker:SetHSVFromRGB(ColorPicker.Value) local DisplayFrame = Library:Create("Frame", { BackgroundColor3 = ColorPicker.Value, BorderColor3 = Library:GetDarkerColor(ColorPicker.Value), BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(0, 28, 0, 14), ZIndex = 6, Parent = ToggleLabel, }) -- Transparency image taken from https://github.com/matas3535/SplixPrivateDrawingLibrary/blob/main/Library.lua cus i'm lazy local CheckerFrame = Library:Create("ImageLabel", { BorderSizePixel = 0, Size = UDim2.new(0, 27, 0, 13), ZIndex = 5, Image = "http://www.roblox.com/asset/?id=12977615774", Visible = not not Info.Transparency, Parent = DisplayFrame, }) -- 1/16/23 -- Rewrote this to be placed inside the Library ScreenGui -- There was some issue which caused RelativeOffset to be way off -- Thus the color picker would never show local PickerFrameOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.fromOffset(DisplayFrame.AbsolutePosition.X, DisplayFrame.AbsolutePosition.Y + 18), Size = UDim2.fromOffset(230, Info.Transparency and 271 or 253), Visible = false, ZIndex = 15, Parent = ScreenGui, }) DisplayFrame:GetPropertyChangedSignal("AbsolutePosition"):Connect(function() PickerFrameOuter.Position = UDim2.fromOffset(DisplayFrame.AbsolutePosition.X, DisplayFrame.AbsolutePosition.Y + 18) end) local PickerFrameInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 16, Parent = PickerFrameOuter, }) local Highlight = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 17, Parent = PickerFrameInner, }) local SatVibMapOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 4, 0, 25), Size = UDim2.new(0, 200, 0, 200), ZIndex = 17, Parent = PickerFrameInner, }) local SatVibMapInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 18, Parent = SatVibMapOuter, }) local SatVibMap = Library:Create("ImageLabel", { BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0), ZIndex = 18, Image = "rbxassetid://4155801252", Parent = SatVibMapInner, }) local CursorOuter = Library:Create("ImageLabel", { AnchorPoint = Vector2.new(0.5, 0.5), Size = UDim2.new(0, 6, 0, 6), BackgroundTransparency = 1, Image = "http://www.roblox.com/asset/?id=9619665977", ImageColor3 = Color3.new(0, 0, 0), ZIndex = 19, Parent = SatVibMap, }) local CursorInner = Library:Create("ImageLabel", { Size = UDim2.new(0, CursorOuter.Size.X.Offset - 2, 0, CursorOuter.Size.Y.Offset - 2), Position = UDim2.new(0, 1, 0, 1), BackgroundTransparency = 1, Image = "http://www.roblox.com/asset/?id=9619665977", ZIndex = 20, Parent = CursorOuter, }) local HueSelectorOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 208, 0, 25), Size = UDim2.new(0, 15, 0, 200), ZIndex = 17, Parent = PickerFrameInner, }) local HueSelectorInner = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0), ZIndex = 18, Parent = HueSelectorOuter, }) local HueCursor = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), AnchorPoint = Vector2.new(0, 0.5), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, 0, 0, 1), ZIndex = 18, Parent = HueSelectorInner, }) local HueBoxOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.fromOffset(4, 228), Size = UDim2.new(0.5, -6, 0, 20), ZIndex = 18, Parent = PickerFrameInner, }) local HueBoxInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 18, Parent = HueBoxOuter, }) Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.fromRGB(212, 212, 212)), }), Rotation = 90, Parent = HueBoxInner, }) local HueBox = Library:Create("TextBox", { BackgroundTransparency = 1, Position = UDim2.new(0, 5, 0, 0), Size = UDim2.new(1, -5, 1, 0), FontFace = Library.Font, PlaceholderColor3 = Color3.fromRGB(190, 190, 190), PlaceholderText = "Hex color", Text = "#FFFFFF", TextColor3 = Library.FontColor, TextSize = 14, TextStrokeTransparency = 0, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 20, Parent = HueBoxInner, }) Library:ApplyTextStroke(HueBox) local RgbBoxBase = Library:Create(HueBoxOuter:Clone(), { Position = UDim2.new(0.5, 2, 0, 228), Size = UDim2.new(0.5, -6, 0, 20), Parent = PickerFrameInner, }) local RgbBox = Library:Create(RgbBoxBase.Frame:FindFirstChild("TextBox"), { Text = "255, 255, 255", PlaceholderText = "RGB color", TextColor3 = Library.FontColor, }) local TransparencyBoxOuter, TransparencyBoxInner, TransparencyCursor if Info.Transparency then TransparencyBoxOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.fromOffset(4, 251), Size = UDim2.new(1, -8, 0, 15), ZIndex = 19, Parent = PickerFrameInner, }) TransparencyBoxInner = Library:Create("Frame", { BackgroundColor3 = ColorPicker.Value, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 19, Parent = TransparencyBoxOuter, }) Library:AddToRegistry(TransparencyBoxInner, { BorderColor3 = "OutlineColor" }) Library:Create("ImageLabel", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, 0), Image = "http://www.roblox.com/asset/?id=12978095818", ZIndex = 20, Parent = TransparencyBoxInner, }) TransparencyCursor = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), AnchorPoint = Vector2.new(0.5, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(0, 1, 1, 0), ZIndex = 21, Parent = TransparencyBoxInner, }) end local DisplayLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 14), Position = UDim2.fromOffset(5, 5), TextXAlignment = Enum.TextXAlignment.Left, TextSize = 14, Text = ColorPicker.Title, --Info.Default; TextWrapped = false, ZIndex = 16, Parent = PickerFrameInner, }) local ContextMenu = {} do ContextMenu.Options = {} ContextMenu.Container = Library:Create("Frame", { BorderColor3 = Color3.new(), ZIndex = 14, Visible = false, Parent = ScreenGui, }) ContextMenu.Inner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.fromScale(1, 1), ZIndex = 15, Parent = ContextMenu.Container, }) ContextMenus[#ContextMenus + 1] = ContextMenu Library:Create("UIListLayout", { Name = "Layout", FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = ContextMenu.Inner, }) Library:Create("UIPadding", { Name = "Padding", PaddingLeft = UDim.new(0, 4), Parent = ContextMenu.Inner, }) local function updateMenuPosition() ContextMenu.Container.Position = UDim2.fromOffset( (DisplayFrame.AbsolutePosition.X + DisplayFrame.AbsoluteSize.X) + 4, DisplayFrame.AbsolutePosition.Y + 1 ) end local function updateMenuSize() local menuWidth = 60 for i, label in next, ContextMenu.Inner:GetChildren() do if label:IsA("TextLabel") then menuWidth = math.max(menuWidth, label.TextBounds.X) end end ContextMenu.Container.Size = UDim2.fromOffset(menuWidth + 8, ContextMenu.Inner.Layout.AbsoluteContentSize.Y + 4) end DisplayFrame:GetPropertyChangedSignal("AbsolutePosition"):Connect(updateMenuPosition) ContextMenu.Inner.Layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(updateMenuSize) task.spawn(updateMenuPosition) task.spawn(updateMenuSize) Library:AddToRegistry(ContextMenu.Inner, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) function ContextMenu:Show() self.Container.Visible = true end function ContextMenu:Hide() self.Container.Visible = false end function ContextMenu:AddOption(Str, Callback) if type(Callback) ~= "function" then Callback = function() end end local Button = Library:CreateLabel({ Active = false, Size = UDim2.new(1, 0, 0, 15), TextSize = 13, Text = Str, ZIndex = 16, Parent = self.Inner, TextXAlignment = Enum.TextXAlignment.Left, }) Library:OnHighlight(Button, Button, { TextColor3 = "AccentColor" }, { TextColor3 = "FontColor" }) Button.InputBegan:Connect(function(Input) if Input.UserInputType ~= Enum.UserInputType.Touch and Input.UserInputType ~= Enum.UserInputType.MouseButton1 then return end Callback() end) end ContextMenu:AddOption("Rainbow toggle", function() ColorPicker.Rainbow = not ColorPicker.Rainbow ColorPicker:Display() end) ContextMenu:AddOption("Copy color", function() Library.ColorClipboard = ColorPicker.Value Library:Notify("Copied color!", 2) end) ContextMenu:AddOption("Paste color", function() if not Library.ColorClipboard then return Library:Notify("You have not copied a color!", 2) end ColorPicker:SetValueRGB(Library.ColorClipboard) end) ContextMenu:AddOption("Copy HEX", function() pcall(setclipboard, ColorPicker.Value:ToHex()) Library:Notify("Copied hex code to clipboard!", 2) end) ContextMenu:AddOption("Copy RGB", function() pcall( setclipboard, table.concat({ math.floor(ColorPicker.Value.R * 255), math.floor(ColorPicker.Value.G * 255), math.floor(ColorPicker.Value.B * 255), }, ", ") ) Library:Notify("Copied RGB values to clipboard!", 2) end) end Library:AddToRegistry( PickerFrameInner, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor" } ) Library:AddToRegistry(Highlight, { BackgroundColor3 = "AccentColor" }) Library:AddToRegistry( SatVibMapInner, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor" } ) Library:AddToRegistry(HueBoxInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor" }) Library:AddToRegistry(RgbBoxBase.Frame, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor" }) Library:AddToRegistry(RgbBox, { TextColor3 = "FontColor" }) Library:AddToRegistry(HueBox, { TextColor3 = "FontColor" }) local SequenceTable = {} for Hue = 0, 1, 0.1 do table.insert(SequenceTable, ColorSequenceKeypoint.new(Hue, Color3.fromHSV(Hue, 1, 1))) end local HueSelectorGradient = Library:Create("UIGradient", { Color = ColorSequence.new(SequenceTable), Rotation = 90, Parent = HueSelectorInner, }) HueBox.FocusLost:Connect(function(enter) if enter then local success, result = pcall(Color3.fromHex, HueBox.Text) if success and typeof(result) == "Color3" then ColorPicker.Hue, ColorPicker.Sat, ColorPicker.Vib = Color3.toHSV(result) end end ColorPicker:Display() end) RgbBox.FocusLost:Connect(function(enter) if enter then local r, g, b = RgbBox.Text:match("(%d+),%s*(%d+),%s*(%d+)") if r and g and b then ColorPicker.Hue, ColorPicker.Sat, ColorPicker.Vib = Color3.toHSV(Color3.fromRGB(r, g, b)) end end ColorPicker:Display() end) function ColorPicker:Display() ColorPicker.Value = Color3.fromHSV(ColorPicker.Hue, ColorPicker.Sat, ColorPicker.Vib) SatVibMap.BackgroundColor3 = Color3.fromHSV(ColorPicker.Hue, 1, 1) if ColorPicker.Rainbow then ColorPicker.Value = Library.CurrentRainbowColor end Library:Create(DisplayFrame, { BackgroundColor3 = ColorPicker.Value, BackgroundTransparency = ColorPicker.Transparency, BorderColor3 = Library:GetDarkerColor(ColorPicker.Value), }) if TransparencyBoxInner then TransparencyBoxInner.BackgroundColor3 = ColorPicker.Value TransparencyCursor.Position = UDim2.new(1 - ColorPicker.Transparency, 0, 0, 0) end CursorOuter.Position = UDim2.new(ColorPicker.Sat, 0, 1 - ColorPicker.Vib, 0) HueCursor.Position = UDim2.new(0, 0, ColorPicker.Hue, 0) HueBox.Text = "#" .. ColorPicker.Value:ToHex() RgbBox.Text = table.concat({ math.floor(ColorPicker.Value.R * 255), math.floor(ColorPicker.Value.G * 255), math.floor(ColorPicker.Value.B * 255), }, ", ") Library:SafeCallback( "ColorPicker_Callback" .. "_" .. (Idx or ""), ColorPicker.Callback, ColorPicker.Value ) Library:SafeCallback( "ColorPicker_Changed" .. "_" .. (Idx or ""), ColorPicker.Changed, ColorPicker.Value ) end function ColorPicker:OnChanged(Func) ColorPicker.Changed = Func Func(ColorPicker.Value) end function ColorPicker:Show() for Frame, Val in next, Library.OpenedFrames do if Frame.Name == "Color" then Frame.Visible = false Library.OpenedFrames[Frame] = nil end end PickerFrameOuter.Visible = true Library.OpenedFrames[PickerFrameOuter] = true end function ColorPicker:Hide() PickerFrameOuter.Visible = false Library.OpenedFrames[PickerFrameOuter] = nil end function ColorPicker:SetValue(HSV, Transparency) local Color = Color3.fromHSV(HSV[1], HSV[2], HSV[3]) ColorPicker.Transparency = Transparency or 0 ColorPicker:SetHSVFromRGB(Color) ColorPicker:Display() end function ColorPicker:SetValueRGB(Color, Transparency) ColorPicker.Transparency = Transparency or 0 ColorPicker:SetHSVFromRGB(Color) ColorPicker:Display() end SatVibMap.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then while InputService:IsMouseButtonPressed(Enum.UserInputType.Touch) or InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do local MinX = SatVibMap.AbsolutePosition.X local MaxX = MinX + SatVibMap.AbsoluteSize.X local MouseX = math.clamp(Mouse.X, MinX, MaxX) local MinY = SatVibMap.AbsolutePosition.Y local MaxY = MinY + SatVibMap.AbsoluteSize.Y local MouseY = math.clamp(Mouse.Y, MinY, MaxY) ColorPicker.Sat = (MouseX - MinX) / (MaxX - MinX) ColorPicker.Vib = 1 - ((MouseY - MinY) / (MaxY - MinY)) ColorPicker:Display() RenderStepped:Wait() end Library:AttemptSave() end end) HueSelectorInner.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then while InputService:IsMouseButtonPressed(Enum.UserInputType.Touch) or InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do local MinY = HueSelectorInner.AbsolutePosition.Y local MaxY = MinY + HueSelectorInner.AbsoluteSize.Y local MouseY = math.clamp(Mouse.Y, MinY, MaxY) ColorPicker.Hue = ((MouseY - MinY) / (MaxY - MinY)) ColorPicker:Display() RenderStepped:Wait() end Library:AttemptSave() end end) DisplayFrame.InputBegan:Connect(function(Input) if ( Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 ) and not Library:MouseIsOverOpenedFrame() then if PickerFrameOuter.Visible then ColorPicker:Hide() else ContextMenu:Hide() ColorPicker:Show() end elseif ( Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 ) and not Library:MouseIsOverOpenedFrame() then ContextMenu:Show() ColorPicker:Hide() end end) if TransparencyBoxInner then TransparencyBoxInner.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then while InputService:IsMouseButtonPressed(Enum.UserInputType.Touch) or InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) do local MinX = TransparencyBoxInner.AbsolutePosition.X local MaxX = MinX + TransparencyBoxInner.AbsoluteSize.X local MouseX = math.clamp(Mouse.X, MinX, MaxX) ColorPicker.Transparency = 1 - ((MouseX - MinX) / (MaxX - MinX)) ColorPicker:Display() RenderStepped:Wait() end Library:AttemptSave() end end) end Library:GiveSignal(InputService.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then local AbsPos, AbsSize = PickerFrameOuter.AbsolutePosition, PickerFrameOuter.AbsoluteSize if Mouse.X < AbsPos.X or Mouse.X > AbsPos.X + AbsSize.X or Mouse.Y < (AbsPos.Y - 20 - 1) or Mouse.Y > AbsPos.Y + AbsSize.Y then ColorPicker:Hide() end if not Library:IsMouseOverFrame(ContextMenu.Container) then ContextMenu:Hide() end end if Input.UserInputType == Enum.UserInputType.MouseButton2 and ContextMenu.Container.Visible then if not Library:IsMouseOverFrame(ContextMenu.Container) and not Library:IsMouseOverFrame(DisplayFrame) then ContextMenu:Hide() end end end)) ColorPicker:Display() ColorPicker.DisplayFrame = DisplayFrame if Idx then Options[Idx] = ColorPicker ColorPickers[Idx] = ColorPicker Library:RegisterOwnerKey("options", Idx) end return self end function Funcs:AddKeyPicker(Idx, Info) local ParentObj = self local ToggleLabel = self.TextLabel local Container = self.Container assert(Info.Default, "AddKeyPicker: Missing default value.") local KeyPicker = { Value = Info.Default, Toggled = false, Mode = Info.Mode or "Toggle", -- Always, Toggle, Hold Type = "KeyPicker", Callback = Info.Callback or function(Value) end, ChangedCallback = Info.ChangedCallback or function(New) end, SyncToggleState = Info.SyncToggleState or false, } if KeyPicker.SyncToggleState then Info.Modes = { "Toggle", "Hold" } Info.Mode = "Toggle" end local PickOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(0, 28, 0, 15), ZIndex = 6, Parent = ToggleLabel, }) local PickInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 7, Parent = PickOuter, }) Library:AddToRegistry(PickInner, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) local DisplayLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 1, 0), TextSize = 13, Text = Info.Default, TextWrapped = true, ZIndex = 8, Parent = PickInner, }) local ModeSelectOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.fromOffset( ToggleLabel.AbsolutePosition.X + ToggleLabel.AbsoluteSize.X + 4, ToggleLabel.AbsolutePosition.Y + 1 ), Size = UDim2.new(0, 60, 0, 60 + 2), Visible = false, ZIndex = 14, Parent = ScreenGui, }) ModeSelectFrames[#ModeSelectFrames + 1] = ModeSelectOuter ToggleLabel:GetPropertyChangedSignal("AbsolutePosition"):Connect(function() ModeSelectOuter.Position = UDim2.fromOffset( ToggleLabel.AbsolutePosition.X + ToggleLabel.AbsoluteSize.X + 4, ToggleLabel.AbsolutePosition.Y + 1 ) end) local ModeSelectInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 15, Parent = ModeSelectOuter, }) Library:AddToRegistry(ModeSelectInner, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = ModeSelectInner, }) local ContainerLabel = Library:CreateLabel({ TextXAlignment = Enum.TextXAlignment.Left, Size = UDim2.new(1, 0, 0, 18), TextSize = 13, Visible = false, ZIndex = 110, Parent = Library.KeybindContainer, }, true) local Modes = Info.Modes or { "Always", "Toggle", "Hold", "Off" } local ModeButtons = {} function KeyPicker:DoClick() if KeyPicker.Mode == "Toggle" and ParentObj.Type == "Toggle" and KeyPicker.SyncToggleState then ParentObj:SetValue(not ParentObj.Value) end if KeyPicker.Mode == "Hold" and ParentObj.Type == "Toggle" and KeyPicker.SyncToggleState then ParentObj:SetValue(KeyPicker.Toggled) end Library:SafeCallback("KeyPicker_Callback" .. "_" .. (Idx or ""), KeyPicker.Callback, KeyPicker.Toggled) Library:SafeCallback("KeyPicker_Clicked" .. "_" .. (Idx or ""), KeyPicker.Clicked, KeyPicker.Toggled) end for Idx, Mode in next, Modes do local ModeButton = {} local Label = Library:CreateLabel({ Active = false, Size = UDim2.new(1, 0, 0, 15), TextSize = 13, Text = Mode, ZIndex = 16, Parent = ModeSelectInner, }) function ModeButton:Select() for _, Button in next, ModeButtons do Button:Deselect() end if Mode == "Always" then KeyPicker.Toggled = true KeyPicker:DoClick() end if Mode == "Off" then KeyPicker.Toggled = false KeyPicker:DoClick() end KeyPicker.Mode = Mode Label.TextColor3 = Library.AccentColor Library.RegistryMap[Label].Properties.TextColor3 = "AccentColor" ModeSelectOuter.Visible = false end function ModeButton:Deselect() KeyPicker.Mode = nil Label.TextColor3 = Library.FontColor Library.RegistryMap[Label].Properties.TextColor3 = "FontColor" end Label.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then ModeButton:Select() Library:AttemptSave() end end) if Mode == KeyPicker.Mode then ModeButton:Select() end ModeButtons[Mode] = ModeButton end function KeyPicker:Update() if Info.NoUI then return end local State = KeyPicker:GetState() ContainerLabel.Text = string.format("[%s] %s (%s)", KeyPicker.Value, Info.Text, KeyPicker.Mode) ContainerLabel.Visible = true ContainerLabel.TextColor3 = State and Library.AccentColor or Library.FontColor Library.RegistryMap[ContainerLabel].Properties.TextColor3 = State and "AccentColor" or "FontColor" local YSize = 0 local XSize = 0 for _, Label in next, Library.KeybindContainer:GetChildren() do if Label:IsA("TextLabel") and Label.Visible then YSize = YSize + 18 if Label.TextBounds.X > XSize then XSize = Label.TextBounds.X end end end Library.KeybindFrame.Size = UDim2.new(0, math.max(XSize + 10, 210), 0, YSize + 23) end function KeyPicker:GetState() if KeyPicker.Mode == "Always" then return true elseif KeyPicker.Mode == "Off" then return false elseif KeyPicker.Mode == "Hold" then if KeyPicker.Value == "N/A" then return false end local Key = KeyPicker.Value if Key == "MB1" then return InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) or InputService.TouchEnabled and #InputService.Touches > 0 elseif Key == "MB2" then return InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) or InputService.TouchEnabled and #InputService.Touches > 1 else return InputService:IsKeyDown(Enum.KeyCode[KeyPicker.Value]) end else return KeyPicker.Toggled end end function KeyPicker:SetValue(Data) local Key, Mode = Data[1], Data[2] DisplayLabel.Text = Key KeyPicker.Value = Key ModeButtons[Mode]:Select() KeyPicker:Update() end function KeyPicker:OnClick(Callback) KeyPicker.Clicked = Callback end function KeyPicker:OnChanged(Callback) KeyPicker.Changed = Callback Callback(KeyPicker.Value) end if ParentObj.Addons then table.insert(ParentObj.Addons, KeyPicker) end local Picking = false PickOuter.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 and not Library:MouseIsOverOpenedFrame() then Picking = true DisplayLabel.Text = "" local Break local Text = "" task.spawn(function() while not Break do if Text == "..." then Text = "" end Text = Text .. "." DisplayLabel.Text = Text wait(0.4) end end) wait(0.2) local Event Event = InputService.InputBegan:Connect(function(Input) local Key if Input.UserInputType == Enum.UserInputType.Keyboard or Input.UserInputType == Enum.UserInputType.Touch then Key = Input.KeyCode.Name elseif Input.UserInputType == Enum.UserInputType.MouseButton1 then Key = "MB1" elseif Input.UserInputType == Enum.UserInputType.MouseButton2 then Key = "MB2" end if Input.KeyCode == Enum.KeyCode.Escape or Input.KeyCode == Enum.KeyCode.Backspace then Key = "N/A" end Break = true Picking = false DisplayLabel.Text = Key KeyPicker.Value = Key Library:SafeCallback( "KeyPicker_ChangedCallback" .. "_" .. (Idx or ""), KeyPicker.ChangedCallback, Input.KeyCode or Input.UserInputType ) Library:SafeCallback( "KeyPicker_Changed" .. "_" .. (Idx or ""), KeyPicker.Changed, Input.KeyCode or Input.UserInputType ) Library:AttemptSave() Event:Disconnect() end) elseif Input.UserInputType == Enum.UserInputType.MouseButton2 and not Library:MouseIsOverOpenedFrame() then ModeSelectOuter.Visible = true end end) Library:GiveSignal(InputService.InputBegan:Connect(function(Input, ProcessedByGame) local textChatService = game:GetService("TextChatService") local userInputService = game:GetService("UserInputService") local chatInputBarConfiguration = textChatService:FindFirstChildOfClass("ChatInputBarConfiguration") if userInputService:GetFocusedTextBox() or (chatInputBarConfiguration and chatInputBarConfiguration.IsFocused) then return end if not Picking then if KeyPicker.Mode == "Toggle" then local Key = KeyPicker.Value if Key == "MB1" or Key == "MB2" then if Key == "MB1" and Input.UserInputType == Enum.UserInputType.MouseButton1 or Key == "MB2" and Input.UserInputType == Enum.UserInputType.MouseButton2 then KeyPicker.Toggled = not KeyPicker.Toggled KeyPicker:DoClick() end elseif Input.UserInputType == Enum.UserInputType.Keyboard then if Input.KeyCode.Name == Key then KeyPicker.Toggled = not KeyPicker.Toggled KeyPicker:DoClick() end elseif Input.UserInputType == Enum.UserInputType.Touch then if Input.KeyCode.Name == Key then KeyPicker.Toggled = not KeyPicker.Toggled KeyPicker:DoClick() end end end if KeyPicker.Mode == "Hold" then pcall(function() local Key = KeyPicker.Value if Key == "MB1" then KeyPicker.Toggled = InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) elseif Key == "MB2" then KeyPicker.Toggled = InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) end if Key == "MB1" or Key == "MB2" then KeyPicker:DoClick() else KeyPicker.Toggled = InputService:IsKeyDown(Enum.KeyCode[Key]) KeyPicker:DoClick() end end) end KeyPicker:Update() end if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then local AbsPos, AbsSize = ModeSelectOuter.AbsolutePosition, ModeSelectOuter.AbsoluteSize if Mouse.X < AbsPos.X or Mouse.X > AbsPos.X + AbsSize.X or Mouse.Y < (AbsPos.Y - 20 - 1) or Mouse.Y > AbsPos.Y + AbsSize.Y then ModeSelectOuter.Visible = false end end end)) Library:GiveSignal(InputService.InputEnded:Connect(function(Input, ProcessedByGame) if not Picking then if KeyPicker.Mode == "Hold" then pcall(function() local Key = KeyPicker.Value if Key == "MB1" then KeyPicker.Toggled = InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton1) elseif Key == "MB2" then KeyPicker.Toggled = InputService:IsMouseButtonPressed(Enum.UserInputType.MouseButton2) end if Key == "MB1" or Key == "MB2" then KeyPicker:DoClick() else KeyPicker.Toggled = InputService:IsKeyDown(Enum.KeyCode[Key]) KeyPicker:DoClick() end end) end KeyPicker:Update() end end)) if Info.Mode == "Always" then KeyPicker.Toggled = true KeyPicker:DoClick() end if Info.Mode == "Off" then KeyPicker.Toggled = false KeyPicker:DoClick() end KeyPicker:Update() if Idx then Options[Idx] = KeyPicker Library:RegisterOwnerKey("options", Idx) end return self end BaseAddons.__index = Funcs BaseAddons.__namecall = function(Table, Key, ...) return Funcs[Key](...) end end local BaseGroupbox = {} do local Funcs = {} function Funcs:AddBlank(Size) local Groupbox = self local Container = Groupbox.Container Library:Create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, Size), ZIndex = 1, Parent = Container, }) end function Funcs:AddLabel(Text, DoesWrap) local Label = {} local Groupbox = self local Container = Groupbox.Container local TextLabel = Library:CreateLabel({ Size = UDim2.new(1, -4, 0, 15), TextSize = 14, Text = Text, TextWrapped = DoesWrap or false, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 5, Parent = Container, }) if DoesWrap then local Y = select( 2, Library:GetTextBounds(Text, Library.Font, 14, Vector2.new(TextLabel.AbsoluteSize.X, math.huge)) ) TextLabel.Size = UDim2.new(1, -4, 0, Y) else Library:Create("UIListLayout", { Padding = UDim.new(0, 4), FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Right, SortOrder = Enum.SortOrder.LayoutOrder, Parent = TextLabel, }) end Label.TextLabel = TextLabel Label.Container = Container function Label:SetText(Text) TextLabel.Text = Text if DoesWrap then local Y = select( 2, Library:GetTextBounds(Text, Library.Font, 14, Vector2.new(TextLabel.AbsoluteSize.X, math.huge)) ) TextLabel.Size = UDim2.new(1, -4, 0, Y) end Groupbox:Resize() end if not DoesWrap then setmetatable(Label, BaseAddons) end Groupbox:AddBlank(5) Groupbox:Resize() return Label end function Funcs:AddButton(...) -- TODO: Eventually redo this local Button = {} local function ProcessButtonParams(Class, Obj, ...) local Props = select(1, ...) if type(Props) == "table" then Obj.Text = Props.Text Obj.Func = Props.Func Obj.DoubleClick = Props.DoubleClick Obj.DoubleClickText = Props.DoubleClickText Obj.Tooltip = Props.Tooltip else Obj.Text = select(1, ...) Obj.Func = select(2, ...) end assert(type(Obj.Func) == "function", "AddButton: `Func` callback is missing.") end ProcessButtonParams("Button", Button, ...) local Groupbox = self local Container = Groupbox.Container local function CreateBaseButton(Button) local Outer = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, -4, 0, 20), ZIndex = 5, }) local Inner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = Outer, }) local Label = Library:CreateLabel({ Size = UDim2.new(1, 0, 1, 0), TextSize = 14, Text = Button.Text, ZIndex = 6, Parent = Inner, }) Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.fromRGB(212, 212, 212)), }), Rotation = 90, Parent = Inner, }) Library:AddToRegistry(Outer, { BorderColor3 = "Black", }) Library:AddToRegistry(Inner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) Library:OnHighlight(Outer, Outer, { BorderColor3 = "AccentColor" }, { BorderColor3 = "Black" }) return Outer, Inner, Label end local function InitEvents(Button) local function WaitForEvent(event, timeout, validator) local bindable = Instance.new("BindableEvent") local connection = event:Once(function(...) if type(validator) == "function" and validator(...) then bindable:Fire(true) else bindable:Fire(false) end end) task.delay(timeout, function() connection:disconnect() bindable:Fire(false) end) return bindable.Event:Wait() end local function ValidateClick(Input) if Library:MouseIsOverOpenedFrame() then return false end if Input.UserInputType ~= Enum.UserInputType.MouseButton1 and Input.UserInputType ~= Enum.UserInputType.Touch then return false end return true end Button.Outer.InputBegan:Connect(function(Input) if not ValidateClick(Input) then return end if Button.Locked then return end if Button.DoubleClick then Library:RemoveFromRegistry(Button.Label) Library:AddToRegistry(Button.Label, { TextColor3 = "AccentColor" }) Button.Label.TextColor3 = Library.AccentColor Button.Label.Text = Button.DoubleClickText or "Are you sure?" Button.Locked = true local clicked = WaitForEvent(Button.Outer.InputBegan, 2, ValidateClick) Library:RemoveFromRegistry(Button.Label) Library:AddToRegistry(Button.Label, { TextColor3 = "FontColor" }) Button.Label.TextColor3 = Library.FontColor Button.Label.Text = Button.Text task.defer(rawset, Button, "Locked", false) if clicked then Library:SafeCallback("Button" .. "_" .. Button.Label.Text, Button.Func) end return end Library:SafeCallback("Button" .. "_" .. Button.Label.Text, Button.Func) end) end Button.Outer, Button.Inner, Button.Label = CreateBaseButton(Button) Button.Outer.Parent = Container InitEvents(Button) function Button:AddTooltip(tooltip) if type(tooltip) == "string" then Library:AddToolTip(tooltip, self.Outer) end return self end function Button:AddButton(...) local SubButton = {} ProcessButtonParams("SubButton", SubButton, ...) self.Outer.Size = UDim2.new(0.5, -2, 0, 20) SubButton.Outer, SubButton.Inner, SubButton.Label = CreateBaseButton(SubButton) SubButton.Outer.Position = UDim2.new(1, 3, 0, 0) SubButton.Outer.Size = UDim2.fromOffset(self.Outer.AbsoluteSize.X - 2, self.Outer.AbsoluteSize.Y) SubButton.Outer.Parent = self.Outer function SubButton:AddTooltip(tooltip) if type(tooltip) == "string" then Library:AddToolTip(tooltip, self.Outer) end return SubButton end if type(SubButton.Tooltip) == "string" then SubButton:AddTooltip(SubButton.Tooltip) end InitEvents(SubButton) return SubButton end if type(Button.Tooltip) == "string" then Button:AddTooltip(Button.Tooltip) end Groupbox:AddBlank(5) Groupbox:Resize() return Button end function Funcs:AddDivider() local Groupbox = self local Container = self.Container local Divider = { Type = "Divider", } Groupbox:AddBlank(2) local DividerOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, -4, 0, 5), ZIndex = 5, Parent = Container, }) local DividerInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = DividerOuter, }) Library:AddToRegistry(DividerOuter, { BorderColor3 = "Black", }) Library:AddToRegistry(DividerInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) Groupbox:AddBlank(9) Groupbox:Resize() end ---Add input function. ---@param Idx string ---@param Info table ---@return any function Funcs:AddInput(Idx, Info) assert(Info.Text, "AddInput: Missing `Text` string.") local Textbox = { Value = Info.Default or "", Numeric = Info.Numeric or false, Finished = Info.Finished or false, Type = "Input", Callback = Info.Callback or function(Value) end, } local Groupbox = self local Container = Groupbox.Container local InputLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 15), TextSize = 14, Text = Info.Text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 5, Parent = Container, }) Groupbox:AddBlank(1) local TextBoxOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, -4, 0, 20), ZIndex = 5, Parent = Container, }) local TextBoxInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = TextBoxOuter, }) Library:AddToRegistry(TextBoxInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) Library:OnHighlight( TextBoxOuter, TextBoxOuter, { BorderColor3 = "AccentColor" }, { BorderColor3 = "Black" } ) if type(Info.Tooltip) == "string" then Library:AddToolTip(Info.Tooltip, TextBoxOuter) end Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.fromRGB(212, 212, 212)), }), Rotation = 90, Parent = TextBoxInner, }) local Container = Library:Create("Frame", { BackgroundTransparency = 1, ClipsDescendants = true, Position = UDim2.new(0, 5, 0, 0), Size = UDim2.new(1, -5, 1, 0), ZIndex = 7, Parent = TextBoxInner, }) local Box = Library:Create("TextBox", { BackgroundTransparency = 1, Position = UDim2.fromOffset(0, 0), Size = UDim2.fromScale(5, 1), FontFace = Library.Font, PlaceholderColor3 = Color3.fromRGB(190, 190, 190), PlaceholderText = Info.Placeholder or "", Text = Info.Default or "", TextColor3 = Library.FontColor, TextSize = 14, TextStrokeTransparency = 0, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 7, Parent = Container, }) Library:ApplyTextStroke(Box) local Connection = nil local function normalizeText(value) if value == nil then return "" end if type(value) ~= "string" then return tostring(value) end return value end function Textbox:SetRawValue(Text) Text = normalizeText(Text) if Info.MaxLength and #Text > Info.MaxLength then Text = Text:sub(1, Info.MaxLength) end if Textbox.Numeric then if (not tonumber(Text)) and Text:len() > 0 then Text = Textbox.Value end end Textbox.Value = Text Box.Text = Text end function Textbox:SetValue(Text) Text = normalizeText(Text) if Info.MaxLength and #Text > Info.MaxLength then Text = Text:sub(1, Info.MaxLength) end if Textbox.Numeric then if (not tonumber(Text)) and Text:len() > 0 then Text = Textbox.Value end end Textbox.Value = Text Box.Text = Text Library:SafeCallback("Textbox_Callback" .. "_" .. (Idx or ""), Textbox.Callback, Textbox.Value) Library:SafeCallback("Textbox_Changed" .. "_" .. (Idx or ""), Textbox.Changed, Textbox.Value) end if Textbox.Finished then Connection = Box.FocusLost:Connect(function(enter) if not enter then return end Textbox:SetValue(Box.Text) Library:AttemptSave() end) else Connection = Box:GetPropertyChangedSignal("Text"):Connect(function() Textbox:SetValue(Box.Text) Library:AttemptSave() end) end -- https://devforum.roblox.com/t/how-to-make-textboxes-follow-current-cursor-position/1368429/6 -- thank you nicemike40 :) local function Update() local PADDING = 2 local reveal = Container.AbsoluteSize.X if not Box:IsFocused() or Box.TextBounds.X <= reveal - 2 * PADDING then -- we aren't focused, or we fit so be normal Box.Position = UDim2.new(0, PADDING, 0, 0) else -- we are focused and don't fit, so adjust position local cursor = Box.CursorPosition if cursor ~= -1 then -- calculate pixel width of text from start to cursor local subtext = string.sub(Box.Text, 1, cursor - 1) local width = TextService:GetTextSize( subtext, Box.TextSize, Box.Font, Vector2.new(math.huge, math.huge) ).X -- check if we're inside the box with the cursor local currentCursorPos = Box.Position.X.Offset + width -- adjust if necessary if currentCursorPos < PADDING then Box.Position = UDim2.fromOffset(PADDING - width, 0) elseif currentCursorPos > reveal - PADDING - 1 then Box.Position = UDim2.fromOffset(reveal - width - PADDING - 1, 0) end end end end task.spawn(Update) Box:GetPropertyChangedSignal("Text"):Connect(Update) Box:GetPropertyChangedSignal("CursorPosition"):Connect(Update) Box.FocusLost:Connect(Update) Box.Focused:Connect(Update) Box.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton2 then Library:Notify("Text copied to clipboard!", 2.5) setclipboard(Box.Text) end end) Library:AddToRegistry(Box, { TextColor3 = "FontColor", }) function Textbox:OnChanged(Func) Textbox.Changed = Func Func(Textbox.Value) end Groupbox:AddBlank(5) Groupbox:Resize() if Idx then Options[Idx] = Textbox Library:RegisterOwnerKey("options", Idx) end return Textbox end function Funcs:AddToggle(Idx, Info) assert(Info.Text, "AddInput: Missing `Text` string.") local Toggle = { Value = Info.Default or false, Type = "Toggle", Callback = Info.Callback or function(Value) end, Addons = {}, Risky = Info.Risky, } local Groupbox = self local Container = Groupbox.Container local ToggleOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(0, 13, 0, 13), ZIndex = 5, Parent = Container, }) Library:AddToRegistry(ToggleOuter, { BorderColor3 = "Black", }) local ToggleInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = ToggleOuter, }) Library:AddToRegistry(ToggleInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) local ToggleLabel = Library:CreateLabel({ Size = UDim2.new(0, 216, 1, 0), Position = UDim2.new(1, 6, 0, 0), TextSize = 14, Text = Info.Text, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 6, Parent = ToggleInner, }) Library:Create("UIListLayout", { Padding = UDim.new(0, 4), FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Right, SortOrder = Enum.SortOrder.LayoutOrder, Parent = ToggleLabel, }) local ToggleRegion = Library:Create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(0, 170, 1, 0), ZIndex = 8, Parent = ToggleOuter, }) Library:OnHighlight(ToggleRegion, ToggleOuter, { BorderColor3 = "AccentColor" }, { BorderColor3 = "Black" }) function Toggle:UpdateColors() Toggle:Display() end if type(Info.Tooltip) == "string" then Library:AddToolTip(Info.Tooltip, ToggleRegion) end function Toggle:Display() ToggleInner.BackgroundColor3 = Toggle.Value and Library.AccentColor or Library.MainColor ToggleInner.BorderColor3 = Toggle.Value and Library.AccentColorDark or Library.OutlineColor Library.RegistryMap[ToggleInner].Properties.BackgroundColor3 = Toggle.Value and "AccentColor" or "MainColor" Library.RegistryMap[ToggleInner].Properties.BorderColor3 = Toggle.Value and "AccentColorDark" or "OutlineColor" end function Toggle:OnChanged(Func) Toggle.Changed = Func Func(Toggle.Value) end function Toggle:SetRawValue(Bool) Bool = not not Bool Toggle.Value = Bool Toggle:Display() for _, Addon in next, Toggle.Addons do if Addon.Type == "KeyPicker" and Addon.SyncToggleState then Addon.Toggled = Bool Addon:Update() end end Library:UpdateDependencyBoxes() end function Toggle:SetValue(Bool) Bool = not not Bool Toggle.Value = Bool Toggle:Display() for _, Addon in next, Toggle.Addons do if Addon.Type == "KeyPicker" and Addon.SyncToggleState then Addon.Toggled = Bool Addon:Update() end end Library:SafeCallback("Toggle_Callback" .. "_" .. (Idx or ""), Toggle.Callback, Toggle.Value) Library:SafeCallback("Toggle_Changed" .. "_" .. (Idx or ""), Toggle.Changed, Toggle.Value) Library:UpdateDependencyBoxes() end ToggleRegion.InputBegan:Connect(function(Input) if ( Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch ) and not Library:MouseIsOverOpenedFrame() then Toggle:SetValue(not Toggle.Value) -- Why was it not like this from the start? Library:AttemptSave() end end) if Toggle.Risky then Library:RemoveFromRegistry(ToggleLabel) ToggleLabel.TextColor3 = Library.RiskColor Library:AddToRegistry(ToggleLabel, { TextColor3 = "RiskColor" }) end Toggle:Display() Groupbox:AddBlank(Info.BlankSize or 5 + 2) Groupbox:Resize() Toggle.TextLabel = ToggleLabel Toggle.Container = Container setmetatable(Toggle, BaseAddons) if Idx then Toggles[Idx] = Toggle Library:RegisterOwnerKey("toggles", Idx) end Library:UpdateDependencyBoxes() return Toggle end function Funcs:AddSlider(Idx, Info) assert(Info.Default, "AddSlider: Missing default value.") assert(Info.Text, "AddSlider: Missing slider text.") assert(Info.Min, "AddSlider: Missing minimum value.") assert(Info.Max, "AddSlider: Missing maximum value.") assert(Info.Rounding, "AddSlider: Missing rounding value.") local Slider = { Value = Info.Default, Min = Info.Min, Max = Info.Max, Rounding = Info.Rounding, MaxSize = 232, Type = "Slider", Callback = Info.Callback or function(Value) end, } local Groupbox = self local Container = Groupbox.Container if not Info.Compact then Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 10), TextSize = 14, Text = Info.Text, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Bottom, ZIndex = 5, Parent = Container, }) Groupbox:AddBlank(3) end local SliderOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, -4, 0, 13), ZIndex = 5, Parent = Container, }) Library:AddToRegistry(SliderOuter, { BorderColor3 = "Black", }) local SliderInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = SliderOuter, }) Library:AddToRegistry(SliderInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) local Fill = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderColor3 = Library.AccentColorDark, Size = UDim2.new(0, 0, 1, 0), ZIndex = 7, Parent = SliderInner, }) Library:AddToRegistry(Fill, { BackgroundColor3 = "AccentColor", BorderColor3 = "AccentColorDark", }) local HideBorderRight = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Position = UDim2.new(1, 0, 0, 0), Size = UDim2.new(0, 1, 1, 0), ZIndex = 8, Parent = Fill, }) Library:AddToRegistry(HideBorderRight, { BackgroundColor3 = "AccentColor", }) local DisplayLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 1, 0), TextSize = 14, Text = "Infinite", ZIndex = 9, Parent = SliderInner, }) Library:OnHighlight(SliderOuter, SliderOuter, { BorderColor3 = "AccentColor" }, { BorderColor3 = "Black" }) if type(Info.Tooltip) == "string" then Library:AddToolTip(Info.Tooltip, SliderOuter) end function Slider:UpdateColors() Fill.BackgroundColor3 = Library.AccentColor Fill.BorderColor3 = Library.AccentColorDark end function Slider:Display() local Suffix = Info.Suffix or "" if Info.Compact then DisplayLabel.Text = Info.Text .. ": " .. Slider.Value .. Suffix elseif Info.HideMax then DisplayLabel.Text = string.format("%s", Slider.Value .. Suffix) else DisplayLabel.Text = string.format("%s/%s", Slider.Value .. Suffix, Slider.Max .. Suffix) end local X = math.ceil(Library:MapValue(Slider.Value, Slider.Min, Slider.Max, 0, Slider.MaxSize)) Fill.Size = UDim2.new(0, X, 1, 0) HideBorderRight.Visible = not (X == Slider.MaxSize or X == 0) end function Slider:OnChanged(Func) Slider.Changed = Func Func(Slider.Value) end local function Round(Value) if Slider.Rounding == 0 then return math.floor(Value) end return tonumber(string.format("%." .. Slider.Rounding .. "f", Value)) end function Slider:GetValueFromXOffset(X) return Round(Library:MapValue(X, 0, Slider.MaxSize, Slider.Min, Slider.Max)) end function Slider:SetRawValue(Value) local Num = tonumber(Value) if not Num then return end Num = math.clamp(Num, Slider.Min, Slider.Max) Slider.Value = Num Slider:Display() end function Slider:SetValue(Str) local Num = tonumber(Str) if not Num then return end Num = math.clamp(Num, Slider.Min, Slider.Max) Slider.Value = Num Slider:Display() Library:SafeCallback("Slider_Callback" .. "_" .. (Idx or ""), Slider.Callback, Slider.Value) Library:SafeCallback("Slider_Changed" .. "_" .. (Idx or ""), Slider.Changed, Slider.Value) end local CurrentAmount = 0.01 local isInputChangedConnected = true local isInputEndedConnected = false SliderInner.InputBegan:Connect(function(Input) isInputEndedConnected = false if ( Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch ) and not Library:MouseIsOverOpenedFrame() then local isTouch = Input.UserInputType == Enum.UserInputType.Touch local startPos = isTouch and Input.Position.X or Mouse.X local startFillPos = Fill.Size.X.Offset local diff = startPos - (Fill.AbsolutePosition.X + startFillPos) while isInputChangedConnected and not isInputEndedConnected do local newPos = isTouch and Input.Position.X or Mouse.X local newX = math.clamp(startFillPos + (newPos - startPos) + diff, 0, Slider.MaxSize) local newValue = Slider:GetValueFromXOffset(newX) local oldValue = Slider.Value Slider.Value = newValue Slider:Display() if newValue ~= oldValue then Library:SafeCallback("Slider_Callback" .. "_" .. (Idx or ""), Slider.Callback, Slider.Value) Library:SafeCallback("Slider_Changed" .. "_" .. (Idx or ""), Slider.Changed, Slider.Value) end RenderStepped:Wait() end Library:AttemptSave() end if Input.KeyCode == Enum.KeyCode.Minus then CurrentAmount = math.max(CurrentAmount / 10, 0.00001) end if Input.KeyCode == Enum.KeyCode.Equals and (CurrentAmount * 10) <= Slider.Max then CurrentAmount = CurrentAmount * 10 end if Input.KeyCode == Enum.KeyCode.Right then Slider:SetValue(Slider.Value + CurrentAmount) end if Input.KeyCode == Enum.KeyCode.Left then Slider:SetValue(Slider.Value - CurrentAmount) end end) SliderInner.InputEnded:Connect(function() isInputEndedConnected = true end) Slider:Display() Groupbox:AddBlank(Info.BlankSize or 6) Groupbox:Resize() if Idx then Options[Idx] = Slider Library:RegisterOwnerKey("options", Idx) end return Slider end function Funcs:AddDropdown(Idx, Info) if Info.SpecialType == "Player" then Info.Values = GetPlayersString() Info.AllowNull = true elseif Info.SpecialType == "Team" then Info.Values = GetTeamsString() Info.AllowNull = true end assert(Info.Values, "AddDropdown: Missing dropdown value list.") assert( Info.AllowNull or Info.Default, "AddDropdown: Missing default value. Pass `AllowNull` as true if this was intentional." ) if not Info.Text then Info.Compact = true end local Dropdown = { Values = Info.Values, Value = Info.Multi and {}, SaveValues = Info.SaveValues or false, Multi = Info.Multi, Type = "Dropdown", SpecialType = Info.SpecialType, -- can be either 'Player' or 'Team' Callback = Info.Callback or function(Value) end, } local Groupbox = self local Container = Groupbox.Container local RelativeOffset = 0 if not Info.Compact then local DropdownLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 10), TextSize = 14, Text = Info.Text, TextXAlignment = Enum.TextXAlignment.Left, TextYAlignment = Enum.TextYAlignment.Bottom, ZIndex = 5, Parent = Container, }) Groupbox:AddBlank(3) end for _, Element in next, Container:GetChildren() do if not Element:IsA("UIListLayout") then RelativeOffset = RelativeOffset + Element.Size.Y.Offset end end local DropdownOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(1, -4, 0, 20), ZIndex = 5, Parent = Container, }) Library:AddToRegistry(DropdownOuter, { BorderColor3 = "Black", }) local DropdownInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 6, Parent = DropdownOuter, }) Library:AddToRegistry(DropdownInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Color3.new(1, 1, 1)), ColorSequenceKeypoint.new(1, Color3.fromRGB(212, 212, 212)), }), Rotation = 90, Parent = DropdownInner, }) local DropdownArrow = Library:Create("ImageLabel", { AnchorPoint = Vector2.new(0, 0.5), BackgroundTransparency = 1, Position = UDim2.new(1, -16, 0.5, 0), Size = UDim2.new(0, 12, 0, 12), Image = "http://www.roblox.com/asset/?id=6282522798", ZIndex = 8, Parent = DropdownInner, }) local ItemList = Library:CreateLabel({ Position = UDim2.new(0, 5, 0, 0), Size = UDim2.new(1, -5, 1, 0), TextSize = 14, Text = "--", TextXAlignment = Enum.TextXAlignment.Left, TextWrapped = true, ZIndex = 7, Parent = DropdownInner, }) Library:OnHighlight( DropdownOuter, DropdownOuter, { BorderColor3 = "AccentColor" }, { BorderColor3 = "Black" } ) if type(Info.Tooltip) == "string" then Library:AddToolTip(Info.Tooltip, DropdownOuter) end local MAX_DROPDOWN_ITEMS = 8 local ListOuter = Library:Create("Frame", { BackgroundColor3 = Color3.new(0, 0, 0), BorderColor3 = Color3.new(0, 0, 0), ZIndex = 20, Visible = false, Name = "ListOuter", Parent = ScreenGui, }) local function RecalculateListPosition() ListOuter.Position = UDim2.fromOffset( DropdownOuter.AbsolutePosition.X, DropdownOuter.AbsolutePosition.Y + DropdownOuter.Size.Y.Offset + 1 ) end local function RecalculateListSize(YSize) ListOuter.Size = UDim2.fromOffset(DropdownOuter.AbsoluteSize.X, YSize or (MAX_DROPDOWN_ITEMS * 20 + 2)) end RecalculateListPosition() RecalculateListSize() DropdownOuter:GetPropertyChangedSignal("AbsolutePosition"):Connect(RecalculateListPosition) local ListInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, BorderSizePixel = 0, Size = UDim2.new(1, 0, 1, 0), ZIndex = 21, Parent = ListOuter, }) Library:AddToRegistry(ListInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) local Scrolling = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, BorderSizePixel = 0, CanvasSize = UDim2.new(0, 0, 0, 0), Size = UDim2.new(1, 0, 1, 0), ZIndex = 21, Parent = ListInner, TopImage = "rbxasset://textures/ui/Scroll/scroll-middle.png", BottomImage = "rbxasset://textures/ui/Scroll/scroll-middle.png", ScrollBarThickness = 3, ScrollBarImageColor3 = Library.AccentColor, }) Library:AddToRegistry(Scrolling, { ScrollBarImageColor3 = "AccentColor", }) Library:Create("UIListLayout", { Padding = UDim.new(0, 0), FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = Scrolling, }) function Dropdown:Display() local Values = Dropdown.Values local Str = "" if Info.Multi then for Idx, Value in next, Values do if Dropdown.Value[Value] then Str = Str .. Value .. ", " end end Str = Str:sub(1, #Str - 2) else Str = Dropdown.Value or "" end ItemList.Text = (Str == "" and "--" or Str) end function Dropdown:GetActiveValues() if Info.Multi then local T = {} for Value, Bool in next, Dropdown.Value do table.insert(T, Value) end return T else return Dropdown.Value and 1 or 0 end end function Dropdown:BuildDropdownList() local Values = Dropdown.Values local Buttons = {} for _, Element in next, Scrolling:GetChildren() do if not Element:IsA("UIListLayout") then Element:Destroy() end end local Count = 0 for Idx, Value in next, Values do local Table = {} Count = Count + 1 local Button = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Middle, Size = UDim2.new(1, -1, 0, 20), ZIndex = 23, Active = true, Parent = Scrolling, }) Library:AddToRegistry(Button, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) local ButtonLabel = Library:CreateLabel({ Active = false, Size = UDim2.new(1, -6, 1, 0), Position = UDim2.new(0, 6, 0, 0), TextSize = 14, Text = Value, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 25, Parent = Button, }) Library:OnHighlight( Button, Button, { BorderColor3 = "AccentColor", ZIndex = 24 }, { BorderColor3 = "OutlineColor", ZIndex = 23 } ) local Selected if Info.Multi then Selected = Dropdown.Value[Value] else Selected = Dropdown.Value == Value end function Table:UpdateButton() if Info.Multi then Selected = Dropdown.Value[Value] else Selected = Dropdown.Value == Value end ButtonLabel.TextColor3 = Selected and Library.AccentColor or Library.FontColor Library.RegistryMap[ButtonLabel].Properties.TextColor3 = Selected and "AccentColor" or "FontColor" end ButtonLabel.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch then local Try = not Selected if Dropdown:GetActiveValues() == 1 and not Try and not Info.AllowNull then else if Info.Multi then Selected = Try if Selected then Dropdown.Value[Value] = true else Dropdown.Value[Value] = nil end else Selected = Try if Selected then Dropdown.Value = Value else Dropdown.Value = nil end for _, OtherButton in next, Buttons do OtherButton:UpdateButton() end Library:UpdateDependencyBoxes() end Table:UpdateButton() Dropdown:Display() Library:SafeCallback( "Dropdown_Callback" .. "_" .. (Idx or ""), Dropdown.Callback, Dropdown.Value ) Library:SafeCallback( "Dropdown_Changed" .. "_" .. (Idx or ""), Dropdown.Changed, Dropdown.Value ) Library:AttemptSave() end end end) Table:UpdateButton() Dropdown:Display() Buttons[Button] = Table end Scrolling.CanvasSize = UDim2.fromOffset(0, (Count * 20) + 1) local Y = math.clamp(Count * 20, 0, MAX_DROPDOWN_ITEMS * 20) + 1 RecalculateListSize(Y) end function Dropdown:SetValues(NewValues) if NewValues then Dropdown.Values = NewValues end Dropdown:BuildDropdownList() end function Dropdown:OpenDropdown() ListOuter.Visible = true Library.OpenedFrames[ListOuter] = true DropdownArrow.Rotation = 180 end function Dropdown:CloseDropdown() ListOuter.Visible = false Library.OpenedFrames[ListOuter] = nil DropdownArrow.Rotation = 0 end function Dropdown:OnChanged(Func) Dropdown.Changed = Func Func(Dropdown.Value) end function Dropdown:SetRawValue(Val) if Dropdown.Multi then local nTable = {} for Value, Bool in next, Val do if table.find(Dropdown.Values, Value) then nTable[Value] = true end end Dropdown.Value = nTable else if not Val then Dropdown.Value = nil elseif table.find(Dropdown.Values, Val) then Dropdown.Value = Val end end Dropdown:BuildDropdownList() end function Dropdown:SetValue(Val) if Dropdown.Multi then local nTable = {} for Value, Bool in next, Val do if table.find(Dropdown.Values, Value) then nTable[Value] = true end end Dropdown.Value = nTable else if not Val then Dropdown.Value = nil elseif table.find(Dropdown.Values, Val) then Dropdown.Value = Val end end Dropdown:BuildDropdownList() Library:SafeCallback("Dropdown_Callback" .. "_" .. (Idx or ""), Dropdown.Callback, Dropdown.Value) Library:SafeCallback("Dropdown_Changed" .. "_" .. (Idx or ""), Dropdown.Changed, Dropdown.Value) end DropdownOuter.InputBegan:Connect(function(Input) if ( Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 ) and not Library:MouseIsOverOpenedFrame() then if ListOuter.Visible then Dropdown:CloseDropdown() else Dropdown:OpenDropdown() end end end) InputService.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then local AbsPos, AbsSize = ListOuter.AbsolutePosition, ListOuter.AbsoluteSize if Mouse.X < AbsPos.X or Mouse.X > AbsPos.X + AbsSize.X or Mouse.Y < (AbsPos.Y - 20 - 1) or Mouse.Y > AbsPos.Y + AbsSize.Y then Dropdown:CloseDropdown() end end end) Dropdown:BuildDropdownList() Dropdown:Display() local Defaults = {} if type(Info.Default) == "string" then local Idx = table.find(Dropdown.Values, Info.Default) if Idx then table.insert(Defaults, Idx) end elseif type(Info.Default) == "table" then for _, Value in next, Info.Default do local Idx = table.find(Dropdown.Values, Value) if Idx then table.insert(Defaults, Idx) end end elseif type(Info.Default) == "number" and Dropdown.Values[Info.Default] ~= nil then table.insert(Defaults, Info.Default) end if next(Defaults) then for i = 1, #Defaults do local Index = Defaults[i] if Info.Multi then Dropdown.Value[Dropdown.Values[Index]] = true else Dropdown.Value = Dropdown.Values[Index] end if not Info.Multi then break end end Dropdown:BuildDropdownList() Dropdown:Display() end Groupbox:AddBlank(Info.BlankSize or 5) Groupbox:Resize() if Idx then Options[Idx] = Dropdown Library:RegisterOwnerKey("options", Idx) end return Dropdown end function Funcs:AddDependencyBox() local Depbox = { Dependencies = {}, } local Groupbox = self local Container = Groupbox.Container local Holder = Library:Create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 0, 0), Visible = false, Parent = Container, }) local Frame = Library:Create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, 0), Visible = true, Parent = Holder, }) local Layout = Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = Frame, }) function Depbox:Resize() Holder.Size = UDim2.new(1, 0, 0, Layout.AbsoluteContentSize.Y) Groupbox:Resize() end Layout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() Depbox:Resize() end) Holder:GetPropertyChangedSignal("Visible"):Connect(function() Depbox:Resize() end) function Depbox:Update() for _, Dependency in next, Depbox.Dependencies do local Elem = Dependency[1] local Value = Dependency[2] if Elem.Type == "Toggle" and Elem.Value ~= Value then Holder.Visible = false Depbox:Resize() return end if Elem.Type == "Dropdown" and Elem.Value ~= Value then Holder.Visible = false Depbox:Resize() return end end Holder.Visible = true Depbox:Resize() end function Depbox:SetupDependencies(Dependencies) for _, Dependency in next, Dependencies do assert(type(Dependency) == "table", "SetupDependencies: Dependency is not of type `table`.") assert(Dependency[1], "SetupDependencies: Dependency is missing element argument.") assert(Dependency[2] ~= nil, "SetupDependencies: Dependency is missing value argument.") end Depbox.Dependencies = Dependencies Depbox:Update() end Depbox.Container = Frame setmetatable(Depbox, BaseGroupbox) table.insert(Library.DependencyBoxes, Depbox) return Depbox end BaseGroupbox.__index = Funcs BaseGroupbox.__namecall = function(Table, Key, ...) return Funcs[Key](...) end end -- < Create other UI elements > do Library.NotificationArea = Library:Create("Frame", { BackgroundTransparency = 1, Position = UDim2.new(0, 0, 0, 40), Size = UDim2.new(0, 300, 0, 200), ZIndex = 100, Parent = ScreenGui, }) Library:Create("UIListLayout", { Padding = UDim.new(0, 4), FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = Library.NotificationArea, }) local WatermarkOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 100, 0, -25), Size = UDim2.new(0, 213, 0, 20), ZIndex = 200, Visible = false, Parent = ScreenGui, }) local WatermarkInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 201, Parent = WatermarkOuter, }) Library:AddToRegistry(WatermarkInner, { BorderColor3 = "OutlineColor", }) local ColorFrame = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 204, Parent = WatermarkInner, }) Library:AddToRegistry(ColorFrame, { BackgroundColor3 = "AccentColor", }, true) local InnerFrame = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BorderSizePixel = 0, Position = UDim2.new(0, 1, 0, 1), Size = UDim2.new(1, -2, 1, -2), ZIndex = 202, Parent = WatermarkInner, }) local Gradient = Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }), Rotation = -90, Parent = InnerFrame, }) Library:AddToRegistry(Gradient, { Color = function() return ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }) end, }) local WatermarkLabel = Library:CreateLabel({ Position = UDim2.new(0, 5, 0, 1), Size = UDim2.new(1, -4, 1, 0), TextColor3 = Library.AccentColor, TextSize = 14, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 203, Parent = InnerFrame, }) Library:AddToRegistry(WatermarkLabel, { TextColor3 = "AccentColor", }, true) Library.Watermark = WatermarkOuter Library.Watermark.Visible = false Library.WatermarkText = WatermarkLabel Library:MakeDraggable(Library.Watermark) local InfoLoggerOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 15, 0.5, 0), Size = UDim2.new(0, 210, 0, 20), Visible = false, ZIndex = 287, Parent = ScreenGui, }) local InfoLoggerInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 288, Parent = InfoLoggerOuter, }) Library:AddToRegistry(InfoLoggerInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local InfoColorFrame = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 299, Parent = InfoLoggerInner, }) Library:AddToRegistry(InfoColorFrame, { BackgroundColor3 = "AccentColor", }, true) local InfoLoggerLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 20), Position = UDim2.fromOffset(5, 2), TextXAlignment = Enum.TextXAlignment.Left, TextColor3 = Library.AccentColor, Text = "Info Logger", TextSize = 14, ZIndex = 300, Parent = InfoLoggerInner, }) Library:AddToRegistry(InfoLoggerLabel, { TextColor3 = "AccentColor", }, true) local InfoLoggerContainer = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, -20), Position = UDim2.new(0, 0, 0, 20), ZIndex = 1, ScrollBarThickness = 0, Parent = InfoLoggerInner, }) local InfoUIListLayout = Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = InfoLoggerContainer, }) InfoUIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() InfoLoggerContainer.CanvasSize = UDim2.fromOffset(0, InfoUIListLayout.AbsoluteContentSize.Y) end) Library:Create("UIPadding", { PaddingLeft = UDim.new(0, 5), Parent = InfoLoggerContainer, }) ---@param InputObject InputObject Library:GiveSignal(InfoLoggerOuter.InputBegan:Connect(function(InputObject) if InputObject.UserInputType ~= Enum.UserInputType.Keyboard then return end if InputObject.KeyCode == Enum.KeyCode.Z and game:GetService("UserInputService"):IsKeyDown(Enum.KeyCode.LeftControl) then local kbh = Library.InfoLoggerData.KeyBlacklistHistory local kbl = Library.InfoLoggerData.KeyBlacklistList local front = kbh[1] if not front then return end kbl[front] = nil table.remove(kbh, 1) Library:RefreshInfoLogger() Library:RefreshEffectLogger() Library:RefreshSkillLogger() if Options and Options.BlacklistedKeys then Options.BlacklistedKeys:SetValues(Library:KeyBlacklists()) end Library:Notify(string.format("Re-whitelisted key '%s' into list.", front)) end if InputObject.KeyCode == Enum.KeyCode.Q then Library.InfoLoggerCycle = math.max(Library.InfoLoggerCycle - 1, 1) Library:RefreshInfoLogger() end if InputObject.KeyCode == Enum.KeyCode.E then Library.InfoLoggerCycle = math.min(Library.InfoLoggerCycle + 1, #Library.InfoLoggerCycles) Library:RefreshInfoLogger() end end)) -- default cycle is animation. Library.InfoLoggerLabel = InfoLoggerLabel Library.InfoLoggerFrame = InfoLoggerOuter Library.InfoLoggerContainer = InfoLoggerContainer Library.InfoLoggerCycle = 1 Library.InfoLoggerCycles = { "Animation", "Existing Anim", "Keyframe", "Telemetry", "UsingSkill", "Part", "Sound", } Library.InfoLoggerData = { MissingDataEntries = {}, KeyBlacklistHistory = {}, KeyBlacklistList = {}, } Library:MakeDraggable(InfoLoggerOuter) Library:RefreshInfoLogger() local EffectLoggerOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 15, 0.5, 200), Size = UDim2.new(0, 210, 0, 20), Visible = false, ZIndex = 287, Parent = ScreenGui, }) local EffectLoggerInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 288, Parent = EffectLoggerOuter, }) Library:AddToRegistry(EffectLoggerInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local EffectColorFrame = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 299, Parent = EffectLoggerInner, }) Library:AddToRegistry(EffectColorFrame, { BackgroundColor3 = "AccentColor", }, true) local EffectLoggerLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 20), Position = UDim2.fromOffset(5, 2), TextXAlignment = Enum.TextXAlignment.Left, TextColor3 = Library.AccentColor, Text = "Effect Logger", TextSize = 14, ZIndex = 300, Parent = EffectLoggerInner, }) Library:AddToRegistry(EffectLoggerLabel, { TextColor3 = "AccentColor", }, true) local EffectLoggerContainer = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, -20), Position = UDim2.new(0, 0, 0, 20), ZIndex = 1, ScrollBarThickness = 0, Parent = EffectLoggerInner, }) local EffectUIListLayout = Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = EffectLoggerContainer, }) EffectUIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() EffectLoggerContainer.CanvasSize = UDim2.fromOffset(0, EffectUIListLayout.AbsoluteContentSize.Y) end) Library:Create("UIPadding", { PaddingLeft = UDim.new(0, 5), Parent = EffectLoggerContainer, }) Library.EffectLoggerLabel = EffectLoggerLabel Library.EffectLoggerFrame = EffectLoggerOuter Library.EffectLoggerContainer = EffectLoggerContainer Library.EffectLoggerData = { Entries = {}, } Library:MakeDraggable(EffectLoggerOuter) Library:RefreshEffectLogger() local SkillLoggerOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 15, 0.5, 400), Size = UDim2.new(0, 210, 0, 20), Visible = false, ZIndex = 287, Parent = ScreenGui, }) local SkillLoggerInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 288, Parent = SkillLoggerOuter, }) Library:AddToRegistry(SkillLoggerInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local SkillColorFrame = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 299, Parent = SkillLoggerInner, }) Library:AddToRegistry(SkillColorFrame, { BackgroundColor3 = "AccentColor", }, true) local SkillLoggerLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 20), Position = UDim2.fromOffset(5, 2), TextXAlignment = Enum.TextXAlignment.Left, TextColor3 = Library.AccentColor, Text = "Skill Logger", TextSize = 14, ZIndex = 300, Parent = SkillLoggerInner, }) Library:AddToRegistry(SkillLoggerLabel, { TextColor3 = "AccentColor", }, true) local SkillLoggerContainer = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, -20), Position = UDim2.new(0, 0, 0, 20), ZIndex = 1, ScrollBarThickness = 0, Parent = SkillLoggerInner, }) local SkillUIListLayout = Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = SkillLoggerContainer, }) SkillUIListLayout:GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() SkillLoggerContainer.CanvasSize = UDim2.fromOffset(0, SkillUIListLayout.AbsoluteContentSize.Y) end) Library:Create("UIPadding", { PaddingLeft = UDim.new(0, 5), Parent = SkillLoggerContainer, }) Library.SkillLoggerLabel = SkillLoggerLabel Library.SkillLoggerFrame = SkillLoggerOuter Library.SkillLoggerContainer = SkillLoggerContainer Library.SkillLoggerData = { Entries = {}, } Library:MakeDraggable(SkillLoggerOuter) Library:RefreshSkillLogger() local KeybindOuter = Library:Create("Frame", { AnchorPoint = Vector2.new(0, 0.5), BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 10, 0.5, 0), Size = UDim2.new(0, 210, 0, 20), Visible = false, ZIndex = 100, Parent = ScreenGui, }) local KeybindInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 101, Parent = KeybindOuter, }) Library:AddToRegistry(KeybindInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local ColorFrame = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 102, Parent = KeybindInner, }) Library:AddToRegistry(ColorFrame, { BackgroundColor3 = "AccentColor", }, true) local KeybindLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 20), Position = UDim2.fromOffset(5, 2), TextXAlignment = Enum.TextXAlignment.Left, TextSize = 14, TextColor3 = Library.AccentColor, Text = "Keybind List", ZIndex = 104, Parent = KeybindInner, }) Library:AddToRegistry(KeybindLabel, { TextColor3 = "AccentColor", }, true) local KeybindContainer = Library:Create("Frame", { BackgroundTransparency = 1, Size = UDim2.new(1, 0, 1, -20), Position = UDim2.new(0, 0, 0, 20), ZIndex = 1, Parent = KeybindInner, }) Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = KeybindContainer, }) Library:Create("UIPadding", { PaddingLeft = UDim.new(0, 5), Parent = KeybindContainer, }) Library.KeybindFrame = KeybindOuter Library.KeybindFrame.Visible = false Library.KeybindContainer = KeybindContainer Library:MakeDraggable(KeybindOuter) end function Library:SetWatermarkVisibility(Bool) Library.Watermark.Visible = Bool end function Library:SetWatermark(Text) local X, Y = Library:GetTextBounds(Text, Library.Font, 14) Library.WatermarkText.Text = Text Library.Watermark.Size = UDim2.new(0, X + 15, 0, (Y * 1.5) + 3) end function Library:ManuallyManagedNotify(Text) if shared.Lycoris.silent then return end local XSize, YSize = Library:GetTextBounds(Text, Library.Font, 14) YSize = YSize + 7 local NotifyOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 100, 0, 10), Size = UDim2.new(0, 0, 0, YSize), ClipsDescendants = true, ZIndex = 100, Parent = Library.NotificationArea, }) local NotifyInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 101, Parent = NotifyOuter, }) Library:AddToRegistry(NotifyInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local InnerFrame = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BorderSizePixel = 0, Position = UDim2.new(0, 1, 0, 1), Size = UDim2.new(1, -2, 1, -2), ZIndex = 102, Parent = NotifyInner, }) local Gradient = Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }), Rotation = -90, Parent = InnerFrame, }) Library:AddToRegistry(Gradient, { Color = function() return ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }) end, }) local NotifyLabel = Library:CreateLabel({ Position = UDim2.new(0, 4, 0, 0), Size = UDim2.new(1, -4, 1, 0), Text = Text, TextXAlignment = Enum.TextXAlignment.Left, TextSize = 14, ZIndex = 103, Parent = InnerFrame, }) local LeftColor = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Position = UDim2.new(0, -1, 0, -1), Size = UDim2.new(0, 3, 1, 2), ZIndex = 104, Parent = NotifyOuter, }) Library:AddToRegistry(LeftColor, { BackgroundColor3 = "AccentColor", }, true) pcall(NotifyOuter.TweenSize, NotifyOuter, UDim2.new(0, XSize + 8 + 4, 0, YSize), "Out", "Quad", 0.4, true) local TweenOutCalled = false local function TweenOut() if TweenOutCalled then return end TweenOutCalled = true pcall(NotifyOuter.TweenSize, NotifyOuter, UDim2.new(0, 0, 0, YSize), "Out", "Quad", 0.4, true) task.wait(0.4) NotifyOuter:Destroy() end local Connection = nil local Connection2 = nil Connection = InnerFrame.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then TweenOut() Connection:Disconnect() end end) Connection2 = InnerFrame.MouseEnter:Connect(function() if game:GetService("UserInputService"):IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then TweenOut() Connection2:Disconnect() end end) return TweenOut end function Library:Notify(Text, Time) if shared.Lycoris.silent then return end local XSize, YSize = Library:GetTextBounds(Text, Library.Font, 14) YSize = YSize + 7 local NotifyOuter = Library:Create("Frame", { BorderColor3 = Color3.new(0, 0, 0), Position = UDim2.new(0, 100, 0, 10), Size = UDim2.new(0, 0, 0, YSize), ClipsDescendants = true, ZIndex = 100, Parent = Library.NotificationArea, }) local NotifyInner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 1, 0), ZIndex = 101, Parent = NotifyOuter, }) Library:AddToRegistry(NotifyInner, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }, true) local InnerFrame = Library:Create("Frame", { BackgroundColor3 = Color3.new(1, 1, 1), BorderSizePixel = 0, Position = UDim2.new(0, 1, 0, 1), Size = UDim2.new(1, -2, 1, -2), ZIndex = 102, Parent = NotifyInner, }) local Gradient = Library:Create("UIGradient", { Color = ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }), Rotation = -90, Parent = InnerFrame, }) Library:AddToRegistry(Gradient, { Color = function() return ColorSequence.new({ ColorSequenceKeypoint.new(0, Library:GetDarkerColor(Library.MainColor)), ColorSequenceKeypoint.new(1, Library.MainColor), }) end, }) local NotifyLabel = Library:CreateLabel({ Position = UDim2.new(0, 4, 0, 0), Size = UDim2.new(1, -4, 1, 0), Text = Text, TextXAlignment = Enum.TextXAlignment.Left, TextSize = 14, ZIndex = 103, Parent = InnerFrame, }) local LeftColor = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Position = UDim2.new(0, -1, 0, -1), Size = UDim2.new(0, 3, 1, 2), ZIndex = 104, Parent = NotifyOuter, }) Library:AddToRegistry(LeftColor, { BackgroundColor3 = "AccentColor", }, true) pcall(NotifyOuter.TweenSize, NotifyOuter, UDim2.new(0, XSize + 8 + 4, 0, YSize), "Out", "Quad", 0.4, true) local function TweenOut() pcall(NotifyOuter.TweenSize, NotifyOuter, UDim2.new(0, 0, 0, YSize), "Out", "Quad", 0.4, true) task.wait(0.4) NotifyOuter:Destroy() end local Connection = nil local Connection2 = nil Connection = InnerFrame.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.MouseButton1 then TweenOut() Connection:Disconnect() end end) Connection2 = InnerFrame.MouseEnter:Connect(function() if game:GetService("UserInputService"):IsMouseButtonPressed(Enum.UserInputType.MouseButton1) then TweenOut() Connection2:Disconnect() end end) task.spawn(function() task.wait(Time or 5) TweenOut() end) end function Library:CreateWindow(...) local Arguments = { ... } local Config = { AnchorPoint = Vector2.zero } if type(...) == "table" then Config = ... else Config.Title = Arguments[1] Config.AutoShow = Arguments[2] or false end if type(Config.Title) ~= "string" then Config.Title = "No title" end if type(Config.TabPadding) ~= "number" then Config.TabPadding = 0 end if type(Config.MenuFadeTime) ~= "number" then Config.MenuFadeTime = 0.2 end if typeof(Config.Position) ~= "UDim2" then Config.Position = UDim2.fromOffset(175, 50) end if typeof(Config.Size) ~= "UDim2" then Config.Size = UDim2.fromOffset(550, 600) end if Config.Center then Config.AnchorPoint = Vector2.new(0.5, 0.5) Config.Position = UDim2.fromScale(0.5, 0.5) end local Window = { Tabs = {}, } local Outer = Library:Create("Frame", { AnchorPoint = Config.AnchorPoint, BackgroundColor3 = Color3.new(0, 0, 0), BorderSizePixel = 0, Position = Config.Position, Size = Config.Size, Visible = false, ZIndex = 1, Parent = ScreenGui, }) Library:MakeDraggable(Outer, 25) local Inner = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.AccentColor, BorderMode = Enum.BorderMode.Inset, Position = UDim2.new(0, 1, 0, 1), Size = UDim2.new(1, -2, 1, -2), ZIndex = 1, Parent = Outer, }) Library:AddToRegistry(Inner, { BackgroundColor3 = "MainColor", BorderColor3 = "AccentColor", }) local WindowLabel = Library:CreateLabel({ Position = UDim2.new(0, 7, 0, 0), Size = UDim2.new(0, 0, 0, 25), Text = Config.Title or "", TextColor3 = Library.AccentColor, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 1, Parent = Inner, }) Library:AddToRegistry(WindowLabel, { TextColor3 = "AccentColor", }) local MainSectionOuter = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, Position = UDim2.new(0, 8, 0, 25), Size = UDim2.new(1, -16, 1, -33), ZIndex = 1, Parent = Inner, }) Library:AddToRegistry(MainSectionOuter, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) local MainSectionInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Color3.new(0, 0, 0), BorderMode = Enum.BorderMode.Inset, Position = UDim2.new(0, 0, 0, 0), Size = UDim2.new(1, 0, 1, 0), ZIndex = 1, Parent = MainSectionOuter, }) Library:AddToRegistry(MainSectionInner, { BackgroundColor3 = "BackgroundColor", }) local TabArea = Library:Create("Frame", { BackgroundTransparency = 1, Position = UDim2.new(0, 8, 0, 8), Size = UDim2.new(1, -16, 0, 21), ZIndex = 1, Parent = MainSectionInner, }) local TabListLayout = Library:Create("UIListLayout", { Padding = UDim.new(0, Config.TabPadding), FillDirection = Enum.FillDirection.Horizontal, SortOrder = Enum.SortOrder.LayoutOrder, Parent = TabArea, }) local TabContainer = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Library.OutlineColor, Position = UDim2.new(0, 8, 0, 30), Size = UDim2.new(1, -16, 1, -38), ZIndex = 2, Parent = MainSectionInner, }) Library:AddToRegistry(TabContainer, { BackgroundColor3 = "MainColor", BorderColor3 = "OutlineColor", }) function Window:SetWindowTitle(Title) WindowLabel.Text = Title end ---Add a tab to the window. ---@param Name string ---@return table function Window:AddTab(Name) local Tab = { GroupboxCount = 0, TabboxCount = 0, Groupboxes = {}, Tabboxes = {}, } local TabButtonWidth = Library:GetTextBounds(Name, Library.Font, 16) local TabButton = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, Size = UDim2.new(0, TabButtonWidth + 8 + 4, 1, 0), ZIndex = 1, Parent = TabArea, }) Library:AddToRegistry(TabButton, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) local TabButtonLabel = Library:CreateLabel({ Position = UDim2.new(0, 0, 0, 0), Size = UDim2.new(1, 0, 1, -1), Text = Name, ZIndex = 1, Parent = TabButton, }) local Blocker = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderSizePixel = 0, Position = UDim2.new(0, 0, 1, 0), Size = UDim2.new(1, 0, 0, 1), BackgroundTransparency = 1, ZIndex = 3, Parent = TabButton, }) Library:AddToRegistry(Blocker, { BackgroundColor3 = "MainColor", }) local TabFrame = Library:Create("Frame", { Name = "TabFrame", BackgroundTransparency = 1, Position = UDim2.new(0, 0, 0, 0), Size = UDim2.new(1, 0, 1, 0), Visible = false, ZIndex = 2, Parent = TabContainer, }) local LeftSide = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, BorderSizePixel = 0, Position = UDim2.new(0, 8 - 1, 0, 8 - 1), Size = UDim2.new(0.5, -12 + 2, 0, 507 + 2), CanvasSize = UDim2.new(0, 0, 0, 0), BottomImage = "", TopImage = "", ScrollBarThickness = 0, ZIndex = 2, Parent = TabFrame, }) local RightSide = Library:Create("ScrollingFrame", { BackgroundTransparency = 1, BorderSizePixel = 0, Position = UDim2.new(0.5, 4 + 1, 0, 8 - 1), Size = UDim2.new(0.5, -12 + 2, 0, 507 + 2), CanvasSize = UDim2.new(0, 0, 0, 0), BottomImage = "", TopImage = "", ScrollBarThickness = 0, ZIndex = 2, Parent = TabFrame, }) Library:Create("UIListLayout", { Padding = UDim.new(0, 8), FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, HorizontalAlignment = Enum.HorizontalAlignment.Center, Parent = LeftSide, }) Library:Create("UIListLayout", { Padding = UDim.new(0, 8), FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, HorizontalAlignment = Enum.HorizontalAlignment.Center, Parent = RightSide, }) for _, Side in next, { LeftSide, RightSide } do Side:WaitForChild("UIListLayout"):GetPropertyChangedSignal("AbsoluteContentSize"):Connect(function() Side.CanvasSize = UDim2.fromOffset(0, Side.UIListLayout.AbsoluteContentSize.Y) end) end function Tab:ShowTab() for _, Tab in next, Window.Tabs do Tab:HideTab() end Blocker.BackgroundTransparency = 0 TabButton.BackgroundColor3 = Library.MainColor Library.RegistryMap[TabButton].Properties.BackgroundColor3 = "MainColor" TabFrame.Visible = true end function Tab:HideTab() Blocker.BackgroundTransparency = 1 TabButton.BackgroundColor3 = Library.BackgroundColor Library.RegistryMap[TabButton].Properties.BackgroundColor3 = "BackgroundColor" TabFrame.Visible = false end function Tab:SetLayoutOrder(Position) TabButton.LayoutOrder = Position TabListLayout:ApplyLayout() end function Tab:AddGroupbox(Info) local Groupbox = { Name = Info.Name } local BoxOuter = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 0, 507 + 2), ZIndex = 2, Parent = Info.Side == 1 and LeftSide or RightSide, }) Library:AddToRegistry(BoxOuter, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) local BoxInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Color3.new(0, 0, 0), -- BorderMode = Enum.BorderMode.Inset; Size = UDim2.new(1, -2, 1, -2), Position = UDim2.new(0, 1, 0, 1), ZIndex = 4, Parent = BoxOuter, }) Library:AddToRegistry(BoxInner, { BackgroundColor3 = "BackgroundColor", }) local Highlight = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 5, Parent = BoxInner, }) Library:AddToRegistry(Highlight, { BackgroundColor3 = "AccentColor", }) local GroupboxLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 0, 18), Position = UDim2.new(0, 4, 0, 2), TextSize = 14, Text = Info.Name, TextXAlignment = Enum.TextXAlignment.Left, ZIndex = 5, Parent = BoxInner, }) local Container = Library:Create("Frame", { BackgroundTransparency = 1, Position = UDim2.new(0, 4, 0, 20), Size = UDim2.new(1, -4, 1, -20), ZIndex = 1, Parent = BoxInner, }) Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = Container, }) function Groupbox:Resize() local Size = 0 for _, Element in next, Groupbox.Container:GetChildren() do if (not Element:IsA("UIListLayout")) and Element.Visible then Size = Size + Element.Size.Y.Offset end end BoxOuter.Size = UDim2.new(1, 0, 0, 20 + Size + 2 + 2) end Groupbox.Container = Container setmetatable(Groupbox, BaseGroupbox) Groupbox:AddBlank(3) Groupbox:Resize() Tab.GroupboxCount = Tab.GroupboxCount + 1 Tab.Groupboxes[Info.Name] = Groupbox return Groupbox end function Tab:AddDynamicGroupbox(Name) if (Tab.GroupboxCount + Tab.TabboxCount) % 2 == 0 then return Tab:AddLeftGroupbox(Name) else return Tab:AddRightGroupbox(Name) end end function Tab:AddLeftGroupbox(Name) return Tab:AddGroupbox({ Side = 1, Name = Name }) end function Tab:AddRightGroupbox(Name) return Tab:AddGroupbox({ Side = 2, Name = Name }) end function Tab:AddTabbox(Info) local Tabbox = { Tabs = {}, } local BoxOuter = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Library.OutlineColor, BorderMode = Enum.BorderMode.Inset, Size = UDim2.new(1, 0, 0, 0), ZIndex = 2, Parent = Info.Side == 1 and LeftSide or RightSide, }) Library:AddToRegistry(BoxOuter, { BackgroundColor3 = "BackgroundColor", BorderColor3 = "OutlineColor", }) local BoxInner = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderColor3 = Color3.new(0, 0, 0), -- BorderMode = Enum.BorderMode.Inset; Size = UDim2.new(1, -2, 1, -2), Position = UDim2.new(0, 1, 0, 1), ZIndex = 4, Parent = BoxOuter, }) Library:AddToRegistry(BoxInner, { BackgroundColor3 = "BackgroundColor", }) local Highlight = Library:Create("Frame", { BackgroundColor3 = Library.AccentColor, BorderSizePixel = 0, Size = UDim2.new(1, 0, 0, 2), ZIndex = 10, Parent = BoxInner, }) Library:AddToRegistry(Highlight, { BackgroundColor3 = "AccentColor", }) local TabboxButtons = Library:Create("Frame", { BackgroundTransparency = 1, Position = UDim2.new(0, 0, 0, 1), Size = UDim2.new(1, 0, 0, 18), ZIndex = 5, Parent = BoxInner, }) Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Horizontal, HorizontalAlignment = Enum.HorizontalAlignment.Left, SortOrder = Enum.SortOrder.LayoutOrder, Parent = TabboxButtons, }) function Tabbox:AddTab(Name) local Tab = {} local Button = Library:Create("Frame", { BackgroundColor3 = Library.MainColor, BorderColor3 = Color3.new(0, 0, 0), Size = UDim2.new(0.5, 0, 1, 0), ZIndex = 6, Parent = TabboxButtons, }) Library:AddToRegistry(Button, { BackgroundColor3 = "MainColor", }) local ButtonLabel = Library:CreateLabel({ Size = UDim2.new(1, 0, 1, 0), TextSize = 14, Text = Name, TextXAlignment = Enum.TextXAlignment.Center, ZIndex = 7, Parent = Button, }) local Block = Library:Create("Frame", { BackgroundColor3 = Library.BackgroundColor, BorderSizePixel = 0, Position = UDim2.new(0, 0, 1, 0), Size = UDim2.new(1, 0, 0, 1), Visible = false, ZIndex = 9, Parent = Button, }) Library:AddToRegistry(Block, { BackgroundColor3 = "BackgroundColor", }) local Container = Library:Create("Frame", { BackgroundTransparency = 1, Position = UDim2.new(0, 4, 0, 20), Size = UDim2.new(1, -4, 1, -20), ZIndex = 1, Visible = false, Parent = BoxInner, }) Library:Create("UIListLayout", { FillDirection = Enum.FillDirection.Vertical, SortOrder = Enum.SortOrder.LayoutOrder, Parent = Container, }) function Tab:Show() for _, Tab in next, Tabbox.Tabs do Tab:Hide() end Container.Visible = true Block.Visible = true Button.BackgroundColor3 = Library.BackgroundColor Library.RegistryMap[Button].Properties.BackgroundColor3 = "BackgroundColor" Tab:Resize() end function Tab:Hide() Container.Visible = false Block.Visible = false Button.BackgroundColor3 = Library.MainColor Library.RegistryMap[Button].Properties.BackgroundColor3 = "MainColor" end function Tab:Resize() local TabCount = 0 for _, Tab in next, Tabbox.Tabs do TabCount = TabCount + 1 end for _, Button in next, TabboxButtons:GetChildren() do if not Button:IsA("UIListLayout") then Button.Size = UDim2.new(1 / TabCount, 0, 1, 0) end end if not Container.Visible then return end local Size = 0 for _, Element in next, Tab.Container:GetChildren() do if (not Element:IsA("UIListLayout")) and Element.Visible then Size = Size + Element.Size.Y.Offset end end BoxOuter.Size = UDim2.new(1, 0, 0, 20 + Size + 2 + 2) end Button.InputBegan:Connect(function(Input) if ( Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 ) and not Library:MouseIsOverOpenedFrame() then Tab:Show() Tab:Resize() end end) Tab.Container = Container Tabbox.Tabs[Name] = Tab setmetatable(Tab, BaseGroupbox) Tab:AddBlank(3) Tab:Resize() -- Show first tab (number is 2 cus of the UIListLayout that also sits in that instance) if #TabboxButtons:GetChildren() == 2 then Tab:Show() end return Tab end Tab.Tabboxes[Info.Name or ""] = Tabbox Tab.TabboxCount = Tab.TabboxCount + 1 return Tabbox end function Tab:AddLeftTabbox(Name) return Tab:AddTabbox({ Name = Name, Side = 1 }) end function Tab:AddRightTabbox(Name) return Tab:AddTabbox({ Name = Name, Side = 2 }) end function Tab:AddDynamicTabbox(Name) if (Tab.GroupboxCount + Tab.TabboxCount) % 2 == 0 then return Tab:AddLeftTabbox(Name) else return Tab:AddRightTabbox(Name) end end TabButton.InputBegan:Connect(function(Input) if Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.MouseButton1 then Tab:ShowTab() end end) -- This was the first tab added, so we show it by default. if #TabContainer:GetChildren() == 1 then Tab:ShowTab() end Window.Tabs[Name] = Tab return Tab end local ModalElement = Library:Create("TextButton", { BackgroundTransparency = 1, Size = UDim2.new(0, 0, 0, 0), Visible = true, Text = "", Modal = false, Parent = ScreenGui, }) local TransparencyCache = {} local Fading = false local FirstTime = false function Library:Toggle() if Fading then return end local FadeTime = Config.MenuFadeTime local ShouldFade = FadeTime > 0.01 if ShouldFade then Fading = true end Toggled = not Toggled ModalElement.Modal = Toggled if Toggled then Outer.Visible = true end if not Toggled then for _, ColorPicker in next, ColorPickers do ColorPicker:Hide() end for _, ContextMenu in next, ContextMenus do ContextMenu:Hide() end for _, Tooltip in next, Tooltips do Tooltip.Visible = false end for _, ModeSelectFrame in next, ModeSelectFrames do ModeSelectFrame.Visible = false end end if ShouldFade or not FirstTime then for _, Desc in next, Outer:GetDescendants() do local Properties = {} if Desc:IsA("ImageLabel") then table.insert(Properties, "ImageTransparency") table.insert(Properties, "BackgroundTransparency") elseif Desc:IsA("TextLabel") or Desc:IsA("TextBox") then table.insert(Properties, "TextTransparency") elseif Desc:IsA("Frame") or Desc:IsA("ScrollingFrame") then table.insert(Properties, "BackgroundTransparency") elseif Desc:IsA("UIStroke") then table.insert(Properties, "Transparency") end local Cache = TransparencyCache[Desc] if not Cache then Cache = {} TransparencyCache[Desc] = Cache end for _, Prop in next, Properties do if not Cache[Prop] then Cache[Prop] = Desc[Prop] end if Cache[Prop] == 1 then continue end TweenService:Create( Desc, TweenInfo.new(FadeTime, Enum.EasingStyle.Linear), { [Prop] = Toggled and Cache[Prop] or 1 } ):Play() end end task.wait(FadeTime) FirstTime = true end Outer.Visible = Toggled Fading = false end Library:GiveSignal(InputService.InputBegan:Connect(function(Input, Processed) if type(Library.ToggleKeybind) == "table" and Library.ToggleKeybind.Type == "KeyPicker" then if ( Input.UserInputType == Enum.UserInputType.Touch or Input.UserInputType == Enum.UserInputType.Keyboard ) and Input.KeyCode.Name == Library.ToggleKeybind.Value then task.spawn(Library.Toggle) end elseif Input.KeyCode == Enum.KeyCode.RightControl or (Input.KeyCode == Enum.KeyCode.RightShift and not Processed) then task.spawn(Library.Toggle) end end)) if Config.AutoShow then task.spawn(Library.Toggle) end Library.KeybindFrame.Visible = not shared.Lycoris.silent Library.Watermark.Visible = not shared.Lycoris.silent Window.Holder = Outer return Window end local function OnPlayerChange() local PlayerList = GetPlayersString() for _, Value in next, Options do if Value.Type == "Dropdown" and Value.SpecialType == "Player" then Value:SetValues(PlayerList) end end end Players.PlayerAdded:Connect(OnPlayerChange) Players.PlayerRemoving:Connect(OnPlayerChange) return Library end)() end) -- MODULE: Utility/Configuration defineModule("Utility/Configuration", function() -- Safe configuration getter methods. -- The menu is the last thing initialized in the script. local Configuration = {} ---Expect toggle value. ---@param key string ---@return any? Configuration.expectToggleValue = LPH_NO_VIRTUALIZE(function(key) if not Toggles then return nil end local toggle = Toggles[key] if not toggle then return nil end return toggle.Value end) ---Expect option value. ---@param key string ---@return any? Configuration.expectOptionValue = LPH_NO_VIRTUALIZE(function(key) if not Options then return nil end local option = Options[key] if not option then return nil end return option.Value end) ---Identify element. ---@param identifier string ---@param topLevelIdentifier string ---@return string Configuration.identify = LPH_NO_VIRTUALIZE(function(identifier, topLevelIdentifier) return identifier .. topLevelIdentifier end) ---Fetch toggle value. ---@param identifier string ---@param topLevelIdentifier string ---@return any Configuration.idToggleValue = LPH_NO_VIRTUALIZE(function(identifier, topLevelIdentifier) if not Toggles then return nil end local toggle = Toggles[identifier .. topLevelIdentifier] if not toggle then return nil end return toggle.Value end) ---Fetch option value. ---@param identifier string ---@param topLevelIdentifier string ---@return any Configuration.idOptionValue = LPH_NO_VIRTUALIZE(function(identifier, topLevelIdentifier) if not Options then return nil end local option = Options[identifier .. topLevelIdentifier] if not option then return nil end return option.Value end) ---Fetch option values. ---@param identifier string ---@param topLevelIdentifier string ---@return any Configuration.idOptionValues = LPH_NO_VIRTUALIZE(function(identifier, topLevelIdentifier) if not Options then return nil end local option = Options[identifier .. topLevelIdentifier] if not option then return nil end return option.Values end) -- Return Configuration module. return Configuration end) -- MODULE: Utility/CoreGuiManager defineModule("Utility/CoreGuiManager", function() ---@note: We need to be careful where we use CoreGui because exploits have this weird permission issue. We need consistent setting of the parent. ---@note: All scripts that must access this module should require it at the top of the file where it gets loaded. local CoreGuiManager = {} -- Instance list. local instances = {} ---Mark an instance to be parented to CoreGui at initialization. ---@param instance Instance ---@return Instance function CoreGuiManager.imark(instance) instances[#instances + 1] = instance return instance end ---Consistently and safely parent(s) all instances to CoreGui. ---@return table function CoreGuiManager.set() local coreGui = game:GetService("CoreGui") for _, instance in next, instances do instance.Parent = coreGui end return instances end ---Remove all stored instances. function CoreGuiManager.clear() for _, instance in next, instances do instance:Destroy() end instances = {} end ---Return CoreGuiManager module. return CoreGuiManager end) -- MODULE: Utility/Deserializer defineModule("Utility/Deserializer", function() -- Deserializer module. local Deserializer = {} ---@module Utility.DeserializerStream local DeserializerStream = require("Utility/DeserializerStream") ---@module Utility.String local String = require("Utility/String") -- Services. local httpService = game:GetService("HttpService") local function isJsonString(value) if type(value) ~= "string" then return false end local first = value:match("%S") return first == "{" or first == "[" end -- Deserialization data map. local byteToDataMap = { [0xc0] = nil, [0xc2] = false, [0xc3] = true, [0xc4] = DeserializerStream.byte, [0xc5] = DeserializerStream.short, [0xc6] = DeserializerStream.int, [0xca] = DeserializerStream.float, [0xcb] = DeserializerStream.double, [0xcc] = DeserializerStream.byte, [0xcd] = DeserializerStream.unsignedShort, [0xce] = DeserializerStream.unsignedInt, [0xcf] = DeserializerStream.unsignedLong, [0xd0] = DeserializerStream.byte, [0xd1] = DeserializerStream.short, [0xd2] = DeserializerStream.int, [0xd3] = DeserializerStream.long, [0xd9] = DeserializerStream.byte, [0xda] = DeserializerStream.unsignedShort, [0xdb] = DeserializerStream.unsignedInt, [0xdc] = DeserializerStream.unsignedShort, [0xdd] = DeserializerStream.unsignedInt, [0xde] = DeserializerStream.unsignedShort, [0xdf] = DeserializerStream.unsignedInt, } ---Decode array with a specific length and recursively read. ---@param stream DeserializerStream ---@param length number ---@return table local function decodeArray(stream, length) local elements = {} for i = 1, length do elements[i] = Deserializer.at(stream) end return elements end ---Decode map with a specific length and recursively read. ---@param stream DeserializerStream ---@param length number ---@return table, number local function decodeMap(stream, length) local elements = {} for _ = 1, length do elements[Deserializer.at(stream)] = Deserializer.at(stream) end return elements end ---Deserialize the data at a specific position. ---@param stream DeserializerStream ---@return any function Deserializer.at(stream) local byte = stream:byte() local byteData = byteToDataMap[byte] or function() error("Unhandled byte data: " .. byte) end if byte == 0xde or byte == 0xdf then return decodeMap(stream, byteData(stream)) end if byte >= 0x80 and byte <= 0x8f then return decodeMap(stream, byte - 0x80) end if byte >= 0x90 and byte <= 0x9f then return decodeArray(stream, byte - 0x90) end if byte == 0xdc or byte == 0xdd then return decodeArray(stream, byteData(stream)) end if byte == 0xc4 or byte == 0xc5 or byte == 0xc6 then return stream:leReadBytes(byteData(stream)) end if byte == 0xd9 or byte == 0xda or byte == 0xdb then return stream:string(byteData(stream)) end if byte >= 0xa0 and byte <= 0xbf then return stream:string(byte - 0xa0) end if byte == 0xc0 or byte == 0xc1 or byte == 0xc2 then return byteToDataMap[byte] end if byte >= 0x00 and byte <= 0x7f then return byte end if byte >= 0xe0 and byte <= 0xff then return -32 + (byte - 0xe0) end return typeof(byteData) == "function" and byteData(stream) or byteData end ---Starts recursively deserializing the data from the first index one time. ---@param data table ---@return any function Deserializer.unmarshal_one(data) if type(data) == "string" then if isJsonString(data) then return httpService:JSONDecode(data) end data = String.tba(data) end return Deserializer.at(DeserializerStream.new(data)) end -- Return Deseralizer module. return Deserializer end) -- MODULE: Utility/DeserializerStream defineModule("Utility/DeserializerStream", function() ---@class DeserializerStream ---@field source table ---@field index number local DeserializerStream = {} DeserializerStream.__index = DeserializerStream ---Read bytes in little endian. ---@param len number ---@return number[] function DeserializerStream:leReadBytes(len) local bytes = {} for idx = self.index + 1, self.index + len do bytes[#bytes + 1] = self.source[idx] end self.index = self.index + len if self.index > #self.source then return error("leReadBytes - read overflow") end return bytes end ---Read bytes in big endianess format. ---@param len number ---@return number[] function DeserializerStream:beReadBytes(len) local bytes = {} for idx = self.index + len, self.index + 1, -1 do bytes[#bytes + 1] = self.source[idx] end self.index = self.index + len if self.index > #self.source then return error("beReadBytes - read overflow") end return bytes end ---Read string. ---@param len number ---@return string function DeserializerStream:string(len) local src = self.source local buf = buffer.create(len) for idx = self.index + 1, self.index + len do buffer.writeu8(buf, idx - self.index - 1, src[idx]) end self.index = self.index + len ---@note: Inlined leReadBytes. if self.index > #self.source then return error("string - read overflow") end return buffer.readstring(buf, 0, len) end ---Read unsigned long. ---@return number function DeserializerStream:unsignedLong() local bytes = self:beReadBytes(8) local p1 = bit32.bor(bytes[1], bit32.lshift(bytes[2], 8), bit32.lshift(bytes[3], 16), bit32.lshift(bytes[4], 24)) local p2 = bit32.bor(bytes[5], bit32.lshift(bytes[6], 8), bit32.lshift(bytes[7], 16), bit32.lshift(bytes[8], 24)) return bit32.bor(p1, bit32.lshift(p2, 32)) end ---Read unsigned int. ---@return number function DeserializerStream:unsignedInt() local bytes = self:beReadBytes(4) return bit32.bor(bytes[1], bit32.lshift(bytes[2], 8), bit32.lshift(bytes[3], 16), bit32.lshift(bytes[4], 24)) end ---Read unsigned short. ---@return number function DeserializerStream:unsignedShort() local bytes = self:beReadBytes(2) return bit32.bor(bytes[1], bit32.lshift(bytes[2], 8)) end ---Read float. ---@return number function DeserializerStream:float() local bytes = self:beReadBytes(4) local sign = (-1) ^ bit32.rshift(bytes[4], 7) local exp = bit32.rshift(bytes[3], 7) + bit32.lshift(bit32.band(bytes[4], 0x7F), 1) local frac = bytes[1] + bit32.lshift(bytes[2], 8) + bit32.lshift(bit32.band(bytes[3], 0x7F), 16) local normal = 1 if exp == 0 then if frac == 0 then return sign * 0 else normal = 0 exp = 1 end elseif exp == 0x7F then if frac == 0 then return sign * (1 / 0) else return sign * (0 / 0) end end return sign * 2 ^ (exp - 127) * (1 + normal / 2 ^ 23) end ---Read double. ---@return number function DeserializerStream:double() local bytes = self:beReadBytes(8) local sign = (-1) ^ bit32.rshift(bytes[8], 7) local exp = bit32.lshift(bit32.band(bytes[8], 0x7F), 4) + bit32.rshift(bytes[7], 4) local frac = bit32.band(bytes[7], 0x0F) * 2 ^ 48 local normal = 1 frac = frac + (bytes[6] * 2 ^ 40) + (bytes[5] * 2 ^ 32) + (bytes[4] * 2 ^ 24) + (bytes[3] * 2 ^ 16) + (bytes[2] * 2 ^ 8) + bytes[1] if exp == 0 then if frac == 0 then return sign * 0 else normal = 0 exp = 1 end elseif exp == 0x7FF then if frac == 0 then return sign * (1 / 0) else return sign * (0 / 0) end end return sign * 2 ^ (exp - 1023) * (normal + frac / 2 ^ 52) end ---Read long. ---@return number function DeserializerStream:long() local value = self:unsignedLong() if bit32.band(value, 0x8000000000000000) ~= 0x0 then value = value - 0x800000000000000 end return value end ---Read int. ---@return number function DeserializerStream:int() local value = self:unsignedInt() if bit32.band(value, 0x80000000) ~= 0 then value = value - 0x100000000 end return value end ---Read short. ---@return number function DeserializerStream:short() local value = self:unsignedShort() if bit32.band(value, 0x8000) ~= 0 then value = value - 0x10000 end return value end ---Read byte. ---@return number function DeserializerStream:byte() local bytes = self:leReadBytes(1) return bytes[1] end ---Create new DeserializerStream object. ---@param source table ---@return DeserializerStream function DeserializerStream.new(source) local self = setmetatable({}, DeserializerStream) self.source = source self.index = 0 return self end -- Return DeserializerStream module. return DeserializerStream end) -- MODULE: Utility/Filesystem defineModule("Utility/Filesystem", function() return LPH_NO_VIRTUALIZE(function() ---@class Filesystem ---@field _path string local Filesystem = {} Filesystem.__index = Filesystem ---Create and get the current path. ---@return string function Filesystem:path() if not isfolder(self._path) then makefolder(self._path) end return self._path end ---Append path to current path. ---@param path string ---@return string function Filesystem:append(path) return self:path() .. "\\" .. path end ---Check if filename is a file. ---@param filename string ---@return boolean function Filesystem:file(filename) return isfile(self:append(filename)) end ---Read file from path. ---@param filename string ---@return string function Filesystem:read(filename) if not self:file(filename) then return error("File does not exist or is a folder.", 2) end return readfile(self:append(filename)) end ---Delete file. ---@param filename string ---@return string function Filesystem:delete(filename) if not self:file(filename) then return error("File does not exist or is a folder.", 2) end return delfile(self:append(filename)) end ---Write file to workspace folder. ---@param filename string ---@param contents string? function Filesystem:write(filename, contents) writefile(self:append(filename), contents and contents or "") end ---List files. ---@param raw boolean? ---@return table function Filesystem:list(raw) local list = listfiles(self:path()) if not list then return error("File list does not exist.", 2) end local new = {} for idx, path in next, list do ---@note: Solara returns full paths. --- C:/Users/brean/Downloads/Workspace/(path_here)/(file_here) --- We must get rid of the C:/Users/brean/Downloads/Workspace and have that be fully dynamic and not break if getexecutorname and getexecutorname():match("Solara") then path = string.sub(path, #listfiles()[1] + 2, #path) end ---@note: Non-raw weird behavior where the path is never detected in the string. Let's manually index remove it. new[idx] = raw and path or string.sub(path, #(self:path() .. "\\") + 1, #path) end return new end ---Create new Filesystem object. ---@param path string ---@return Filesystem function Filesystem.new(path) local self = setmetatable({}, Filesystem) self._path = path return self end -- Return Filesystem module. return Filesystem end)() end) -- MODULE: Utility/Logger defineModule("Utility/Logger", function() return LPH_NO_VIRTUALIZE(function() -- Logger module. local Logger = {} Logger.__index = Logger ---@module GUI.Library local Library = require("GUI/Library") ---Build a string with a prefix. ---@param str string ---@return string local function buildPrefixString(str) return string.format("[%s %s] [Cottom hub AB]: %s", os.date("%x"), os.date("%X"), str) end ---Create a manually managed notification. ---@param str string ---@return function function Logger.mnnotify(str, ...) return Library:ManuallyManagedNotify(string.format(str, ...)) end ---Notify message with a default short cooldown to create consistent cooldowns between files. ---@param str string function Logger.notify(str, ...) Library:Notify(string.format(str, ...), 3.0) end ---Notify message with a default long cooldown to create consistent cooldowns between files. ---@param str string function Logger.longNotify(str, ...) Library:Notify(string.format(str, ...), 30.0) end ---Warn message. ---@param str string function Logger.warn(str, ...) if shared.Lycoris.silent then return end warn(string.format(buildPrefixString(str), ...)) end ---Trace & warn message. ---@param str string function Logger.trace(str, ...) if shared.Lycoris.silent then return end Logger.warn(str, ...) warn(debug.traceback(2)) end -- Return Logger module. return Logger end)() end) -- MODULE: Utility/Maid defineModule("Utility/Maid", function() -- https://github.com/Quenty/NevermoreEngine/blob/version2/Modules/Shared/Events/Maid.lua ---@class Maid local Maid = {} Maid.__type = "maid" ---Create new Maid object. ---@return Maid Maid.new = LPH_NO_VIRTUALIZE(function() return setmetatable({ _tasks = {}, }, Maid) end) ---Return maid[key] - if not, it's not apart of the maid metatable - so we return the relevant task. -- @return value Maid.__index = LPH_NO_VIRTUALIZE(function(self, index) if Maid[index] then return Maid[index] else return self._tasks[index] end end) ---Clean or add a task with a specific key. ---@param index any ---@param newTask any Maid.__newindex = LPH_NO_VIRTUALIZE(function(self, index, newTask) if Maid[index] ~= nil then return warn(("'%s' is reserved"):format(tostring(index)), 2) end local tasks = self._tasks local oldTask = tasks[index] if oldTask == newTask then return end tasks[index] = newTask if oldTask then if typeof(oldTask) == "thread" then return coroutine.status(oldTask) == "suspended" and task.cancel(oldTask) or nil end if type(oldTask) == "function" then oldTask() elseif typeof(oldTask) == "RBXScriptConnection" then oldTask:Disconnect() elseif typeof(oldTask) == "Instance" and oldTask:IsA("Tween") then oldTask:Pause() oldTask:Cancel() oldTask:Destroy() elseif oldTask.Destroy then oldTask:Destroy() elseif oldTask.detach then oldTask:detach() end end end) ---Add a task without a specific ID and return the task. ---@param task any ---@return any Maid.mark = LPH_NO_VIRTUALIZE(function(self, task) self:add(task) return task end) ---Get a unique ID for a task. ---@return number Maid.uid = LPH_NO_VIRTUALIZE(function(self) return #self._tasks + 1 end) ---Add a task without a specific ID. ---@param task any ---@return number Maid.add = LPH_NO_VIRTUALIZE(function(self, task) if not task then return error("task cannot be false or nil", 2) end local taskId = self:uid() self[taskId] = task return taskId end) ---Remove task without cleaning it. ---@param taskId number Maid.removeTask = LPH_NO_VIRTUALIZE(function(self, taskId) local tasks = self._tasks tasks[taskId] = nil end) ---Clean up all tasks. Maid.clean = LPH_NO_VIRTUALIZE(function(self) local tasks = self._tasks -- Disconnect all events first - as we know this is safe. for index, task in pairs(tasks) do if typeof(task) == "RBXScriptConnection" then tasks[index] = nil task:Disconnect() end end -- Clear out tasks table completely, even if clean up tasks add more tasks to the maid. local index, _task = next(tasks) while _task ~= nil do tasks[index] = nil if typeof(_task) == "thread" then if coroutine.status(_task) == "suspended" then task.cancel(_task) end else if type(_task) == "function" then _task() elseif typeof(_task) == "RBXScriptConnection" then _task:Disconnect() elseif typeof(_task) == "Instance" and _task:IsA("Tween") then _task:Pause() _task:Cancel() _task:Destroy() elseif typeof(_task) == "Instance" and _task:IsA("AnimationTrack") then _task:Stop() elseif _task.Destroy then _task:Destroy() elseif _task.detach then _task:detach() end end index, _task = next(tasks) end end) -- Return Maid module. return Maid end) -- MODULE: Utility/OriginalStore defineModule("Utility/OriginalStore", function() ---@class OriginalStore ---@param data any ---@param index any ---@param value any ---@field stored boolean local OriginalStore = {} OriginalStore.__index = OriginalStore ---Get stored data value. ---@return any OriginalStore.get = LPH_NO_VIRTUALIZE(function(self) if not self.stored then return nil end return self.value end) ---Set something, run a callback, and then restore. ---@param data table|Instance ---@param index any ---@param value any ---@param callback fun(): any OriginalStore.run = LPH_NO_VIRTUALIZE(function(self, data, index, value, callback) self:set(data, index, value) callback() self:restore() end) ---Mark data value. ---@param data table|Instance ---@param index any OriginalStore.mark = LPH_NO_VIRTUALIZE(function(self, data, index) if self.stored and self.data ~= data then self:restore() end if not self.stored then self.data = data self.index = index self.value = data[index] self.stored = true end end) ---Set data value. ---@param data table|Instance ---@param index any ---@param value any OriginalStore.set = LPH_NO_VIRTUALIZE(function(self, data, index, value) self:mark(data, index) data[index] = value end) ---Restore data value. OriginalStore.restore = LPH_NO_VIRTUALIZE(function(self) if not self.stored then return end pcall(function() self.data[self.index] = self.value end) self.stored = false end) ---Detach OriginalStore object. OriginalStore.detach = LPH_NO_VIRTUALIZE(function(self) self:restore() self.data = nil self.index = nil self.value = nil self.stored = false end) ---Create new OriginalStore object. ---@return OriginalStore OriginalStore.new = LPH_NO_VIRTUALIZE(function() local self = setmetatable({}, OriginalStore) self.data = nil self.index = nil self.value = nil self.stored = false return self end) -- Return OriginalStore module. return OriginalStore end) -- MODULE: Utility/Profiler defineModule("Utility/Profiler", function() return LPH_NO_VIRTUALIZE(function() -- Profile code time. -- Determine what parts of our script are lagging us through the microprofiler. local Profiler = {} ---Runs a function with a specified profiler label. ---@param label string ---@param functionToProfile function function Profiler.run(label, functionToProfile, ...) -- Profile under label. debug.profilebegin(label) -- Call function to profile. local ret_values = table.pack(functionToProfile(...)) -- End most recent profiling. debug.profileend() -- Return values. return unpack(ret_values) end ---Wrap function in a profiler statement with label. ---@param label string ---@param functionToProfile function ---@return function function Profiler.wrap(label, functionToProfile) return function(...) return Profiler.run(label, functionToProfile, ...) end end -- Return profiler module. return Profiler end)() end) -- MODULE: Utility/Serializer defineModule("Utility/Serializer", function() --[[ * MessagePack serializer / decode (0.6.1) written in pure Lua 5.3 / Lua 5.4 * written by Sebastian Steinhauer * modified by the Lycoris Team * * This is free and unencumbered software released into the public domain. * * Anyone is free to copy, modify, publish, use, compile, sell, or * distribute this software, either in source code form or as a compiled * binary, for any purpose, commercial or non-commercial, and by any * means. * * In jurisdictions that recognize copyright laws, the author or authors * of this software dedicate any and all copyright interest in the * software to the public domain. We make this dedication for the benefit * of the public at large and to the detriment of our heirs and * successors. We intend this dedication to be an overt act of * relinquishment in perpetuity of all present and future rights to this * software under copyright law. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. * IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR * OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, * ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR * OTHER DEALINGS IN THE SOFTWARE. * * For more information, please refer to ]] -- Serializer module. local Serializer = {} -- Services. local httpService = game:GetService("HttpService") ---Does a specified table match the layout of an array. ---@param tbl table ---@return boolean local function isAnArray(tbl) local expected = 1 for k in next, tbl do if k ~= expected then return false end expected = expected + 1 end return true end ---Serialize number to a float. ---@param value number ---@return string local function serializeFloat(value) local serializedFloat = string.unpack("f", string.pack("f", value)) if serializedFloat == value then return string.pack(">Bf", 0xca, value) end return string.pack(">Bd", 0xcb, value) end ---Serialize number to a signed int. ---@param value number ---@return string local function serializeSignedInt(value) if value < 128 then return string.pack("B", value) elseif value <= 0xff then return string.pack("BB", 0xcc, value) elseif value <= 0xffff then return string.pack(">BI2", 0xcd, value) elseif value <= 0xffffffff then return string.pack(">BI4", 0xce, value) end return string.pack(">BI8", 0xcf, value) end ---Serialize number to a unsigned int. ---@param value number ---@return string local function serializeUnsignedInt(value) if value >= -32 then return string.pack("B", 0xe0 + (value + 32)) elseif value >= -128 then return string.pack("Bb", 0xd0, value) elseif value >= -32768 then return string.pack(">Bi2", 0xd1, value) elseif value >= -2147483648 then return string.pack(">Bi4", 0xd2, value) end return string.pack(">Bi8", 0xd3, value) end ---Serialize string to a UTF8 string. ---@param value string ---@return string local function serializeUtf8(value) local len = #value if len < 32 then return string.pack("B", 0xa0 + len) .. value elseif len < 256 then return string.pack(">Bs1", 0xd9, value) elseif len < 65536 then return string.pack(">Bs2", 0xda, value) end return string.pack(">Bs4", 0xdb, value) end ---Serialize string to a string of bytes. ---@param value string ---@return string local function serializeStringBytes(value) local len = #value if len < 256 then return string.pack(">Bs1", 0xc4, value) elseif len < 65536 then return string.pack(">Bs2", 0xc5, value) end return string.pack(">Bs4", 0xc6, value) end ---Serialize table to a array. ---@param value table ---@return string local function serializeArray(value) local elements = {} for i, v in pairs(value) do if type(v) ~= "function" and type(v) ~= "thread" and type(v) ~= "userdata" then elements[i] = Serializer.marshal(v) end end local result = table.concat(elements) local length = #elements if length < 16 then return string.pack(">B", 0x90 + length) .. result elseif length < 65536 then return string.pack(">BI2", 0xdc, length) .. result end return string.pack(">BI4", 0xdd, length) .. result end ---Serialize table to a map. ---@param value table ---@return string local function serializeMap(value) local elements = {} for k, v in pairs(value) do if type(v) ~= "function" and type(v) ~= "thread" and type(v) ~= "userdata" then elements[#elements + 1] = Serializer.marshal(k) elements[#elements + 1] = Serializer.marshal(v) end end local length = math.floor(#elements / 2) if length < 16 then return string.pack(">B", 0x80 + length) .. table.concat(elements) elseif length < 65536 then return string.pack(">BI2", 0xde, length) .. table.concat(elements) end return string.pack(">BI4", 0xdf, length) .. table.concat(elements) end ---Serialize nil to a binary string. ---@return string local function serializeNil() return string.pack("B", 0xc0) end ---serialize table to a binary string ---@param value table ---@return string local function serializeTable(value) return isAnArray(value) and serializeArray(value) or serializeMap(value) end ---serialize boolean to a binary string ---@param value boolean ---@return string local function serializeBoolean(value) return string.pack("B", value and 0xc3 or 0xc2) end ---serialize int to a binary string ---@param value number ---@return string local function serializeInt(value) return value >= 0 and serializeSignedInt(value) or serializeUnsignedInt(value) end ---serialize number to a binary string ---@param value number ---@return string local function serializeNumber(value) return value % 1 == 0 and serializeInt(value) or serializeFloat(value) end ---serialize string to a binary string ---@param value number ---@return string local function serializeString(value) return utf8.len(value) and serializeUtf8(value) or serializeStringBytes(value) end -- Types mapping to functions that serialize it. local typeToSerializeMap = { ["nil"] = serializeNil, ["boolean"] = serializeBoolean, ["number"] = serializeNumber, ["string"] = serializeString, ["table"] = serializeTable, } ---Marshal a value into a binary string. ---@param value any ---@return string function Serializer.marshal(value) return httpService:JSONEncode(value) end -- Return Serializer module. return Serializer end) -- MODULE: Utility/Signal defineModule("Utility/Signal", function() -- Wrapper for Roblox's signals for safe connections to signals. -- Automatically profiles signals & wraps them in a safe alternative. ---@class Signal ---@field signal RBXScriptSignal Underlying roblox script signal local Signal = {} Signal.__index = Signal ---@module Utility.Profiler local Profiler = require("Utility/Profiler") ---@module Utility.Logger local Logger = require("Utility/Logger") ---Safely connect to Roblox's signal. ---@param label string ---@param eventFunction function ---@return RBXScriptConnection function Signal:connect(label, eventFunction) ---Log event errors. ---@param error string local function onEventFunctionError(error) Logger.trace("onEventFunctionError - (%s) - %s", label, error) end -- Connect to signal. Wrap function with profiler and error handling. local connection = self.signal:Connect(Profiler.wrap( label, LPH_NO_VIRTUALIZE(function(...) return xpcall(eventFunction, onEventFunctionError, ...) end) )) -- Return connection. return connection end ---Create new wrapper signal object. ---@param robloxSignal RBXScriptSignal ---@return Signal function Signal.new(robloxSignal) -- Create new wrapper signal object. local self = setmetatable({}, Signal) self.signal = robloxSignal -- Return new wrapper signal object. return self end -- Return Signal module. return Signal end) -- MODULE: Utility/String defineModule("Utility/String", function() local String = {} -- Generate mapping. local charByteMap = {} for idx = 0, 255 do charByteMap[string.char(idx)] = idx end ---String to byte array. ---@param str string ---@return table function String.tba(str) local chars = {} local idx = 1 if #str == 0 then return {} end repeat chars[idx] = charByteMap[str:sub(idx, idx)] idx = idx + 1 until idx == #str + 1 return chars end -- Return String module. return String end) -- MODULE: Utility/Table defineModule("Utility/Table", function() return LPH_NO_VIRTUALIZE(function() -- Table utility functions. local Table = {} ---Take a chunk out of an array into a new array. ---@param input any[] ---@param start number ---@param stop number ---@return any[] function Table.slice(input, start, stop) local out = {} if start == nil then start = 1 elseif start < 0 then start = #input + start + 1 end if stop == nil then stop = #input elseif stop < 0 then stop = #input + stop + 1 end for idx = start, stop do table.insert(out, input[idx]) end return out end -- Return Table module. return Table end)() end) -- MODULE: Utility/TaskSpawner defineModule("Utility/TaskSpawner", function() -- Task spawner module. local TaskSpawner = {} ---@module Utility.Profiler local Profiler = require("Utility/Profiler") ---@module Utility.Logger local Logger = require("Utility/Logger") -- Services. local RunService = game:GetService("RunService") ---Spawn delayed task where the delay can be variable. ---@param label string ---@param delay function ---@param callback function ---@vararg any function TaskSpawner.delay(label, delay, callback, ...) ---Log task errors. ---@param error string local function onTaskFunctionError(error) Logger.trace("onTaskFunctionError - (%s) - %s", label, error) end -- Wrap callback in profiler and error handling and delay handling. local taskFunction = Profiler.wrap( label, LPH_NO_VIRTUALIZE(function(...) local timestamp = os.clock() while os.clock() - timestamp < delay() do RunService.RenderStepped:Wait() end return xpcall(callback, onTaskFunctionError, ...) end) ) return task.spawn(taskFunction, ...) end ---Spawn task. ---@param label string ---@param callback function ---@vararg any function TaskSpawner.spawn(label, callback, ...) ---Log task errors. ---@param error string local function onTaskFunctionError(error) Logger.trace("onTaskFunctionError - (%s) - %s", label, error) end -- Wrap callback in profiler and error handling. local taskFunction = Profiler.wrap( label, LPH_NO_VIRTUALIZE(function(...) return xpcall(callback, onTaskFunctionError, ...) end) ) -- Return reference. return task.spawn(taskFunction, ...) end -- Return TaskSpawner module. return TaskSpawner end) local CoreGuiManager = require("Utility/CoreGuiManager") local Library = require("GUI/Library") local Logger = require("Utility/Logger") local SaveManager = require("Game/Timings/SaveManager") local AttributeListener = require("Features/Combat/AttributeListener") local Defense = require("Features/Combat/Defense") local InputClient = require("Game/InputClient") local AutoCombo = require("Features/Combat/AutoCombo") local Players = game:GetService("Players") local userInputService = game:GetService("UserInputService") local Configuration = require("Utility/Configuration") local TaskSpawner = require("Utility/TaskSpawner") CoreGuiManager.set() SaveManager.init() local window = Library:CreateWindow({ Title = "Cottom hub AB", AutoShow = true }) Library.ToggleKeybind = { Type = "KeyPicker", Value = "M" } Library.OwnerTag = "Cottom" Library.OwnerKeys = { toggles = {}, options = {} } do local loggerTab = window:AddTab("Logger") local loggerWindowGroup = loggerTab:AddLeftGroupbox("Logger Window") local showLoggerToggle = loggerWindowGroup:AddToggle("ShowLoggerWindow", { Text = "Show Logger Window", Default = true, }) showLoggerToggle:AddKeyPicker( "ShowLoggerWindowKeyBind", { Default = "N/A", SyncToggleState = true, Text = "Logger Window" } ) showLoggerToggle:OnChanged(function(value) if Library.InfoLoggerFrame then Library.InfoLoggerFrame.Visible = value end end) local animVisToggle = loggerWindowGroup:AddToggle("ShowAnimationVisualizer", { Text = "Show Animation Visualizer", Default = false, }) animVisToggle:AddKeyPicker( "AnimationVisualizerKeyBind", { Default = "N/A", SyncToggleState = true, Text = "Animation Visualizer" } ) loggerWindowGroup:AddButton("Test Jump", function() if not InputClient.jump() then Logger.longNotify("Test Jump failed: VIM unavailable or blocked.") end end) loggerWindowGroup:AddToggle("LogAllAnimations", { Text = "Log All Animations (Everywhere)", Default = false, }) loggerWindowGroup:AddToggle("LogStandAnimations", { Text = "Log Stand Animations", Default = false, }) loggerWindowGroup:AddToggle("LogLocalStandAnimations", { Text = "Log Local Stand Animations", Default = false, }) local timingDefaultsGroup = loggerTab:AddLeftGroupbox("Timing Defaults") timingDefaultsGroup:AddSlider("DefaultPunishableWindow", { Text = "Default Punishable Window", Min = 0, Max = 2, Default = 0.7, Suffix = "s", Rounding = 2, }) timingDefaultsGroup:AddSlider("DefaultAfterWindow", { Text = "Default After Window", Min = 0, Max = 2, Default = 0.1, Suffix = "s", Rounding = 2, }) local skillLoggerGroup = loggerTab:AddLeftGroupbox("Skill Logger") skillLoggerGroup:AddToggle("LogUsingSkillLocal", { Text = "Log UsingSkill (Local)", Default = false, }) skillLoggerGroup:AddToggle("LogUsingSkillOthers", { Text = "Log UsingSkill (Others)", Default = false, }) local showSkillLoggerToggle = skillLoggerGroup:AddToggle("ShowSkillLoggerWindow", { Text = "Show Skill Logger Window", Default = true, }) showSkillLoggerToggle:AddKeyPicker( "ShowSkillLoggerWindowKeyBind", { Default = "N/A", SyncToggleState = true, Text = "Skill Logger" } ) showSkillLoggerToggle:OnChanged(function(value) if Library.SkillLoggerFrame then Library.SkillLoggerFrame.Visible = value end end) local effectLoggerGroup = loggerTab:AddLeftGroupbox("Effect Logger") effectLoggerGroup:AddToggle("LogEffects", { Text = "Log Effects", Default = false, }) local showEffectLoggerToggle = effectLoggerGroup:AddToggle("ShowEffectLoggerWindow", { Text = "Show Effect Logger Window", Default = true, }) showEffectLoggerToggle:AddKeyPicker( "ShowEffectLoggerWindowKeyBind", { Default = "N/A", SyncToggleState = true, Text = "Effect Logger" } ) showEffectLoggerToggle:OnChanged(function(value) if Library.EffectLoggerFrame then Library.EffectLoggerFrame.Visible = value end end) effectLoggerGroup:AddDropdown("EffectLoggerFilter", { Text = "Effect Filter", Values = { "All", "Players", "NPCs", "Local", "Other" }, Default = "All", }) local loggerRangeGroup = loggerTab:AddRightGroupbox("Logger Range") loggerRangeGroup:AddSlider("MinimumLoggerDistance", { Text = "Minimum Logger Distance", Min = 0, Max = 1000, Default = 0, Suffix = "m", Rounding = 0, }) loggerRangeGroup:AddSlider("MaximumLoggerDistance", { Text = "Maximum Logger Distance", Min = 0, Max = 1000, Default = 1000, Suffix = "m", Rounding = 0, }) local blacklistGroup = loggerTab:AddRightGroupbox("Key Blacklist") local blacklistedKeys = blacklistGroup:AddDropdown("BlacklistedKeys", { Text = "Blacklisted Keys", Default = {}, Values = Library:KeyBlacklists(), Multi = true, AllowNull = true, }) blacklistGroup:AddButton("Remove Selected Keys", function() if not blacklistedKeys.Value then return end for selected, _ in next, blacklistedKeys.Value do Library.InfoLoggerData.KeyBlacklistList[selected] = nil end blacklistedKeys:SetValues(Library:KeyBlacklists()) blacklistedKeys:SetValue({}) blacklistedKeys:Display() end) local animationPreviewGroup = loggerTab:AddRightGroupbox("Animation Preview") local previewAnimationId = animationPreviewGroup:AddInput("PreviewAnimationId", { Text = "Animation ID", Placeholder = "rbxassetid://123 or 123", }) animationPreviewGroup:AddLabel("Preview Target: Dummy") local previewSpawnButton = animationPreviewGroup:AddButton("Spawn Preview Dummy", function() end) local previewDespawnButton = animationPreviewGroup:AddButton("Despawn Preview Dummy", function() end) local previewDurationLabel = animationPreviewGroup:AddLabel("Duration: 0.000s") local previewTimeLabel = animationPreviewGroup:AddLabel("Time: 0.000s / 0.000s (0.0%)") local previewCamDistance = animationPreviewGroup:AddSlider("PreviewCameraDistance", { Text = "Camera Distance", Min = 3, Max = 80, Default = 14, Suffix = "m", Rounding = 1, }) local previewCamHeight = animationPreviewGroup:AddSlider("PreviewCameraHeight", { Text = "Camera Height", Min = -10, Max = 30, Default = 3, Suffix = "m", Rounding = 1, }) local previewCamFollow = animationPreviewGroup:AddToggle("PreviewCameraFollow", { Text = "Follow Dummy", Default = true, }) local previewSpeedSlider = animationPreviewGroup:AddSlider("PreviewTrackSpeed", { Text = "Track Speed", Min = 0, Max = 3, Default = 1, Suffix = "x", Rounding = 2, }) local previewTimeSlider = animationPreviewGroup:AddSlider("PreviewTrackTime", { Text = "Time Position", Min = 0, Max = 1, Default = 0, Suffix = "s", Rounding = 3, }) local previewViewGroup = loggerTab:AddRightGroupbox("Preview Camera Views") local previewViewFrontButton = previewViewGroup:AddButton("View Front", function() end) local previewViewBackButton = previewViewGroup:AddButton("View Back", function() end) local previewViewLeftButton = previewViewGroup:AddButton("View Left", function() end) local previewViewRightButton = previewViewGroup:AddButton("View Right", function() end) local previewViewTopButton = previewViewGroup:AddButton("View Top", function() end) local previewViewResetButton = previewViewGroup:AddButton("Reset Camera", function() end) local RunService = game:GetService("RunService") local workspaceService = game:GetService("Workspace") local previewState = { track = nil, animation = nil, length = 0, id = nil, dummyModel = nil, dummyBase = nil, dummyRoot = nil, dummyHumanoid = nil, cameraActive = false, cameraMode = nil, cameraConn = nil, cameraType = nil, cameraSubject = nil, cameraCFrame = nil, paused = true, speed = 1, } local function parsePreviewAnimationId(raw) if type(raw) ~= "string" then return nil end local digits = raw:match("%d+") if not digits then return nil end return "rbxassetid://" .. digits end local function resolvePreviewHumanoid() if previewState.dummyHumanoid and previewState.dummyHumanoid.Parent then return previewState.dummyHumanoid end return nil end local function clearPreviewDummy() if previewState.dummyModel then previewState.dummyModel:Destroy() end if previewState.dummyBase then previewState.dummyBase:Destroy() end previewState.dummyModel = nil previewState.dummyBase = nil previewState.dummyRoot = nil previewState.dummyHumanoid = nil end local function clearPreviewTrack() if previewState.track then pcall(previewState.track.Stop, previewState.track) pcall(previewState.track.Destroy, previewState.track) end if previewState.animation then pcall(previewState.animation.Destroy, previewState.animation) end previewState.track = nil previewState.animation = nil previewState.length = 0 previewState.id = nil end local function resetPreviewCamera() local cam = workspaceService.CurrentCamera if cam and previewState.cameraActive then cam.CameraType = previewState.cameraType or Enum.CameraType.Custom if previewState.cameraSubject then cam.CameraSubject = previewState.cameraSubject end if previewState.cameraCFrame then cam.CFrame = previewState.cameraCFrame end end previewState.cameraActive = false previewState.cameraMode = nil if previewState.cameraConn then previewState.cameraConn:Disconnect() previewState.cameraConn = nil end end local function spawnPreviewDummy() local localPlayer = Players.LocalPlayer local character = localPlayer and localPlayer.Character if not character then return Logger.longNotify("Cannot spawn preview dummy; local character not found.") end clearPreviewTrack() clearPreviewDummy() local cam = workspaceService.CurrentCamera local basePos = cam and cam.CFrame.Position or Vector3.new(0, 0, 0) local forward = cam and cam.CFrame.LookVector or Vector3.new(0, 0, -1) basePos = basePos + forward * 40 + Vector3.new(0, 150, 0) local base = Instance.new("Part") base.Name = "CottomPreviewCircle" base.Shape = Enum.PartType.Cylinder base.Size = Vector3.new(0.2, 40, 40) base.Anchored = true base.CanCollide = false base.CanTouch = false base.CanQuery = false base.Transparency = 0.35 base.Material = Enum.Material.Neon base.Color = Color3.fromRGB(0, 85, 255) base.CFrame = CFrame.new(basePos) * CFrame.Angles(0, 0, math.rad(90)) base.Parent = workspaceService local dummy = character:Clone() dummy.Name = "CottomPreviewDummy" for _, inst in ipairs(dummy:GetDescendants()) do if inst:IsA("Script") or inst:IsA("LocalScript") or inst:IsA("ModuleScript") then inst:Destroy() elseif inst:IsA("BasePart") then inst.Anchored = false inst.CanCollide = false inst.CanTouch = false inst.CanQuery = false elseif inst:IsA("Humanoid") then inst.DisplayDistanceType = Enum.HumanoidDisplayDistanceType.None inst.BreakJointsOnDeath = false end end for _, inst in ipairs(dummy:GetChildren()) do if inst:IsA("Tool") then inst:Destroy() end end dummy.Parent = workspaceService local root = dummy:FindFirstChild("HumanoidRootPart") or dummy:FindFirstChild("Torso") if root then root.Anchored = true root.CanCollide = false root.CanTouch = false root.CanQuery = false dummy.PrimaryPart = root dummy:PivotTo(CFrame.new(basePos + Vector3.new(0, 3, 0))) end previewState.dummyModel = dummy previewState.dummyBase = base previewState.dummyRoot = root previewState.dummyHumanoid = dummy:FindFirstChildOfClass("Humanoid") end local function updatePreviewCamera() if not previewState.cameraActive or not previewState.cameraMode then return end local cam = workspaceService.CurrentCamera if not cam or not previewState.dummyRoot then return end local root = previewState.dummyRoot local distance = previewCamDistance.Value or 14 local height = previewCamHeight.Value or 0 local focus = root.Position + Vector3.new(0, height, 0) local dir = root.CFrame.LookVector local right = root.CFrame.RightVector local pos = focus if previewState.cameraMode == "front" then pos = focus + dir * distance elseif previewState.cameraMode == "back" then pos = focus - dir * distance elseif previewState.cameraMode == "left" then pos = focus - right * distance elseif previewState.cameraMode == "right" then pos = focus + right * distance elseif previewState.cameraMode == "top" then pos = focus + Vector3.new(0, distance, 0) end cam.CFrame = CFrame.new(pos, focus) end local function setPreviewCameraMode(mode) if not previewState.dummyRoot then return Logger.longNotify("Spawn the preview dummy first.") end local cam = workspaceService.CurrentCamera if not cam then return end if not previewState.cameraActive then previewState.cameraType = cam.CameraType previewState.cameraSubject = cam.CameraSubject previewState.cameraCFrame = cam.CFrame end previewState.cameraActive = true previewState.cameraMode = mode cam.CameraType = Enum.CameraType.Scriptable cam.CameraSubject = previewState.dummyRoot updatePreviewCamera() if not previewState.cameraConn then previewState.cameraConn = RunService.RenderStepped:Connect(function() if previewState.cameraActive and previewCamFollow.Value then updatePreviewCamera() end end) Library:GiveSignal(previewState.cameraConn) end end local function updatePreviewLabels(timeOverride) local length = previewState.length or 0 local timePos = timeOverride if timePos == nil then timePos = previewState.track and previewState.track.TimePosition or 0 end local pct = length > 0 and (timePos / length) * 100 or 0 previewDurationLabel:SetText(string.format("Duration: %.3fs", length)) previewTimeLabel:SetText(string.format("Time: %.3fs / %.3fs (%.1f%%)", timePos, length, pct)) end local function setPreviewTime(value) if not previewState.track then return end local length = previewState.length or 0 local pos = math.clamp(value or 0, 0, length) previewState.track.TimePosition = pos updatePreviewLabels(pos) end local function setPreviewSpeed(value) previewState.speed = value if previewState.track and not previewState.paused then previewState.track:AdjustSpeed(value) end end local function loadPreviewAnimation() local animId = parsePreviewAnimationId(previewAnimationId.Value or "") if not animId then return Logger.longNotify("Please enter a valid animation ID.") end local humanoid = resolvePreviewHumanoid() if not humanoid then return Logger.longNotify("Cannot load animation; preview dummy not spawned.") end clearPreviewTrack() local animator = humanoid:FindFirstChildOfClass("Animator") if not animator then animator = Instance.new("Animator") animator.Parent = humanoid end local animation = Instance.new("Animation") animation.AnimationId = animId local ok, track = pcall(function() return animator:LoadAnimation(animation) end) if not ok or not track then animation:Destroy() return Logger.longNotify("Failed to load animation '%s'.", animId) end track.Looped = true track:Play(0, 1, 0) previewState.track = track previewState.animation = animation previewState.id = animId previewState.length = track.Length previewState.paused = true previewState.speed = previewSpeedSlider.Value or 1 local start = os.clock() while previewState.length <= 0 and os.clock() - start < 2 do task.wait() if not previewState.track then break end previewState.length = previewState.track.Length end if previewState.length <= 0 then previewState.length = 0 end previewTimeSlider.Max = math.max(previewState.length, 0.001) previewTimeSlider:SetRawValue(0) updatePreviewLabels(0) previewState.track.TimePosition = 0 previewState.track:AdjustSpeed(0) end previewSpeedSlider:OnChanged(function(value) setPreviewSpeed(value) end) previewTimeSlider:OnChanged(function(value) setPreviewTime(value) end) previewCamDistance:OnChanged(function() updatePreviewCamera() end) previewCamHeight:OnChanged(function() updatePreviewCamera() end) previewCamFollow:OnChanged(function(value) if not value then updatePreviewCamera() end end) previewSpawnButton.Func = function() spawnPreviewDummy() setPreviewCameraMode("front") end previewDespawnButton.Func = function() clearPreviewTrack() clearPreviewDummy() resetPreviewCamera() previewState.paused = true updatePreviewLabels(0) previewTimeSlider:SetRawValue(0) end previewViewFrontButton.Func = function() setPreviewCameraMode("front") end previewViewBackButton.Func = function() setPreviewCameraMode("back") end previewViewLeftButton.Func = function() setPreviewCameraMode("left") end previewViewRightButton.Func = function() setPreviewCameraMode("right") end previewViewTopButton.Func = function() setPreviewCameraMode("top") end previewViewResetButton.Func = function() resetPreviewCamera() end animationPreviewGroup:AddButton("Load Animation", loadPreviewAnimation) animationPreviewGroup:AddButton("Play", function() if previewState.track then local speed = previewState.speed or previewSpeedSlider.Value or 1 if speed <= 0 then speed = 1 previewSpeedSlider:SetRawValue(speed) end previewState.paused = false previewState.track:AdjustSpeed(speed) end end) animationPreviewGroup:AddButton("Pause", function() if previewState.track then previewState.paused = true previewState.track:AdjustSpeed(0) end end) animationPreviewGroup:AddButton("Stop", function() clearPreviewTrack() previewState.paused = true updatePreviewLabels(0) previewTimeSlider:SetRawValue(0) end) animationPreviewGroup:AddButton("Log Timestamp", function() local timePos = previewState.track and previewState.track.TimePosition or 0 local length = previewState.length or 0 Library:AddTelemetryEntry("Anim preview %s at %.3fs / %.3fs.", previewState.id or "N/A", timePos, length) end) local combatTab = window:AddTab("Combat") local autoDefenseGroup = combatTab:AddLeftGroupbox("Auto Defense") local autoDefenseToggle = autoDefenseGroup:AddToggle("EnableAutoDefense", { Text = "Enable Auto Defense", Default = true, }) autoDefenseToggle:AddKeyPicker( "EnableAutoDefenseKeybind", { Default = "N/A", SyncToggleState = true, Text = "Auto Defense" } ) autoDefenseToggle:OnChanged(function(value) if not value then InputClient.block(false) end end) local autoDefenseDepBox = autoDefenseGroup:AddDependencyBox() autoDefenseDepBox:AddToggle("EnableNotifications", { Text = "Enable Notifications", Default = false, }) autoDefenseDepBox:AddToggle("EnableVisualizations", { Text = "Enable Visualizations", Default = false, }) autoDefenseDepBox:AddToggle("UseControllerM1", { Text = "Use Controller M1 (VIM)", Default = false, Tooltip = "Send controller M1 (ButtonR2) instead of mouse clicks.", }) autoDefenseDepBox:AddDropdown("DefaultDashDirection", { Text = "Default Dash Direction", Values = { "W", "A", "S", "D", "Random" }, Default = "S", Tooltip = "Dash direction when no movement key is held.", }) autoDefenseDepBox:AddSlider("BlockHoldSeconds", { Text = "Block Hold Seconds", Min = 0, Max = 2, Default = 0.4, Suffix = "s", Rounding = 2, }) autoDefenseDepBox:SetupDependencies({ { autoDefenseToggle, true }, }) local m1TradeTab = window:AddTab("M1 Auto Trade") local m1TradeGroup = m1TradeTab:AddLeftGroupbox("M1 Auto Trade") local m1TradeToggle = m1TradeGroup:AddToggle("EnableM1AutoTrade", { Text = "Enable M1 Auto Trade", Default = false, }) m1TradeToggle:AddKeyPicker( "EnableM1AutoTradeKeybind", { Default = "N/A", SyncToggleState = true, Text = "M1 Auto Trade" } ) m1TradeToggle:OnChanged(function(value) if value and Toggles["EnableAutoDefense"] then Toggles["EnableAutoDefense"]:SetValue(false) end end) local m1TradeDep = m1TradeGroup:AddDependencyBox() m1TradeDep:AddSlider("M1TradeBlockHoldMs", { Text = "Block Hold (ms)", Min = 10, Max = 500, Default = 56, Suffix = "ms", Rounding = 0, Tooltip = "How long to hold block before M1 chain.", }) m1TradeDep:AddSlider("M1TradeBlockWaitMs", { Text = "Block Wait (ms)", Min = 0, Max = 1000, Default = 99, Suffix = "ms", Rounding = 0, Tooltip = "Delay before tapping block.", }) m1TradeDep:AddSlider("M1TradeDistanceHit", { Text = "Distance To Hit", Min = 0, Max = 50, Default = 9.05, Suffix = "meters", Rounding = 2, }) m1TradeDep:AddSlider("M1TradeMaxTargets", { Text = "How Many Targets", Min = 1, Max = 8, Default = 1, Rounding = 0, }) m1TradeDep:AddSlider("M1TradeClickGapMs", { Text = "M1 Click Gap (ms)", Min = 10, Max = 200, Default = 10, Suffix = "ms", Rounding = 0, Tooltip = "Delay between auto M1 clicks.", }) m1TradeDep:AddSlider("M1TradeClickHoldMs", { Text = "M1 Click Hold (ms)", Min = 10, Max = 200, Default = 10, Suffix = "ms", Rounding = 0, Tooltip = "How long each auto M1 press is held.", }) m1TradeDep:AddToggle("UseControllerM1", { Text = "Use Controller M1 (VIM)", Default = false, Tooltip = "Send controller M1 (ButtonR2) instead of mouse clicks.", }) m1TradeDep:AddToggle("M1TradeManualOnly", { Text = "Manual M1 Only (Allow Hold M1)", Default = false, Tooltip = "Only tap block; skip auto M1 clicks.", }) m1TradeDep:AddToggle("M1TradeOneAutoM1", { Text = "Do One Auto M1", Default = false, Tooltip = "Fire a single auto M1 instead of a full chain.", }) m1TradeDep:AddDropdown("M1TradeSelectionType", { Text = "Target Selection", Values = { "Closest In Distance", "Closest To Crosshair", "Least Health", }, Default = "Closest In Distance", }) m1TradeDep:SetupDependencies({ { m1TradeToggle, true }, }) autoDefenseToggle:OnChanged(function(value) if value and Toggles["EnableM1AutoTrade"] then Toggles["EnableM1AutoTrade"]:SetValue(false) end end) local targetingTabbox = combatTab:AddRightTabbox("Targeting") local targetingTab = targetingTabbox:AddTab("Targeting") targetingTab:AddDropdown("PlayerSelectionType", { Text = "Player Selection Type", Values = { "Closest In Distance", "Closest To Crosshair", "Least Health", }, Default = "Closest In Distance", }) targetingTab:AddSlider("DistanceLimit", { Text = "Distance Limit", Min = 0, Max = 100000, Default = 99999, Suffix = "studs", Rounding = 0, }) targetingTab:AddSlider("MaxTargets", { Text = "Max Targets", Min = 1, Max = 64, Default = 1, Rounding = 0, }) targetingTab:AddToggle("IgnorePlayers", { Text = "Ignore Players", Default = false, }) targetingTab:AddToggle("IgnoreMobs", { Text = "Ignore Mobs", Default = false, }) targetingTab:AddToggle("IgnoreAllies", { Text = "Ignore Allies", Default = false, }) local whitelistTab = targetingTabbox:AddTab("Whitelisting") local usernameList = whitelistTab:AddDropdown("UsernameList", { Text = "Username List", Values = {}, SaveValues = true, Multi = true, AllowNull = true, Default = {}, }) local usernameInput = whitelistTab:AddInput("UsernameInput", { Text = "Username Input", Placeholder = "Display name or username.", }) whitelistTab:AddButton("Add Username To List", function() local username = usernameInput.Value or "" if #username <= 0 then return Logger.longNotify("Please enter a valid username.") end local values = usernameList.Values if not table.find(values, username) then table.insert(values, username) end usernameList:SetValues(values) usernameList:SetValue({}) usernameList:Display() end) whitelistTab:AddButton("Remove Selected Usernames", function() if not usernameList.Value then return end local values = usernameList.Values for selected, _ in next, usernameList.Value do local index = table.find(values, selected) if index then table.remove(values, index) end end usernameList:SetValues(values) usernameList:SetValue({}) usernameList:Display() end) local miscTab = window:AddTab("Misc") local predictionGroup = miscTab:AddLeftGroupbox("Prediction") predictionGroup:AddSlider("PredictionExtraSeconds", { Text = "Prediction Extra Seconds", Min = 0, Max = 0.5, Default = 0, Suffix = "s", Rounding = 3, Tooltip = "Extra lead time added to base prediction.", }) local miscMovementGroup = miscTab:AddRightGroupbox("Movement") miscMovementGroup:AddToggle("AutoJumpOnEnemyJump", { Text = "Auto Jump (Enemy Jump)", Default = false, Tooltip = "Jump immediately when an enemy jumps.", }) local desyncTab = window:AddTab("Desync") local desyncGroup = desyncTab:AddLeftGroupbox("Desync") local desyncToggle = desyncGroup:AddToggle("EnableDesync", { Text = "Enable Desync", Default = false, }) desyncToggle:AddKeyPicker("EnableDesyncKeybind", { Default = "N/A", SyncToggleState = true, Text = "Desync" }) local LocalPlayer = Players.LocalPlayer local desyncState = { conn = nil, localRoot = nil, localHumanoid = nil, warned = false, } local function resolveLocalRoot(character) if not character then return nil end local humanoid = character:FindFirstChildOfClass("Humanoid") if humanoid then local root = humanoid.RootPart if root and root.Parent then return root end end local root = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso") if root and root.Parent then return root end return nil end local function clearDesyncRoot(root) if not root then return end if type(sethiddenproperty) ~= "function" then return end pcall(sethiddenproperty, root, "PhysicsRepRootPart", nil) end local function getNearestLiveRoot() local live = (workspaceService and workspaceService:FindFirstChild("Live")) or workspace:FindFirstChild("Live") if not live then return nil end local localRoot = desyncState.localRoot if not localRoot then return nil end local nearest, dist = nil, math.huge for _, model in ipairs(live:GetChildren()) do if model:IsA("Model") and model.Name ~= (LocalPlayer and LocalPlayer.Name or "") then local hum = model:FindFirstChildOfClass("Humanoid") local root = hum and hum.RootPart if not root then root = model:FindFirstChild("HumanoidRootPart") or model:FindFirstChild("Torso") end if root then local d = (localRoot.Position - root.Position).Magnitude if d < dist then dist = d nearest = root end end end end return nearest end local function startDesync() if desyncState.conn then return true end if type(sethiddenproperty) ~= "function" then if not desyncState.warned then desyncState.warned = true Logger.longNotify("Desync unavailable: sethiddenproperty missing.") end return false end desyncState.conn = RunService.Heartbeat:Connect(function() if not desyncState.localRoot or not desyncState.localRoot.Parent then local character = LocalPlayer and LocalPlayer.Character desyncState.localHumanoid = character and character:FindFirstChildOfClass("Humanoid") or nil desyncState.localRoot = resolveLocalRoot(character) end local root = desyncState.localRoot if not root then return end local target = getNearestLiveRoot() pcall(sethiddenproperty, root, "PhysicsRepRootPart", target) end) if Library and Library.GiveSignal then Library:GiveSignal(desyncState.conn) end return true end local function stopDesync() if desyncState.conn then desyncState.conn:Disconnect() desyncState.conn = nil end clearDesyncRoot(desyncState.localRoot) end desyncToggle:OnChanged(function(value) if value then if not startDesync() then desyncToggle:SetValue(false) end else stopDesync() end end) if LocalPlayer then LocalPlayer.CharacterAdded:Connect(function(character) desyncState.localHumanoid = character and character:FindFirstChildOfClass("Humanoid") or nil desyncState.localRoot = resolveLocalRoot(character) if not Configuration.expectToggleValue("EnableDesync") then clearDesyncRoot(desyncState.localRoot) end end) LocalPlayer.CharacterRemoving:Connect(function() desyncState.localHumanoid = nil desyncState.localRoot = nil end) if LocalPlayer.Character then desyncState.localHumanoid = LocalPlayer.Character:FindFirstChildOfClass("Humanoid") desyncState.localRoot = resolveLocalRoot(LocalPlayer.Character) if not Configuration.expectToggleValue("EnableDesync") then clearDesyncRoot(desyncState.localRoot) end end end do local autoComboTab = window:AddTab("Auto-Combo") local autoComboControl = autoComboTab:AddLeftGroupbox("Control") local autoComboToggle = autoComboControl:AddToggle("EnableAutoCombo", { Text = "Enable Auto-Combo", Default = false, }) autoComboToggle:AddKeyPicker("AutoComboKeybind", { Default = "N/A", Mode = "Hold", Modes = { "Hold" }, Text = "Auto-Combo", }) local autoComboOnM1Toggle = autoComboControl:AddToggle("AutoComboOnM1Hit", { Text = "Auto-Combo On Local M1 Hit", Default = true, Tooltip = "Start Auto-Combo when your local M1 hits (LastHit + window).", }) local autoComboOnM1Dep = autoComboControl:AddDependencyBox() autoComboOnM1Dep:AddSlider("AutoComboOnM1HoldMs", { Text = "M1 Hit Window", Min = 50, Max = 1000, Default = 250, Suffix = "ms", Rounding = 0, Tooltip = "Time window after local M1 animation to match LastHit.", }) autoComboOnM1Dep:SetupDependencies({ { autoComboOnM1Toggle, true }, }) local autoComboStopToggle = autoComboControl:AddToggle("AutoComboStopKeybindEnabled", { Text = "Stop Auto-Combo Keybind", Default = true, }) autoComboStopToggle:AddKeyPicker("AutoComboStopKeybind", { Default = "N/A", Mode = "Hold", Modes = { "Hold" }, Text = "Stop Auto-Combo", }) autoComboControl:AddSlider("AutoComboDefaultStepDelayMs", { Text = "Default Step Delay", Min = 0, Max = 5000, Default = 100, Suffix = "ms", Rounding = 0, }) autoComboControl:AddToggle("AutoComboLockTarget", { Text = "Lock Target", Default = true, }) autoComboControl:AddToggle("AutoComboRetargetOnInvalid", { Text = "Retarget On Invalid", Default = true, }) autoComboControl:AddSlider("AutoComboTweenMaxDistance", { Text = "Tween Max Distance", Min = 0, Max = 50, Default = 15, Suffix = "studs", Rounding = 1, }) autoComboControl:AddSlider("AutoComboTweenSpeed", { Text = "Tween Speed", Min = 1, Max = 200, Default = 60, Suffix = "studs/s", Rounding = 0, }) autoComboControl:AddSlider("AutoComboMoveToDurationSeconds", { Text = "MoveTo Duration", Min = 0, Max = 10, Default = 1.2, Suffix = "s", Rounding = 2, }) autoComboControl:AddSlider("AutoComboMoveToStopDistance", { Text = "MoveTo Stop Distance", Min = 0, Max = 20, Default = 4, Suffix = "studs", Rounding = 1, }) autoComboControl:AddSlider("AutoComboSprintHoldMs", { Text = "Sprint Hold", Min = 0, Max = 2000, Default = 350, Suffix = "ms", Rounding = 0, }) autoComboControl:AddSlider("AutoComboUptiltJumpHoldMs", { Text = "Uptilt Jump Hold", Min = 0, Max = 600, Default = 120, Suffix = "ms", Rounding = 0, }) autoComboControl:AddSlider("AutoComboUptiltM1HoldMs", { Text = "Uptilt M1 Hold", Min = 0, Max = 300, Default = 60, Suffix = "ms", Rounding = 0, }) local autoComboProfileGroup = autoComboTab:AddRightGroupbox("Profiles") local autoComboProfileList = autoComboProfileGroup:AddDropdown("AutoComboProfile", { Text = "Profile", Values = AutoCombo.listProfiles(), AllowNull = true, }) local autoComboProfileName = autoComboProfileGroup:AddInput("AutoComboProfileName", { Text = "Profile Name", Placeholder = "Enter profile name.", }) local autoComboStepsGroup = autoComboTab:AddLeftGroupbox("Steps") local autoComboStepList = autoComboStepsGroup:AddDropdown("AutoComboStepList", { Text = "Steps", Values = {}, AllowNull = true, }) local autoComboEditorGroup = autoComboTab:AddRightGroupbox("Step Editor") local autoComboStepType = autoComboEditorGroup:AddDropdown("AutoComboStepType", { Text = "Step Type", Values = { "Skill", "Wait", "M1", "Uptilt", "Upfling", "JumpHold", "Jump", "TweenToHead", "MoveToTarget", "Dash" }, Default = "Skill", }) local autoComboParallel = autoComboEditorGroup:AddToggle("AutoComboStepParallel", { Text = "Parallel (Non-Blocking)", Default = false, Tooltip = "Run this step in the background and continue to the next step immediately.", }) local skillDep = autoComboEditorGroup:AddDependencyBox() local skillSlot = skillDep:AddSlider("AutoComboStepSkillSlot", { Text = "Skill Slot", Min = 1, Max = 4, Default = 1, Rounding = 0, }) local skillToolName = skillDep:AddInput("AutoComboStepSkillToolName", { Text = "Tool Name", Placeholder = "Optional tool name.", }) local skillDelayAfter = skillDep:AddInput("AutoComboStepSkillDelayAfter", { Text = "Delay After (ms)", Numeric = true, Placeholder = "Optional delay.", }) skillDep:SetupDependencies({ { autoComboStepType, "Skill" }, }) local waitDep = autoComboEditorGroup:AddDependencyBox() local waitDuration = waitDep:AddInput("AutoComboStepWaitDuration", { Text = "Duration (ms)", Numeric = true, Placeholder = "e.g. 30000", }) waitDep:SetupDependencies({ { autoComboStepType, "Wait" }, }) local m1Dep = autoComboEditorGroup:AddDependencyBox() local m1Hold = m1Dep:AddInput("AutoComboStepM1Hold", { Text = "Hold (ms)", Numeric = true, }) local m1Repeat = m1Dep:AddInput("AutoComboStepM1Repeat", { Text = "Repeat", Numeric = true, }) local m1Gap = m1Dep:AddInput("AutoComboStepM1Gap", { Text = "Gap (ms)", Numeric = true, }) m1Dep:SetupDependencies({ { autoComboStepType, "M1" }, }) local uptiltDep = autoComboEditorGroup:AddDependencyBox() local uptiltJumpHold = uptiltDep:AddInput("AutoComboStepUptiltJumpHold", { Text = "Jump Hold (ms)", Numeric = true, }) local uptiltM1Hold = uptiltDep:AddInput("AutoComboStepUptiltM1Hold", { Text = "M1 Hold (ms)", Numeric = true, }) local uptiltDelay = uptiltDep:AddInput("AutoComboStepUptiltDelay", { Text = "Delay Before M1 (ms)", Numeric = true, }) uptiltDep:SetupDependencies({ { autoComboStepType, "Uptilt" }, }) local upflingDep = autoComboEditorGroup:AddDependencyBox() local upflingJumpHold = upflingDep:AddInput("AutoComboStepUpflingJumpHold", { Text = "Jump Hold (ms)", Numeric = true, }) local upflingM1Hold = upflingDep:AddInput("AutoComboStepUpflingM1Hold", { Text = "M1 Hold (ms)", Numeric = true, }) upflingDep:SetupDependencies({ { autoComboStepType, "Upfling" }, }) local jumpHoldDep = autoComboEditorGroup:AddDependencyBox() local jumpHoldMs = jumpHoldDep:AddInput("AutoComboStepJumpHold", { Text = "Hold (ms)", Numeric = true, }) jumpHoldDep:SetupDependencies({ { autoComboStepType, "JumpHold" }, }) local jumpDep = autoComboEditorGroup:AddDependencyBox() local jumpCount = jumpDep:AddInput("AutoComboStepJumpCount", { Text = "Jump Count", Numeric = true, }) local jumpHoldTap = jumpDep:AddInput("AutoComboStepJumpHoldTap", { Text = "Jump Hold (ms)", Numeric = true, }) local jumpGap = jumpDep:AddInput("AutoComboStepJumpGap", { Text = "Jump Gap (ms)", Numeric = true, }) jumpDep:SetupDependencies({ { autoComboStepType, "Jump" }, }) local tweenDep = autoComboEditorGroup:AddDependencyBox() local tweenMaxDistance = tweenDep:AddInput("AutoComboStepTweenMaxDistance", { Text = "Max Distance", Numeric = true, }) local tweenDuration = tweenDep:AddInput("AutoComboStepTweenDuration", { Text = "Duration (s)", Numeric = true, }) local tweenAlignFeet = tweenDep:AddToggle("AutoComboStepTweenAlignFeet", { Text = "Align Feet On Head", Default = false, }) local tweenSpeed = tweenDep:AddInput("AutoComboStepTweenSpeed", { Text = "Tween Speed (studs/s)", Numeric = true, }) local tweenOffsetX = tweenDep:AddInput("AutoComboStepTweenOffsetX", { Text = "Offset X", Numeric = true, }) local tweenOffsetY = tweenDep:AddInput("AutoComboStepTweenOffsetY", { Text = "Offset Y", Numeric = true, }) local tweenOffsetZ = tweenDep:AddInput("AutoComboStepTweenOffsetZ", { Text = "Offset Z", Numeric = true, }) tweenDep:SetupDependencies({ { autoComboStepType, "TweenToHead" }, }) local moveDep = autoComboEditorGroup:AddDependencyBox() local moveDuration = moveDep:AddInput("AutoComboStepMoveDuration", { Text = "Duration (s)", Numeric = true, }) local moveStopDistance = moveDep:AddInput("AutoComboStepMoveStopDistance", { Text = "Stop Distance", Numeric = true, }) local moveDelayAfter = moveDep:AddInput("AutoComboStepMoveDelayAfter", { Text = "Delay After (ms)", Numeric = true, }) local moveUseSprint = moveDep:AddToggle("AutoComboStepMoveUseSprint", { Text = "Use Sprint", Default = true, }) moveDep:SetupDependencies({ { autoComboStepType, "MoveToTarget" }, }) local dashDep = autoComboEditorGroup:AddDependencyBox() local dashDir = dashDep:AddDropdown("AutoComboStepDashDir", { Text = "Direction", Values = { "left", "right", "forward", "back" }, Default = "right", }) dashDep:SetupDependencies({ { autoComboStepType, "Dash" }, }) local autoComboState = { profile = { name = "", steps = {} }, stepIndex = nil, stepIndexMap = {}, updating = false, } local function cloneStep(step) local copy = {} for key, value in pairs(step) do if type(value) == "table" then local inner = {} for k, v in pairs(value) do inner[k] = v end copy[key] = inner else copy[key] = value end end return copy end local function defaultStep(stepType) if stepType == "Skill" then return { type = "Skill", slot = 1, toolName = "", delayAfterMs = nil, parallel = false } end if stepType == "Wait" then return { type = "Wait", durationMs = 1000, parallel = false } end if stepType == "M1" then return { type = "M1", holdMs = 60, ["repeat"] = 1, gapMs = 60, parallel = false } end if stepType == "Uptilt" then return { type = "Uptilt", jumpHoldMs = Options["AutoComboUptiltJumpHoldMs"] and Options["AutoComboUptiltJumpHoldMs"].Value or 120, m1HoldMs = Options["AutoComboUptiltM1HoldMs"] and Options["AutoComboUptiltM1HoldMs"].Value or 60, delayBeforeM1Ms = 30, parallel = false, } end if stepType == "Upfling" then return { type = "Upfling", jumpHoldMs = 400, m1HoldMs = 230, parallel = false } end if stepType == "JumpHold" then return { type = "JumpHold", holdMs = 400, parallel = false } end if stepType == "Jump" then return { type = "Jump", count = 2, holdMs = 50, gapMs = 120, parallel = false } end if stepType == "TweenToHead" then return { type = "TweenToHead", maxDistance = Options["AutoComboTweenMaxDistance"] and Options["AutoComboTweenMaxDistance"].Value or 15, durationSeconds = nil, alignFeet = false, speed = Options["AutoComboTweenSpeed"] and Options["AutoComboTweenSpeed"].Value or 60, offset = { x = 0, y = 0, z = 0 }, parallel = false, } end if stepType == "MoveToTarget" then return { type = "MoveToTarget", durationSeconds = Options["AutoComboMoveToDurationSeconds"] and Options["AutoComboMoveToDurationSeconds"].Value or 1.2, stopDistance = Options["AutoComboMoveToStopDistance"] and Options["AutoComboMoveToStopDistance"].Value or 4, delayAfterMs = nil, useSprint = true, parallel = false, } end if stepType == "Dash" then return { type = "Dash", dir = "right", parallel = false } end return { type = stepType } end local function stepSummary(step, index) if not step then return string.format("%02d: ", index) end local stype = step.type or "Unknown" local prefix = step.parallel and "[P] " or "" if stype == "Skill" then return string.format("%02d: %sSkill slot %s", index, prefix, tostring(step.slot or 1)) elseif stype == "Wait" then return string.format("%02d: %sWait %sms", index, prefix, tostring(step.durationMs or 0)) elseif stype == "M1" then return string.format("%02d: %sM1 x%s", index, prefix, tostring(step["repeat"] or 1)) elseif stype == "Uptilt" then return string.format("%02d: %sUptilt", index, prefix) elseif stype == "Upfling" then return string.format("%02d: %sUpfling", index, prefix) elseif stype == "JumpHold" then return string.format("%02d: %sJumpHold %sms", index, prefix, tostring(step.holdMs or 0)) elseif stype == "Jump" then return string.format("%02d: %sJump x%s", index, prefix, tostring(step.count or 1)) elseif stype == "TweenToHead" then return string.format("%02d: %sTweenToHead", index, prefix) elseif stype == "MoveToTarget" then local dur = step.durationSeconds or Options["AutoComboMoveToDurationSeconds"] and Options["AutoComboMoveToDurationSeconds"].Value if dur and tonumber(dur) then return string.format("%02d: %sMoveTo %.2fs", index, prefix, tonumber(dur)) end return string.format("%02d: %sMoveToTarget", index, prefix) elseif stype == "Dash" then return string.format("%02d: %sDash %s", index, prefix, tostring(step.dir or "right")) end return string.format("%02d: %s%s", index, prefix, tostring(stype)) end local function refreshStepList(selectIndex) autoComboState.updating = true autoComboState.stepIndexMap = {} local values = {} for i, step in ipairs(autoComboState.profile.steps or {}) do local label = stepSummary(step, i) table.insert(values, label) autoComboState.stepIndexMap[label] = i end autoComboStepList:SetValues(values) if selectIndex and values[selectIndex] then autoComboStepList:SetValue(values[selectIndex]) elseif #values > 0 then autoComboStepList:SetValue(values[1]) else autoComboStepList:SetValue(nil) end autoComboStepList:Display() autoComboState.updating = false end local function currentStep() if not autoComboState.stepIndex then return nil end return autoComboState.profile.steps and autoComboState.profile.steps[autoComboState.stepIndex] end local function loadStepToEditor(step) autoComboState.updating = true if not step then autoComboStepType:SetValue("Skill") autoComboParallel:SetValue(false) skillSlot:SetRawValue(1) skillToolName:SetRawValue("") skillDelayAfter:SetRawValue("") waitDuration:SetRawValue("") m1Hold:SetRawValue("") m1Repeat:SetRawValue("") m1Gap:SetRawValue("") uptiltJumpHold:SetRawValue("") uptiltM1Hold:SetRawValue("") uptiltDelay:SetRawValue("") upflingJumpHold:SetRawValue("") upflingM1Hold:SetRawValue("") jumpHoldMs:SetRawValue("") jumpCount:SetRawValue("") jumpHoldTap:SetRawValue("") jumpGap:SetRawValue("") tweenDuration:SetRawValue("") tweenAlignFeet:SetValue(false) tweenMaxDistance:SetRawValue("") tweenSpeed:SetRawValue("") tweenOffsetX:SetRawValue("") tweenOffsetY:SetRawValue("") tweenOffsetZ:SetRawValue("") moveDuration:SetRawValue("") moveStopDistance:SetRawValue("") moveDelayAfter:SetRawValue("") moveUseSprint:SetValue(false) dashDir:SetValue("right") Library:UpdateDependencyBoxes() autoComboState.updating = false return end autoComboStepType:SetValue(step.type or "Skill") autoComboParallel:SetValue(step.parallel == true) skillSlot:SetRawValue(step.slot or 1) skillToolName:SetRawValue(step.toolName or "") skillDelayAfter:SetRawValue(step.delayAfterMs or "") waitDuration:SetRawValue(step.durationMs or "") m1Hold:SetRawValue(step.holdMs or "") m1Repeat:SetRawValue(step["repeat"] or "") m1Gap:SetRawValue(step.gapMs or "") uptiltJumpHold:SetRawValue(step.jumpHoldMs or "") uptiltM1Hold:SetRawValue(step.m1HoldMs or "") uptiltDelay:SetRawValue(step.delayBeforeM1Ms or "") upflingJumpHold:SetRawValue(step.jumpHoldMs or "") upflingM1Hold:SetRawValue(step.m1HoldMs or "") jumpHoldMs:SetRawValue(step.holdMs or "") jumpCount:SetRawValue(step.count or "") jumpHoldTap:SetRawValue(step.holdMs or "") jumpGap:SetRawValue(step.gapMs or "") tweenDuration:SetRawValue(step.durationSeconds or "") tweenAlignFeet:SetValue(step.alignFeet == true) tweenMaxDistance:SetRawValue(step.maxDistance or "") local defaultSpeed = Options["AutoComboTweenSpeed"] and Options["AutoComboTweenSpeed"].Value or 60 tweenSpeed:SetRawValue(step.speed or defaultSpeed) local offset = step.offset or {} tweenOffsetX:SetRawValue(offset.x or 0) tweenOffsetY:SetRawValue(offset.y or 0) tweenOffsetZ:SetRawValue(offset.z or 0) moveDuration:SetRawValue(step.durationSeconds or "") moveStopDistance:SetRawValue(step.stopDistance or "") moveDelayAfter:SetRawValue(step.delayAfterMs or "") moveUseSprint:SetValue(step.useSprint == true) dashDir:SetValue(step.dir or "right") Library:UpdateDependencyBoxes() autoComboState.updating = false end local function selectStep(index) if not autoComboState.profile.steps then return end autoComboState.stepIndex = index loadStepToEditor(currentStep()) refreshStepList(index) end local function refreshProfiles(selectName) local list = AutoCombo.listProfiles() autoComboProfileList:SetValues(list) if selectName then autoComboProfileList:SetValue(selectName) elseif list[1] then autoComboProfileList:SetValue(list[1]) else autoComboProfileList:SetValue(nil) end autoComboProfileList:Display() end autoComboProfileName:OnChanged(function(value) if autoComboState.updating then return end autoComboState.profile.name = value or "" end) autoComboProfileList:OnChanged(function(value) if autoComboState.updating then return end if value then autoComboProfileName:SetRawValue(value) end end) autoComboStepList:OnChanged(function(value) if autoComboState.updating then return end local idx = value and autoComboState.stepIndexMap[value] or nil if not idx then return end selectStep(idx) end) autoComboStepType:OnChanged(function(value) if autoComboState.updating then return end local step = currentStep() if not step then return end if step.type == value then return end local defaults = defaultStep(value) for key, val in pairs(defaults) do step[key] = val end step.type = value refreshStepList(autoComboState.stepIndex) loadStepToEditor(step) end) local function updateStepField(field, value, refreshList) if autoComboState.updating then return end local step = currentStep() if not step then return end step[field] = value if refreshList then refreshStepList(autoComboState.stepIndex) end end skillSlot:OnChanged(function(value) updateStepField("slot", tonumber(value) or 1, true) end) skillToolName:OnChanged(function(value) updateStepField("toolName", value or "") end) skillDelayAfter:OnChanged(function(value) local num = tonumber(value) updateStepField("delayAfterMs", num) end) waitDuration:OnChanged(function(value) updateStepField("durationMs", tonumber(value) or 0, true) end) m1Hold:OnChanged(function(value) updateStepField("holdMs", tonumber(value) or 60) end) m1Repeat:OnChanged(function(value) updateStepField("repeat", tonumber(value) or 1, true) end) m1Gap:OnChanged(function(value) updateStepField("gapMs", tonumber(value) or 60) end) uptiltJumpHold:OnChanged(function(value) updateStepField("jumpHoldMs", tonumber(value) or 0) end) uptiltM1Hold:OnChanged(function(value) updateStepField("m1HoldMs", tonumber(value) or 0) end) uptiltDelay:OnChanged(function(value) updateStepField("delayBeforeM1Ms", tonumber(value) or 0) end) upflingJumpHold:OnChanged(function(value) updateStepField("jumpHoldMs", tonumber(value) or 0) end) upflingM1Hold:OnChanged(function(value) updateStepField("m1HoldMs", tonumber(value) or 0) end) jumpHoldMs:OnChanged(function(value) updateStepField("holdMs", tonumber(value) or 0) end) jumpCount:OnChanged(function(value) updateStepField("count", tonumber(value) or 1, true) end) jumpHoldTap:OnChanged(function(value) updateStepField("holdMs", tonumber(value) or 0) end) jumpGap:OnChanged(function(value) updateStepField("gapMs", tonumber(value) or 0) end) tweenMaxDistance:OnChanged(function(value) updateStepField("maxDistance", tonumber(value) or 0) end) tweenDuration:OnChanged(function(value) updateStepField("durationSeconds", tonumber(value) or 0) end) tweenAlignFeet:OnChanged(function(value) updateStepField("alignFeet", value == true) end) tweenSpeed:OnChanged(function(value) updateStepField("speed", tonumber(value) or 0) end) tweenOffsetX:OnChanged(function(value) if autoComboState.updating then return end local step = currentStep() if not step then return end step.offset = step.offset or {} step.offset.x = tonumber(value) or 0 end) tweenOffsetY:OnChanged(function(value) if autoComboState.updating then return end local step = currentStep() if not step then return end step.offset = step.offset or {} step.offset.y = tonumber(value) or 0 end) tweenOffsetZ:OnChanged(function(value) if autoComboState.updating then return end local step = currentStep() if not step then return end step.offset = step.offset or {} step.offset.z = tonumber(value) or 0 end) moveStopDistance:OnChanged(function(value) updateStepField("stopDistance", tonumber(value) or 0) end) moveDuration:OnChanged(function(value) updateStepField("durationSeconds", tonumber(value) or 0) end) moveDelayAfter:OnChanged(function(value) updateStepField("delayAfterMs", tonumber(value) or 0) end) moveUseSprint:OnChanged(function(value) updateStepField("useSprint", value == true) end) dashDir:OnChanged(function(value) updateStepField("dir", value or "right", true) end) autoComboParallel:OnChanged(function(value) updateStepField("parallel", value == true, true) end) autoComboStepsGroup:AddButton("Add Step", function() local stepType = autoComboStepType.Value or "Skill" local step = defaultStep(stepType) table.insert(autoComboState.profile.steps, step) selectStep(#autoComboState.profile.steps) end) autoComboStepsGroup:AddButton("Remove Step", function() if not autoComboState.stepIndex then return end table.remove(autoComboState.profile.steps, autoComboState.stepIndex) if #autoComboState.profile.steps == 0 then autoComboState.stepIndex = nil loadStepToEditor(nil) refreshStepList(nil) return end autoComboState.stepIndex = math.clamp(autoComboState.stepIndex, 1, #autoComboState.profile.steps) selectStep(autoComboState.stepIndex) end) autoComboStepsGroup:AddButton("Move Step Up", function() local idx = autoComboState.stepIndex if not idx or idx <= 1 then return end local steps = autoComboState.profile.steps steps[idx], steps[idx - 1] = steps[idx - 1], steps[idx] selectStep(idx - 1) end) autoComboStepsGroup:AddButton("Move Step Down", function() local idx = autoComboState.stepIndex local steps = autoComboState.profile.steps if not idx or idx >= #steps then return end steps[idx], steps[idx + 1] = steps[idx + 1], steps[idx] selectStep(idx + 1) end) autoComboStepsGroup:AddButton("Duplicate Step", function() local step = currentStep() if not step then return end local copy = cloneStep(step) table.insert(autoComboState.profile.steps, copy) selectStep(#autoComboState.profile.steps) end) autoComboProfileGroup:AddButton("Create Profile", function() local name = autoComboProfileName.Value or "" if name == "" then return Logger.longNotify("Please enter a profile name.") end autoComboState.profile = { name = name, steps = {} } autoComboState.stepIndex = nil loadStepToEditor(nil) refreshStepList(nil) local ok, err = AutoCombo.saveProfile(name, autoComboState.profile) if not ok then Logger.longNotify("Failed to create profile '%s' (%s).", name, tostring(err)) end refreshProfiles(name) end) autoComboProfileGroup:AddButton("Save Profile", function() local name = autoComboProfileName.Value or autoComboProfileList.Value or "" if name == "" then return Logger.longNotify("Please enter a profile name.") end autoComboState.profile.name = name local ok, err = AutoCombo.saveProfile(name, autoComboState.profile) if not ok then return Logger.longNotify("Failed to save profile '%s' (%s).", name, tostring(err)) end Logger.notify("Auto-Combo profile '%s' saved.", name) refreshProfiles(name) end) autoComboProfileGroup:AddButton("Load Profile", function() local name = autoComboProfileList.Value if not name or name == "" then return Logger.longNotify("Please select a profile to load.") end local profile, err = AutoCombo.loadProfile(name) if not profile then return Logger.longNotify("Failed to load profile '%s' (%s).", name, tostring(err)) end autoComboState.profile = profile autoComboState.stepIndex = (#profile.steps > 0) and 1 or nil autoComboState.updating = true autoComboProfileName:SetRawValue(profile.name) autoComboState.updating = false refreshStepList(autoComboState.stepIndex) loadStepToEditor(currentStep()) end) autoComboProfileGroup:AddButton("Delete Profile", function() local name = autoComboProfileList.Value if not name or name == "" then return Logger.longNotify("Please select a profile to delete.") end local ok, err = AutoCombo.deleteProfile(name) if not ok then return Logger.longNotify("Failed to delete profile '%s' (%s).", name, tostring(err)) end Logger.notify("Auto-Combo profile '%s' deleted.", name) if autoComboState.profile.name == name then autoComboState.profile = { name = "", steps = {} } autoComboState.stepIndex = nil loadStepToEditor(nil) refreshStepList(nil) end refreshProfiles(nil) end) autoComboProfileGroup:AddButton("Duplicate Profile", function() local source = autoComboProfileList.Value local newName = autoComboProfileName.Value if not source or source == "" then return Logger.longNotify("Please select a profile to duplicate.") end if not newName or newName == "" then return Logger.longNotify("Please enter a new profile name.") end local profile, err = AutoCombo.loadProfile(source) if not profile then return Logger.longNotify("Failed to load profile '%s' (%s).", source, tostring(err)) end profile.name = newName local ok, saveErr = AutoCombo.saveProfile(newName, profile) if not ok then return Logger.longNotify("Failed to save profile '%s' (%s).", newName, tostring(saveErr)) end refreshProfiles(newName) end) autoComboToggle:OnChanged(function(value) if not value then AutoCombo.stop() end end) refreshProfiles(nil) refreshStepList(nil) loadStepToEditor(nil) end end do local autoCounterTab = window:AddTab("Auto-Counter") local autoCounterGroup = autoCounterTab:AddLeftGroupbox("Keybind Press") autoCounterGroup:AddDropdown("AutoCounterKey", { Text = "Auto Counter Key", Values = { "1", "2", "3", "4" }, Default = "1", }) local autoCounterKeybindToggle = autoCounterGroup:AddToggle("AutoCounterKeybindPress", { Text = "Keybind Press", Default = false, Tooltip = "Press the selected key immediately when the keybind is pressed.", }) autoCounterKeybindToggle:AddKeyPicker("AutoCounterKeybind", { Default = "V", Mode = "Hold", Modes = { "Hold" }, Text = "Keybind Press", }) local autoCounterOptions = autoCounterTab:AddRightGroupbox("Options") autoCounterOptions:AddToggle("SkillAutoCounter", { Text = "Skill Auto Counter", Default = false, Tooltip = "Enable skill auto counter behavior.", }) autoCounterOptions:AddToggle("M1AutoCounter", { Text = "M1 Auto Counter", Default = false, Tooltip = "Enable M1 auto counter behavior.", }) autoCounterOptions:AddToggle("PartAutoCounter", { Text = "Part Auto Counter", Default = false, Tooltip = "Enable part-based auto counter behavior.", }) autoCounterOptions:AddSlider("M1AutoCounterDistance", { Text = "M1 Counter Distance", Min = 1, Max = 60, Default = 11.1, Suffix = "m", Rounding = 1, }) autoCounterOptions:AddSlider("SkillAutoCounterDistance", { Text = "Skill Counter Distance", Min = 1, Max = 80, Default = 30, Suffix = "m", Rounding = 1, }) autoCounterOptions:AddSlider("SkillAutoCounterDelaySeconds", { Text = "Skill Counter Delay", Min = 0, Max = 2, Default = 0, Suffix = "s", Rounding = 2, Tooltip = "Delay before pressing the key for skill counters.", }) autoCounterOptions:AddSlider("PartAutoCounterDistance", { Text = "Part Counter Distance", Min = 1, Max = 80, Default = 25, Suffix = "m", Rounding = 1, }) -- UI Config system removed. local AnimationBuilderSection = require("Menu/Objects/AnimationBuilderSection") local PartBuilderSection = require("Menu/Objects/PartBuilderSection") local SoundBuilderSection = require("Menu/Objects/SoundBuilderSection") local AnimationTiming = require("Game/Timings/AnimationTiming") local PartTiming = require("Game/Timings/PartTiming") local SoundTiming = require("Game/Timings/SoundTiming") local animationBuilder = nil local partBuilder = nil local soundBuilder = nil local function refreshBuilders() if animationBuilder then animationBuilder:reset() animationBuilder:refresh() end if partBuilder then partBuilder:reset() partBuilder:refresh() end if soundBuilder then soundBuilder:reset() soundBuilder:refresh() end end local builderTab = window:AddTab("Builder") local saveGroup = builderTab:AddDynamicGroupbox("Save Manager") local autoSaveToggle = saveGroup:AddToggle("PeriodicAutoSave", { Text = "Auto Save Periodically", Default = false, }) local autoSaveDepBox = saveGroup:AddDependencyBox() autoSaveDepBox:AddSlider("PeriodicAutoSaveInterval", { Text = "Auto Save Interval", Min = 5, Max = 300, Default = 60, Suffix = "s", Rounding = 0, }) autoSaveDepBox:SetupDependencies({ { autoSaveToggle, true }, }) local configName = saveGroup:AddInput("ConfigName", { Text = "Config Name", }) local configList = saveGroup:AddDropdown("ConfigList", { Text = "Config List", Values = SaveManager.list(), AllowNull = true, }) local mergeConfigList = nil saveGroup :AddButton("Create Config", function() SaveManager.create(configName.Value) SaveManager.refresh(configList) end) :AddButton({ Text = "Load Config", DoubleClick = true, Func = function() SaveManager.load(configList.Value) refreshBuilders() end, }) saveGroup:AddButton({ Text = "Overwrite Config", DoubleClick = true, Func = function() SaveManager.save(configList.Value) end, }) saveGroup:AddButton({ Text = "Clear Config", DoubleClick = true, Func = function() SaveManager.clear(configList.Value) end, }) saveGroup:AddButton("Refresh List", function() SaveManager.refresh(configList) if mergeConfigList then SaveManager.refresh(mergeConfigList) end refreshBuilders() end) saveGroup:AddButton("Set To Auto Load", function() SaveManager.autoload(configList.Value) end) local mergeGroup = builderTab:AddDynamicGroupbox("Merge Manager") mergeConfigList = mergeGroup:AddDropdown("MergeConfigList", { Text = "Config List", Values = SaveManager.list(), AllowNull = true, }) local mergeConfigType = mergeGroup:AddDropdown("MergeConfigType", { Text = "Merge Type", Values = { "Add New Timings", "Overwrite and Add Everything" }, Default = 1, }) mergeGroup:AddButton({ Text = "Merge With Current Config", DoubleClick = true, Func = function() SaveManager.merge(mergeConfigList.Value, mergeConfigType.Value) refreshBuilders() end, }) partBuilder = PartBuilderSection.new("Part", builderTab:AddDynamicTabbox(), SaveManager.ps, PartTiming.new()) animationBuilder = AnimationBuilderSection.new( "Animation", builderTab:AddDynamicTabbox(), SaveManager.as, AnimationTiming.new() ) soundBuilder = SoundBuilderSection.new("Sound", builderTab:AddDynamicTabbox(), SaveManager.ss, SoundTiming.new()) partBuilder:init() animationBuilder:init() soundBuilder:init() refreshBuilders() Library.OwnerTag = nil Library:Toggle() AttributeListener.init() Defense.init() local UsingSkillWatcher = require("Features/Combat/UsingSkillWatcher") local M1AutoTrade = require("Features/Combat/M1AutoTrade") local ReplicatedStorage = game:GetService("ReplicatedStorage") M1AutoTrade.init() local EFFECT_MAX_DEPTH = 6 local EFFECT_MAX_TABLE_ENTRIES = 60 local EFFECT_MAX_STRING_LEN = 256 local EFFECT_MAX_TOTAL_LEN = 12000 local function formatEffectNumber(value) if type(value) ~= "number" then return tostring(value) end if math.abs(value - math.floor(value)) < 0.0001 then return tostring(math.floor(value)) end return string.format("%.4f", value) end local function serializeEffectValue(value, depth, seen) if depth > EFFECT_MAX_DEPTH then return "" end local valueType = typeof(value) if valueType == "Instance" then local fullName = value.Name if value.GetFullName then local ok, name = pcall(value.GetFullName, value) if ok and name and name ~= "" then fullName = name end end local out = string.format("Instance(%s:%s)", value.ClassName, fullName) if value:IsA("ValueBase") then out = out .. "=" .. serializeEffectValue(value.Value, depth + 1, seen) end return out elseif valueType == "Vector3" then return string.format( "Vector3(%s,%s,%s)", formatEffectNumber(value.X), formatEffectNumber(value.Y), formatEffectNumber(value.Z) ) elseif valueType == "Vector2" then return string.format("Vector2(%s,%s)", formatEffectNumber(value.X), formatEffectNumber(value.Y)) elseif valueType == "CFrame" then local comps = { value:GetComponents() } for i, v in ipairs(comps) do comps[i] = formatEffectNumber(v) end return "CFrame(" .. table.concat(comps, ",") .. ")" elseif valueType == "Color3" then return string.format( "Color3(%s,%s,%s)", formatEffectNumber(value.R), formatEffectNumber(value.G), formatEffectNumber(value.B) ) elseif valueType == "BrickColor" then return "BrickColor(" .. tostring(value) .. ")" elseif valueType == "EnumItem" then return tostring(value) elseif valueType == "NumberRange" then return string.format("NumberRange(%s,%s)", formatEffectNumber(value.Min), formatEffectNumber(value.Max)) elseif valueType == "NumberSequence" or valueType == "ColorSequence" then return tostring(value) elseif valueType == "table" then if seen[value] then return "" end seen[value] = true local parts = {} local count = 0 for k, v in next, value do count = count + 1 if count > EFFECT_MAX_TABLE_ENTRIES then parts[#parts + 1] = "..." break end parts[#parts + 1] = string.format( "[%s]=%s", serializeEffectValue(k, depth + 1, seen), serializeEffectValue(v, depth + 1, seen) ) end seen[value] = nil return "{" .. table.concat(parts, ", ") .. "}" elseif valueType == "string" then local str = value if #str > EFFECT_MAX_STRING_LEN then str = str:sub(1, EFFECT_MAX_STRING_LEN - 3) .. "..." end return string.format("%q", str) elseif valueType == "boolean" then return tostring(value) elseif valueType == "nil" then return "nil" end return tostring(value) end local function formatEffectPayload(args) local effectName = tostring(args[1] or "Unknown") local seen = {} local parts = {} for i = 2, args.n do parts[#parts + 1] = string.format("arg%d=%s", i - 1, serializeEffectValue(args[i], 0, seen)) end local payload = effectName if #parts > 0 then payload = payload .. " | " .. table.concat(parts, " ") end if #payload > EFFECT_MAX_TOTAL_LEN then payload = payload:sub(1, EFFECT_MAX_TOTAL_LEN - 3) .. "..." end return payload end local function classifyEffectCategory(args) local live = workspace:FindFirstChild("Live") for i = 1, args.n do local value = args[i] if typeof(value) == "Instance" then local model = nil if value:IsA("Model") then model = value else model = value:FindFirstAncestorWhichIsA("Model") end if model then local player = Players:GetPlayerFromCharacter(model) if player then if player == Players.LocalPlayer then return "Local" end return "Players" end if model:FindFirstChildOfClass("Humanoid") then return "NPCs" end if live and model:IsDescendantOf(live) then return "NPCs" end end end end return "Other" end local function logEffect(source, ...) if not Configuration.expectToggleValue("LogEffects") then return end local args = table.pack(...) local category = classifyEffectCategory(args) local filter = Options["EffectLoggerFilter"] and Options["EffectLoggerFilter"].Value or "All" if filter ~= "All" and filter ~= category then return end local payload = formatEffectPayload(args) if Library and Library.AddEffectEntry then Library:AddEffectEntry(tostring(args[1] or "Unknown"), source, category, payload) end end local function resolveEffectReturnsCallback(remote) local getter = getcallbackvalue or (syn and syn.getcallbackvalue) or rawget(_G, "getcallbackvalue") if type(getter) ~= "function" then return nil end local ok, cb = pcall(getter, remote, "OnClientInvoke") if ok and type(cb) == "function" then return cb end return nil end do local effect = ReplicatedStorage:WaitForChild("Effect") effect.OnClientEvent:Connect(function(...) logEffect("Effect", ...) end) local effectReliable = ReplicatedStorage:WaitForChild("EffectReliable") effectReliable.OnClientEvent:Connect(function(...) logEffect("EffectReliable", ...) end) local pEffect = ReplicatedStorage:WaitForChild("PEffect") pEffect.OnClientEvent:Connect(function(...) logEffect("PEffect", ...) end) local effectBindable = ReplicatedStorage:WaitForChild("EffectBindable") if effectBindable and effectBindable.Event then effectBindable.Event:Connect(function(...) logEffect("EffectBindable", ...) end) end local effectReturns = ReplicatedStorage:WaitForChild("EffectReturns") if effectReturns then local prev = resolveEffectReturnsCallback(effectReturns) if prev then effectReturns.OnClientInvoke = function(...) logEffect("EffectReturns", ...) return prev(...) end else local ok, logger = pcall(require, "Utility/Logger") if ok and logger and logger.warn then logger.warn("EffectReturns logging skipped: OnClientInvoke callback unavailable.") end end end end local autoJumpState = { playerConns = {}, charConns = {}, spaceConns = {}, } local AUTOCOUNTER_METERS_TO_STUDS = 3.57 local autoCounterHotbarRef = nil local function resolveAutoCounterHotbar() if autoCounterHotbarRef and autoCounterHotbarRef.Parent then return autoCounterHotbarRef end local playerGui = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("PlayerGui") if not playerGui then return nil end local hud = playerGui:FindFirstChild("HUD") local backpackGui = hud and hud:FindFirstChild("Backpack") local hotbar = backpackGui and backpackGui:FindFirstChild("Hotbar") if not hotbar then for _, inst in ipairs(playerGui:GetDescendants()) do if inst.Name == "Hotbar" then hotbar = inst break end end end autoCounterHotbarRef = hotbar return hotbar end local function resolveAutoCounterToolName(slot) local hotbar = resolveAutoCounterHotbar() if not hotbar then return nil end local slotFrame = hotbar:FindFirstChild(tostring(slot)) if not slotFrame then return nil end local toolNameObj = slotFrame:FindFirstChild("ToolName") or slotFrame:FindFirstChild("Name") if not toolNameObj then return nil end if toolNameObj:IsA("TextLabel") or toolNameObj:IsA("TextButton") then return toolNameObj.Text end if toolNameObj:IsA("StringValue") then return toolNameObj.Value end return nil end local function resolveAutoCounterTool(slot) local backpack = Players.LocalPlayer and Players.LocalPlayer:FindFirstChild("Backpack") if backpack then for _, tool in ipairs(backpack:GetChildren()) do if tool:IsA("Tool") then local attrSlot = tool:GetAttribute("Slot") or tool:GetAttribute("HotbarSlot") or tool:GetAttribute("Index") if attrSlot and tonumber(attrSlot) == slot then return tool end local slotValue = tool:FindFirstChild("Slot") or tool:FindFirstChild("Index") if slotValue and tonumber(slotValue.Value) == slot then return tool end end end end local character = Players.LocalPlayer and Players.LocalPlayer.Character return character and character:FindFirstChildOfClass("Tool") or nil end local function tryFireAutoCounter() if not InputClient or not InputClient.fireTool then return false end local selected = Options["AutoCounterKey"] and Options["AutoCounterKey"].Value or "1" local slot = tonumber(selected) if not slot then return false end local toolName = resolveAutoCounterToolName(slot) local tool = resolveAutoCounterTool(slot) if tool then return InputClient.fireToolObject and InputClient.fireToolObject(tool) or InputClient.fireTool(tool.Name) end if toolName and toolName ~= "" then return InputClient.fireTool(toolName) end return false end local function resolveAutoCounterKeyCode() local selected = Options["AutoCounterKey"] and Options["AutoCounterKey"].Value or "1" if selected == "1" then return Enum.KeyCode.One elseif selected == "2" then return Enum.KeyCode.Two elseif selected == "3" then return Enum.KeyCode.Three elseif selected == "4" then return Enum.KeyCode.Four end return nil end local function pressAutoCounterKey(force) if not force and not Configuration.expectToggleValue("AutoCounterKeybindPress") then return end tryFireAutoCounter() end handleAutoCounterTiming = function(timing, source, distance) if not timing or timing.tag == "Counter" or timing.tag == "Critical" then return end if type(distance) ~= "number" then return end if source == "Part" and not Configuration.expectToggleValue("PartAutoCounter") then return end if source == "Part" then local maxMeters = Configuration.expectOptionValue("PartAutoCounterDistance") or 25 if distance > maxMeters * AUTOCOUNTER_METERS_TO_STUDS then return end end if timing.tag == "M1" then if Configuration.expectToggleValue("M1AutoCounter") then local maxMeters = Configuration.expectOptionValue("M1AutoCounterDistance") or 11.1 if distance <= maxMeters * AUTOCOUNTER_METERS_TO_STUDS then pressAutoCounterKey(true) end end return end if timing.tag == "Skill" or timing.tag == "Mantra" then if Configuration.expectToggleValue("SkillAutoCounter") then local maxMeters = Configuration.expectOptionValue("SkillAutoCounterDistance") or 30 if distance <= maxMeters * AUTOCOUNTER_METERS_TO_STUDS then local delaySeconds = Configuration.expectOptionValue("SkillAutoCounterDelaySeconds") or 0 if delaySeconds > 0 then task.delay(delaySeconds, function() pressAutoCounterKey(true) end) else pressAutoCounterKey(true) end end end end end local function shouldAutoJump() return Configuration.expectToggleValue("AutoJumpOnEnemyJump") == true end local function tryAutoJump() if not shouldAutoJump() then return end InputClient.jump() end local function hasJumpPenalty(character) if not character then return false end local penalty = character:FindFirstChild("JumpPenalty") if not penalty then return false end if penalty:IsA("BoolValue") then return penalty.Value == true end return true end local function detachSpaceBool(spaceValue) local conn = spaceValue and autoJumpState.spaceConns[spaceValue] if conn then conn:Disconnect() end autoJumpState.spaceConns[spaceValue] = nil end local function attachSpaceBool(spaceValue, character) if not spaceValue or autoJumpState.spaceConns[spaceValue] then return end local lastValue = spaceValue.Value autoJumpState.spaceConns[spaceValue] = spaceValue:GetPropertyChangedSignal("Value"):Connect(function() local current = spaceValue.Value if current == true and lastValue == false then if not hasJumpPenalty(character) then tryAutoJump() end end lastValue = current end) end local function attachCharacterJumpWatcher(character) if not character or autoJumpState.charConns[character] then return end local function tryAttachSpace(descendant) if descendant and descendant:IsA("BoolValue") and descendant.Name == "Space" then attachSpaceBool(descendant, character) return end end local childAdded = character.ChildAdded:Connect(tryAttachSpace) local childRemoving = character.ChildRemoved:Connect(function(child) if child and child:IsA("BoolValue") and child.Name == "Space" then detachSpaceBool(child) end end) autoJumpState.charConns[character] = { childAdded, childRemoving } for _, child in ipairs(character:GetChildren()) do tryAttachSpace(child) end end local function detachCharacterJumpWatcher(character) local conns = character and autoJumpState.charConns[character] if conns then for _, conn in ipairs(conns) do if conn then conn:Disconnect() end end autoJumpState.charConns[character] = nil end for spaceValue, _ in pairs(autoJumpState.spaceConns) do if spaceValue and spaceValue.Parent == character then detachSpaceBool(spaceValue) end end end local function attachPlayerJumpWatcher(player) if not player or player == Players.LocalPlayer then return end if autoJumpState.playerConns[player] then return end local function onCharacterAdded(character) if not character then return end attachCharacterJumpWatcher(character) end local function onCharacterRemoving(character) if not character then return end detachCharacterJumpWatcher(character) end local addedConn = player.CharacterAdded:Connect(onCharacterAdded) local removingConn = player.CharacterRemoving:Connect(onCharacterRemoving) autoJumpState.playerConns[player] = { addedConn, removingConn } if player.Character then onCharacterAdded(player.Character) end end local function detachPlayerJumpWatcher(player) local conns = player and autoJumpState.playerConns[player] if not conns then return end if player.Character then detachCharacterJumpWatcher(player.Character) end for _, conn in ipairs(conns) do if conn then conn:Disconnect() end end autoJumpState.playerConns[player] = nil end for _, player in ipairs(Players:GetPlayers()) do attachPlayerJumpWatcher(player) end Players.PlayerAdded:Connect(attachPlayerJumpWatcher) Players.PlayerRemoving:Connect(detachPlayerJumpWatcher) if Options["AutoCounterKeybind"] then Options["AutoCounterKeybind"]:OnClick(function(toggled) if toggled then pressAutoCounterKey(false) end end) end local autoComboKeyHeld = false if Options["AutoComboKeybind"] then Options["AutoComboKeybind"]:OnClick(function(toggled) if not toggled then autoComboKeyHeld = false return end if autoComboKeyHeld then return end autoComboKeyHeld = true if not Configuration.expectToggleValue("EnableAutoCombo") then return end local profileName = Options["AutoComboProfile"] and Options["AutoComboProfile"].Value or "" if profileName == "" then return Logger.longNotify("Please select an Auto-Combo profile.") end AutoCombo.run(profileName) end) end local autoComboStopHeld = false if Options["AutoComboStopKeybind"] then Options["AutoComboStopKeybind"]:OnClick(function(toggled) if not toggled then autoComboStopHeld = false return end if autoComboStopHeld then return end autoComboStopHeld = true if not Configuration.expectToggleValue("AutoComboStopKeybindEnabled") then return end AutoCombo.stop() end) end activeSkillDef = nil local function chooseDashDir(def, dir) if not def or not dir then return dir end local character = def.character local localChar = Players.LocalPlayer and Players.LocalPlayer.Character if not character or not localChar then return dir end local targetRoot = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso") local localRoot = localChar:FindFirstChild("HumanoidRootPart") or localChar:FindFirstChild("Torso") if not targetRoot or not localRoot then return dir end local toTarget = (targetRoot.Position - localRoot.Position) if toTarget.Magnitude < 0.01 then return dir end local rightDot = localRoot.CFrame.RightVector:Dot(toTarget.Unit) local forwardDot = localRoot.CFrame.LookVector:Dot(toTarget.Unit) if dir == "right" and rightDot < 0 then return "left" end if dir == "left" and rightDot > 0 then return "right" end if dir == "forward" and forwardDot < 0 then return "back" end if dir == "back" and forwardDot > 0 then return "forward" end return dir end local blockFor local defenseActionState = { blockIssued = 0, dashAttempt = 0, dashOk = true, } local function dash(dir) if not Configuration.expectToggleValue("EnableAutoDefense") then return false end local def = activeSkillDef if type(dir) == "table" then def = dir dir = nil end local unresolved = type(dir) ~= "string" or dir == "" if def and dir then dir = chooseDashDir(def, dir) unresolved = type(dir) ~= "string" or dir == "" end defenseActionState.dashAttempt = (defenseActionState.dashAttempt or 0) + 1 local ok = InputClient.dash(dir) defenseActionState.dashOk = (ok == true) and not unresolved return ok end local function dashAfter(seconds, dir) local delaySeconds = seconds or 0 if delaySeconds <= 0 then return dash(dir) end task.delay(delaySeconds, function() dash(dir) end) return true end local function blockAfter(seconds, duration) local delaySeconds = seconds or 0 if delaySeconds <= 0 then return blockFor(duration) end task.delay(delaySeconds, function() blockFor(duration) end) return true end local function jumpTwice(delaySeconds) local gap = delaySeconds or 0.2 InputClient.jump() task.delay(gap, function() InputClient.jump() end) end executeDefensiveAction = function(def, rule, actionFn) if type(actionFn) ~= "function" then return nil end local _ = def local beforeBlock = defenseActionState.blockIssued or 0 local beforeDash = defenseActionState.dashAttempt or 0 local result = actionFn() local afterBlock = defenseActionState.blockIssued or 0 local afterDash = defenseActionState.dashAttempt or 0 local minBlock = (rule and rule.minBlockSeconds) or NO_HITBOX_MIN_BLOCK_SECONDS local needsBlockFallback = (afterBlock <= beforeBlock) or (afterDash > beforeDash and defenseActionState.dashOk == false) if needsBlockFallback then blockFor(math.max(minBlock, 0.05)) end return result end local function trackSignalWindow(def, key, rule, delaySeconds, windowSeconds, intervalSeconds, callback) local delay = delaySeconds or 0 local window = windowSeconds or 0 local _ = intervalSeconds if window <= 0 then return false end local info = def.stateInfo and def.stateInfo[key] if not info then return false end info.trackToken = (info.trackToken or 0) + 1 local token = info.trackToken local extraDelay = 0 if type(skillDelaySeconds) == "function" then extraDelay = skillDelaySeconds() else local defenderModule = require("Features/Combat/Objects/Defender") local baseDelay = type(defenderModule) == "table" and type(defenderModule.rdelay) == "function" and defenderModule.rdelay() or 0 extraDelay = math.clamp(math.max(0, baseDelay + NO_HITBOX_REACTION_BIAS_SECONDS), 0, 0.06) end local delayWithPing = delay + extraDelay task.delay(delayWithPing, LPH_NO_VIRTUALIZE(function() if not Configuration.expectToggleValue("EnableAutoDefense") then return end if info.trackToken ~= token then return end if UsingSkillWatcher._allowActions and not UsingSkillWatcher._allowActions("Skill") then return end if UsingSkillWatcher._passesDistance and not UsingSkillWatcher._passesDistance(def, rule) then return end if type(callback) == "function" then local ok, err = pcall(function() executeDefensiveAction(def, rule, callback) end) if not ok then if Logger and Logger.warn then Logger.warn("SignalWindow callback failed: %s", tostring(err)) else warn(string.format("[SignalWindow] callback failed: %s", tostring(err))) end end end end)) return true end local function getWorkspaceService() if workspaceService then return workspaceService end local ok, resolved = pcall(game.GetService, game, "Workspace") if ok and resolved then workspaceService = resolved return resolved end return workspace end local function iterGenkiCgaBrown() local results = {} local ws = getWorkspaceService() local thrown = ws and ws:FindFirstChild("Thrown") if thrown then for _, child in ipairs(thrown:GetChildren()) do if child:IsA("BasePart") and child.Name == "Genki" then if child.BrickColor and child.BrickColor.Name == "CGA brown" then results[#results + 1] = child end end end end if #results > 0 then return results end local fallback = ws and ws:FindFirstChild("Genki", true) if fallback and fallback:IsA("BasePart") then if fallback.BrickColor and fallback.BrickColor.Name == "CGA brown" then results[#results + 1] = fallback end end return results end local PART_METERS_TO_STUDS = 3.57 local partPulseActive = false local partPulseTask = nil local partLastActionAt = {} local PART_ACTION_COOLDOWN = 0.25 local babylonGateCounts = {} local BABYLON_M1_BLOCK_SECONDS = 0.3 local BABYLON_M1_RESET_SECONDS = 3.0 local BABYLON_M1_TRIGGER_COUNT = 5 local BABYLON_MULTIPLE_THRESHOLD = 2 local function noHitboxPartPollSeconds() return NO_HITBOX_PART_POLL_SECONDS end local function localRootPart() local character = LocalPlayer and LocalPlayer.Character if not character then return nil end return character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso") end local function distanceToPartMeters(part) local root = localRootPart() if not root or not part then return nil end local studs = (part.Position - root.Position).Magnitude return studs / PART_METERS_TO_STUDS end local function isPartComingToUs(part) local root = localRootPart() if not root or not part then return false end local velocity = part.AssemblyLinearVelocity if not velocity or velocity.Magnitude < 1 then return false end local toUs = (root.Position - part.Position) if toUs.Magnitude <= 0.01 then return false end local dot = velocity.Unit:Dot(toUs.Unit) return dot > 0.35 end local function canPulsePart(part) local last = partLastActionAt[part] local now = os.clock() if last and now - last < PART_ACTION_COOLDOWN then return false end partLastActionAt[part] = now return true end local safeLower do local lowerFn = string and string.lower safeLower = function(value) if lowerFn then return lowerFn(tostring(value or "")) end return tostring(value or "") end end local function isCharacterUsingSkill(character) if not character then return false end local state = character:GetAttribute("CurrentState") if state == "Skill" then return true end local skillValue = character:FindFirstChild("UsingSkill", true) if skillValue and skillValue:IsA("ValueBase") then local raw = skillValue.Value if type(raw) == "string" then local value = safeLower(raw) if value ~= "" and value ~= "none" and value ~= "idle" then return true end end end return false end local function resolvePartOwnerPlayer(part) if not part then return nil end local model = part:FindFirstAncestorWhichIsA("Model") local owner = model and Players:GetPlayerFromCharacter(model) or nil local creator = part:FindFirstChild("creator") or part:FindFirstChild("Creator") if not owner and creator and creator.Value and creator.Value:IsA("Player") then owner = creator.Value end local ownerAttr = part:GetAttribute("Owner") if not owner and typeof(ownerAttr) == "Instance" and ownerAttr:IsA("Player") then owner = ownerAttr end local ownerId = part:GetAttribute("OwnerUserId") if not owner and type(ownerId) == "number" then owner = Players:GetPlayerByUserId(ownerId) end if not owner then local closest = nil local closestDist = math.huge for _, plr in ipairs(Players:GetPlayers()) do if plr == Players.LocalPlayer then continue end local char = plr.Character local root = char and (char:FindFirstChild("HumanoidRootPart") or char:FindFirstChild("Torso")) if root then local dist = (root.Position - part.Position).Magnitude if dist < closestDist then closestDist = dist closest = plr end end end owner = closest end if owner == Players.LocalPlayer then return nil end return owner end local function countBabylonGates() local count = 0 local ws = getWorkspaceService() local thrown = ws and ws:FindFirstChild("Thrown") if thrown then for _, child in ipairs(thrown:GetChildren()) do if child:IsA("BasePart") and safeLower(child.Name) == "babylongate" then count = count + 1 end end end return count end local function handleBallPart(part) if not part or not part:IsA("BasePart") then return end if safeLower(part.Name) ~= "ball" then return end if allowActions and not allowActions("Skill") then return end if not canPulsePart(part) then return end local brick = part.BrickColor and part.BrickColor.Name or "" local dist = distanceToPartMeters(part) or math.huge if safeLower(brick) == safeLower("CGA brown") then if not isPartComingToUs(part) then return end if dist <= 40 then blockFor(1.3) elseif dist <= 80 then blockFor(1.7) else blockFor(2.0) end return end if safeLower(brick) == safeLower("Medium blue") then if dist >= 80 then dashAfter(0.5, "right") else dash("right") end return end dash("right") end local function handleBabylonGatePart(part) if not part or not part:IsA("BasePart") then return end if safeLower(part.Name) ~= "babylongate" then return end if allowActions and not allowActions("Skill") then return end if not canPulsePart(part) then return end local owner = resolvePartOwnerPlayer(part) if not owner then return end local character = owner.Character if not character or isCharacterUsingSkill(character) then babylonGateCounts[owner] = nil return end local now = os.clock() local state = babylonGateCounts[owner] if not state or (now - (state.lastAt or 0)) > BABYLON_M1_RESET_SECONDS then state = { count = 0, lastAt = 0 } end state.count = state.count + 1 state.lastAt = now babylonGateCounts[owner] = state blockFor(BABYLON_M1_BLOCK_SECONDS) if state.count >= BABYLON_M1_TRIGGER_COUNT then local gateCount = countBabylonGates() if gateCount >= BABYLON_MULTIPLE_THRESHOLD and not isCharacterUsingSkill(character) then dash("back") end state.count = 0 end end local function handleCutPastelBlueGreen(part) if not part or not part:IsA("BasePart") then return end if safeLower(part.Name) ~= "cut" then return end local brick = part.BrickColor and part.BrickColor.Name or "" if safeLower(brick) ~= safeLower("Pastel blue-green") then return end if allowActions and not allowActions("Skill") then return end if not canPulsePart(part) then return end local dist = distanceToPartMeters(part) if dist and dist <= 40 then blockFor(1.4) end end local function startPartPulse() if partPulseActive then return end partPulseActive = true partPulseTask = task.spawn(LPH_NO_VIRTUALIZE(function() while partPulseActive do task.wait(NO_HITBOX_ONLY and noHitboxPartPollSeconds() or 0.12) if not Configuration.expectToggleValue("EnableAutoDefense") then continue end local ok, err = pcall(function() local ws = getWorkspaceService() local thrown = ws and ws:FindFirstChild("Thrown") if thrown then for _, child in ipairs(thrown:GetChildren()) do handleBabylonGatePart(child) handleBallPart(child) handleCutPastelBlueGreen(child) end end end) if not ok then if Logger and Logger.warn then Logger.warn("Part pulse tick failed: %s", tostring(err)) else warn(string.format("[PartPulse] tick failed: %s", tostring(err))) end end end end)) end local DASH_BLOCK_DELAY_SECONDS = 0.05 blockFor = function(seconds) if not Configuration.expectToggleValue("EnableAutoDefense") then return false end local hold = seconds or 0 if hold <= 0 then hold = NO_HITBOX_MIN_BLOCK_SECONDS end defenseActionState.blockIssued = (defenseActionState.blockIssued or 0) + 1 return UsingSkillWatcher.blockHoldSeconds(hold) end local function dashThenBlock(dir, seconds, parallel) local holdSeconds = seconds or 0 if parallel then task.defer(dash, dir) else dash(dir) end if holdSeconds <= 0 then return false end task.delay(DASH_BLOCK_DELAY_SECONDS, function() blockFor(holdSeconds) end) return true end local function normalizeCharName(value) return string.lower(tostring(value or "")):gsub("[^%w]", "") end local function getDefLastLoadedChar(def) local plr = def and def.player if plr then local raw = plr:GetAttribute("LastLoadedChar") if raw ~= nil then return raw end end local character = def and def.character if character then local raw = character:GetAttribute("LastLoadedChar") if raw ~= nil then return raw end end return nil end local function isChar(def, name) local raw = getDefLastLoadedChar(def) if not raw then return false end return normalizeCharName(raw) == normalizeCharName(name) end local function hasKaioken(def) local character = def and def.character if not character then return false end return character:FindFirstChild("KAIOKEN") ~= nil or character:FindFirstChild("Kaioken") ~= nil end local function distanceToDef(def) local character = def and def.character if not character then return nil end local root = character:FindFirstChild("HumanoidRootPart") or character:FindFirstChild("Torso") if not root then return nil end local localChar = LocalPlayer and LocalPlayer.Character if not localChar then return nil end local localRoot = localChar:FindFirstChild("HumanoidRootPart") or localChar:FindFirstChild("Torso") if not localRoot then return nil end return (root.Position - localRoot.Position).Magnitude end local function rushBlockSecondsForDistance(distance) if not distance or distance <= 40 then return 1.2 end if distance <= 80 then return 1.45 end return 1.8 end UsingSkillWatcher.setRules({ Kamehameha = { token = "kamehameha", shape = "Box", hitbox = Vector3.new(11, 12.5, 200), fhb = true, onStart = function(def) trackSignalWindow(def, "Kamehameha", UsingSkillWatcher.rules.Kamehameha, 0, 0.6, 0.05, function() InputClient.jump() dash("right") end) end, }, RushKaioken = { token = "rush", match = function(def) return hasKaioken(def) end, fhb = true, onStart = function(def) local seconds = rushBlockSecondsForDistance(distanceToDef(def)) blockFor(seconds) dash("back") dashAfter(1.2, "back") end, }, Rush = { token = "rush", match = function(def) return not hasKaioken(def) end, fhb = true, onStart = function(def) local seconds = rushBlockSecondsForDistance(distanceToDef(def)) dashThenBlock("right", seconds, true) end, }, GetsugaSlash = { token = "getsuga", spacedToken = "getsuga slash", fhb = true, ihbc = true, onStart = function() dash("right") end, }, MultiCut = { token = "multicut", spacedToken = "multi cut", fhb = true, ihbc = true, onStart = function() task.defer(dash, "back") blockFor(1.4) end, }, Lunge = { token = "lunge", fhb = true, ihbc = true, onStart = function() blockAfter(0.216, 1.2) end, }, FireflyLight = { token = "fireflylight", spacedToken = "firefly light", fhb = true, ihbc = true, onStart = function(def) InputClient.jump() local dist = distanceToDef(def) if dist and dist >= 40 then dash("forward") else dash("back") end end, }, DragonCombo = { token = "dragoncombo", spacedToken = "dragon combo", shape = "Box", hitbox = Vector3.new(60, 60, 60), fhb = true, onStart = function(def) local holdSeconds = hasKaioken(def) and 2.2 or 1.8 dashThenBlock("back", holdSeconds, true) end, }, CrossKicks = { token = "crosskicks", spacedToken = "cross kicks", shape = "Box", hitbox = Vector3.new(24, 17, 24), fhb = true, onStart = function() task.defer(dash, "back") blockFor(0.9) end, }, SpecialBeamCannon = { token = "specialbeamcannon", spacedToken = "special beam cannon", shape = "Box", hitbox = Vector3.new(10, 10, 150), fhb = true, onStart = function() dash("right") end, }, GutKick = { token = "gutkick", spacedToken = "gut kick", shape = "Box", hitbox = Vector3.new(10, 10, 10), fhb = true, onStart = function() blockFor(1.2) end, }, Uppercut = { token = "uppercut", shape = "Box", hitbox = Vector3.new(35, 35, 35), fhb = true, onStart = function() blockFor(1.2) dash("back") dashAfter(1.2, "back") end, }, BusterCannon = { token = "bustercannon", spacedToken = "buster cannon", shape = "Box", hitbox = Vector3.new(15, 20, 180), fhb = true, onStart = function() dash("right") end, }, RapidSwordStream = { token = "rapidswordstream", spacedToken = "rapid sword stream", shape = "Ball", hitbox = Vector3.new(17, 22, 14), fhb = true, onStart = function() dashThenBlock("back", 1.2, false) end, }, BlazingRush = { token = "blazingrush", spacedToken = "blazing rush", shape = "Box", hitbox = Vector3.new(10, 10, 70), ihbc = true, fhb = true, onStart = function() blockFor(0.9) end, }, FlashFist = { token = "flashfist", spacedToken = "flash fist", shape = "Box", hitbox = Vector3.new(15, 15, 100), fhb = true, onStart = function() blockFor(1.4) end, }, RetirementKick = { token = "retirementkick", spacedToken = "retirement kick", shape = "Box", hitbox = Vector3.new(30, 30, 30), fhb = true, onStart = function() dashThenBlock("back", 1.6, true) end, }, KiBlitz = { token = "kiblitz", spacedToken = "ki blitz", shape = "Box", hitbox = Vector3.new(15, 15, 150), fhb = true, onStart = function() blockFor(2.1) end, }, KiTraps = { token = "kitraps", spacedToken = "ki traps", shape = "Box", hitbox = Vector3.new(15, 15, 150), fhb = true, onStart = function() blockFor(1.7) end, }, ScatterShot = { token = "scattershot", spacedToken = "scatter shot", shape = "Box", hitbox = Vector3.new(30, 30, 150), fhb = true, onStart = function() blockFor(0.9) InputClient.jump() end, }, ComboBlitz = { token = "comboblitz", spacedToken = "combo blitz", shape = "Box", hitbox = Vector3.new(15, 15, 30), fhb = true, onStart = function(def) trackSignalWindow(def, "ComboBlitz", UsingSkillWatcher.rules.ComboBlitz, 0, 2.0, 0.05, function() blockFor(2.9) end) end, }, ExplosiveField = { token = "explosivefield", spacedToken = "explosive field", shape = "Ball", hitbox = Vector3.new(45, 45, 45), fhb = true, onStart = function(def) trackSignalWindow(def, "ExplosiveField", UsingSkillWatcher.rules.ExplosiveField, 0.35, 0.8, 0.05, function() dash("back") end) end, }, SuperVolley = { token = "supervolley", spacedToken = "super volley", ihbc = true, fhb = true, onStart = function() blockFor(1.9) end, }, HellzoneGrenade = { token = "hellzonegrenade", spacedToken = "hellzone grenade", ihbc = true, fhb = true, onStart = function() dashAfter(0.9, "back") end, }, DestructoDisk = { token = "destructodisk", spacedToken = "destructo disk", ihbc = true, fhb = true, onStart = function() jumpTwice() task.defer(dash, "right") end, }, SuperBomber = { token = "superbomber", spacedToken = "super bomber", shape = "Box", hitbox = Vector3.new(50, 50, 150), fhb = true, onStart = function() blockFor(1.45) end, }, EnergyFist = { token = "energyfist", spacedToken = "energy fist", ihbc = true, fhb = true, onStart = function() blockFor(1.25) end, }, KiBlast = { token = "kiblast", spacedToken = "ki blast", ihbc = true, fhb = true, onStart = function() blockFor(1.6) end, }, ElbowKick = { token = "elbowkick", spacedToken = "elbow kick", shape = "Box", hitbox = Vector3.new(45, 45, 45), fhb = true, onStart = function(def) trackSignalWindow(def, "ElbowKick", UsingSkillWatcher.rules.ElbowKick, 0, 1.7, 0.05, function() blockFor(1.4) end) end, }, KameBlast = { token = "kameblast", spacedToken = "kame blast", shape = "Box", hitbox = Vector3.new(15, 16, 120), fhb = true, onStart = function(def) trackSignalWindow(def, "KameBlast", UsingSkillWatcher.rules.KameBlast, 0, 2.0, 0.05, function() InputClient.jump() task.defer(dash, "right") task.delay(0.3, function() blockFor(0.45) end) end) end, }, RapidKickRush = { token = "rapidkickrush", spacedToken = "rapid kick rush", shape = "Box", hitbox = Vector3.new(17, 24, 20), fhb = true, onStart = function(def) trackSignalWindow(def, "RapidKickRush", UsingSkillWatcher.rules.RapidKickRush, 0.297, 1.0, 0.05, function() jumpTwice() task.defer(dash, "back") end) end, }, CosmicRush = { token = "cosmicrush", spacedToken = "cosmic rush", shape = "Box", hitbox = Vector3.new(18, 20, 180), fhb = true, onStart = function(def) trackSignalWindow(def, "CosmicRush", UsingSkillWatcher.rules.CosmicRush, 0, 1.2, 0.05, function() blockFor(0.95) end) end, }, Grab = { token = "grab", shape = "Box", hitbox = Vector3.new(15, 15, 120), fhb = true, onStart = function(def) trackSignalWindow(def, "Grab", UsingSkillWatcher.rules.Grab, 0.455, 1.3, 0.05, function() blockFor(1.1) end) end, }, PunchOnslaught = { token = "punchonslaught", spacedToken = "punch onslaught", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "PunchOnslaught", UsingSkillWatcher.rules.PunchOnslaught, 0, 1.7, 0.05, function() dash("back") task.delay(0.4, function() blockFor(1.125) end) end) end, }, FastFlashAttack = { token = "fastflashattack", spacedToken = "fast flash attack", shape = "Box", hitbox = Vector3.new(15, 15, 100), fhb = true, onStart = function(def) trackSignalWindow(def, "FastFlashAttack", UsingSkillWatcher.rules.FastFlashAttack, 0, 0.3, 0.05, function() blockFor(0.9) end) end, }, FinalStrike = { token = "finalstrike", spacedToken = "final strike", shape = "Box", hitbox = Vector3.new(25, 15, 120), fhb = true, onStart = function(def) trackSignalWindow(def, "FinalStrike", UsingSkillWatcher.rules.FinalStrike, 0.371, 1.2, 0.05, function() dash("right") end) end, }, PunisherDrive = { token = "punisherdrive", spacedToken = "punisher drive", shape = "Box", hitbox = Vector3.new(12, 12, 45), fhb = true, onStart = function(def) trackSignalWindow(def, "PunisherDrive", UsingSkillWatcher.rules.PunisherDrive, 0.07, 0.3, 0.05, function() blockFor(1.2) end) end, }, EnergyWave = { token = "energywave", spacedToken = "energy wave", ihbc = true, fhb = true, onStart = function() jumpTwice() task.defer(dash, "right") end, }, SpiralingJustice = { token = "spiralingjustice", spacedToken = "spiraling justice", ihbc = true, fhb = true, onStart = function() task.delay(0.195, function() blockFor(0.8) end) end, }, MadDance = { token = "maddance", spacedToken = "mad dance", ihbc = true, fhb = true, onStart = function() dash("back") end, }, HeavyBlow = { token = "heavyblow", spacedToken = "heavy blow", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "HeavyBlow", UsingSkillWatcher.rules.HeavyBlow, 0, 0.9, 0.05, function() dash("back") end) end, }, PowerRoar = { token = "powerroar", spacedToken = "power roar", shape = "Box", hitbox = Vector3.new(15, 15, 45), fhb = true, onStart = function(def) trackSignalWindow(def, "PowerRoar", UsingSkillWatcher.rules.PowerRoar, 0, 0.8, 0.05, function() jumpTwice() task.defer(dash, "right") end) end, }, WraithfulCrash = { token = "wraithfulcrash", spacedToken = "wraithful crash", shape = "Box", hitbox = Vector3.new(15, 15, 60), fhb = true, onStart = function(def) trackSignalWindow(def, "WraithfulCrash", UsingSkillWatcher.rules.WraithfulCrash, 0, 1.2, 0.05, function() blockFor(1.2) end) end, }, Skill_1731404034 = { token = "1731404034", shape = "Box", hitbox = Vector3.new(40, 40, 80), fhb = true, onStart = function(def) trackSignalWindow(def, "Skill_1731404034", UsingSkillWatcher.rules.Skill_1731404034, 0.14, 0.6, 0.05, function() blockFor(1.2) end) end, }, Skill_92993354909172 = { token = "92993354909172", shape = "Box", hitbox = Vector3.new(15, 15, 60), fhb = true, onStart = function(def) trackSignalWindow(def, "Skill_92993354909172", UsingSkillWatcher.rules.Skill_92993354909172, 0.26, 1.2, 0.05, function() blockFor(1.2) end) end, }, Skill_1886062099 = { token = "1886062099", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "Skill_1886062099", UsingSkillWatcher.rules.Skill_1886062099, 0, 0.9, 0.05, function() dash("back") end) end, }, FritAssorti = { token = "fritassorti", spacedToken = "frit assorti", shape = "Box", hitbox = Vector3.new(24, 24, 40), fhb = true, onStart = function(def) trackSignalWindow(def, "FritAssorti", UsingSkillWatcher.rules.FritAssorti, 0, 1.2, 0.05, function() task.delay(0.6, function() dash("right") end) end) end, }, Spectre = { token = "spectre", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "Spectre", UsingSkillWatcher.rules.Spectre, 0, 1.25, 0.05, function() task.defer(dash, "back") blockFor(1.6) end) end, }, TableParty = { token = "tableparty", spacedToken = "table party", shape = "Ball", hitbox = Vector3.new(45, 45, 45), fhb = true, onStart = function(def) trackSignalWindow(def, "TableParty", UsingSkillWatcher.rules.TableParty, 0, 1.5, 0.05, function() blockFor(1.4) end) end, }, Skill_10006551970 = { token = "10006551970", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "Skill_10006551970", UsingSkillWatcher.rules.Skill_10006551970, 0.307, 1.1, 0.05, function() jumpTwice() task.defer(dash, "right") end) end, }, CounterShock = { token = "countershock", spacedToken = "counter shock", ihbc = true, fhb = true, onStart = function() jumpTwice() task.defer(dash, "back") end, }, GammaKnife = { token = "gammaknife", spacedToken = "gamma knife", ihbc = true, fhb = true, onStart = function() jumpTwice() task.defer(dash, "back") end, }, Gatling = { token = "gatling", shape = "Box", hitbox = Vector3.new(15, 15, 15), fhb = true, onStart = function(def) trackSignalWindow(def, "Gatling", UsingSkillWatcher.rules.Gatling, 0, 1.5, 0.05, function() task.defer(dash, "back") blockFor(2.2) end) end, }, BusoGatling = { token = "busogatling", spacedToken = "buso gatling", fhb = true, ihbc = true, onStart = function() task.defer(dash, "back") blockFor(2.2) end, }, Skill_1731336080 = { token = "1731336080", shape = "Box", hitbox = Vector3.new(15, 15, 35), fhb = true, onStart = function(def) trackSignalWindow(def, "Skill_1731336080", UsingSkillWatcher.rules.Skill_1731336080, 0.159, 1.0, 0.05, function() dash("right") end) end, }, Bazooka = { token = "bazooka", shape = "Box", hitbox = Vector3.new(15, 15, 120), fhb = true, onStart = function(def) trackSignalWindow(def, "Bazooka", UsingSkillWatcher.rules.Bazooka, 0, 1.2, 0.05, function() dashAfter(0.645, "right") end) end, }, Rocket = { token = "rocket", ihbc = true, fhb = true, onStart = function(def) local distance = distanceToDef(def) local seconds = 1.2 if distance and distance > 80 then seconds = 2.0 elseif distance and distance > 40 then seconds = 1.7 end blockFor(seconds) end, }, Pistol = { token = "pistol", ihbc = true, fhb = true, onStart = function(def) local distance = distanceToDef(def) local seconds = 1.2 if distance and distance > 80 then seconds = 2.0 elseif distance and distance > 40 then seconds = 1.7 end blockFor(seconds) end, }, Saikuru = { token = "saikuru", shape = "Ball", hitbox = Vector3.new(50, 20, 0), offset = Vector3.new(0, 0, 20), fhb = true, onStart = function(def) trackSignalWindow(def, "Saikuru", UsingSkillWatcher.rules.Saikuru, 0, 0.4, 0.05, function() dash("back") end) end, }, SpinningMeteor = { token = "spinningmeteor", spacedToken = "spinning meteor", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, onStart = function(def) trackSignalWindow(def, "SpinningMeteor", UsingSkillWatcher.rules.SpinningMeteor, 0, 1.2, 0.05, function() dash("back") end) end, }, KickAwaySledgehammer = { token = "sledgehammer", spacedToken = "kick away", ihbc = true, fhb = true, onStart = function() task.delay(0.085, function() blockFor(0.7) end) end, }, Begone = { token = "begone", ihbc = true, fhb = true, onStart = function() dash("right") end, }, RedHawk = { token = "redhawk", spacedToken = "red hawk", ihbc = true, fhb = true, onStart = function() blockAfter(0.384, 1.3) end, }, HellMemories = { token = "hellmemories", spacedToken = "hell memories", ihbc = true, fhb = true, onStart = function() dashAfter(0.6, "right") end, }, GrillShot = { token = "grillshot", spacedToken = "grill shot", ihbc = true, fhb = true, onStart = function() blockAfter(0.3, 1.4) end, }, ViceGrip = { token = "vicegrip", spacedToken = "vice grip", ihbc = true, fhb = true, onStart = function() blockAfter(0.4, 0.7) end, }, LigerBomb = { token = "ligerbomb", spacedToken = "liger bomb", ihbc = true, fhb = true, onStart = function() blockAfter(0.1, 0.2) end, }, Lariat = { token = "lariat", ihbc = true, fhb = true, onStart = function() dashAfter(0.33, "right") end, }, GuillotineDrop = { token = "guillotinedrop", spacedToken = "guillotine drop", ihbc = true, fhb = true, onStart = function() dashAfter(0.35, "right") end, }, RaigaBomb = { token = "raigabomb", spacedToken = "raiga bomb", ihbc = true, fhb = true, onStart = function() blockAfter(0.1, 0.2) end, }, LightningStraight = { token = "lightningstraight", spacedToken = "lightning straight", ihbc = true, fhb = true, onStart = function() blockAfter(0.1, 3.7) end, }, WhirlwindThrow = { token = "whirlwindthrow", spacedToken = "whirlwind throw", ihbc = true, fhb = true, onStart = function() blockAfter(0.1, 0.2) end, }, JetPistol = { token = "jetpistol", spacedToken = "jet pistol", ihbc = true, fhb = true, onStart = function() task.defer(dash, "back") blockFor(1.4) end, }, GrizzlyMagnum = { token = "grizzlymagnum", spacedToken = "grizzly magnum", ihbc = true, fhb = true, onStart = function(def) task.delay(0.687, function() local dist = distanceToDef(def) if dist and dist <= 20 then dash("forward") else dash("back") end end) end, }, SanzenSekai = { token = "sanzensekai", spacedToken = "sanzen sekai", ihbc = true, fhb = true, onStart = function() dashAfter(0.38, "right") end, }, TigerHunt = { token = "tigerhunt", spacedToken = "tiger hunt", ihbc = true, fhb = true, onStart = function() dashAfter(0.196, "right") end, }, BurstEnergy = { token = "burstenergy", spacedToken = "burst energy", shape = "Box", hitbox = Vector3.new(15, 20, 120), fhb = true, onStart = function() dash("right") end, }, VanishKick = { token = "vanishkick", spacedToken = "vanish kick", shape = "Box", hitbox = Vector3.new(18, 15, 20), fhb = true, deferHitboxSeconds = 0.7, onStart = function(def) UsingSkillWatcher.deferHitbox(def, "VanishKick", 0.7, function() dash("back") end) end, }, DashBarrage = { token = "dashbarrage", spacedToken = "dash barrage", shape = "Box", hitbox = Vector3.new(24, 24, 24), fhb = true, deferHitboxSeconds = 0.388, onStart = function(def) UsingSkillWatcher.deferHitbox(def, "DashBarrage", 0.388, function() dash("right") end) end, }, Punish = { token = "punish", shape = "Box", hitbox = Vector3.new(20, 15, 24), fhb = true, onStart = function() dash("right") end, }, SpiritSword = { token = "spiritsword", spacedToken = "spirit sword", shape = "Box", hitbox = Vector3.new(33, 6, 65), fhb = true, onStart = function() blockFor(1.4) task.delay(1.4, function() blockFor(1.6) end) end, }, SavageStrike = { token = "savagestrike", spacedToken = "savage strike", ihbc = true, fhb = true, onStart = function() dashAfter(0.37, "back") end, }, DeathBeam = { token = "deathbeam", spacedToken = "death beam", shape = "Box", hitbox = Vector3.new(3, 3, 100), offset = Vector3.new(-1, 0, 0), ihbc = true, fhb = true, onStart = function(def) trackSignalWindow(def, "DeathBeam", UsingSkillWatcher.rules.DeathBeam, 0, 0.8, 0.05, function() InputClient.jump() task.defer(dash, "right") end) end, }, TailSlam = { token = "tailslam", spacedToken = "tail slam", shape = "Box", hitbox = Vector3.new(24, 24, 24), ihbc = true, fhb = true, onStart = function(def) trackSignalWindow(def, "TailSlam", UsingSkillWatcher.rules.TailSlam, 0.4, 0.9, 0.05, function() blockFor(1.2) end) end, }, PlanetDestroyer = { token = "planetdestroyer", spacedToken = "planet destroyer", ihbc = true, fhb = true, onStart = function(def) local info = def.stateInfo and def.stateInfo.PlanetDestroyer if not info then return end info.genkiBlockUntil = 0 end, onTick = function(def) local info = def.stateInfo and def.stateInfo.PlanetDestroyer if not info then return end if info.genkiBlockUntil and os.clock() < info.genkiBlockUntil then return end local root = localRootPart() if not root then return end for _, part in ipairs(iterGenkiCgaBrown()) do local radius = math.max(17, math.max(part.Size.X, part.Size.Y, part.Size.Z) * 0.5) if (part.Position - root.Position).Magnitude <= (radius + 4) then blockFor(1.2) info.genkiBlockUntil = os.clock() + 1.2 return end end end, }, }) UsingSkillWatcher.init() startPartPulse() end local function standDistanceMeters(model) if not model or not model:IsA("Model") then return 0 end local localCharacter = Players.LocalPlayer and Players.LocalPlayer.Character if not localCharacter then return 0 end local localRoot = localCharacter:FindFirstChild("HumanoidRootPart") if not localRoot then return 0 end local root = model:FindFirstChild("HumanoidRootPart") if not root then return 0 end return (root.Position - localRoot.Position).Magnitude end local standOwnerCache = {} standOwnerCache.logLast = standOwnerCache.logLast or {} standOwnerCache.logCooldown = standOwnerCache.logCooldown or 0.35 function standOwnerCache.shouldLogStand(key) local now = os.clock() local last = standOwnerCache.logLast[key] if last and now - last < standOwnerCache.logCooldown then return false end standOwnerCache.logLast[key] = now return true end standOwnerCache.standM1FallbackTiming = standOwnerCache.standM1FallbackTiming or { tag = "M1", actions = { _data = { { _type = "Start Block" }, { _type = "End Block", _when = 300 }, }, }, } function standOwnerCache.resolveStandOwnerForScan(live, standModel) local cached = standOwnerCache[standModel] if cached and cached.Parent then return cached end standOwnerCache[standModel] = nil if not live then return nil end for _, entity in next, live:GetChildren() do local standValue = entity:FindFirstChild("STANDOBJ") if standValue and standValue.Value == standModel then standOwnerCache[standModel] = entity return entity end end return nil end function standOwnerCache.resolveStandTimingForScan(fullId, track) local timingStore = SaveManager.as if not timingStore then return nil end local timing = timingStore:index(fullId) if timing then return timing end local trackName = track and track.Name if trackName and trackName ~= "" then timing = timingStore:find(trackName) if timing then return timing end end local animName = track and track.Animation and track.Animation.Name if animName and animName ~= "" then return timingStore:find(animName) end return nil end function standOwnerCache.standAnimIsLikelyM1(name) local lowered = string.lower(tostring(name or "")) if lowered == "" then return false end if lowered == "m1" then return true end if lowered:find("m1_") or lowered:find("_m1") or lowered:find(" m1") or lowered:find("m1 ") or lowered:find("-m1") or lowered:find("m1-") then return true end if lowered:find("light") or lowered:find("basic") or lowered:find("jab") then return true end return false end function standOwnerCache.isLikelyStandM1ForScan(track, timing) if timing and timing.tag == "M1" then return true end if timing and standOwnerCache.standAnimIsLikelyM1(timing.name) then return true end local trackName = track and track.Name if standOwnerCache.standAnimIsLikelyM1(trackName) then return true end local animName = track and track.Animation and track.Animation.Name if standOwnerCache.standAnimIsLikelyM1(animName) then return true end return false end function standOwnerCache.collectStandModelsForScan(standsFolder, live, localStand) local standModels = {} local function addStandModel(standModel) if standModel and standModel:IsA("Model") then standModels[standModel] = true end end if standsFolder then for _, descendant in next, standsFolder:GetDescendants() do local standModel = descendant:FindFirstAncestorWhichIsA("Model") if standModel then addStandModel(standModel) end end end if live then for _, entity in next, live:GetChildren() do local standValue = entity:FindFirstChild("STANDOBJ") if standValue and standValue.Value and standValue.Value:IsA("Model") then addStandModel(standValue.Value) end end end if localStand then addStandModel(localStand) end return standModels end function standOwnerCache.processStandTracksForScan(standModel, tracks, live, localStand, flags) if not standModel or not tracks then return end local owner = standOwnerCache.resolveStandOwnerForScan(live, standModel) local ownerName = owner and owner.Name or standModel.Name local ownerDistance = owner and standDistanceMeters(owner) or standDistanceMeters(standModel) for _, track in next, tracks do if not track then continue end local aid = (track.Animation and track.Animation.AnimationId) or track.AnimationId local aidStr = tostring(aid or "") if aidStr == "" then continue end local numericId = aidStr:match("%d+") local fullId = numericId and ("rbxassetid://" .. numericId) or aidStr local timing = standOwnerCache.resolveStandTimingForScan(fullId, track) if flags.m1Trade then if timing and timing.tag == "M1" then M1AutoTrade.handleEntity(timing, owner or standModel, ownerDistance) elseif standOwnerCache.isLikelyStandM1ForScan(track, timing) then M1AutoTrade.handleEntity(standOwnerCache.standM1FallbackTiming, owner or standModel, ownerDistance) end end if flags.logStand or flags.logAll or (flags.logLocal and localStand and standModel == localStand) then local key = standModel:GetFullName() .. "|" .. fullId if standOwnerCache.shouldLogStand(key) then local distance = ownerDistance if timing then Library:AddExistAnimEntry(ownerName, distance, timing) else Library:AddMissEntry("Stand Anim", fullId, ownerName, distance, "Stand") end end end end end function standOwnerCache.scanStandModelForScan(standModel, live, localStand, flags) if not standModel then return end for _, desc in next, standModel:GetDescendants() do if desc:IsA("Animator") then standOwnerCache.processStandTracksForScan(standModel, desc:GetPlayingAnimationTracks(), live, localStand, flags) elseif desc:IsA("Humanoid") then local animator = desc:FindFirstChildOfClass("Animator") if animator then standOwnerCache.processStandTracksForScan( standModel, animator:GetPlayingAnimationTracks(), live, localStand, flags ) end elseif desc:IsA("AnimationController") then standOwnerCache.processStandTracksForScan(standModel, desc:GetPlayingAnimationTracks(), live, localStand, flags) end end end function standOwnerCache.shouldProcessStandModelForScan(standModel, localStand, flags) if localStand and standModel == localStand then return flags.logLocal or flags.logAll or flags.m1Trade end return flags.logStand or flags.logAll or flags.m1Trade end standOwnerCache.scanStandAnimations = function() if not standOwnerCache.standsFolderRef or not standOwnerCache.standsFolderRef.Parent then standOwnerCache.standsFolderRef = workspace:FindFirstChild("Stands") end local standsFolder = standOwnerCache.standsFolderRef local live = workspace:FindFirstChild("Live") local localStand = nil if Players.LocalPlayer and Players.LocalPlayer.Character then local standValue = Players.LocalPlayer.Character:FindFirstChild("STANDOBJ") if standValue and standValue.Value and standValue.Value:IsA("Model") then localStand = standValue.Value end end local logStand = Configuration.expectToggleValue("LogStandAnimations") local logAll = Configuration.expectToggleValue("LogAllAnimations") local logLocal = Configuration.expectToggleValue("LogLocalStandAnimations") local m1Trade = Configuration.expectToggleValue("EnableM1AutoTrade") if not (logStand or logAll or logLocal or m1Trade) then return end local flags = { logStand = logStand, logAll = logAll, logLocal = logLocal, m1Trade = m1Trade, } local standModels = standOwnerCache.collectStandModelsForScan(standsFolder, live, localStand) for standModel in next, standModels do if standOwnerCache.shouldProcessStandModelForScan(standModel, localStand, flags) then standOwnerCache.scanStandModelForScan(standModel, live, localStand, flags) end end end do (game:GetService("RunService").PostSimulation or game:GetService("RunService").Heartbeat):Connect(LPH_NO_VIRTUALIZE(function() if Configuration.expectToggleValue("LogAllAnimations") or Configuration.expectToggleValue("LogStandAnimations") or Configuration.expectToggleValue("LogLocalStandAnimations") or Configuration.expectToggleValue("EnableM1AutoTrade") then standOwnerCache.scanStandAnimations() end end)) end