--------------------------------------------------------------------------------
--               Batched Yield-Safe Signal Implementation                     --
-- This is a Signal class which has effectively identical behavior to a       --
-- normal RBXScriptSignal, with the only difference being a couple extra      --
-- stack frames at the bottom of the stack trace when an error is thrown.     --
-- This implementation caches runner coroutines, so the ability to yield in   --
-- the signal handlers comes at minimal extra cost over a naive signal        --
-- implementation that either always or never spawns a thread.                --
--                                                                            --
-- API:                                                                       --
--   local Signal = require(THIS MODULE)                                      --
--   local sig = Signal.new()                                                 --
--   local connection = sig:Connect(function(arg1, arg2, ...) ... end)        --
--   sig:Fire(arg1, arg2, ...)                                                --
--   connection:Disconnect()                                                  --
--   sig:DisconnectAll()                                                      --
--   local arg1, arg2, ... = sig:Wait()                                       --
--                                                                            --
-- Licence:                                                                   --
--   Licenced under the MIT licence.                                          --
--                                                                            --
-- Authors:                                                                   --
--   stravant - July 31st, 2021 - Created the file.                           --
--------------------------------------------------------------------------------
--!nocheck

-- The currently idle thread to run the next handler on
local freeRunnerThread = nil

-- Function which acquires the currently idle handler runner thread, runs the
-- function fn on it, and then releases the thread, returning it to being the
-- currently idle one.
-- If there was a currently idle runner thread already, that's okay, that old
-- one will just get thrown and eventually GCed.
local function acquireRunnerThreadAndCallEventHandler(fn, ...)
	local acquiredRunnerThread = freeRunnerThread
	freeRunnerThread = nil
	fn(...)
	-- The handler finished running, this runner thread is free again.
	freeRunnerThread = acquiredRunnerThread
end

-- Coroutine runner that we create coroutines of. The coroutine can be
-- repeatedly resumed with functions to run followed by the argument to run
-- them with.
local function runEventHandlerInFreeThread()
	-- Note: We cannot use the initial set of arguments passed to
	-- runEventHandlerInFreeThread for a call to the handler, because those
	-- arguments would stay on the stack for the duration of the thread's
	-- existence, temporarily leaking references. Without access to raw bytecode
	-- there's no way for us to clear the "..." references from the stack.
	while true do
		acquireRunnerThreadAndCallEventHandler(coroutine.yield())
	end
end

-- Connection class
local Connection = {}
Connection.__index = Connection

function Connection.new(signal, fn)
	return setmetatable({
		_connected = true,
		_signal = signal,
		_fn = fn,
		_next = false,
	}, Connection)
end

function Connection:Disconnect()
	self._connected = false

	-- Unhook the node, but DON'T clear it. That way any fire calls that are
	-- currently sitting on this node will be able to iterate forwards off of
	-- it, but any subsequent fire calls will not hit it, and it will be GCed
	-- when no more fire calls are sitting on it.
	if self._signal._handlerListHead == self then
		self._signal._handlerListHead = self._next
	else
		local prev = self._signal._handlerListHead
		while prev and prev._next ~= self do
			prev = prev._next
		end
		if prev then
			prev._next = self._next
		end
	end
end

-- Make Connection strict
setmetatable(Connection, {
	__index = function(_, key)
		error(("Attempt to get Connection::%s (not a valid member)"):format(tostring(key)), 2)
	end,
	__newindex = function(_, key)
		error(("Attempt to set Connection::%s (not a valid member)"):format(tostring(key)), 2)
	end,
})

export type Connection = {
	Disconnect: (self: Connection) -> (),
}

export type Signal<T...> = {
	Connect: (self: Signal<T...>, callback: (T...) -> ()) -> Connection,
	Once: (self: Signal<T...>, callback: (T...) -> ()) -> Connection,
	Fire: (self: Signal<T...>, T...) -> (),
	Wait: (self: Signal<T...>) -> (),
}

-- Signal class
local Signal = {}
Signal.__index = Signal

function Signal.new<T...>(): Signal<T...>
	return setmetatable({
		_handlerListHead = false,
	}, Signal) :: any
end

function Signal:Connect(fn)
	local connection = Connection.new(self, fn)
	if self._handlerListHead then
		connection._next = self._handlerListHead
		self._handlerListHead = connection
	else
		self._handlerListHead = connection
	end
	return connection
end

-- Disconnect all handlers. Since we use a linked list it suffices to clear the
-- reference to the head handler.
function Signal:DisconnectAll()
	self._handlerListHead = false
end

-- Signal:Fire(...) implemented by running the handler functions on the
-- coRunnerThread, and any time the resulting thread yielded without returning
-- to us, that means that it yielded to the Roblox scheduler and has been taken
-- over by Roblox scheduling, meaning we have to make a new coroutine runner.
function Signal:Fire(...)
	local item = self._handlerListHead
	while item do
		if item._connected then
			if not freeRunnerThread then
				freeRunnerThread = coroutine.create(runEventHandlerInFreeThread)
				-- Get the freeRunnerThread to the first yield
				coroutine.resume(freeRunnerThread)
			end
			task.spawn(freeRunnerThread, item._fn, ...)
		end
		item = item._next
	end
end

-- Implement Signal:Wait() in terms of a temporary connection using
-- a Signal:Connect() which disconnects itself.
function Signal:Wait()
	local waitingCoroutine = coroutine.running()
	local cn
	cn = self:Connect(function(...)
		cn:Disconnect()
		task.spawn(waitingCoroutine, ...)
	end)
	return coroutine.yield()
end

-- Implement Signal:Once() in terms of a connection which disconnects
-- itself before running the handler.
function Signal:Once(fn)
	local cn
	cn = self:Connect(function(...)
		if cn._connected then
			cn:Disconnect()
		end
		fn(...)
	end)
	return cn
end

-- Make signal strict
setmetatable(Signal, {
	__index = function(_, key)
		error(("Attempt to get Signal::%s (not a valid member)"):format(tostring(key)), 2)
	end,
	__newindex = function(_, key)
		error(("Attempt to set Signal::%s (not a valid member)"):format(tostring(key)), 2)
	end,
})

return Signal


local Settings = require(script.Parent.Settings)
local Cleanup = require(script.Parent.Cleanup)
local Http = require(script.Parent.Http)

local Signal = require(script.Signal)

local Server = {
	Changed = Signal.new();
	Disconnected = false;
	Connecting = false;
	Connected = false;
	StartTime = nil;
}

function Server:Connect(Yield: boolean | nil, Force: boolean | nil)
	if Server.Disconnected and not Force then
		return
	end

	if not Server.Connecting then
		Server.Connecting = true
		Server.Connected = false

		Server.Changed:Fire(false)

		Cleanup:Hook().PingCheck = task.spawn(function()
			local Timeout = 3

			while Timeout > 0 and not Server.Connected do
				Server.Connected = Http:Ping()

				task.wait(1)
				Timeout -= 1
			end

			Server.Disconnected = false
			Server.Connecting = false

			Server.Changed:Fire(Server.Connected)
		end)
	end

	if Yield and Server.Connecting then
		Server.Changed:Wait()
	end

	return Server.Connected
end

function Server:Update()
	if Server.Connected and not Server.Connecting then
		if Http:Ping() then
			return
		end
		Server:Disconnect(true)
	end
end

function Server:Disconnect(LostConnection: boolean | nil)
	Server.Disconnected = not LostConnection
	Server.Connected = false
	Server.StartTime = nil
	task.wait(1)
	Server.Changed:Fire(Server.Connected)
end

function Server:AutoConnect(...)
	if Settings.autoConnect then
		return Server:Connect(...)
	end
	return
end

function Server:UpdateStartTime()
	if not Server.StartTime then
		Server.StartTime = DateTime.now().UnixTimestampMillis - 2000
	end
end

return Server


local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")

local Settings = {
	["autoConnect"] = true;
	["port"] = 50583;
}

local function Update(Save: boolean | nil)
	for Setting, Value in (Settings) do
		if typeof(Value) ~= "function" then
			local SavedValue = nil

			if not Save then
				SavedValue = plugin:GetSetting(`NexusSync_{Setting}`)
			end

			if SavedValue == nil then
				Settings[Setting] = Value
				task.spawn(plugin.SetSetting, plugin, `NexusSync_{Setting}`, Value)
			else
				Settings[Setting] = SavedValue
			end
		end
	end
end

function Settings:Save()
	Update(true)
end

function Settings:Load()
	if plugin then
		Update()
	end
end

return Settings


