Cottom Hub LEAKED
Key RequiredUniversal

Cottom Hub LEAKED

Universal ScriptScriptLeakDCJeffyrrScriptLeakDCJeffyrr 2.9K views 970 executesUpdated Apr 29, 2026
Unrated
00
Lua Script•29751 lines (720824 chars)
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<AnimationTrack, PlaybackData> Playback data to be recorded.
---@field rpbdata table<string, PlaybackData> Recorded playback data. Optimization so we don't have to constantly reiterate over recorded data.
---@field manimations table<number, Animation>
---@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<string, function> Hooked functions that we can restore on clean-up.
---@field markers table<string, boolean> 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<string, Action>
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<string, Action>
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<number, number> 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<string, Timing>
---@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 <s.steinhauer@yahoo.de>
 * modified by the Lycoris Team <discord.gg/lyc>
 *
 * 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 <http://unlicense.org/>
]]

-- 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: <none>", 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 "<max depth>"
	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 "<cycle>"
		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

Features & Information

LEAKED COTTOM HUB ABA! FULL SCRIPT

Community Comments (3)

RocketDaMoonRocketDaMoonMay 1, 2026

This is not the real Cottom Hub and the picture shown is clickbait. Do not use this it is potentially malicious and could get you banned on ABA or even your account hacked.

Replying to @RocketDaMoon

@RocketDaMoon this is the real cottom hub and it is not malicous.

xinzxinzMay 16, 2026

@RocketDaMoon , this is open source

More Scripts for Universal Scripts