return function()
	local Settings = Instance.new("Frame")
	Settings.Name = "Settings"
	Settings.AnchorPoint = Vector2.new(0.5, 0.5)
	Settings.BackgroundColor3 = Color3.fromRGB(46, 46, 46)
	Settings.BorderColor3 = Color3.fromRGB(43, 43, 43)
	Settings.ClipsDescendants = true
	Settings.Position = UDim2.fromScale(0.5, 0.5)
	Settings.Size = UDim2.fromOffset(250, 206)
	Settings.Visible = false

	local Label = Instance.new("TextLabel")
	Label.Name = "Label"
	Label.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Label.BackgroundTransparency = 1
	Label.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Label.BorderSizePixel = 0
	Label.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.Medium,
	  Enum.FontStyle.Normal
	)
	Label.Size = UDim2.new(1, 0, 0, 50)
	Label.Text = "Plugin Settings"
	Label.TextColor3 = Color3.fromRGB(255, 255, 255)
	Label.TextSize = 18
	Label.TextWrapped = true
	Label.Parent = Settings

	local Divider = Instance.new("Frame")
	Divider.Name = "Divider"
	Divider.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Divider.BackgroundTransparency = 0.5
	Divider.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Divider.BorderSizePixel = 0
	Divider.Position = UDim2.fromOffset(0, 50)
	Divider.Size = UDim2.new(1, 0, 0, 1)

	local UIGradient = Instance.new("UIGradient")
	UIGradient.Name = "UIGradient"
	UIGradient.Transparency = NumberSequence.new({
	  NumberSequenceKeypoint.new(0, 1),
	  NumberSequenceKeypoint.new(0.2, 0.75),
	  NumberSequenceKeypoint.new(0.466, 0.731),
	  NumberSequenceKeypoint.new(0.799, 0.737),
	  NumberSequenceKeypoint.new(1, 1),
	})
	UIGradient.Parent = Divider

	Divider.Parent = Settings

	local List = Instance.new("Frame")
	List.Name = "List"
	List.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	List.BackgroundTransparency = 1
	List.BorderColor3 = Color3.fromRGB(0, 0, 0)
	List.BorderSizePixel = 0
	List.Position = UDim2.fromOffset(0, 50)
	List.Size = UDim2.new(1, 0, 1, -50)

	local UIListLayout = Instance.new("UIListLayout")
	UIListLayout.Name = "UIListLayout"
	UIListLayout.SortOrder = Enum.SortOrder.LayoutOrder
	UIListLayout.Parent = List

	local Port = Instance.new("Frame")
	Port.Name = "Port"
	Port.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Port.BackgroundTransparency = 1
	Port.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Port.BorderSizePixel = 0
	Port.Size = UDim2.new(1, 0, 0, 50)

	local Desc = Instance.new("TextLabel")
	Desc.Name = "Desc"
	Desc.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Desc.BackgroundTransparency = 1
	Desc.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Desc.BorderSizePixel = 0
	Desc.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.Medium,
	  Enum.FontStyle.Normal
	)
	Desc.Position = UDim2.fromOffset(15, 22)
	Desc.Size = UDim2.new(1, 0, 0, 20)
	Desc.Text = "vscode port"
	Desc.TextColor3 = Color3.fromRGB(166, 166, 166)
	Desc.TextSize = 14
	Desc.TextWrapped = true
	Desc.TextXAlignment = Enum.TextXAlignment.Left
	Desc.Parent = Port

	local Text = Instance.new("Frame")
	Text.Name = "Text"
	Text.AnchorPoint = Vector2.new(1, 0.5)
	Text.BackgroundColor3 = Color3.fromRGB(53, 53, 53)
	Text.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Text.BorderSizePixel = 0
	Text.Position = UDim2.new(1, -15, 0.5, 0)
	Text.Size = UDim2.fromOffset(60, 25)

	local UICorner = Instance.new("UICorner")
	UICorner.Name = "UICorner"
	UICorner.CornerRadius = UDim.new(0, 4)
	UICorner.Parent = Text

	local UIStroke = Instance.new("UIStroke")
	UIStroke.Name = "UIStroke"
	UIStroke.Color = Color3.fromRGB(97, 97, 97)
	UIStroke.Thickness = 1.33
	UIStroke.Parent = Text

	local Box = Instance.new("TextBox")
	Box.Name = "Box"
	Box.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Box.BackgroundTransparency = 1
	Box.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Box.BorderSizePixel = 0
	Box.ClearTextOnFocus = false
	Box.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.Bold,
	  Enum.FontStyle.Normal
	)
	Box.PlaceholderColor3 = Color3.fromRGB(178, 178, 178)
	Box.PlaceholderText = "50583"
	Box.Size = UDim2.fromScale(1, 1)
	Box.Text = "50583"
	Box.TextColor3 = Color3.fromRGB(255, 255, 255)
	Box.TextSize = 14
	Box.TextWrapped = true
	Box.Parent = Text

	Text.Parent = Port

	local Title = Instance.new("TextLabel")
	Title.Name = "Title"
	Title.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Title.BackgroundTransparency = 1
	Title.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Title.BorderSizePixel = 0
	Title.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.SemiBold,
	  Enum.FontStyle.Normal
	)
	Title.Position = UDim2.fromOffset(15, -1)
	Title.Size = UDim2.new(1, 0, 0, 35)
	Title.Text = "port number"
	Title.TextColor3 = Color3.fromRGB(255, 255, 255)
	Title.TextSize = 14
	Title.TextWrapped = true
	Title.TextXAlignment = Enum.TextXAlignment.Left

	local UIGradient1 = Instance.new("UIGradient")
	UIGradient1.Name = "UIGradient"
	UIGradient1.Color = ColorSequence.new({
	  ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 255, 255)),
	  ColorSequenceKeypoint.new(0.505, Color3.fromRGB(255, 255, 255)),
	  ColorSequenceKeypoint.new(0.576, Color3.fromRGB(223, 223, 223)),
	  ColorSequenceKeypoint.new(1, Color3.fromRGB(255, 255, 255)),
	})
	UIGradient1.Rotation = 90
	UIGradient1.Parent = Title

	Title.Parent = Port

	local Divider1 = Instance.new("Frame")
	Divider1.Name = "Divider"
	Divider1.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Divider1.BackgroundTransparency = 0.5
	Divider1.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Divider1.BorderSizePixel = 0
	Divider1.Position = UDim2.fromOffset(0, 50)
	Divider1.Size = UDim2.new(1, 0, 0, 1)

	local UIGradient2 = Instance.new("UIGradient")
	UIGradient2.Name = "UIGradient"
	UIGradient2.Transparency = NumberSequence.new({
	  NumberSequenceKeypoint.new(0, 1),
	  NumberSequenceKeypoint.new(0.2, 0.75),
	  NumberSequenceKeypoint.new(0.466, 0.731),
	  NumberSequenceKeypoint.new(0.799, 0.737),
	  NumberSequenceKeypoint.new(1, 1),
	})
	UIGradient2.Parent = Divider1

	Divider1.Parent = Port

	Port.Parent = List

	local Auto = Instance.new("Frame")
	Auto.Name = "Auto"
	Auto.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Auto.BackgroundTransparency = 1
	Auto.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Auto.BorderSizePixel = 0
	Auto.Size = UDim2.new(1, 0, 0, 50)

	local Desc1 = Instance.new("TextLabel")
	Desc1.Name = "Desc"
	Desc1.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Desc1.BackgroundTransparency = 1
	Desc1.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Desc1.BorderSizePixel = 0
	Desc1.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.Medium,
	  Enum.FontStyle.Normal
	)
	Desc1.Position = UDim2.fromOffset(15, 22)
	Desc1.Size = UDim2.new(1, 0, 0, 20)
	Desc1.Text = "connect on startup"
	Desc1.TextColor3 = Color3.fromRGB(166, 166, 166)
	Desc1.TextSize = 14
	Desc1.TextWrapped = true
	Desc1.TextXAlignment = Enum.TextXAlignment.Left
	Desc1.Parent = Auto

	local Title1 = Instance.new("TextLabel")
	Title1.Name = "Title"
	Title1.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Title1.BackgroundTransparency = 1
	Title1.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Title1.BorderSizePixel = 0
	Title1.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.SemiBold,
	  Enum.FontStyle.Normal
	)
	Title1.Position = UDim2.fromOffset(15, -1)
	Title1.Size = UDim2.new(1, 0, 0, 35)
	Title1.Text = "auto connect"
	Title1.TextColor3 = Color3.fromRGB(255, 255, 255)
	Title1.TextSize = 14
	Title1.TextWrapped = true
	Title1.TextXAlignment = Enum.TextXAlignment.Left

	local UIGradient3 = Instance.new("UIGradient")
	UIGradient3.Name = "UIGradient"
	UIGradient3.Color = ColorSequence.new({
	  ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 255, 255)),
	  ColorSequenceKeypoint.new(0.505, Color3.fromRGB(255, 255, 255)),
	  ColorSequenceKeypoint.new(0.576, Color3.fromRGB(223, 223, 223)),
	  ColorSequenceKeypoint.new(1, Color3.fromRGB(255, 255, 255)),
	})
	UIGradient3.Rotation = 90
	UIGradient3.Parent = Title1

	Title1.Parent = Auto

	local Divider2 = Instance.new("Frame")
	Divider2.Name = "Divider"
	Divider2.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Divider2.BackgroundTransparency = 0.5
	Divider2.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Divider2.BorderSizePixel = 0
	Divider2.Position = UDim2.fromOffset(0, 50)
	Divider2.Size = UDim2.new(1, 0, 0, 1)

	local UIGradient4 = Instance.new("UIGradient")
	UIGradient4.Name = "UIGradient"
	UIGradient4.Transparency = NumberSequence.new({
	  NumberSequenceKeypoint.new(0, 1),
	  NumberSequenceKeypoint.new(0.2, 0.75),
	  NumberSequenceKeypoint.new(0.466, 0.731),
	  NumberSequenceKeypoint.new(0.799, 0.737),
	  NumberSequenceKeypoint.new(1, 1),
	})
	UIGradient4.Parent = Divider2

	Divider2.Parent = Auto

	local Toggle = Instance.new("TextButton")
	Toggle.Name = "Toggle"
	Toggle.Active = false
	Toggle.AnchorPoint = Vector2.new(1, 0.5)
	Toggle.BackgroundColor3 = Color3.fromRGB(53, 53, 53)
	Toggle.BackgroundTransparency = 1
	Toggle.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Toggle.BorderSizePixel = 0
	Toggle.Position = UDim2.new(1, -15, 0.5, 0)
	Toggle.Selectable = false
	Toggle.Size = UDim2.fromOffset(25, 25)
	Toggle.Text = ""
	Toggle.TextScaled = true
	Toggle.TextWrapped = true

	local Off = Instance.new("Frame")
	Off.Name = "Off"
	Off.BackgroundColor3 = Color3.fromRGB(53, 53, 53)
	Off.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Off.BorderSizePixel = 0
	Off.Size = UDim2.fromScale(1, 1)

	local Icon = Instance.new("ImageLabel")
	Icon.Name = "Icon"
	Icon.AnchorPoint = Vector2.new(0.5, 0.5)
	Icon.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Icon.BackgroundTransparency = 1
	Icon.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Icon.BorderSizePixel = 0
	Icon.Image = "http://www.roblox.com/asset/?id=18892569568"
	Icon.ImageTransparency = 0.9
	Icon.Position = UDim2.fromScale(0.5, 0.5)
	Icon.Size = UDim2.fromOffset(21, 21)
	Icon.Parent = Off

	local Stroke = Instance.new("UIStroke")
	Stroke.Name = "Stroke"
	Stroke.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
	Stroke.Color = Color3.fromRGB(97, 97, 97)
	Stroke.Thickness = 1.33
	Stroke.Transparency = 0.6
	Stroke.Parent = Off

	local Corner = Instance.new("UICorner")
	Corner.Name = "Corner"
	Corner.CornerRadius = UDim.new(0, 4)
	Corner.Parent = Off

	Off.Parent = Toggle

	local On = Instance.new("Frame")
	On.Name = "On"
	On.BackgroundColor3 = Color3.fromRGB(42, 144, 74)
	On.BorderColor3 = Color3.fromRGB(0, 0, 0)
	On.BorderSizePixel = 0
	On.Size = UDim2.fromScale(1, 1)
	On.ZIndex = 3

	local Corner1 = Instance.new("UICorner")
	Corner1.Name = "Corner"
	Corner1.CornerRadius = UDim.new(0, 4)
	Corner1.Parent = On

	local Gradient = Instance.new("UIGradient")
	Gradient.Name = "Gradient"
	Gradient.Color = ColorSequence.new({
	  ColorSequenceKeypoint.new(0, Color3.fromRGB(255, 255, 255)),
	  ColorSequenceKeypoint.new(1, Color3.fromRGB(220, 220, 220)),
	})
	Gradient.Parent = On

	local Stroke1 = Instance.new("UIStroke")
	Stroke1.Name = "Stroke"
	Stroke1.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
	Stroke1.Color = Color3.fromRGB(45, 45, 45)
	Stroke1.Thickness = 1.33
	Stroke1.Parent = On

	local Icon1 = Instance.new("ImageLabel")
	Icon1.Name = "Icon"
	Icon1.AnchorPoint = Vector2.new(0.5, 0.5)
	Icon1.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Icon1.BackgroundTransparency = 1
	Icon1.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Icon1.BorderSizePixel = 0
	Icon1.Image = "http://www.roblox.com/asset/?id=18904285408"
	Icon1.Position = UDim2.fromScale(0.5, 0.5)
	Icon1.Size = UDim2.fromOffset(21, 21)
	Icon1.ZIndex = 3
	Icon1.Parent = On

	On.Parent = Toggle

	Toggle.Parent = Auto

	Auto.Parent = List

	List.Parent = Settings

	local Status = Instance.new("Frame")
	Status.Name = "Status"
	Status.Active = true
	Status.AnchorPoint = Vector2.new(1, 1)
	Status.BackgroundColor3 = Color3.fromRGB(37, 43, 32)
	Status.BackgroundTransparency = 1
	Status.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Status.BorderSizePixel = 0
	Status.Position = UDim2.new(1, -15, 1, -15)
	Status.Size = UDim2.new(0.5, -20, 0, 25)
	Status.ZIndex = 15

	local UICorner1 = Instance.new("UICorner")
	UICorner1.Name = "UICorner"
	UICorner1.CornerRadius = UDim.new(0, 4)
	UICorner1.Parent = Status

	local Stroke2 = Instance.new("UIStroke")
	Stroke2.Name = "Stroke"
	Stroke2.Color = Color3.fromRGB(65, 65, 65)
	Stroke2.Thickness = 1.33
	Stroke2.Parent = Status

	local Label1 = Instance.new("TextLabel")
	Label1.Name = "Label"
	Label1.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Label1.BackgroundTransparency = 1
	Label1.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Label1.BorderSizePixel = 0
	Label1.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.SemiBold,
	  Enum.FontStyle.Normal
	)
	Label1.Size = UDim2.fromScale(1, 1)
	Label1.Text = "not connected"
	Label1.TextColor3 = Color3.fromRGB(111, 111, 111)
	Label1.TextSize = 14
	Label1.TextWrapped = true
	Label1.ZIndex = 50
	Label1.Parent = Status

	Status.Parent = Settings

	local Connect = Instance.new("TextButton")
	Connect.Name = "Connect"
	Connect.AnchorPoint = Vector2.new(0, 1)
	Connect.BackgroundColor3 = Color3.fromRGB(43, 43, 43)
	Connect.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Connect.BorderSizePixel = 0
	Connect.Position = UDim2.new(0, 15, 1, -15)
	Connect.Selectable = false
	Connect.Size = UDim2.new(0.5, -20, 0, 25)
	Connect.Text = ""
	Connect.TextScaled = true
	Connect.TextWrapped = true
	Connect.ZIndex = 15

	local UICorner2 = Instance.new("UICorner")
	UICorner2.Name = "UICorner"
	UICorner2.CornerRadius = UDim.new(0, 4)
	UICorner2.Parent = Connect

	local Stroke3 = Instance.new("UIStroke")
	Stroke3.Name = "Stroke"
	Stroke3.ApplyStrokeMode = Enum.ApplyStrokeMode.Border
	Stroke3.Color = Color3.fromRGB(97, 97, 97)
	Stroke3.Thickness = 1.33
	Stroke3.Parent = Connect

	local Label2 = Instance.new("TextLabel")
	Label2.Name = "Label"
	Label2.BackgroundColor3 = Color3.fromRGB(255, 255, 255)
	Label2.BackgroundTransparency = 1
	Label2.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Label2.BorderSizePixel = 0
	Label2.FontFace = Font.new(
	  "rbxasset://fonts/families/GothamSSm.json",
	  Enum.FontWeight.SemiBold,
	  Enum.FontStyle.Normal
	)
	Label2.Size = UDim2.fromScale(1, 1)
	Label2.Text = "connect"
	Label2.TextColor3 = Color3.fromRGB(255, 255, 255)
	Label2.TextSize = 14
	Label2.TextWrapped = true
	Label2.ZIndex = 50
	Label2.Parent = Connect

	Connect.Parent = Settings

	local Overlay = Instance.new("Frame")
	Overlay.Name = "Overlay"
	Overlay.AnchorPoint = Vector2.new(0, 1)
	Overlay.BackgroundColor3 = Color3.fromRGB(46, 46, 46)
	Overlay.BorderColor3 = Color3.fromRGB(0, 0, 0)
	Overlay.BorderSizePixel = 0
	Overlay.Position = UDim2.fromScale(0, 1)
	Overlay.Size = UDim2.new(1, 0, 0, 45)
	Overlay.ZIndex = 12
	Overlay.Parent = Settings

	return Settings
end


return {
	["Connect"] = {
		[true] = {
			["TextButton"] = {
				["BackgroundColor3"] = Color3.fromRGB(43, 43, 43);
			},

			["TextLabel"] = {
				["TextColor3"] = Color3.fromRGB(255, 255, 255);
				["TextTransparency"] = 0
			},

			["UIStroke"] = {
				["Color"] = Color3.fromRGB(97, 97, 97),
				["Transparency"] = 0
			}
		},

		[false] = {
			["TextButton"] = {
				["BackgroundColor3"] = Color3.fromRGB(36, 36, 36);
			},

			["TextLabel"] = {
				["TextColor3"] = Color3.fromRGB(255, 255, 255);
				["TextTransparency"] = 0.5;
			},

			["UIStroke"] = {
				["Color"] = Color3.fromRGB(33, 33, 33),
				["Transparency"] = 0
			}
		}
	},

	["Disconnected"] = {
		["TextLabel"] = {
			["TextColor3"] = Color3.fromRGB(111, 111, 111)
		},

		["UIStroke"] = {
			["Color"] = Color3.fromRGB(255, 255, 255),
			["Transparency"] = 0.88
		}
	},

	["Connecting"] = {
		["TextLabel"] = {
			["TextColor3"] = Color3.fromRGB(218, 171, 53)
		},

		["UIStroke"] = {
			["Color"] = Color3.fromRGB(255, 195, 42),
			["Transparency"] = 0.88
		}
	},

	["Connected"] = {
		["TextLabel"] = {
			["TextColor3"] = Color3.fromRGB(104, 218, 102)
		},

		["UIStroke"] = {
			["Color"] = Color3.fromRGB(71, 255, 83),
			["Transparency"] = 0.88
		}
	},
}


return {
	["Disconnected"] = "not connected";
	["Connected"] = "connected";
	["Connecting"] = "connecting";
}


return {
	["On"] = {
		[true] = {
			["Frame"] = {
				["BackgroundTransparency"] = 0
			},

			["UIStroke"] = {
				["Transparency"] = 0;
			},

			["ImageLabel"] = {
				["ImageTransparency"] = 0
			}
		},
		[false] = {
			["Frame"] = {
				["BackgroundTransparency"] = 1
			},

			["UIStroke"] = {
				["Transparency"] = 1;
			},

			["ImageLabel"] = {
				["ImageTransparency"] = 1
			}
		}
	},

	["Off"] = {
		[true] = {
			["Frame"] = {
				["BackgroundTransparency"] = 0
			},

			["UIStroke"] = {
				["Transparency"] = 0.6;
			},

			["ImageLabel"] = {
				["ImageTransparency"] = 0.9
			}
		},
		[false] = {
			["Frame"] = {
				["BackgroundTransparency"] = 1
			},

			["UIStroke"] = {
				["Transparency"] = 1;
			},

			["ImageLabel"] = {
				["ImageTransparency"] = 1
			}
		}
	}
}


local Tween = require(script.Parent.Tween)

local Interface = {}
Interface.List = {}

function Interface:Get(Name, Value)
	if Value then
		return Interface.List[Name][Value]
	end
	return Interface.List[Name]
end

function Interface:Apply(GuiObject, Data)
	if Data[GuiObject.ClassName] then
		for Property, Value in (Data[GuiObject.ClassName]) do
			GuiObject[Property] = Value
		end
	end

	for i, v in (GuiObject:GetChildren()) do
		local TweenData = Data[v.ClassName]

		if TweenData then
			for Property, Value in (TweenData) do
				v[Property] = Value
			end
		end
	end
end

function Interface:Tween(GuiObject: GuiObject, Data: {}, Duration: number?)
	if not Duration then
		Duration = 0.2
	end

	if Data[GuiObject.ClassName] then
		Tween(GuiObject, Data[GuiObject.ClassName], Duration)
	end

	for i, v in (GuiObject:GetChildren()) do
		local TweenData = Data[v.ClassName]

		if TweenData then
			Tween(v, TweenData, Duration)
		end
	end
end

for i, v in (script:GetChildren()) do
	if v.ClassName == "ModuleScript" then
		Interface.List[v.Name] = require(v)
	end
end

return Interface


local TweenService = game:GetService("TweenService")

return function (Object, Goal, Duration, Style, Direction)
	Direction = Direction or Enum.EasingDirection.Out
	Style = Style or Enum.EasingStyle.Quad

	local TweenData = TweenInfo.new(
		Duration,
		Style,
		Direction
	)

	local Animation = TweenService:Create(Object, TweenData, Goal)
	Animation:Play()

	return Animation
end


local plugin = plugin or script:FindFirstAncestorWhichIsA("Plugin")
local toolbar = plugin:CreateToolbar("Nexus Sync")

local Settings = require(script.Parent.Settings)
local Server = require(script.Parent.Server)

local Interface = require(script.Interface)
local Gui = require(script.Gui)

local Widget = {
	["Icons"] = {
		[false] = "rbxassetid://18905679801";
		[true] = "rbxassetid://18905678175",
	}
}

local button = toolbar:CreateButton("Nexus Sync", "Open the widget", Widget.Icons[false])

local widgetInfo = DockWidgetPluginGuiInfo.new(
	Enum.InitialDockState.Float, false, false, 250, 230
)

local widgetGui = plugin:CreateDockWidgetPluginGui("Nexus Sync", widgetInfo)
widgetGui.Title = "Nexus Sync"
widgetGui.Name = "Nexus Sync"

local Frame = Gui()
Frame.Size = UDim2.fromScale(1, 1)
Frame.Visible = true
Frame.Parent = widgetGui

widgetGui:GetPropertyChangedSignal("Enabled"):Connect(function()
	button:SetActive(widgetGui.Enabled)
end)

button.Click:Connect(function()
	widgetGui.Enabled = not widgetGui.Enabled
end)

local function GetButtonState(Name, On)
	if (Name == "On" and On) or (Name == "Off" and not On) then
		return true
	end
	return false
end

local function AnimateToggleButton(Button, On)
	local Data = Interface:Get("Toggle")

	for i, StateFrame in(Button:GetChildren()) do
		local ButtonState = GetButtonState(StateFrame.Name, On)
		Interface:Tween(StateFrame, Data[StateFrame.Name][ButtonState])
	end
end

local function UpdateStatus(Type)
	local Data = Interface:Get("Status")
	Interface:Tween(Frame.Status, Data[Type])

	local Text = Interface:Get("Text")
	Frame.Status.Label.Text = Text[Type]
end

local function UpdateConnectButton(Toggle, Text)
	local Data = Interface:Get("Status")["Connect"]
	Interface:Tween(Frame.Connect, Data[Toggle])

	Frame.Connect.AutoButtonColor = Toggle
	Frame.Connect.Label.Text = Text
end

local function Connect()
	if not Server.Connected and not Server.Connecting then
		UpdateConnectButton(false, "Connect")
		UpdateStatus("Connecting")
		Server:Connect(true, true)
	end
end

local function Disconnect()
	if Server.Connected then
		UpdateConnectButton(false, "Disconnect")
		Server:Disconnect()
	end
end

function Widget:Load()
	widgetGui.Enabled = false

	UpdateConnectButton(true, "Connect")
	UpdateStatus("Disconnected")

	for i, SettingFrame in(Frame.List:GetChildren()) do
		local Toggle = Settings.autoConnect

		if SettingFrame:FindFirstChild("Toggle") then
			AnimateToggleButton(SettingFrame.Toggle, Toggle)

			SettingFrame.Toggle.MouseButton1Click:Connect(function()
				Toggle = not Toggle

				AnimateToggleButton(SettingFrame.Toggle, Toggle)

				Settings.autoConnect = Toggle
				Settings:Save()
			end)

		elseif SettingFrame:FindFirstChild("Text") then
			SettingFrame.Text.Box.Text = Settings.port

			SettingFrame.Text.Box.FocusLost:Connect(function()
				local Port = tonumber(SettingFrame.Text.Box.Text)

				if Port then
					Settings.port = Port
					Settings:Save()
				else
					SettingFrame.Text.Box.Text = Settings.port
				end
			end)
		end
	end

	Frame.Connect.MouseButton1Click:Connect(function()
		if Frame.Connect.Active then
			Frame.Connect.Active = false

			if Server.Connected then
				Disconnect()
			else
				Connect()
			end
			Frame.Connect.Active = true
		end
	end)

	Server.Changed:Connect(function(Connected)
		button.Icon = Widget.Icons[Connected == true]

		if Connected then
			UpdateConnectButton(true, "Disconnect")
			UpdateStatus("Connected")

		elseif not Server.Connecting then
			UpdateConnectButton(true, "Connect")
			UpdateStatus("Disconnected")
		end
	end)

	if Settings.autoConnect then
		UpdateConnectButton(false, "Connect")
		UpdateStatus("Connecting")
	end
end

function Widget:Close()
	widgetGui.Enabled = false
end

return Widget


local RunService = game:GetService("RunService")

if RunService:IsRunMode() then
	return
end

plugin.Name = script.Parent.Name

local Settings = require(script.Modules.Settings)
local Cleanup = require(script.Modules.Cleanup)
local Widget = require(script.Modules.Widget)
local Output = require(script.Modules.Output)
local Server = require(script.Modules.Server)

Settings:Load()
Widget:Load()
Output:Load()

local function Deactivate()
	Cleanup:Clear()
	Widget:Close()
end

Cleanup:Hook().Ping = task.spawn(function()
	while task.wait(1) do
		Server:Update()
	end
end)

plugin.Unloading:Connect(function()
	Deactivate()
end)

plugin.Deactivation:Connect(function()
	Deactivate()
end)


--!strict
--!native
--!optimize 2

local RunService = game:GetService("RunService")

local Settings = require(script.Parent.Settings)
local Types = require(script.Parent.Types)
local Solvers = require(script.Parent.Solvers)
local Visualizer = require(script.Parent.Visualizers)

function CreateHitbox(instance: Instance?, raycastParams: RaycastParams?): Types.Hitbox
	local hitbox = {
		Instance = instance,
		RaycastParams = raycastParams or RaycastParams.new(),
		CastData = { CastType = "Raycast", Size = Vector3.zero, CFrame = CFrame.identity, Radius = 0 },
		Active = false,
		Resolution = 60,
		Callbacks = {},
		Attributes = {},
		FilterPartsHit = false,
	}

	local filterList: { [Instance]: boolean } = {}
	local callbacksStart: { Types.StartCallback } = {}
	local callbacksUpdate: { Types.UpdateCallback } = {}
	local callbacksHits: { Types.HitCallback } = {}
	local callbacksStopped: { Types.StopCallback } = {}
	local callbacksDestroying: { Types.DestroyingCallback } = {}

	local activeTimer: number = 0
	local lastClock: number = 0

	local postSimulation: RBXScriptConnection
	local segments: { [Instance]: Types.Segment } = {}

	local function ReturnHitboxAfterCallbackInsertion(
		list: {
			Types.HitCallback
			| Types.StartCallback
			| Types.UpdateCallback
			| Types.StopCallback
			| Types.DestroyingCallback
		},
		callback: Types.HitCallback | Types.StartCallback | Types.UpdateCallback | Types.StopCallback | Types.DestroyingCallback
	): Types.Hitbox
		table.insert(list, callback)
		return hitbox
	end

	function hitbox:GetAllSegments(): { [Instance]: Types.Segment }
		return segments
	end

	function hitbox:GetSegment(instance: Instance): Types.Segment
		return segments[instance]
	end

	function hitbox:SetResolution(resolution: number): Types.Hitbox
		hitbox.Resolution = resolution
		return hitbox
	end

	function hitbox:SetCastData(castData: Types.CastData): Types.Hitbox
		hitbox.CastData = {
			CastType = castData.CastType or "Raycast",
			Size = castData.Size or Vector3.zero,
			CFrame = castData.CFrame or CFrame.identity,
			Radius = castData.Radius or 0,
		}

		for _, segment in hitbox:GetAllSegments() do
			segment.CastData = {
				CastType = segment.Instance:GetAttribute("CastType") or hitbox.CastData.CastType,
				Size = segment.Instance:GetAttribute("CastSize") or hitbox.CastData.Size,
				CFrame = segment.Instance:GetAttribute("CastCFrame") or hitbox.CastData.CFrame,
				Radius = segment.Instance:GetAttribute("CastRadius") or hitbox.CastData.Radius,
			}
		end

		return hitbox
	end

	function hitbox:OnDestroying(callback: Types.DestroyingCallback): Types.Hitbox
		return ReturnHitboxAfterCallbackInsertion(callbacksDestroying, callback)
	end

	function hitbox:BeforeStart(callback: Types.StartCallback): Types.Hitbox
		return ReturnHitboxAfterCallbackInsertion(callbacksStart, callback)
	end

	function hitbox:OnUpdate(callback: Types.UpdateCallback): Types.Hitbox
		return ReturnHitboxAfterCallbackInsertion(callbacksUpdate, callback)
	end

	function hitbox:OnHit(callback: Types.HitCallback): Types.Hitbox
		return ReturnHitboxAfterCallbackInsertion(callbacksHits, callback)
	end

	function hitbox:OnStopped(callback: Types.StopCallback): Types.Hitbox
		return ReturnHitboxAfterCallbackInsertion(callbacksStopped, callback)
	end

	function hitbox:RemoveSegment(descendant: any): Types.Hitbox
		if segments[descendant] then
			segments[descendant] = nil
		end

		return hitbox
	end

	function hitbox:AddSegment(descendant: any): Types.Segment
		local segment = {}

		if descendant:HasTag(Settings.Tag) or descendant.Name == Settings.Tag then
			local solver = Solvers[descendant.ClassName]

			if not solver then
				error(
					`{descendant:GetFullName()}'s class type {descendant.ClassName} is not supported by ShapecastHitbox`
				)
			end

			segment.Distance = 0
			segment.Instance = descendant
			segment.Position = solver:ToPosition(segment)
			segment.LastDirection = -Vector3.one
			segment.CastData = {
				CastType = segment.Instance:GetAttribute("CastType") or hitbox.CastData.CastType,
				Size = segment.Instance:GetAttribute("CastSize") or hitbox.CastData.Size,
				CFrame = segment.Instance:GetAttribute("CastCFrame") or hitbox.CastData.CFrame,
				Radius = segment.Instance:GetAttribute("CastRadius") or hitbox.CastData.Radius,
			}

			function segment:_update()
				if segment.Instance.Parent == nil then
					segments[descendant] = nil
					return
				end

				local castType: Types.CastData = segment.CastData.CastType
				local result, dir: Vector3 = Solvers[castType]:Cast(segment, hitbox.RaycastParams)
				local magnitude: number = dir.Magnitude

				if result then
					hitbox.RaycastResult = result

					if hitbox.FilterPartsHit then
						local currentFilter = hitbox.RaycastParams.FilterDescendantsInstances
						table.insert(currentFilter, result.Instance)

						filterList[result.Instance] = true
						hitbox.RaycastParams.FilterDescendantsInstances = currentFilter
					end

					for _, callback in callbacksHits do
						task.defer(callback, result, segment)
					end
				end

				if Settings.Debug_Visible then
					local adornment: Types.AdornmentData? = Visualizer[castType]:GetAdornment()

					if adornment then
						Visualizer[castType]:ToRender(adornment, segment, solver:ToVisual(segment), magnitude, result)
						task.delay(
							Settings[`Debug_Instance_{castType}_Destroy_Time`],
							Visualizer[castType].ReturnAdornment,
							Visualizer[castType],
							adornment
						)
					end
				end

				if magnitude > 0 then
					segment.LastDirection = dir
					segment.Distance += magnitude
				end

				segment.Position = segment.Instance.WorldPosition
			end

			segments[descendant] = segment
		end

		return segment
	end

	function hitbox:Reconcile(): Types.Hitbox
		assert(hitbox.Instance ~= nil, "No instance tied to this hitbox! Please set with Hitbox.Instance!")
		table.clear(segments)

		for _, descendant: any in hitbox.Instance:GetDescendants() do
			hitbox:AddSegment(descendant)
		end

		return hitbox
	end

	function hitbox:HitStart(timer: number?, overrideParams: RaycastParams?): Types.Hitbox
		for _, segment in hitbox:GetAllSegments() do
			segment.Position = nil -- Reset its state
			segment.Position = Solvers[segment.Instance.ClassName]:ToPosition(segment)
			segment.Distance = 0
		end

		for _, callback in callbacksStart do
			callback()
		end

		hitbox.RaycastResult = nil
		hitbox.RaycastParams = overrideParams or hitbox.RaycastParams
		hitbox.Active = true

		lastClock = os.clock()
		activeTimer = timer or math.huge

		return hitbox
	end

	function hitbox:HitStop(): Types.Hitbox
		if hitbox.Active then
			activeTimer = 0
			hitbox.Active = false

			for _, callback in callbacksStopped do
				callback(function()
					table.clear(callbacksStart)
					table.clear(callbacksUpdate)
					table.clear(callbacksHits)
					table.clear(callbacksStopped)
				end)
			end

			if hitbox.FilterPartsHit then
				local currentFilter = hitbox.RaycastParams.FilterDescendantsInstances

				for instance in filterList do
					local index = table.find(currentFilter, instance)

					if index then
						table.remove(currentFilter, index)
					end
				end

				table.clear(filterList)
				hitbox.RaycastParams.FilterDescendantsInstances = currentFilter
			end
		end

		return hitbox
	end

	function hitbox:Destroy()
		postSimulation:Disconnect()
		hitbox:HitStop()

		for _, callback in callbacksDestroying do
			task.spawn(callback)
		end

		table.clear(hitbox)
		table.clear(callbacksStart)
		table.clear(callbacksUpdate)
		table.clear(callbacksHits)
		table.clear(callbacksStopped)
		table.clear(callbacksDestroying)
	end

	local function Init()
		postSimulation = RunService.PostSimulation:Connect(function()
			if activeTimer > 0 then
				local currentClock: number = os.clock()
				local delta: number = currentClock - lastClock

				if delta >= (1 / hitbox.Resolution) then
					lastClock = currentClock

					for _, callback in callbacksUpdate do
						callback(delta)
					end

					for _, segment in hitbox:GetAllSegments() do
						task.defer(segment._update, segment)
					end
				end

				activeTimer -= delta
			end

			if activeTimer <= 0 then
				hitbox:HitStop()
			end
		end)

		hitbox:Reconcile()
	end

	Init()
	return hitbox
end

return CreateHitbox


return {
	-- The tag or name of the object to find.
	Tag = "DmgPoint",

	Minimum_Stationary_Length = 1,

	-- Reveal if the debug lines should be visible or not.
	Debug_Visible = false,
	Debug_Instance_Raycast_Destroy_Time = 1,
	Debug_Instance_Spherecast_Destroy_Time = 0.1,
	Debug_Instance_Blockcast_Destroy_Time = 0.1,

	Debug_Instance_Transparency = 0.3,
	Debug_Instance_Color = Color3.fromRGB(255, 0, 0),
	Debug_Instance_Hit_Color = Color3.fromRGB(0, 255, 0),
	Debug_Instance_Raycast_Width = 10, --- Only applies to Raycast lines
	Debug_Instance_Derender_Location = CFrame.new(0, math.huge, 0),
}


--!strict
--!native
--!optimize 2

local Settings = require(script.Parent.Parent.Settings)
local Types = require(script.Parent.Parent.Types)

local Solver = {}

function Solver:ToPosition(segment: Types.Segment): Vector3
	return segment.Position or segment.Instance.WorldPosition
end

function Solver:ToDirection(segment: Types.Segment): Vector3
	local direction: Vector3 = segment.Instance.WorldPosition - Solver:ToPosition(segment)

	if direction.Magnitude == 0 then
		direction = segment.LastDirection.Unit * Settings.Minimum_Stationary_Length
	end

	return direction
end

function Solver:ToVisual(segment: Types.Segment): CFrame
	return CFrame.lookAt(Solver:ToPosition(segment), segment.Instance.WorldPosition)
end

return Solver


--!strict
--!native
--!optimize 2

local Types = require(script.Parent.Parent.Types)
local Solver = { Solvers = {} }

function Solver:Cast(segment: Types.Segment, raycastParams: RaycastParams?): RaycastResult?
	local direction: Vector3 = Solver.Solvers[segment.Instance.ClassName]:ToDirection(segment)
	local lastCFrame: CFrame? = segment.CastData._LastCFrameBlockCast

	local result: RaycastResult = workspace:Blockcast(
		(lastCFrame or segment.Instance.WorldCFrame) * segment.CastData.CFrame,
		segment.CastData.Size,
		direction,
		raycastParams
	)

	-- Since Blockcast has an offset, we'll need to defer the lastCFrame position to determine where it is at
	task.defer(function()
		lastCFrame = segment.Instance.WorldCFrame
	end)

	return result, direction
end

return Solver


--!strict
--!native
--!optimize 2

local Types = require(script.Parent.Parent.Types)
local Solver = {}

function Solver:ToPosition(segment: Types.Segment): Vector3
	return segment.Instance.TransformedWorldCFrame.Position
end

function Solver:ToDirection(segment: Types.Segment): Vector3
	if segment.Position then
		return Solver:ToPosition(segment) - segment.Position
	end

	return Vector3.zero
end

function Solver:ToVisual(segment: Types.Segment): CFrame
	return CFrame.lookAt(Solver:ToPosition(segment), segment.Position or Vector3.zero)
end

return Solver


--!strict
--!native
--!optimize 2

local Types = require(script.Parent.Parent.Types)
local Solver = { Solvers = {} }

function Solver:Cast(segment: Types.Segment, raycastParams: RaycastParams?): RaycastResult?
	local solver = Solver.Solvers[segment.Instance.ClassName]
	local origin: Vector3 = solver:ToPosition(segment)
	local direction: Vector3 = solver:ToDirection(segment)

	return workspace:Raycast(origin, direction, raycastParams), direction
end

return Solver


--!strict
--!native
--!optimize 2

local Types = require(script.Parent.Parent.Types)
local Solver = { Solvers = {} }

function Solver:Cast(segment: Types.Segment, raycastParams: RaycastParams?): RaycastResult?
	local solver = Solver.Solvers[segment.Instance.ClassName]
	local direction: Vector3 = solver:ToDirection(segment)
	local position: Vector3 = solver:ToPosition(segment)
	local result: RaycastResult = workspace:Spherecast(position, segment.CastData.Radius, direction, raycastParams)

	return result, direction
end

return Solver


local Solvers = {}

for _, solver in script:GetChildren() do
	local load = require(solver)
	load.Solvers = Solvers
	Solvers[solver.Name] = load
end

return Solvers


--[=[
	@class Hitbox
	Create easy hitboxes using Raycasts and Shapecasts.

	To start using ShapecastHitbox, first initialize your hitbox via
	ShapecastHitbox.new(instance). After you have configured it, you
	can activate the hitbox by calling `Hitbox:HitStart()`.

	To stop the Hitbox, call `Hitbox:HitStop()`. If you are done using
	the hitbox, it is recommended to call `Hitbox:Destroy()` to clean
	up any connections left behind.

	Example usage:
	```lua
	local hitbox = ShapecastHitbox.new(swordHandle, raycastParams)

	-- The hitbox will automatically disable itself after 3 seconds
	-- The duration field is optional and will last indefinitely otherwise
	local duration = 3

	-- HitStart is chainable into OnUpdate. You can alternatively use
	-- `Hitbox:OnUpdate()` on a new line if you prefer multi-lined usage.
	hitbox:HitStart(duration):OnUpdate(function()

		-- We hit something!
		if hitbox.RaycastResult then

			-- HitStop is chainable into OnUpdate or HitStart.
			-- For this example, we'll destroy the hitbox right
			-- after hitting something.
			hitbox:HitStop():Destroy()
		end
	end)

	-- Alternative, we can manually destroy the hitbox at some other time.
	task.delay(10, hitbox.Destroy, hitbox)
	```
]=]

--[=[
	@function new
	@param instance Instance
	@param raycastParams? RaycastParams
	@within Hitbox
	Creates a new hitbox.
]=]

--[=[
	@function GetAllSegments
	@return { [Instance]: Types.Segment }
	@within Hitbox
	Gets all segments of a hitbox. Segments refers to each individual
	point that are being raycasted out of.
]=]

--[=[
	@function GetSegment
	@param instance Instance
	@return Types.Segment
	@within Hitbox
	Gets one segment from the hitbox using the original instance
	as the reference.
]=]

--[=[
	@function SetResolution
	@param resolution number
	@return Types.Hitbox
	@within Hitbox
	Sets the resolution of the hitbox's raycasting. This resolution is capped to
	the user's current frame rate (lower frames = less accurate). Lower accuracy
	can result in better ShapecastHitbox performance so adjust according to the
	context of your project.

	Default: 60
]=]

--[=[
	@function SetCastData
	@param castData Types.CastData
	@return Types.Hitbox
	@within Hitbox
	Sets the current CastData of this hitbox. This is where you can set if the Hitbox should use
	Raycast, Blockcast, or Spherecast. There is also additional funtionality (read the devforum post)
	on `only` adjusting specific segments to use those raycast types.

	Default: { CastType = "Raycast", Size = Vector3.zero, CFrame = CFrame.identity, Radius = 0 }

	Example usage:
	```lua
	hitbox:SetCastData({ CastType = "Blockcast", CFrame = CFrame.new(0, 5, 0) * CFrame.Angles(math.rad(90), 0, 0) })
	hitbox:SetCastData({ CastType = "Spherecast", Radius = 10 })
	```
]=]

--[=[
	@function BeforeStart
	@param startCallback Types.StartCallback
	@return Types.Hitbox
	@within Hitbox
	This callback runs before `HitStart` activates the hitbox.
	Use OnStopped to clean up these callbacks or alternatively use
	`Hitbox:Destroy()`.

	Example usage:
	```lua
	local damage = 0

	hitbox:BeforeStart(function()
		damage = 5
	end):HitStart():OnHit(function(raycastResult, segmentHit)
		raycastResult.Instance.Parent.Humanoid:TakeDamage(damage)
	end)
	```
]=]

--[=[
	@function OnUpdate
	@param updateCallback Types.UpdateCallback
	@return Types.Hitbox
	@within Hitbox
	This callback runs per frame while the hitbox exists. Do note that
	it will still run even if the hitbox is stopped. You can use
	`Hitbox.Active` to determine if a hitbox is active or not. Use OnStopped
	to clean up these callbacks or alternatively `Hitbox:Destroy()`.

	Example usage:
	```lua
	hitbox:HitStart():OnUpdate(function(deltaTime: number)
		if hitbox.Active then
			if hitbox.RaycastResult then
				hitbox:HitStop()
				print("We hit something!")
			end
		else
			--- Hitbox is inactive.
		end
	end)
	```
]=]

--[=[
	@function OnHit
	@param hitCallback Types.HitCallback
	@return Types.Hitbox
	@within Hitbox
	This callback is activated upon the hitbox returning a RaycastResult.
	Use OnStopped to clean up these callbacks or alternatively `Hitbox:Destroy()`.

	Example usage:
	```lua
	hitbox:HitStart():OnHit(function(raycastResult, segmentHit)
		raycastResult.Instance.Parent.Humanoid:TakeDamage(20)

		--- Place a part at the specific place hit
		local part = Instance.new("Part")
		part.Position = raycastResult.Position
		part.CanCollide = false
		part.CanQuery = false
		part.CanTouch = false
		part.Anchored = true
		part.Size = Vector3.one
		part.Color = Color3.fromRGB(0, 255, 0)
		part.Parent = workspace

		task.delay(5, part.Destroy, part)
	end)
	```
]=]

--[=[
	@function OnStopped
	@param stopCallback Types.StopCallback
	@return Types.Hitbox
	@within Hitbox
	This callback runs after HitStop has activated. Do note that
	`Hitbox:Destroy()` does not automatically run this function. This callback
	has a parameter which helps you auto-cleanup every callback used so far.
	If you don't clean up, you may end up having duplicate callback calls
	when reusing the hitbox.

	Example usage:
	```lua
	local damage = 0
	local duration = 3

	hitbox:BeforeStart(function()
		damage = 5
	end):HitStart(duration):OnHit(function(raycastResult, segmentHit)
		raycastResult.Instance.Parent.Humanoid:TakeDamage(damage)
	end):OnUpdate(function(deltaTime)
		damage += (0.1 * deltaTime)
	end):OnStopped(function(cleanCallbacks)
		cleanCallbacks() --- This will clean up this chain
		hitbox:Destroy()
	end)
	```
]=]

--[=[
	@function Reconcile
	@return Types.Hitbox
	@within Hitbox
	Runs automatically the first time a hitbox is initialized. Can be re-ran
	again to make the hitbox re-search the instance for any new changes in the
	hitbox. Do not run frequently as it is not performant.

	Example usage:
	```lua
	local weapon = sword.Handle
	local hitbox = ShapecastHitbox.new(weapon)

	local attachment = Instance.new("Attachment")
	attachment:AddTag("DmgPoint")
	attachment.Parent = weapon

	hitbox:Reconcile()
	```
]=]

--[=[
	@function HitStart
	@param timer? number
	@param overrideParams? RaycastParams
	@return Types.Hitbox
	@within Hitbox
	Activates the hitbox. Can be given an optional timer parameter to make
	the hitbox automatically stop after a certain amount of seconds. OverrideParams
	can be used to switch RaycastParams on the fly (which the hitbox will default to).
	Will activate all BeforeStart callbacks before activating the hitbox.

	Example usage:
	```lua
	local raycastParams = RaycastParams.new()

	hitbox:HitStart(5, raycastParams)
	```
]=]

--[=[
	@function HitStop
	@return Types.Hitbox
	@within Hitbox
	Deactivates the hitbox. Will call all OnStopped callbacks after deactivation.
]=]

--[=[
	@function Destroy
	@within Hitbox
	Disconnects the scheduler. When no references to the hitbox remain, it will be
	automatically garbage collected.
]=]

export type Hitbox = {
	Instance: Instance?,
	RaycastParams: RaycastParams?,
	RaycastResult: RaycastResult?,
	Active: boolean,
	Resolution: number,
	CastData: CastData,
	Attributes: { [any]: any },
	FilterPartsHit: boolean,
	GetAllSegments: (self: Hitbox) -> { [any]: Segment },
	GetSegment: (self: Hitbox, instance: Instance) -> Segment,
	AddSegment: (self: Hitbox, descendant: any) -> Segment,
	RemoveSegment: (self: Hitbox, descendant: any) -> Hitbox,
	Reconcile: (self: Hitbox) -> (),
	HitStart: (self: Hitbox, timer: number?, raycastParams: RaycastParams?) -> Hitbox,
	HitStop: (self: Hitbox) -> Hitbox,
	Destroy: (self: Hitbox) -> (),
	OnDestroying: (self: Hitbox, destroyCallback: DestroyingCallback) -> (),
	SetResolution: (self: Hitbox, resolution: number) -> Hitbox,
	SetCastData: (self: Hitbox, castData: CastData) -> Hitbox,
	BeforeStart: (self: Hitbox, startCallback: StartCallback) -> Hitbox,
	OnUpdate: (self: Hitbox, updateCallback: UpdateCallback) -> Hitbox,
	OnHit: (self: Hitbox, hitCallback: HitCallback) -> Hitbox,
	OnStopped: (self: Hitbox, stopCallback: StopCallback) -> Hitbox,
}

--[=[
	@class Segment
]=]

--[=[
	@within Segment
	@prop Instance any
	The instance that this segment belongs to.
]=]

--[=[
	@within Segment
	@prop Distance number
	How far this segment has traveled while the hitbox is active.
]=]

--[=[
	@within Segment
	@prop Position Vector3
	The current position of this segment in the frame.
]=]

--[=[
	@within Segment
	@prop RaycastResult? RaycastResult
	The last RaycastResult returned by this segment.
]=]

--[=[
	@within Segment
	@prop CastData CastData
	The current CastData of the segment. Defaults to the parent Hitbox's
	CastData. Can be individually modified to cast differently than other
	segments.
]=]

export type Segment = {
	Instance: any,
	Distance: number,
	Position: Vector3?,
	LastDirection: Vector3,
	RaycastResult: RaycastResult?,
	CastData: CastData,
	_update: (self: Segment) -> (),
}

--[=[
	@class CastData
]=]

--[=[
	@within CastData
	@prop CastType string
	The current cast to use. Can be Blockcast, Spherecast, or Raycast.

	Defaults to `Raycast`.
]=]

--[=[
	@within CastData
	@prop CFrame CFrame
	The current CFrame of Blockcast. Does nothing if it is not this CastType.

	Defaults to `CFrame.identity`.
]=]

--[=[
	@within CastData
	@prop Size Vector3
	The current size of Blockcast. Does nothing if it is not this CastType.

	Defaults to `Vector3.zero`.
]=]

--[=[
	@within CastData
	@prop Radius number
	The current radius size of Spherecast. Does nothing if it is not this CastType.

	Defaults to `0`.
]=]

export type CastData = {
	CastType: string,
	CFrame: CFrame,
	Size: Vector3,
	Radius: number,
	_LastCFrameBlockCast: CFrame?,
}

--[=[
	@class AdornmentData
]=]

--[=[
	@within AdornmentData
	@prop Adornment LineHandleAdornment
]=]

export type AdornmentData = {
	Adornment: LineHandleAdornment,
	LastUse: number,
}

--[=[
	@class SphereAdornmentData
]=]

--[=[
	@within SphereAdornmentData
	@prop Adornment SphereHandleAdornment
]=]

export type SphereAdornmentData = {
	Adornment: SphereHandleAdornment,
	LastUse: number,
}

--[=[
	@class BoxAdornmentData
]=]

--[=[
	@within BoxAdornmentData
	@prop Adornment BoxHandleAdornment
]=]

export type BoxAdornmentData = {
	Adornment: BoxHandleAdornment,
	LastUse: number,
}

export type DestroyingCallback = () -> ()
export type StartCallback = () -> ()
export type UpdateCallback = (delta: number) -> ()
export type HitCallback = (raycastResult: RaycastResult, segmentHit: Segment) -> ()
export type StopCallback = typeof(function(clearCallbacks) end)

return {}


--!native
--!strict
--!optimize 2

--- While using Trails as debugging lines would be nice (and more performant!), they are susceptible to frame drops and are inaccurate
--- at low graphic settings which can create false results
-- @author Phin

local Types = require(script.Parent.Parent.Types)
local Settings = require(script.Parent.Parent.Settings)

local cache = {}
cache.__index = cache
cache.__type = `ShapecastHitbox_{script.Name}`
cache._AdornmentInUse = {}
cache._AdornmentInReserve = {}

--- BoxAdornmentData type

--- Internal function to create an BoxAdornmentData type
--- Creates a LineHandleAdornment and a timer value
function cache:_CreateAdornment(): Types.BoxAdornmentData
	local line: BoxHandleAdornment = Instance.new("BoxHandleAdornment")
	line.Name = "_ShapecastHitboxDebugger"
	line.Color3 = Settings.Debug_Instance_Color
	line.Transparency = Settings.Debug_Instance_Transparency
	line.Size = Vector3.zero
	line.CFrame = Settings.Debug_Instance_Derender_Location

	line.Adornee = workspace.Terrain
	line.Parent = workspace.Terrain

	return {
		Adornment = line,
		LastUse = 0,
	}
end

--- Gets an BoxAdornmentData type. Creates one if there isn't one currently available.
function cache:GetAdornment(): Types.BoxAdornmentData?
	if #cache._AdornmentInReserve <= 0 then
		--- Create a new LineAdornmentHandle if none are in reserve
		local adornment: Types.BoxAdornmentData = cache:_CreateAdornment()
		table.insert(cache._AdornmentInReserve, adornment)
	end

	local adornment: Types.BoxAdornmentData? = table.remove(cache._AdornmentInReserve, 1)

	if adornment then
		adornment.Adornment.Visible = true
		adornment.LastUse = os.clock()
		table.insert(cache._AdornmentInUse, adornment)
	end

	return adornment
end

function cache:ToRender(
	adornment: Types.BoxAdornmentData,
	segment: Types.Segment,
	cframe: CFrame,
	magnitude: number,
	raycastResult: RaycastResult
)
	adornment.Adornment.Size = segment.CastData.Size
	adornment.Adornment.CFrame = (segment.CastData._LastCFrameBlockCast or segment.Instance.WorldCFrame)
		* segment.CastData.CFrame
	adornment.Adornment.Color3 = raycastResult and Settings.Debug_Instance_Hit_Color or Settings.Debug_Instance_Color
	adornment.Adornment.ZIndex = raycastResult and 999 or -1
end

--- Returns an BoxAdornmentData back into the cache.
-- @param BoxAdornmentData
function cache:ReturnAdornment(adornment: Types.BoxAdornmentData)
	adornment.Adornment.Size = Vector3.zero
	adornment.Adornment.Visible = false
	adornment.Adornment.CFrame = Settings.Debug_Instance_Derender_Location
	table.insert(cache._AdornmentInReserve, adornment)
end

--- Clears the cache in reserve. Should only be used if you want to free up some memory.
--- If you end up turning on the visualizer again for this session, the cache will fill up again.
--- Does not clear adornments that are currently in use.
function cache:Clear()
	for i: number = #cache._AdornmentInReserve, 1, -1 do
		if cache._AdornmentInReserve[i].Adornment then
			cache._AdornmentInReserve[i].Adornment:Destroy()
		end

		table.remove(cache._AdornmentInReserve, i)
	end
end

return cache


--!native
--!strict
--!optimize 2

--- While using Trails as debugging lines would be nice (and more performant!), they are susceptible to frame drops and are inaccurate
--- at low graphic settings which can create false results
-- @author Phin

local Types = require(script.Parent.Parent.Types)
local Settings = require(script.Parent.Parent.Settings)

local cache = {}
cache.__index = cache
cache.__type = `ShapecastHitbox_{script.Name}`
cache._AdornmentInUse = {}
cache._AdornmentInReserve = {}

--- AdornmentData type

--- Internal function to create an AdornmentData type
--- Creates a LineHandleAdornment and a timer value
function cache:_CreateAdornment(): Types.AdornmentData
	local line: LineHandleAdornment = Instance.new("LineHandleAdornment")
	line.Name = "_ShapecastHitboxDebugger"
	line.Color3 = Settings.Debug_Instance_Color
	line.Thickness = Settings.Debug_Instance_Raycast_Width
	line.Transparency = Settings.Debug_Instance_Transparency

	line.Length = 0
	line.CFrame = Settings.Debug_Instance_Derender_Location

	line.Adornee = workspace.Terrain
	line.Parent = workspace.Terrain

	return {
		Adornment = line,
		LastUse = 0,
	}
end

--- Gets an AdornmentData type. Creates one if there isn't one currently available.
function cache:GetAdornment(): Types.AdornmentData?
	if #cache._AdornmentInReserve <= 0 then
		--- Create a new LineAdornmentHandle if none are in reserve
		local adornment: Types.AdornmentData = cache:_CreateAdornment()
		table.insert(cache._AdornmentInReserve, adornment)
	end

	local adornment: Types.AdornmentData? = table.remove(cache._AdornmentInReserve, 1)

	if adornment then
		adornment.Adornment.Visible = true
		adornment.LastUse = os.clock()
		table.insert(cache._AdornmentInUse, adornment)
	end

	return adornment
end

function cache:ToRender(
	adornment: Types.AdornmentData,
	segment: Types.Segment,
	cframe: CFrame,
	magnitude: number,
	raycastResult: RaycastResult
)
	adornment.Adornment.Length = magnitude
	adornment.Adornment.CFrame = cframe
	adornment.Adornment.Color3 = raycastResult and Settings.Debug_Instance_Hit_Color or Settings.Debug_Instance_Color
	adornment.Adornment.ZIndex = raycastResult and 999 or -1
end

--- Returns an AdornmentData back into the cache.
-- @param AdornmentData
function cache:ReturnAdornment(adornment: Types.AdornmentData)
	adornment.Adornment.Length = 0
	adornment.Adornment.Visible = false
	adornment.Adornment.CFrame = Settings.Debug_Instance_Derender_Location
	table.insert(cache._AdornmentInReserve, adornment)
end

--- Clears the cache in reserve. Should only be used if you want to free up some memory.
--- If you end up turning on the visualizer again for this session, the cache will fill up again.
--- Does not clear adornments that are currently in use.
function cache:Clear()
	for i: number = #cache._AdornmentInReserve, 1, -1 do
		if cache._AdornmentInReserve[i].Adornment then
			cache._AdornmentInReserve[i].Adornment:Destroy()
		end

		table.remove(cache._AdornmentInReserve, i)
	end
end

return cache


--!native
--!strict
--!optimize 2

--- While using Trails as debugging lines would be nice (and more performant!), they are susceptible to frame drops and are inaccurate
--- at low graphic settings which can create false results
-- @author Phin

local Types = require(script.Parent.Parent.Types)
local Settings = require(script.Parent.Parent.Settings)

local cache = {}
cache.__index = cache
cache.__type = `ShapecastHitbox_{script.Name}`
cache._AdornmentInUse = {}
cache._AdornmentInReserve = {}

--- SphereAdornmentData type

--- Internal function to create an SphereAdornmentData type
--- Creates a LineHandleAdornment and a timer value
function cache:_CreateAdornment(): Types.SphereAdornmentData
	local line: SphereHandleAdornment = Instance.new("SphereHandleAdornment")
	line.Name = "_ShapecastHitboxDebugger"
	line.Color3 = Settings.Debug_Instance_Color
	line.Transparency = Settings.Debug_Instance_Transparency
	line.Radius = 1
	line.CFrame = Settings.Debug_Instance_Derender_Location

	line.Adornee = workspace.Terrain
	line.Parent = workspace.Terrain

	return {
		Adornment = line,
		LastUse = 0,
	}
end

--- Gets an SphereAdornmentData type. Creates one if there isn't one currently available.
function cache:GetAdornment(): Types.SphereAdornmentData?
	if #cache._AdornmentInReserve <= 0 then
		--- Create a new LineAdornmentHandle if none are in reserve
		local adornment: Types.SphereAdornmentData = cache:_CreateAdornment()
		table.insert(cache._AdornmentInReserve, adornment)
	end

	local adornment: Types.SphereAdornmentData? = table.remove(cache._AdornmentInReserve, 1)

	if adornment then
		adornment.Adornment.Visible = true
		adornment.LastUse = os.clock()
		table.insert(cache._AdornmentInUse, adornment)
	end

	return adornment
end

function cache:ToRender(
	adornment: Types.SphereAdornmentData,
	segment: Types.Segment,
	cframe: CFrame,
	magnitude: number,
	raycastResult: RaycastResult
)
	adornment.Adornment.Radius = segment.CastData.Radius
	adornment.Adornment.CFrame = cframe
	adornment.Adornment.Color3 = raycastResult and Settings.Debug_Instance_Hit_Color or Settings.Debug_Instance_Color
	adornment.Adornment.ZIndex = raycastResult and 999 or -1
end

--- Returns an SphereAdornmentData back into the cache.
-- @param SphereAdornmentData
function cache:ReturnAdornment(adornment: Types.SphereAdornmentData)
	adornment.Adornment.Radius = 0
	adornment.Adornment.Visible = false
	adornment.Adornment.CFrame = Settings.Debug_Instance_Derender_Location
	table.insert(cache._AdornmentInReserve, adornment)
end

--- Clears the cache in reserve. Should only be used if you want to free up some memory.
--- If you end up turning on the visualizer again for this session, the cache will fill up again.
--- Does not clear adornments that are currently in use.
function cache:Clear()
	for i: number = #cache._AdornmentInReserve, 1, -1 do
		if cache._AdornmentInReserve[i].Adornment then
			cache._AdornmentInReserve[i].Adornment:Destroy()
		end

		table.remove(cache._AdornmentInReserve, i)
	end
end

return cache


local Visualizers = {}

for _, visualizer in script:GetChildren() do
	local load = require(visualizer)
	Visualizers[visualizer.Name] = load
end

return Visualizers


--!strict
--!native
--!optimize 2

-- ShapecastHitbox
-- Phin
-- August 2024

--[=[
	Documentation of the hitbox can be found inside at ShapecastHitbox/Types.luau.
	This module is still relatively new and experimental, use at your own risk for
	production-level projects.
]=]

local Types = require(script.Types)

export type Segment = Types.Segment
export type CastData = Types.CastData
export type Hitbox = Types.Hitbox
export type UpdateCallback = Types.UpdateCallback
export type HitCallback = Types.HitCallback
export type StopCallback = Types.StopCallback
export type AdornmentData = Types.AdornmentData
export type SphereAdornmentData = Types.SphereAdornmentData
export type BoxAdornmentData = Types.BoxAdornmentData

local ActiveHitboxes = {}

local function NewHitbox(...): Hitbox
	local hitbox = require(script.Hitbox)(...)
	local instance = select(1, ...)

	if not ActiveHitboxes[instance] then
		ActiveHitboxes[instance] = {}
	end

	table.insert(ActiveHitboxes[instance], hitbox)

	hitbox:OnDestroying(function()
		if not ActiveHitboxes[instance] then
			return
		end

		local totalHitboxes: number = #ActiveHitboxes[instance]

		for i = totalHitboxes, 1, -1 do
			if ActiveHitboxes[instance][i] == hitbox then
				table.remove(ActiveHitboxes[instance], i)

				if totalHitboxes == 1 then
					ActiveHitboxes[instance] = nil
				end
			end
		end
	end)

	return hitbox
end

local ShapecastHitbox = {
	new = NewHitbox,
	Settings = require(script.Settings),
	CastTypes = {
		Raycast = "Raycast",
		Blockcast = "Blockcast",
		Spherecast = "Spherecast",
	},
}

function ShapecastHitbox:GetHitboxesFromInstance(instance: Instance): { Hitbox }
	return ActiveHitboxes[instance] or {}
end

function ShapecastHitbox:GetActiveInstances(): { [Instance]: { Hitbox } }
	return ActiveHitboxes
end

return ShapecastHitbox


-------------------------------------------------------------------------------------
-- Bitbuf implements a bit-level buffer, suitable for
-- serialization and storing data in-memory.
-------------------------------------------------------------------------------------

--!native
--!strict

local Bitbuf = {}

-------------------------------------------------------------------------------------
-- Internal utility functions.
-------------------------------------------------------------------------------------

local function int_from_uint(size: number, v: number): number
	local n = 2 ^ size
	v %= n

	if v >= n / 2 then
		return v - n
	end

	return v
end

local function int_to_uint(size: number, v: number): number
	return v % 2 ^ size
end

local function float_to_fixed(i: number, f: number, v: number): number
	return int_from_uint(i + f, math.floor(v * 2 ^ f))
end

local function float_from_fixed(i: number, f: number, v: number): number
	return math.floor(int_from_uint(i + f, v)) * 2 ^ -f
end

local function float_to_ufixed(i: number, f: number, v: number): number
	return int_to_uint(i + f, math.floor(v * 2 ^ f))
end

local function float_from_ufixed(i: number, f: number, v: number): number
	return math.floor(int_to_uint(i + f, v)) * 2 ^ -f
end

-------------------------------------------------------------------------------------
-- Buffer is a variable-size bit-level buffer with methods for reading and
-- writing various common types.
--
-- The buffer has a cursor to determine where data is read and written, indexed
-- in bits. Methods that read and write advance the cursor automatically by the
-- given size. The buffer grows when the cursor moves beyond the length of the
-- buffer. Bits read past the length of the buffer are returned as zeros.
--
-- Bits are written in little-endian.
-------------------------------------------------------------------------------------

local Buffer = {}
Buffer.__index = Buffer

export type Class = typeof(setmetatable({} :: {
	buf: { number },
	len: number,
	i: number,
}, Buffer))

-------------------------------------------------------------------------------------
-- Returns a new Buffer *size* bits in length, with the
-- cursor set to 0. Defaults to a zero-length buffer.
-------------------------------------------------------------------------------------

function Bitbuf.new(size: number?): Class
	size = size or 0
	assert(size)

	return setmetatable({
		buf = table.create(math.ceil(size / 32), 0),
		len = size,
		i = 0,
	}, Buffer)
end

-------------------------------------------------------------------------------------
-- Returns a Buffer with the contents initialized
-- with the bits of *s*. The cursor is set to 0.
-------------------------------------------------------------------------------------

function Bitbuf.fromString(s: string): Class
	local n = math.ceil(#s / 4)

	local self = {
		buf = table.create(n, 0),
		len = #s * 8,
		i = 0,
	}

	for i = 0, math.floor(#s / 4) - 1 do
		local a, b, c, d = string.byte(s, i * 4 + 1, i * 4 + 4)
		self.buf[i + 1] = bit32.bor(a, b * 256, c * 65536, d * 16777216)
	end

	for i = 0, #s % 4 - 1 do
		local b = assert(s:byte((n - 1) * 4 + i + 1))
		self.buf[n] = bit32.bor(self.buf[n], b * 256 ^ i)
	end

	return setmetatable(self, Buffer)
end

-------------------------------------------------------------------------------------
-- Converts the content of the buffer to a string. If the length is not
-- a multiple of 8, then the result will be padded with zeros until it is.
-------------------------------------------------------------------------------------

function Buffer.String(self: Class): string
	local n = math.ceil(self.len / 32)
	local s = table.create(n, "")

	for i in ipairs(s) do
		local v = self.buf[i]

		if v then
			s[i] = string.pack("<I4", v)
		else
			s[i] = "\0\0\0\0"
		end
	end

	local rem = self.len % 32

	if rem > 0 then
		-- Truncate to length.
		local v = bit32.band(self.buf[n] or 0, bit32.lshift(1, rem) - 1)
		local width = math.floor((self.len - 1) / 8) % 4 + 1
		s[n] = string.pack("<I" .. width, v)
	end

	return table.concat(s)
end

-------------------------------------------------------------------------------------
-- Writes the first *size* bits of the unsigned integer *v* to
-- the buffer, and advances the cursor by *size* bits. *size* must be
-- an integer between 0 and 32. *v* is normalized according to the bit32 library.
--
-- The capacity of the buffer is extended as needed to write the value.
-- The buffer is assumed to be a sequence of 32-bit unsigned integers.
-------------------------------------------------------------------------------------

function Buffer.WriteUnit(self: Class, size: number, v: number)
	assert(size >= 0 and size <= 32, "size must be in range [0, 32]")

	if size == 0 then
		return
	end

	-- Index of unit in buffer.
	local i = bit32.rshift(self.i, 5) + 1

	-- Index of first unread bit in unit.
	local u = self.i % 32

	if u == 0 and size == 32 then
		self.buf[i] = bit32.band(v, 0xFFFFFFFF)
	else
		-- Index of of unit end relative to first unread bit.
		local f = 32 - u
		local r = size - f

		if r <= 0 then
			-- Size fits within current unit.
			self.buf[i] = bit32.replace(self.buf[i] or 0, v, u, size)
		else
			-- Size extends into next unit.
			self.buf[i] = bit32.replace(self.buf[i] or 0, bit32.extract(v, 0, f), u, f)
			self.buf[i + 1] = bit32.replace(self.buf[i + 1] or 0, bit32.extract(v, f, r), 0, r)
		end
	end

	self.i += size

	if self.i > self.len then
		self.len = self.i
	end
end

-------------------------------------------------------------------------------------
-- Reads *size* bits as an unsigned integer from the buffer, and advances
-- the cursor by *size* bits. *size* must be an integer between 0 and 32.
--
-- The buffer is assumed to be a sequence of 32-bit unsigned integers.
-- Bits past the length of the buffer are read as zeros.
-------------------------------------------------------------------------------------

function Buffer.ReadUnit(self: Class, size: number): number
	assert(size >= 0 and size <= 32, "size must be in range [0, 32]")

	if size == 0 then
		return 0
	end

	local i = bit32.rshift(self.i, 5) + 1
	local u = self.i % 32

	self.i += size

	if self.i > self.len then
		self.len = self.i
	end

	if u == 0 and size == 32 then
		return self.buf[i] or 0
	end

	local f = 32 - u
	local r = f - size

	if r >= 0 then
		return bit32.extract(self.buf[i] or 0, u, size)
	end

	return bit32.bor(bit32.extract(self.buf[i] or 0, u, f), bit32.lshift(bit32.extract(self.buf[i + 1] or 0, 0, -r), f))
end

-------------------------------------------------------------------------------------
-- Returns the length of the buffer in bits.
-------------------------------------------------------------------------------------

function Buffer.Len(self: Class): number
	return self.len
end

-------------------------------------------------------------------------------------
-- Shrinks or grows the length of the buffer. Shrinking truncates
-- the buffer, and growing pads the buffer with zeros. If the
-- cursor is greater than *size*, then it is set to *size*.
-------------------------------------------------------------------------------------

function Buffer.SetLen(self: Class, size: number)
	-- Clear removed portion of buffer.
	size = if size < 0 then 0 else size

	if size < self.len then
		local lower = math.floor(size / 32) + 1

		-- Truncate lower unit.
		if size % 32 == 0 then
			self.buf[lower] = nil
		else
			self.buf[lower] = bit32.band(self.buf[lower], 2 ^ (size % 32) - 1)
		end

		-- Clear everything after lower unit.
		local upper = math.floor((self.len - 1) / 32) + 1

		for i = lower + 1, upper do
			self.buf[i] = nil
		end
	end

	self.len = size

	if self.i > size then
		self.i = size
	end
end

-------------------------------------------------------------------------------------
-- Returns the position of the cursor, in bits.
-------------------------------------------------------------------------------------

function Buffer.Index(self: Class): number
	return self.i
end

-------------------------------------------------------------------------------------
-- Sets the position of the cursor to *i*, in bits. If *i* is greater
-- than the length of the buffer, then buffer is grown to length *i*.
-------------------------------------------------------------------------------------

function Buffer.SetIndex(self: Class, i: number)
	i = (if i < 0 then 0 else i)
	self.i = i

	if i > self.len then
		self.len = i
	end
end

-------------------------------------------------------------------------------------
-- Returns true if *size* bits can be read from or
-- written to the buffer without exceeding its length.
-------------------------------------------------------------------------------------

function Buffer.Fits(self: Class, size: number): boolean
	return size <= self.len - self.i
end

-------------------------------------------------------------------------------------
-- Pads the buffer with *size* zero bits. Does nothing
-- if *size* is less than or equal to zero.
-------------------------------------------------------------------------------------

local function writePad(self: Class, size: number)
	for i = 1, math.floor(size / 32) do
		self:WriteUnit(32, 0)
	end

	self:WriteUnit(size % 32, 0)
end

function Buffer.WritePad(self: Class, size: number)
	if size > 0 then
		writePad(self, size)
	end
end

-------------------------------------------------------------------------------------
-- Moves the cursor by *size* bits without reading any data.
-- Does nothing if *size* is less than or equal to zero.
-------------------------------------------------------------------------------------

local function readPad(self: Class, size: number)
	self.i += size

	if self.i > self.len then
		self.len = self.i
	end
end

function Buffer.ReadPad(self: Class, size: number)
	if size > 0 then
		readPad(self, size)
	end
end

-------------------------------------------------------------------------------------
-- Pads the buffer with zero bits until the position of the cursor is a
-- multiple of *size*. Does nothing if *size* is less than or equal to 1.
-------------------------------------------------------------------------------------

function Buffer.WriteAlign(self: Class, size: number)
	assert(type(size) == "number", "number expected")

	if size > 1 and self.i % size ~= 0 then
		size = math.floor(math.ceil(self.i / size) * size - self.i)
		writePad(self, size)
	end
end

-------------------------------------------------------------------------------------
-- Moves the cursor until its position is a multiple of *size* without
-- reading any data. Does nothing if *size* is less than or equal to 1.
-------------------------------------------------------------------------------------

function Buffer.ReadAlign(self: Class, size: number)
	assert(type(size) == "number", "number expected")

	if size > 1 and self.i % size ~= 0 then
		size = math.floor(math.ceil(self.i / size) * size - self.i)
		readPad(self, size)
	end
end

-------------------------------------------------------------------------------------
-- Clears the buffer, setting the length and cursor to 0.
-------------------------------------------------------------------------------------

function Buffer.Reset(self: Class)
	self.i = 0
	self.len = 0
	table.clear(self.buf)
end

-------------------------------------------------------------------------------------
-- Writes a raw sequence of bytes by assuming
-- that the buffer is aligned to 8 bits
-------------------------------------------------------------------------------------

local function fastWriteBytes(self: Class, s: string)
	-- Handle short string.
	if #s <= 4 then
		self:WriteUnit(#s * 8, (string.unpack("<I" .. #s, s)))
		return
	end

	-- Write until cursor is aligned to unit.
	local a = math.floor(3 - (self.i / 8 - 1) % 4)

	if a > 0 then
		self:WriteUnit(a * 8, (string.unpack("<I" .. a, s)))
	end

	-- Write unit-aligned groups of 32 bits.
	local c = math.floor((#s - a) / 4)
	local n = bit32.rshift(self.i, 5) + 1

	for i = 0, c - 1 do
		self.buf[n + i] = string.unpack("<I4", s, a + i * 4 + 1)
	end

	self.i += c * 32

	if self.i > self.len then
		self.len = self.i
	end

	-- Write remainder.
	local r = (#s - a) % 4

	if r > 0 then
		self:WriteUnit(r * 8, string.unpack("<I" .. r, s, #s - r + 1))
	end
end

-------------------------------------------------------------------------------------
-- Writes *v* by interpreting it as a raw sequence of bytes.
-------------------------------------------------------------------------------------

function Buffer.WriteBytes(self: Class, v: string)
	assert(type(v) == "string", "string expected")

	if v == "" then
		return
	end

	if self.i % 8 == 0 then
		fastWriteBytes(self, v)
		return
	end

	for i = 1, #v do
		self:WriteUnit(8, string.byte(v, i))
	end
end

-------------------------------------------------------------------------------------
-- Reads a raw sequence of bytes by assuming
-- that the buffer is aligned to 8 bits.
-------------------------------------------------------------------------------------

local function fastReadBytes(self: Class, size: number): string
	-- Handle short string.
	if size <= 4 then
		return string.pack("<I" .. size, self:ReadUnit(size * 8))
	end

	local a = math.floor(3 - (self.i / 8 - 1) % 4)
	local r = (size - a) % 4

	local v = table.create((size - a) / 4 + r)
	local i = 1

	-- Read until cursor is aligned to unit.
	if a > 0 then
		v[i] = string.pack("<I" .. a, self:ReadUnit(a * 8))
		i += 1
	end

	-- Read unit-aligned groups of 32 bits.
	local c = math.floor((size - a) / 4)
	local n = bit32.rshift(self.i, 5) + 1

	for j = 0, c - 1 do
		local x = self.buf[n + j]

		if x then
			v[i] = string.pack("<I4", x)
		else
			v[i] = "\0\0\0\0"
		end

		i += 1
	end

	self.i += c * 32

	if i > self.len then
		self.len = i
	end

	-- Read remainder.
	if r > 0 then
		v[i] = string.pack("<I" .. r, self:ReadUnit(r * 8))
	end

	return table.concat(v)
end

-------------------------------------------------------------------------------------
-- Reads *size* bytes from the buffer as a raw sequence of bytes.
-------------------------------------------------------------------------------------

function Buffer.ReadBytes(self: Class, size: number): string
	assert(type(size) == "number", "number expected")

	if size == 0 then
		return ""
	end

	if self.i % 8 == 0 then
		return fastReadBytes(self, size)
	end

	local v = table.create(size, "")

	for i = 1, size do
		v[i] = string.char(self:ReadUnit(8))
	end

	return table.concat(v)
end

-------------------------------------------------------------------------------------
-- Writes *v* as an unsigned integer of *size* bits.
-- *size* must be an integer between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.WriteUint(self: Class, size: number, v: number)
	assert(type(size) == "number", "number expected for size")
	assert(type(v) == "number", "number expected for v")
	assert(size >= 0 and size <= 53, "size must be in range [0, 53]")

	if size == 0 then
		return
	elseif size <= 32 then
		self:WriteUnit(size, v)
		return
	end

	v %= 2 ^ size
	self:WriteUnit(32, v)
	self:WriteUnit(size - 32, math.floor(v / 2 ^ 32))
end

-------------------------------------------------------------------------------------
-- Reads *size* bits as an unsigned integer.
-- *size* must be an integer between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.ReadUint(self: Class, size: number): number
	assert(type(size) == "number", "number expected for size")
	assert(size >= 0 and size <= 53, "size must be in range [0, 53]")

	if size == 0 then
		return 0
	elseif size <= 32 then
		return self:ReadUnit(size)
	end

	return self:ReadUnit(32) + self:ReadUnit(size - 32) * 2 ^ 32
end

-------------------------------------------------------------------------------------
-- Writes a 0 bit if *v* is falsy, or a 1 bit if *v* is truthy.
-------------------------------------------------------------------------------------

function Buffer.WriteBool(self: Class, v: any?)
	self:WriteUnit(1, if v then 1 else 0)
end

-------------------------------------------------------------------------------------
-- Reads one bit and returns true if the bit is set to 1.
-------------------------------------------------------------------------------------

function Buffer.ReadBool(self: Class): boolean
	return self:ReadUnit(1) == 1
end

-------------------------------------------------------------------------------------
-- Shorthand for `Buffer:WriteUint(8, v)`.
-------------------------------------------------------------------------------------

function Buffer.WriteByte(self: Class, v: number)
	assert(type(v) == "number", "number expected for v")
	self:WriteUnit(8, v)
end

-------------------------------------------------------------------------------------
-- Shorthand for `Buffer:ReadUint(8, v)`.
-------------------------------------------------------------------------------------

function Buffer.ReadByte(self: Class): number
	return self:ReadUnit(8)
end

-------------------------------------------------------------------------------------
-- Writes *v* as a signed integer of *size* bits.
-- *size* must be an integer between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.WriteInt(self: Class, size: number, v: number)
	assert(type(size) == "number", "number expected for argument: size")
	assert(type(v) == "number", "number expected for argument: v")
	assert(size >= 0 and size <= 53, "size must be in range [0, 53]")

	if size == 0 then
		return
	end

	v = int_to_uint(size, v)

	if size <= 32 then
		self:WriteUnit(size, v)
		return
	end

	self:WriteUnit(32, v)
	self:WriteUnit(size - 32, math.floor(v / 2 ^ 32))
end

-------------------------------------------------------------------------------------
-- Reads *size* bits as a signed integer.
-- *size* must be an integer between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.ReadInt(self: Class, size: number): number
	assert(type(size) == "number", "number expected")
	assert(size >= 0 and size <= 53, "size must be in range [0, 53]")

	if size == 0 then
		return 0
	end

	local v: number

	if size <= 32 then
		v = self:ReadUnit(size)
	else
		v = self:ReadUnit(32) + self:ReadUnit(size - 32) * 2 ^ 32
	end

	return int_from_uint(size, v)
end

-------------------------------------------------------------------------------------
-- Writes *v* as a floating-point number. Throws an error
-- if *size* is not one of the following values:
--
-- - `32`: IEEE 754 binary32
-- - `64`: IEEE 754 binary64
-------------------------------------------------------------------------------------

function Buffer.WriteFloat(self: Class, size: number, v: number)
	assert(size == 32 or size == 64, "size must be 32 or 64")
	assert(type(v) == "number", "number expected")

	if size == 32 then
		self:WriteBytes(string.pack("<f", v))
	else
		self:WriteBytes(string.pack("<d", v))
	end
end

-------------------------------------------------------------------------------------
-- Reads a floating-point number. Throws an error
-- if *size* is not one of the following values:
--
-- - `32`: IEEE 754 binary32
-- - `64`: IEEE 754 binary64
-------------------------------------------------------------------------------------

function Buffer.ReadFloat(self: Class, size: number): number
	assert(type(size) == "number", "number expected")
	assert(size == 32 or size == 64, "size must be 32 or 64")

	local s = self:ReadBytes(size / 8)

	if size == 32 then
		return string.unpack("<f", s)
	else
		return string.unpack("<d", s)
	end
end

-------------------------------------------------------------------------------------
-- Writes *v* as an unsigned fixed-point number. *i* is the number of bits
-- used for the integer portion, and *f* is the number of bits used for the
-- fractional portion. Their combined size must be between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.WriteUfixed(self: Class, i: number, f: number, v: number)
	assert(type(i) == "number", "number expected for argument: i")
	assert(type(f) == "number", "number expected for argument: f")
	assert(type(v) == "number", "number expected for argument: v")

	assert(i >= 0, "integer size must be >= 0")
	assert(f >= 0, "fractional size must be >= 0")
	assert(i + f <= 53, "combined size must be <= 53")

	self:WriteUint(i + f, float_to_ufixed(i, f, v))
end

-------------------------------------------------------------------------------------
-- Reads an unsigned fixed-point number. *i* is the number of bits used
-- for the integer portion, and *f* is the number of bits used for the
-- fractional portion. Their combined size must be between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.ReadUfixed(self: Class, i: number, f: number): number
	assert(type(i) == "number", "number expected")
	assert(type(f) == "number", "number expected")

	assert(i >= 0, "integer size must be >= 0")
	assert(f >= 0, "fractional size must be >= 0")
	assert(i + f <= 53, "combined size must be <= 53")

	return float_from_ufixed(i, f, self:ReadUint(i + f))
end

-------------------------------------------------------------------------------------
-- Writes *v* as a signed fixed-point number. *i* is the number of bits used
-- for the integer portion, and *f* is the number of bits used for the
-- fractional portion. Their combined size must be between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.WriteFixed(self: Class, i: number, f: number, v: number)
	assert(type(i) == "number", "number expected for argument: i")
	assert(type(f) == "number", "number expected for argument: f")
	assert(type(v) == "number", "number expected for argument: v")

	assert(i >= 0, "integer size must be >= 0")
	assert(f >= 0, "fractional size must be >= 0")
	assert(i + f <= 53, "combined size must be <= 53")

	self:WriteInt(i + f, float_to_fixed(i, f, v))
end

-------------------------------------------------------------------------------------
-- Reads a signed fixed-point number. *i* is the number of bits used
-- for the integer portion, and *f* is the number of bits used for the
-- fractional portion. Their combined size must be between 0 and 53.
-------------------------------------------------------------------------------------

function Buffer.ReadFixed(self: Class, i: number, f: number): number
	assert(type(i) == "number", "number expected for argument: i")
	assert(type(f) == "number", "number expected for arugment: f")

	assert(i >= 0, "integer size must be >= 0")
	assert(f >= 0, "fractional size must be >= 0")
	assert(i + f <= 53, "combined size must be <= 53")

	return float_from_fixed(i, f, self:ReadInt(i + f))
end

-------------------------------------------------------------------------------------
-- Returns true if the provided value is a Buffer.
-------------------------------------------------------------------------------------

function Bitbuf.isBuffer(value: any): boolean
	return getmetatable(value) == Buffer
end

-------------------------------------------------------------------------------------
-- Module export.
-------------------------------------------------------------------------------------

table.freeze(Bitbuf)
table.freeze(Buffer)

return Bitbuf

-------------------------------------------------------------------------------------


--!strict

local CageBuilder = {}
CageBuilder.__index = CageBuilder

local Modules = script.Parent
local Trove = require(Modules.Trove)
local JointTree = require(Modules.JointTree)
local RobloxMesh = require(Modules.RobloxMesh)

type Trove = Trove.Class
type RobloxMesh = RobloxMesh.Class

export type Class = typeof(setmetatable({} :: {
	_model: Model,
	_trove: Trove,

	_targets: {
		[WrapTarget]: RobloxMesh,
	},
}, CageBuilder))

function CageBuilder.new(target: Model)
	local trove = Trove.new()

	local builder = {
		_model = target,
		_trove = trove,
		_targets = {},
	}

	local function onDescendantAdded(desc: Instance)
		if desc:IsA("WrapTarget") then
			local cage = RobloxMesh.fromAsset(desc.CageMeshId)
			builder._targets[desc] = cage:Transform(desc.CageOrigin)
		end
	end

	local function onDescendantRemoving(desc: Instance)
		if desc:IsA("WrapTarget") and builder._targets[desc] then
			builder._targets[desc] = nil
		end
	end

	trove:Connect(target.DescendantAdded, onDescendantAdded)
	trove:Connect(target.DescendantRemoving, onDescendantRemoving)

	for i, desc in target:GetDescendants() do
		onDescendantAdded(desc)
	end

	return setmetatable(builder, CageBuilder)
end

function CageBuilder.BuildSnapshot(self: Class): RobloxMesh
	local cage = RobloxMesh.empty()
	local spanTree, rootPart = JointTree.BuildTree(self._model)

	local assembly = JointTree.ApplyTree(spanTree, {
		[rootPart] = CFrame.identity,
	})

	for part, offset in assembly do
		local target = part:FindFirstChildOfClass("WrapTarget")

		-- stylua: ignore
		local mesh = if target
			then self._targets[target]
			else nil

		if mesh then
			local scale: Vector3?

			if part:IsA("MeshPart") then
				scale = part.Size / part.MeshSize
			end

			mesh = mesh:Transform(offset, scale)
			cage = cage:Append(mesh)
		end
	end

	return cage
end

function CageBuilder.Destroy(self: Class)
	self._trove:Destroy()
	table.clear(self._targets)
end

return CageBuilder


--!native
--!strict

local CageSolver = {}
CageSolver.__index = CageSolver

local Root = script.Parent
local Matrix = require(Root.Matrix)
local Octree = require(Root.Octree)
local QuadTree = require(Root.QuadTree)
local RobloxMesh = require(Root.RobloxMesh)
local CageBuilder = require(Root.CageBuilder)

type Octree = Octree.Class
type QuadTree = QuadTree.Class
type Vertex = RobloxMesh.Vertex
type RobloxMesh = RobloxMesh.Class
type CageBuilder = CageBuilder.Class

export type Class = typeof(setmetatable({} :: {
	Lookup2D: QuadTree,
	Lookup3D: Octree,

	InnerCage: RobloxMesh,
	OuterCage: RobloxMesh,

	Links: {
		[Vertex]: Vertex,
	},
}, CageSolver))

local function hashUV(uv: Vector2)
	local x = math.round(uv.X * 1e3) / 1e3
	local y = math.round(uv.Y * 1e3) / 1e3
	return math.round(x * 73856093 + y * 19351301)
end

function CageSolver.new(innerCage: RobloxMesh, outerCage: RobloxMesh): Class
	local lookup3D = Octree.new(10)
	local lookup2D = QuadTree.new(0.2)

	local innerVerts = innerCage.Verts
	local outerVerts = outerCage.Verts

	local uvMap = {} :: {
		[number]: {
			InnerVert: Vertex,
			OuterVert: Vertex?,
		},
	}

	for _, vert in innerVerts do
		local pos = vert.Position
		local uv = vert.UV

		local link = {
			InnerVert = vert,
			OuterVert = nil,
		}

		local hash = hashUV(uv)
		uvMap[hash] = link

		lookup2D:CreateNode(uv, link)
		lookup3D:CreateNode(pos, link)
	end

	local linkMap = {} :: {
		[Vertex]: Vertex,
	}

	for i, outerVert in outerVerts do
		local hash = hashUV(outerVert.UV)
		local link = uvMap[hash]

		if link then
			local innerVert = link.InnerVert
			linkMap[innerVert] = outerVert
			link.OuterVert = outerVert
		end
	end

	return setmetatable({
		Lookup2D = lookup2D,
		Lookup3D = lookup3D,

		InnerCage = innerCage,
		OuterCage = outerCage,

		Links = linkMap,
	}, CageSolver)
end

function CageSolver.SolveRbf(self: Class): { Vector3 }
	local _keys = self.InnerCage.Verts
	local _values = self.OuterCage.Verts

	-- Deduplicate verts
	local inputs = #_keys
	local keys = table.create(inputs)
	local values = table.create(#_values)

	for i = 1, inputs do
		local isDup = false
		local key = _keys[i].Position
		local val = _values[i].Position

		for _, otherKey in keys do
			if key:FuzzyEq(otherKey, 1e-5) then
				isDup = true
				break
			end
		end

		if not isDup then
			table.insert(keys, key)
			table.insert(values, val)
		end
	end

	-- Allocate Matrix & Targets
	local size = #keys
	local dists = table.create(size)
	local targets = table.create(size)

	for i = 1, size do
		local pos = values[i]
		dists[i] = table.create(size)
		targets[i] = { pos.X, pos.Y, pos.Z }
	end

	-- Compute Distances
	for i = 1, size do
		for j = i, size do
			local dist = 0

			if i ~= j then
				local keyI = keys[i]
				local keyJ = keys[j]

				dist = (keyJ - keyI).Magnitude
				dists[j][i] = dist
			end

			dists[i][j] = dist
		end
	end

	local solution = Matrix.SolveUsingLU(dists, targets)
	local weights = table.create(size)

	for i, xyz in solution do
		local x, y, z = table.unpack(xyz, 1, 3)
		weights[i] = Vector3.new(x, y, z)
	end

	return weights
end

return CageSolver


--!strict
local JointTree = {}

type Joints = {
	[BasePart]: SpanningTree,
}

type Assembly = {
	[BasePart]: CFrame,
}

type SpanningTree = {
	[number]: {
		Joint: JointInstance,
		Part: BasePart,
	},
}

local function addJointEdge(joints: Joints, joint: JointInstance)
	local _part0 = joint.Part0
	local _part1 = joint.Part1

	if _part0 and _part1 then
		local twoWay = {
			[_part0] = _part1,
			[_part1] = _part0,
		}

		for part0, part1 in twoWay do
			local edges = joints[part0]

			if edges == nil then
				edges = {}
				joints[part0] = edges
			end

			table.insert(edges, {
				Joint = joint,
				Part = part1,
			})
		end
	end
end

local function getRootPart(model: Model, tree: Joints): BasePart
	local rootPart = model.PrimaryPart

	if rootPart == nil then
		local bestPriority = -128
		local bestPart: BasePart?

		for part in tree do
			local priority = part.RootPriority

			if priority < bestPriority then
				continue
			end

			if priority == bestPriority then
				local mass = part.Mass

                -- stylua: ignore
				local bestMass = if bestPart
					then bestPart.Mass
					else 0

				if mass > bestMass then
					bestPart = part
				end
			else
				bestPart = part
			end

			if bestPart == part then
				bestPriority = priority
			end
		end

		rootPart = bestPart
	end

	return assert(rootPart, "Model does not have any assembly root!")
end

local function expandTree(joints: Joints, part0: BasePart, edges: SpanningTree?): SpanningTree
	local adjacent = joints[part0]
	local tree: SpanningTree = edges or {}

	if adjacent then
		-- We only want to iterate over part's edges once, remove edges to mark as visited
		joints[part0] = nil

		for i, edge in adjacent do
			local part = edge.Part

			-- Checks if we've already included this part. This will at least be a list with edge back
			-- to parent unless we've already visited this part through another joint and removed it.
			-- Breaks cycles and prioritizes shortest path to root.

			if joints[part] then
				-- Add the parent-child joint edge to the tree list
				table.insert(tree, edge)

				-- Recursively add child's edges, DFS order. BFS would
				-- have been fine too, but either works just as well.
				expandTree(joints, part, tree)
			end
		end
	end

	return tree
end

local CFrameIndex: any = setmetatable({}, {
	__index = function(self, part: BasePart)
		return part.CFrame
	end,

	__newindex = function(self, part: BasePart, cf: CFrame)
		part.CFrame = cf
	end,
})

--[[
	 Returns a list of assembly edges in some tree-sorted order that can be used by `applyTree` to
	 position other parts in `model` relative to `rootPart` if they would be in the same Assembly
	 under a `WorldRoot`. This roughly imitates what the internal spanning tree that `WorldRoot` uses
	 to build an internal transform hierarchy of parts in an assembly, with some limitations:

	 - Only supports Motor6D, and Weld. Didn't bother with legacy Motor, Snap, ManualWeld.
	 - Doesn't support Motor/Motor6D.CurrentAngle and co.
	 - Doesn't support WeldConstraints. Can't. Transform isn't exposed to Lua.
	 - Doesn't prioritize by joint type. Weld should take priority over Motor.
	 - Doesn't prioritize by joint/part GUID. Can't. Not exposed to Lua.

	 For a reasonable model, like an R15 character, that doesn't have duplicate or unsupported joints
	 it should produce the same results as the Roblox spanning tree when applied.

	 { { joint, childPart }, ... }
]]
--

function JointTree.BuildTree(model: Model): (SpanningTree, BasePart)
	local joints: Joints = {}

	-- Gather the part-joint graph.
	for i, desc in model:GetDescendants() do
		if desc:IsA("JointInstance") and desc.Enabled then
			local p0 = desc.Part0
			local p1 = desc.Part1

			if p0 and p1 then
				-- Add edge to both parts. Assembly joints are bidirectional.
				addJointEdge(joints, desc)
			end
		end
	end

	-- Build the tree, in order, by recursively following edges out from the root part
	-- Joint edge list map: { [part] = { { joint, otherPart }, ...}, ... }
	local rootPart = getRootPart(model, joints)
	return expandTree(joints, rootPart), rootPart
end

function JointTree.ApplyTree(tree: SpanningTree, cframes: Assembly?): Assembly
	if cframes == nil then
		cframes = CFrameIndex
	end

	assert(cframes)

	for i, edge in ipairs(tree) do
		local joint = edge.Joint
		local childPart = edge.Part

		local p0 = joint.Part0
		local p1 = joint.Part1

		if p0 and p1 then
			local c0 = joint.C0
			local c1 = joint.C1

			if p1 == childPart then
				cframes[p1] = cframes[p0] * c0 * c1:Inverse()
			else
				cframes[p0] = cframes[p1] * c1 * c0:Inverse()
			end
		end
	end

	return cframes
end

return JointTree

--------------------------------------------------------------------------------------------------------------------------------------------------------