local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")

local SideBar = require(script.Parent.SideBar)
local Utils = require(ReplicatedStorage.Modules.Utils)

local LocalPlayer = Players.LocalPlayer

local StatsMenu = {
    Opened = false,
    Enabled = true,
    UIInstance = nil,
}

local Prefabs = {
    Section = script.Prefabs.Section,
    Stat = script.Prefabs.Stat,
}

local UIParent = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "Menus")

function StatsMenu:Init()
    if StatsMenu.UIInstance then
        StatsMenu.UIInstance.Parent = UIParent
        StatsMenu.UIInstance.Size = Utils.UDim.Zero
        StatsMenu.UIInstance.Visible = false
        return
    end

    local UI = script.StatsMenu
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    UI.Parent = UIParent
    StatsMenu.UIInstance = UI
    local Content = UI.StatsContainer.Content

    local CurrentLayoutOrder = 0

    local Folders = Utils.Instance.FindFirstChild(LocalPlayer, "PlayerData.Stats"):GetChildren()

    table.sort(Folders, function(a, b)
        return a:GetAttribute("LayoutOrder") < b:GetAttribute("LayoutOrder")
    end)

    for _, StatFolder in ipairs(Folders) do
        CurrentLayoutOrder += 1

        local Section = Prefabs.Section:Clone()
        Section.Name = StatFolder.Name
        Section.TextLabel.Text = StatFolder:GetAttribute("DisplayTitle")
        Section.LayoutOrder = CurrentLayoutOrder

        local StatsInSection = StatFolder:GetChildren()

        table.sort(StatsInSection, function(a, b)
            return a:GetAttribute("LayoutOrder") < b:GetAttribute("LayoutOrder")
        end)

        for _, StatValue in StatsInSection do
            CurrentLayoutOrder += 1

            local Stat = Prefabs.Stat:Clone()
            Stat.Name = StatValue.Name

            local DisplayTitle = StatValue:GetAttribute("DisplayTitle")

            if StatValue:GetAttribute("PostDisplayTitle") ~= nil then
                DisplayTitle = DisplayTitle.." ("..StatValue:GetAttribute("PostDisplayTitle")..")"
            end

            Stat.Text = DisplayTitle..": "

            if StatValue:GetAttribute("FormatTime") == true then
                Stat.Text = Stat.Text..Utils.Math.ConvertToDHMS(StatValue.Value)
            else
                Stat.Text = Stat.Text..tostring(StatValue.Value)
            end

            Stat.LayoutOrder = CurrentLayoutOrder

            StatValue.Changed:Connect(function(value)
                Stat.Text = DisplayTitle..": "

                local RealValue = StatValue:GetAttribute("FormatTime") == true and Utils.Math.ConvertToDHMS(value) or tostring(value)
                Stat.Text = Stat.Text..RealValue
            end)

            Stat.Parent = Content
        end

        Section.Parent = Content
    end

    Utils.Instance.ObserveProperty(StatsMenu.UIInstance, "Size", function(value: UDim2)
        StatsMenu.UIInstance.Visible = value ~= Utils.UDim.Zero
    end)
end

function StatsMenu.Open(Time: number)
    if not StatsMenu.Enabled or not LocalPlayer.Character.Role or LocalPlayer.Character.Role.Value ~= "Spectator" or not SideBar.Enabled then
        return "Prevented"
    end

    TweenService:Create(StatsMenu.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.Full20Offset}):Play()
    StatsMenu.Opened = true
    return
end

function StatsMenu.Close(Time: number)
    if Time > 0 then
        TweenService:Create(StatsMenu.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.Zero}):Play()
    else
        StatsMenu.UIInstance.Size = Utils.UDim.Zero
    end
    StatsMenu.Opened = false
end

function StatsMenu.Toggle(toggle: boolean)
    if not StatsMenu.Enabled and toggle then
        StatsMenu:Init()
    end
    if not toggle and StatsMenu.Enabled then
        StatsMenu.UIInstance.Parent = script
        StatsMenu.UIInstance.Size = Utils.UDim.Zero
    end
    StatsMenu.Enabled = toggle
end

return StatsMenu


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Utils = require(ReplicatedStorage.Modules.Utils)

return {
    Init = function(_)
        local TimeStats = ReplicatedStorage.RoundInfo

        local TimeRemaining = TimeStats.TimeRemaining
        local State = TimeStats.CurrentState

        local Frame = Utils.Instance.FindFirstChild(script, "TimeInfo")
        Frame.Parent = Utils.Instance.FindFirstChild(game:GetService("Players").LocalPlayer.PlayerGui, "Time")
        local TimeLabel = Frame.TimeLabel
        local Label = Frame.Label

        local EnoughPlayers = ReplicatedStorage.RoundInfo.EnoughPlayers

        local LabelTexts = {
        	Lobby = "Round begins in...",
        	InRound = "Round ends in...",
        	NotEnough = "Waiting for players...",
            Frozen = "Time frozen!",
        }

        TimeLabel.Text = Utils.Math.ConvertToMinSec(math.ceil(TimeRemaining.Value))
        Label.Text = LabelTexts[State.Value]

        TimeRemaining.Changed:Connect(function(value: number)
        	TimeLabel.Text = Utils.Math.ConvertToMinSec(math.ceil(value))
        end)

        local function Update(value: string)
            if workspace:GetAttribute("FreezeTime") then
                Label.Text = LabelTexts.Frozen
            elseif value == "Lobby" and not EnoughPlayers.Value then
                Label.Text = LabelTexts.NotEnough
        	else
        		Label.Text = LabelTexts[value] or "PLACEHOLDER"
        	end
        end

        State.Changed:Connect(Update)
        EnoughPlayers.Changed:Connect(function()
            Update(State.Value)
        end)
        Utils.Instance.ObserveAttribute(workspace, "FreezeTime", function()
            Update(State.Value)
        end)
        Update(State.Value)
    end,
}


--credits to PirosMacska for this since i was too lazy to write it myself -Dyscarn (Driftwood thing)

local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Utils = require(ReplicatedStorage.Modules.Utils)

local module = {}

local Player = game:GetService("Players").LocalPlayer
local ScreenGUI

local frames = {}
local frame_threads = {}

local music: Sound

function module:Init()
	frames = {}
	frame_threads = {}
	ScreenGUI = Utils.Instance.FindFirstChild(Player.PlayerGui, "VideoPlayer")
end

function module:LoadVideo(assetids: {string})
	self:Reset()

	local threads = {}

	for _, rbxassetid in assetids do
		local label = Instance.new("ImageLabel")
		label.Size = Utils.UDim.Full
		label.Parent = ScreenGUI:FindFirstChild("Frames")
		label.BackgroundTransparency = 0.999
		label.ImageTransparency = 0.999
		label.Image = rbxassetid

		table.insert(frames, label)
		local thread = task.spawn(function()
			--Wait until loaded
			while not label.IsLoaded do
				task.wait()
			end

			label.BackgroundTransparency = 1
			label.ImageTransparency = 1
			Utils.Misc.Print("loaded")
		end)
		table.insert(threads, thread)
	end

	--Wait for all frames to load
	if #frame_threads > 0 then
		while #threads > 0 do
			if coroutine.status(threads[1]) == "dead" then
				table.remove(threads, 1)
			else
				task.wait()
			end
		end
	end

	Utils.Misc.Print("loaded")
end

function module:Reset()
	--Wait for all frames to load
	if #frame_threads > 0 then
		while #frame_threads > 0 do
			if coroutine.status(frame_threads[1]) == "dead" then
				table.remove(frame_threads, 1)
			else
				task.wait()
			end
		end
	end
	frame_threads = {}

	--Delete All Frames
	for _, frame in frames do
		frame:Destroy()
	end
	frames = {}
end

function module:AddFrame(rbxassetid: string)
	Utils.Misc.Print("loading video...")

	local label = Instance.new("ImageLabel")
	label.Size = Utils.UDim.Full
	label.Parent = ScreenGUI.Frames
	label.BackgroundTransparency = 0.999
	label.ImageTransparency = 0.999
	label.Image = rbxassetid

	table.insert(frames, label)

	local thread = task.spawn(function()
		if not label.IsLoaded then
			while not label.IsLoaded do
				task.wait(0.01)
			end
		end

		label.Parent = game:GetService("ReplicatedStorage")
		label.BackgroundTransparency = 1
		label.ImageTransparency = 1

		Utils.Misc.Print("loaded")
	end)

	table.insert(frame_threads, thread)
end

function module:SetAudio(assetid: string)
	music = Instance.new("Sound")
	music.Parent = workspace
	music.SoundId = assetid
	music.Looped = false
	music.PlaybackSpeed = 1

	Utils.Misc.Print(music.TimeLength)

	if not music.IsLoaded then
		while not music.IsLoaded do
			task.wait()
		end
	end
end

function module:Play(fps: number)
	Utils.Misc.Print("Play")

	--Wait for individualualy added frames to load
	if #frame_threads > 0 then
		while #frame_threads > 0 do
			if coroutine.status(frame_threads[1]) == "dead" then
				table.remove(frame_threads, 1)
			else
				task.wait()
			end
		end
	end

	fps = fps or 30

	local spf = 1 / fps

	ScreenGUI.Enabled = true
	Utils.Misc.Print("Play")

	local startTime = time()

	if music then
		music:Play()
		while not music.IsPlaying do
			task.wait()
		end
	end

	for frameIndex = 1, #frames do
		frames[frameIndex].Parent = ScreenGUI.Frames
		frames[frameIndex].BackgroundTransparency = 0
		frames[frameIndex].ImageTransparency = 0
		local targetTime = spf * (frameIndex - 1)
		local actualTime = time() - startTime
		local timeDelay =  actualTime - targetTime
		if timeDelay > 0 then
			task.wait(spf - timeDelay)
		end
	end

	if music then
		while music and music.IsPlaying do
			task.wait()
		end
	end

	Utils.Misc.Print(time() - startTime)

	if music then
		music:Stop()
	end

	for _, frame in frames do
		frame:Destroy()
	end

	ScreenGUI.Enabled = false
end

return module


--!optimize 2
--!native

-- Services.
local TextService = game:GetService("TextService")

-- Option defaults.
local defaults = require(script.Parent.Defaults)

-- User fonts.
local userFonts = require(script.Parent.Fonts)

-- Option lists for validity checks.
local scaleSizeTypes = {
	RootX = true,
	RootY = true,
	RootXY = true,

	FrameX = true,
	FrameY = true,
	FrameXY = true
}

local xAlignments = {
	Left = true,
	Center = true,
	Right = true,
	Justified = true
}
local yAlignments = {
	Top = true,
	Center = true,
	Bottom = true,
	Justified = true
}

-- Text params for verifying Roblox fonts.
local textBoundsParams = Instance.new("GetTextBoundsParams")
textBoundsParams.Text = ""

-- Options corrector.
return function(options)
	if not scaleSizeTypes[options.ScaleSize] then
		options.ScaleSize = defaults.ScaleSize
	end
	if not scaleSizeTypes[options.ScaleSize] then
		-- Scale-size disabled.
		options.ScaleSize = nil
		options.MinimumSize = nil
		options.MaximumSize = nil

		if type(options.Size) ~= "number" then
			options.Size = defaults.Size
		elseif options.Size < 1 then
			options.Size = 1
		end
	else
		-- Scale-size enabled.
		if type(options.MinimumSize) ~= "number" then
			options.MinimumSize = defaults.MinimumSize
		end
		if type(options.MinimumSize) ~= "number" then
			options.MinimumSize = nil
		elseif options.MinimumSize < 1 then
			options.MinimumSize = 1
		end

		if type(options.MaximumSize) ~= "number" then
			options.MaximumSize = defaults.MaximumSize
		end
		if type(options.MaximumSize) ~= "number" then
			options.MaximumSize = nil
		elseif options.MaximumSize < 1 then
			options.MaximumSize = 1
		end

		if type(options.Size) ~= "number" then
			options.Size = defaults.Size
		end
	end

	local font = options.Font
	if font == nil then
		options.Font = defaults.Font

		-- Roblox font size limit.
		if options.Size > 100 then
			options.Size = 100
		end
	elseif typeof(font) == "Font" then -- Roblox font.
		-- Verify font.
		textBoundsParams.Font = options.Font
		textBoundsParams.Text = ""
		local _, result = pcall(TextService.GetTextBoundsAsync, TextService, textBoundsParams)
		if type(result) == "string" then
			warn("Invalid font. Fallback to default.")
			options.Font = defaults.Font
		end

		-- Roblox font size limit.
		if options.Size > 100 then
			options.Size = 100
		end
	else
		if not userFonts[font] then
			-- Warn about invalid font.
			warn("Invalid font. Fallback to default.")

			-- Apply default font.
			options.Font = defaults.Font

			-- Roblox font size limit.
			if options.Size > 100 then
				options.Size = 100
			end
		end
	end

	local lineHeight = options.LineHeight
	if type(lineHeight) ~= "number" then
		options.LineHeight = defaults.LineHeight
	elseif lineHeight < 0 then
		options.LineHeight = 0
	end
	local characterSpacing = options.CharacterSpacing
	if type(characterSpacing) ~= "number" then
		options.CharacterSpacing = defaults.CharacterSpacing
	elseif characterSpacing < 0 then
		options.CharacterSpacing = 0
	end

	if typeof(options.Color) ~= "Color3" then
		options.Color = defaults.Color
	end
	if type(options.Transparency) ~= "number" then
		options.Transparency = defaults.Transparency
	end

	local pixelated = options.Pixelated
	if pixelated == false then
		options.Pixelated = nil
	elseif pixelated ~= true then
		options.Pixelated = defaults.Pixelated
	end

	if typeof(options.Offset) ~= "Vector2" then
		options.Offset = defaults.Offset
	end
	if type(options.Rotation) ~= "number" then
		options.Rotation = defaults.Rotation
	end

	local strokeSize = options.StrokeSize
	local strokeColor = options.StrokeColor
	local strokeTransparency = options.StrokeTransparency
	if type(strokeSize) ~= "number" then
		if typeof(strokeColor) == "Color3" then
			options.StrokeSize = defaults.StrokeSize
			if type(strokeTransparency) ~= "number" then
				options.StrokeTransparency = options.Transparency
			end
		elseif type(strokeTransparency) == "number" then
			options.StrokeSize = defaults.StrokeSize
			if type(strokeColor) ~= "number" then
				options.StrokeColor = defaults.StrokeColor
			end
		else
			options.StrokeSize = nil
			options.StrokeColor = nil
			options.StrokeTransparency = nil
		end
	else
		if strokeSize < 1 then
			options.StrokeSize = 1
		end
		if typeof(strokeColor) ~= "Color3" then
			options.StrokeColor = defaults.StrokeColor
		end
		if type(strokeTransparency) ~= "number" then
			options.StrokeTransparency = options.Transparency
		end
	end

	local shadowOffset = options.ShadowOffset
	local shadowColor = options.ShadowColor
	local shadowTransparency = options.ShadowTransparency
	if typeof(shadowOffset) ~= "Vector2" then
		if typeof(shadowColor) == "Color3" then
			options.ShadowOffset = defaults.ShadowOffset
			if type(shadowTransparency) ~= "number" then
				options.ShadowTransparency = options.Transparency
			end
		elseif type(shadowTransparency) == "number" then
			options.ShadowOffset = defaults.ShadowOffset
			if type(shadowColor) ~= "number" then
				options.ShadowColor = defaults.ShadowColor
			end
		else
			options.ShadowOffset = nil
			options.ShadowColor = nil
			options.ShadowTransparency = nil
		end
	else
		if typeof(shadowColor) ~= "Color3" then
			options.ShadowColor = defaults.ShadowColor
		end
		if type(shadowTransparency) ~= "number" then
			options.ShadowTransparency = options.Transparency
		end
	end

	local truncate = options.Truncate
	if truncate == false then
		options.Truncate = nil
	elseif truncate ~= true then
		options.Truncate = defaults.Truncate
	end

	if not xAlignments[options.XAlignment] then
		options.XAlignment = defaults.XAlignment
	end
	if not yAlignments[options.YAlignment] then
		options.YAlignment = defaults.YAlignment
	end

	local wordSorting = options.WordSorting
	if wordSorting == false then
		options.WordSorting = nil
	elseif wordSorting ~= true then
		options.WordSorting = defaults.WordSorting
	end

	local lineSorting = options.LineSorting
	if lineSorting == false then
		options.LineSorting = nil
	elseif lineSorting ~= true then
		options.LineSorting = defaults.LineSorting
	end
end


--!optimize 2

-- Services.
local CollectionService = game:GetService("CollectionService")

-- Attempt to find the plugin object.
local plugin = script:FindFirstAncestorOfClass("Plugin")

-- Options list for validation.
local optionsList = require(script.Parent.Options)

-- Default defaults.
local defaults = {
	Font = Font.new("rbxasset://fonts/families/SourceSansPro.json"),

	Size = 14,

	ScaleSize = nil,
	MinimumSize = nil,
	MaximumSize = nil,

	Color = Color3.fromRGB(0, 0, 0),
	Transparency = 0,

	Pixelated = false,

	Offset = Vector2.zero,
	Rotation = 0,

	StrokeSize = 5,
	StrokeColor = Color3.fromRGB(0, 0, 0),

	ShadowOffset = Vector2.new(0, 20),
	ShadowColor = Color3.fromRGB(50, 50, 50),

	LineHeight = 1,
	CharacterSpacing = 1,

	Truncate = false,

	XAlignment = "Left",
	YAlignment = "Top",

	WordSorting = false,
	LineSorting = false,

	Dynamic = false
}

-- Merge user defaults.
local userDefaults
if plugin then
	for _, instance in plugin:GetDescendants() do
		if instance:HasTag("TextDefaults") then
			userDefaults = require(instance)
			break
		end
	end
else
	userDefaults = CollectionService:GetTagged("TextDefaults")[1]
	if userDefaults then userDefaults = require(userDefaults) end
end
if userDefaults and type(userDefaults) == "table" then
	for key in userDefaults do
		if optionsList[key] then defaults[key] = userDefaults[key] end
	end
end

-- Remove false booleans.
for key, value in defaults do
	if value == false then defaults[key] = nil end
end

-- Return final defaults.
return defaults


--!optimize 2

-- Services.
local CollectionService = game:GetService("CollectionService")
local Players = game:GetService("Players")

-- Load and store all custom fonts.
local userFonts = {}

for _, module in CollectionService:GetTagged("Fonts") do
	if module:IsA("ModuleScript") then
		local fullModuleName = module:GetFullName()

		local fonts = require(module)

		if type(fonts) ~= "table" then
			warn("'"..fullModuleName.."' font data is not a table.")
		else
			if not next(fonts) then
				warn("'"..fullModuleName.."' font data table is empty.")
			else
				local player = Players.LocalPlayer
				local load
				if player then -- If running on client.
					local screenGui = Instance.new("ScreenGui")
					screenGui.Parent = player.PlayerGui

					local loading = 0
					load = function(image) -- For preloading the font image assets.
						-- Increment counter for currently loading images.
						loading += 1

						-- Setup image label for loading the current font.
						local label = Instance.new("ImageLabel")
						label.Size = UDim2.fromOffset(1, 1) -- As small as possible.
						label.BackgroundTransparency = 1
						label.ImageTransparency = 0.999 -- Trick to make the image invisible and still have it be loaded.
						label.ResampleMode = Enum.ResamplerMode.Pixelated
						label.Image = "rbxassetid://"..tostring(image)
						label.Parent = screenGui -- It's crucial that we put it in a visible ScreenGui, otherwise it won't be loaded.

						-- Detect load.
						coroutine.resume(coroutine.create(function()
							while true do
								task.wait()
								if label.IsLoaded then
									if loading == 1 then
										screenGui:Destroy()
									else
										loading -= 1
									end
									return
								end
							end
						end))
					end
				end

				local function handleCharacters(characters, size)
					local invertedFontSize = 1/size -- To avoid expensive division.

					for key, value in characters do
						-- Verify format.
						if type(key) ~= "string" then return end
						if type(value) ~= "table" then return end
						if type(value[1]) ~= "number" then return end
						if type(value[2]) ~= "number" then return end
						if typeof(value[3]) ~= "Vector2" then return end
						if type(value[4]) ~= "number" then return end
						if type(value[5]) ~= "number" then return end
						if type(value[6]) ~= "number" then return end

						-- Precalculate normalized offset and x-advance.
						value[4] *= invertedFontSize
						value[5] *= invertedFontSize
						value[6] *= invertedFontSize
					end

					return true
				end

				local remove = {} -- Because immediate removal will throw off the loop.
				local freeze = {} -- Because freezing before removal will result in errors.

				local processFonts

				local function handleTable(key, value, currentPath)
					if value.Image or value.Size or value.Characters then
						-- Verify format.
						if type(value.Image) ~= "number" then
							warn("Missing an image ID in '"..currentPath.."'")
							table.insert(remove, key)
							return
						end
						if type(value.Size) ~= "number" then
							warn("Missing a size in '"..currentPath.."'")
							table.insert(remove, key)
							return
						end
						if type(value.Characters) ~= "table" then
							warn("Missing characters in '"..currentPath.."'")
							table.insert(remove, key)
							return
						end
						if not handleCharacters(value.Characters, value.Size) then -- If not valid characters then.
							warn("Invalid characters in '"..currentPath.."'")
							table.insert(remove, key)
							return
						end

						-- Insert for later freeze.
						table.insert(freeze, key)

						-- Insert the font into raw fonts table.
						userFonts[value] = true

						-- Preload images.
						if player then -- If running on client.
							load(value.Image)
						end
					else
						processFonts(value, currentPath)
						table.freeze(value)
					end
				end
				processFonts = function(parent, parentPath)
					for key, value in parent do
						if type(value) ~= "table" then
							table.insert(remove, key)
						else
							handleTable(key, value, parentPath.."."..key)
						end
					end

					for index, key in remove do
						parent[key] = nil
						remove[index] = nil
					end
					for index, key in freeze do
						table.freeze(parent[key])
						freeze[index] = nil
					end
				end

				handleTable("", fonts, fullModuleName)
				if #freeze > 0 then table.freeze(fonts) end
			end
		end
	end
end

-- Return the global user fonts table.
return userFonts


return {
	Font = true,

	Size = true,

	ScaleSize = true,
	MinimumSize = true,
	MaximumSize = true,

	Color = true,
	Transparency = true,

	Pixelated = true,

	Offset = true,
	Rotation = true,

	StrokeSize = true,
	StrokeColor = true,
	StrokeTransparency = true,

	ShadowOffset = true,
	ShadowColor = true,
	ShadowTransparency = true,

	LineHeight = true,
	CharacterSpacing = true,

	Truncate = true,

	XAlignment = true,
	YAlignment = true,

	WordSorting = true,
	LineSorting = true,

	Dynamic = true
}


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Packages.Matter)
local components = require(ReplicatedStorage.Shared.components)

local function roombasHurt(world)
	for _, _, model in world:query(components.Roomba, components.Model) do
		for _, part in Matter.useEvent(model.model.PrimaryPart, "Touched") do
			local touchedModel = part:FindFirstAncestorWhichIsA("Model")
			if not touchedModel then
				continue
			end

			local player = Players:GetPlayerFromCharacter(touchedModel)

			if not player then
				continue
			end

			if player ~= Players.LocalPlayer then
				continue
			end

			local humanoid = touchedModel:FindFirstChildWhichIsA("Humanoid")

			if not humanoid then
				continue
			end

			humanoid:TakeDamage(5)
		end
	end
end

return roombasHurt


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local components = require(ReplicatedStorage.Shared.components)

local function spinSpinners(world, _, ui)
	if ui.checkbox("Disable Spinning"):checked() then
		return
	end

	local transparency = ui.slider(1)

	local randomize = ui.button("Randomize colors!"):clicked()

	for _, model in world:query(components.Model, components.Spinner) do
		model.model.PrimaryPart.CFrame = model.model.PrimaryPart.CFrame * CFrame.Angles(0, math.rad(5), 0)
		model.model.PrimaryPart.Transparency = transparency

		if randomize then
			model.model.PrimaryPart.BrickColor = BrickColor.random()
		end
	end
end

return {
	system = spinSpinners,
	event = "Stepped",
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local start = require(ReplicatedStorage.Shared.start)
local receiveReplication = require(ReplicatedStorage.Client.systems.receiveReplication)

local world, state = start({
	ReplicatedStorage.Shared.systems,
	ReplicatedStorage.Client.systems,
})

receiveReplication.system(world, state, nil)


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Packages.Matter)
local components = require(ReplicatedStorage.Shared.components)

local function mothershipsSpawnRoombas(world)
	for id, model, lasering, transform in
		world:query(components.Model, components.Lasering, components.Transform, components.Mothership)
	do
		model.model.Beam.Transparency = 1 - lasering.remainingTime

		lasering = lasering:patch({
			remainingTime = lasering.remainingTime - Matter.useDeltaTime(),
		})

		if not lasering.spawned then
			local spawnPosition = Vector3.new(transform.cframe.p.X, 11, transform.cframe.p.Z)

			world:spawn(
				components.Roomba(),
				components.Charge({
					charge = 100,
				}),
				components.Transform({
					cframe = CFrame.new(spawnPosition),
				})
			)

			lasering = lasering:patch({ spawned = true })
		end

		if lasering.remainingTime <= 0 then
			world:remove(id, components.Lasering)
		else
			world:insert(id, lasering)
		end
	end
end

return mothershipsSpawnRoombas


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Packages.Matter)
local components = require(ReplicatedStorage.Shared.components)

local function removeMissingModels(world)
	for id, model in world:query(components.Model) do
		for _ in Matter.useEvent(model.model, "AncestryChanged") do
			if model.model:IsDescendantOf(game) == false then
				world:remove(id, components.Model)
				break
			end
		end
		if not model.model.PrimaryPart then
			world:remove(id, components.Model)
		end
	end

	for _id, modelRecord in world:queryChanged(components.Model) do
		if modelRecord.new == nil then
			if modelRecord.old and modelRecord.old.model then
				modelRecord.old.model:Destroy()
			end
		end
	end
end

return removeMissingModels


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local components = require(ReplicatedStorage.Shared.components)
local routes = require(ReplicatedStorage.Shared.routes)
local useEvent = require(ReplicatedStorage.Packages.Matter).useEvent

local REPLICATED_COMPONENTS = {
	"Roomba",
	"Model",
	"Health",
	"Target",
	"Mothership",
	"Spinner",
}

local replicatedComponents = {}

for _, name in REPLICATED_COMPONENTS do
	replicatedComponents[components[name]] = true
end

local function replication(world, _state, _ui)
	for _, player in useEvent(Players, "PlayerAdded") do
		local payload = {}

		for entityId, entityData in world do
			local entityPayload = {}
			payload[tostring(entityId)] = entityPayload

			for component, componentData in entityData do
				if replicatedComponents[component] then
					entityPayload[tostring(component)] = { data = componentData }
				end
			end
		end

		print("Sending initial payload to", player)
		routes.MatterReplication:send(payload):to(player)
	end

	local changes = {}

	for component in replicatedComponents do
		for entityId, record in world:queryChanged(component) do
			local key = tostring(entityId)
			local name = tostring(component)

			if changes[key] == nil then
				changes[key] = {}
			end

			if world:contains(entityId) then
				changes[key][name] = { data = record.new }
			end
		end
	end

	if next(changes) then
		routes.MatterReplication:send(changes)
	end
end

return {
	system = replication,
	priority = math.huge,
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local components = require(ReplicatedStorage.Shared.components)

local function roombasMove(world)
	local targets = {}
	for _, model in world:query(components.Model, components.Target) do
		table.insert(targets, model.model.PrimaryPart.CFrame.p)
	end

	for _, _, charge, model in world:query(components.Roomba, components.Charge, components.Model) do
		if charge.charge <= 0 then
			continue
		end

		local closestPosition, closestDistance
		local currentPosition = model.model.PrimaryPart.CFrame.p

		for _, target in ipairs(targets) do
			local distance = (currentPosition - target).magnitude
			if not closestPosition or distance < closestDistance then
				closestPosition = target
				closestDistance = distance
			end
		end

		if closestPosition then
			local body = model.model.Roomba
			local force = body:GetMass() * 20

			if closestDistance < 4 then
				force = 0
			end

			local lookVector = body.CFrame.LookVector
			local desiredLookVector = (closestPosition - currentPosition).unit

			force = force * lookVector:Dot(desiredLookVector)
			body.VectorForce.Force = Vector3.new(force, 0, 0)

			local absoluteAngle = math.atan2(desiredLookVector.Z, desiredLookVector.X)
			local roombaAngle = math.atan2(lookVector.Z, lookVector.X)

			local angle = math.deg(absoluteAngle - roombaAngle)

			angle = angle % 360
			angle = (angle + 360) % 360
			if angle > 180 then
				angle -= 360
			end

			local angularVelocity = body.AssemblyAngularVelocity

			local sign = math.sign(angle)
			local motor = math.sqrt(math.abs(angle)) * sign * -1 * 20
			local friction = angularVelocity.Y * -12
			local torque = body:GetMass() * (motor + friction)

			body.Torque.Torque = Vector3.new(0, torque, 0)
		end
	end
end

return roombasMove


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local components = require(ReplicatedStorage.Shared.components)

local function spawnRoombas(world)
	for id, _ in world:query(components.Transform, components.Roomba):without(components.Model) do
		local model = ReplicatedStorage.Assets.KillerRoomba:Clone()
		model.Parent = workspace
		model.PrimaryPart:SetNetworkOwner(nil)

		world:insert(
			id,
			components.Model({
				model = model,
			})
		)
	end
end

return spawnRoombas


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Matter = require(ReplicatedStorage.Packages.Matter)

local COMPONENTS = {
	"Roomba",
	"Model",
	"Charge",
	"Health",
	"Target",
	"Transform",
	"Mothership",
	"Lasering",
	"DebugLabel",
	"Spinner",
}

local components = {}

for _, name in ipairs(COMPONENTS) do
	components[name] = Matter.component(name)
end

return components


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages

type ComponentInstance<T> = T & { patch: (self: T, data: { any: any }) -> ComponentInstance<T> }

local Net = require(Packages.Net)

local Route = Net.Route
type Route<U...> = Net.Route<U...>

local defaultConfiguration: Net.Configuration = {
	Channel = "Reliable",
	Event = "default",
}

-- Replicate Matter Components
local MatterReplication: Route<{ ComponentInstance<any> }> = Route.new(defaultConfiguration)

return {
	MatterReplication = MatterReplication,
}


local RunService = game:GetService("RunService")
local UserInputService = game:GetService("UserInputService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Packages = ReplicatedStorage.Packages
local Matter = require(ReplicatedStorage.Packages.Matter)
local Plasma = require(Packages.Plasma)
local HotReloader = require(Packages.Rewire).HotReloader
local Net = require(Packages.Net)
local components = require(script.Parent.components)
local routes = require(script.Parent.routes)

local function start(containers)
	local world = Matter.World.new()
	local state = {}

	local debugger = Matter.Debugger.new(Plasma)

	debugger.findInstanceFromEntity = function(id)
		if not world:contains(id) then
			return
		end

		local model = world:get(id, components.Model)

		return model and model.model or nil
	end

	local loop = Matter.Loop.new(world, state, debugger:getWidgets())

	-- Hook Net to middleware
	Net.start(loop, routes)

	-- Set up hot reloading

	local hotReloader = HotReloader.new()

	local firstRunSystems = {}
	local systemsByModule = {}

	local function loadModule(module, context)
		local originalModule = context.originalModule

		local ok, system = pcall(require, module)

		if not ok then
			warn("Error when hot-reloading system", module.name, system)
			return
		end

		if firstRunSystems then
			table.insert(firstRunSystems, system)
		elseif systemsByModule[originalModule] then
			loop:replaceSystem(systemsByModule[originalModule], system)
			debugger:replaceSystem(systemsByModule[originalModule], system)
		else
			loop:scheduleSystem(system)
		end

		systemsByModule[originalModule] = system
	end

	local function unloadModule(_, context)
		if context.isReloading then
			return
		end

		local originalModule = context.originalModule
		if systemsByModule[originalModule] then
			loop:evictSystem(systemsByModule[originalModule])
			systemsByModule[originalModule] = nil
		end
	end

	for _, container in containers do
		hotReloader:scan(container, loadModule, unloadModule)
	end

	loop:scheduleSystems(firstRunSystems)
	firstRunSystems = {}

	debugger:autoInitialize(loop)

	-- Begin running our systems

	loop:begin({
		default = RunService.Heartbeat,
		Stepped = RunService.Stepped,
	})

	if RunService:IsClient() then
		UserInputService.InputBegan:Connect(function(input)
			if input.KeyCode == Enum.KeyCode.F4 then
				debugger:toggle()

				state.debugEnabled = debugger.enabled
			end
		end)
	end

	return world, state
end

return start


local RunService = game:GetService("RunService")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local components = require(ReplicatedStorage.Shared.components)

local name = RunService:IsServer() and "serverEntityId" or "clientEntityId"

local function updateModelAttribute(world)
	for id, record in world:queryChanged(components.Model) do
		if record.new then
			record.new.model:SetAttribute(name, id)
		end
	end
end

return updateModelAttribute


type ConnectionImpl = {
	__index: ConnectionImpl,
	_callback: <U...>(U...) -> (),

	connected: boolean,

	disconnect: (Connection) -> (),
	reconnect: (Connection) -> (),

	new: <U...>(callback: (U...) -> ()) -> Connection,
}

export type Connection = typeof(setmetatable({} :: ConnectionImpl, {} :: ConnectionImpl))

--[=[
    @class Connection

    Stores a callback that will be called by YetAnotherNet at the end of a frame
    if [Connection.connected] is true.

    You can disconnect the connection by using [Connection:disconnect] and
    reconnect by using [Connection:reconnect].

    For more information, see [YetAnotherNet.createConnection].
]=]
local Connection: ConnectionImpl = {} :: ConnectionImpl
Connection.__index = Connection

--- @prop connected boolean
--- @within Connection

--- @within Connection
function Connection:disconnect()
	self.connected = false

	return
end

--- @within Connection
function Connection:reconnect()
	self.connected = true

	return
end

--- @within Connection
function Connection.new<U...>(callback: (U...) -> ())
	local self = {}
	setmetatable(self, Connection)

	self.connected = true
	self._callback = function(...: U...)
		if not self.connected then
			return
		end

		task.spawn(callback, ...)
	end

	return self :: any
end

return Connection


local identifier = require("@serdes/datatypes/custom/identifier")
export type Identifier = buffer

local function new(uniquePosition: number?): buffer
	return identifier.serialize(uniquePosition)
end

local function read(_buffer: buffer): number
	return identifier.deserialize(_buffer)
end

return {
	new = new,
	read = read,
}


local Bridge = require("@yetanothernet/Bridge")
type Bridge = Bridge.Bridge

type Recipient = Bridge.Recipient
type IncomingPacket = Bridge.IncomingPacket
type OutgoingPacket = Bridge.OutgoingPacket

type IncomingQueue = Bridge.IncomingQueue
type OutgoingQueue = Bridge.OutgoingQueue

type NetServer = Bridge.NetServer

local Identifier = require("@yetanothernet/Identifier")
export type Identifier = Identifier.Identifier

local read = Identifier.read

type ClientQueryResult<U...> = {
	__index: ClientQueryResult<U...>,
	__iter: (self: ClientQueryResult<U...>) -> () -> (number, U...),

	_snapshot: { IncomingPacket },
	_identifier: Identifier,
	_senders: { NetServer },

	iter: (self: ClientQueryResult<U...>) -> () -> (number, U...),
}

type ServerQueryResult<U...> = {
	__index: ServerQueryResult<U...>,
	__iter: (self: ServerQueryResult<U...>) -> () -> (number, Player, U...),

	_snapshot: { IncomingPacket },
	_identifier: Identifier,
	_senders: { Player },
	_view: "server",

	iter: (self: ServerQueryResult<U...>) -> () -> (number, Player, U...),
	from: (self: QueryResult<U...>, ...Player) -> QueryResult<U...>,
}

--[=[
	@class QueryResult
	@since v0.4.0

	An iterable object returned as the result of [Route:query] that can filter snapshots
	by Identifier and Senders.

	:::note
	Due to certain limitations with the Luau Type System, iterating over the QueryResult Object
	will not return typed values. In order to fix this, call ``:__iter()`` on the QueryResult Object.

	For example:
	```lua
	for i, sender, ... in net:query():__iter() do
		-- Do something
	end
	```
	:::

	See [Querying Data](/docs/getting-started/routes#querying) for more information.
]=]
type QueryResultImpl<U...> = {
	__index: QueryResultImpl<U...>,
	__iter: (self: QueryResult<U...>) -> () -> (number, Player | NetServer, U...),

	_snapshot: { IncomingPacket },
	_identifier: Identifier,
	_senders: { Player | NetServer },
	_view: "client"?,

	iter: (self: QueryResult<U...>) -> () -> (number, Player | NetServer, U...),
	from: (self: QueryResult<U...>, ...(Player | NetServer)) -> QueryResult<U...>,

	client: (self: QueryResult<U...>) -> ClientQueryResult<U...>,
	server: (self: QueryResult<U...>) -> ServerQueryResult<U...>,

	new: (
		snapshot: { IncomingPacket },
		identifier: Identifier,
		senders: { Recipient }?
	) -> QueryResult<U...>,
}

export type QueryResult<U...> = typeof(setmetatable(
	{} :: QueryResultImpl<U...>,
	{} :: QueryResultImpl<U...>
))

local QueryResult: QueryResultImpl<...any> = {} :: QueryResultImpl<...any>
QueryResult.__index = QueryResult

--[=[
	@method __iter
	@within QueryResult

	Iterating over a QueryResult will return the Packets in the QueryResult's Snapshot that
	match the supplied Senders from [QueryResult:from].

	Usage:
	```lua
	for pos, sender, ... in net:query():from(...senders) do
		-- Do something
	end
	```

	@return number
	@return Player | string
	@return ...any
]=]
function QueryResult:__iter()
	local senders = self._senders
	local identifier = self._identifier

	local filteredSnapshot = {}

	local view = self._view

	for i = 1, #self._snapshot do
		local packet = self._snapshot[i]
		if not packet then
			continue
		end

		local filterSenders = #senders > 0
		local isSenderAllowed = if filterSenders then table.find(senders, packet.sender) else true

		if read(packet.identifier) ~= read(identifier) or not isSenderAllowed then
			continue
		end

		table.insert(filteredSnapshot, packet)
	end

	local i = 0

	return function(): ...any
		i += 1

		local packet = filteredSnapshot[i]
		if not packet then
			return
		end

		if view == "client" then
			return i, unpack(packet.data)
		end

		return i, packet.sender, unpack(packet.data)
	end
end

--[=[
	@method iter
	@within QueryResult

	An iterator method for typechecking.

	```lua
	for i, sender, ... in route:query():iter() do
		-- ...
	end
	```
]=]
function QueryResult:iter()
	return self:__iter()
end

--[=[
	@method from
	@within QueryResult

	Filters Packets from the QueryResult's Snapshot based on the provided Senders.

	@param senders ...sender -- The Senders to filter
	@return QueryResult
]=]
function QueryResult:from(...)
	local senders = {} :: { { Player } | Player | "NET_SERVER" }

	local params = { ... }
	for _, param in params do
		if typeof(param) == "table" and param.__type ~= "Player" then
			for _, player in param do
				table.insert(senders, player)
			end

			continue
		end

		table.insert(senders, param)
	end
	return self.new(self._snapshot, self._identifier, senders)
end

--[=[
	@method client
	@within QueryResult

	Switches the query to a client view, for typechecking.
	The client view will not return a `sender` when iterating.

	```lua
	for i, ... in route:query():client():iter() do
		-- ...
	end
	```
]=]
function QueryResult:client()
	self._view = "client"
	return self :: any
end

--[=[
	@method server
	@within QueryResult

	Switches the query to a server view, for typechecking.

	```lua
	for i, player, ... in route:query():server():iter() do
		-- ...
	end
	```
]=]
function QueryResult:server()
	return self :: any
end

--[=[
	@function new
	@within QueryResult
	@private

	Creates a new QueryResult with the supplied Snapshot.

	@return QueryResult
]=]
function QueryResult.new(snapshot, identifier, senders)
	return setmetatable({
		_snapshot = snapshot,
		_identifier = identifier,
		_senders = senders or {},
	}, QueryResult) :: any
end

return QueryResult


local RunService = game:GetService("RunService")

local Bridge = require("@yetanothernet/Bridge")
type Bridge = Bridge.Bridge

type Recipient = Bridge.Recipient
type IncomingPacket = Bridge.IncomingPacket
type OutgoingPacket = Bridge.OutgoingPacket

type IncomingQueue = Bridge.IncomingQueue
type OutgoingQueue = Bridge.OutgoingQueue

type NetServer = Bridge.NetServer

local Identifier = require("@yetanothernet/Identifier")
export type Identifier = Identifier.Identifier

local isRunning = RunService:IsRunning()
local isClient = RunService:IsClient()

--[=[
	@class SendRequest
	@since v0.4.0

	Allows for modification of queued packets before they're sent.
]=]
type SendRequestImpl = {
	__index: SendRequestImpl,

	_outgoingQueue: { OutgoingPacket },
	_position: number,

	to: (self: SendRequest, recipient: Recipient) -> (),
}

export type SendRequest = typeof(setmetatable({} :: SendRequestImpl, {} :: SendRequestImpl))

local SendRequest: SendRequestImpl = {} :: SendRequestImpl
SendRequest.__index = SendRequest

--[=[
	@method to
	@within SendRequest

	Modifies the recipients of the packet to the one(s) provided in the parameter.

	@param recipient { Player } | Player | "NET_SERVER"
]=]
function SendRequest:to(recipient: Recipient)
	if isRunning and isClient then
		warn("You should not use the SendRequest:to() method on the Client")
	end

	local position = self._position

	local packet = self._outgoingQueue[position]
	local newPacket = {
		channel = packet.channel,
		identifier = packet.identifier,
		recipient = recipient,
		data = packet.data,
	}

	self._outgoingQueue[position] = newPacket

	return
end

return SendRequest


--!nonstrict
local root = script.Parent.Parent
local Bridge = require(root.Bridge)

local utils = root.__utils__
local mockRemoteEvents = require(utils.mockRemoteEvents)
local mockPlayers = require(utils.mockPlayers)

local ServerRemoteEvent = mockRemoteEvents.ServerRemoteEvent
local ClientRemoteEvent = mockRemoteEvents.ClientRemoteEvent

local n = 0

local mockedModule = table.clone(Bridge)
mockedModule.__index = mockedModule

function mockedModule:_getContext()
	return self.context
end

function mockedModule:_isPlayer(player)
	if table.find(mockPlayers.getAllPlayers(), player) then
		return true
	end

	return false
end

function mockedModule:_getRemotes()
	local identifier = tostring(n)

	if self:_getContext() == "server" then
		local reliable = ServerRemoteEvent.new(identifier, "Reliable")
		local unreliable = ServerRemoteEvent.new(identifier, "Unreliable")
		return reliable, unreliable
	elseif self:_getContext() == "client" then
		local reliable = ClientRemoteEvent.new(self._owner, identifier, "Reliable")
		local unreliable = ClientRemoteEvent.new(self._owner, identifier, "Unreliable")
		return reliable, unreliable
	end

	return
end

function mockedModule.new(context: "client" | "server", owner: any)
	local self = {}
	setmetatable(self, mockedModule)

	self.context = context
	self._owner = owner

	self:_initialize()

	return self
end

export type MockedBridge = typeof(mockedModule)

return function()
	n += 1
	return mockedModule
end


local JestGlobals = require("@dev-packages/JestGlobals")
local Bridge = require("@yetanothernet/Bridge")
local beforeAll = JestGlobals.beforeAll
local describe = JestGlobals.describe
local expect = JestGlobals.expect
local test = JestGlobals.test
local jest = JestGlobals.jest

local root = script.Parent.Parent
local BridgeModule = root.Bridge

local mockPlayers = require("@yetanothernet/__utils__/mockPlayers")
local mockBridge = require("@yetanothernet/__mocks__/mockBridge")

local Identifier = require("@yetanothernet/Identifier")

local MockedBridge

local serverBridge
local clientBridge
local player

local globalIdentifier = Identifier.new()

function queueFakePackets(bridge: any, mockPlayer: any, ...)
	local channel = "Reliable"

	local queue = {
		{
			channel = "Reliable",
			identifier = globalIdentifier,
			recipient = mockPlayer,
			data = { 1, 2, 3 },
		},
		...,
	}

	for _, packet in queue do
		table.insert(bridge._outgoingQueue[channel], packet)
	end
end

describe("Bridge", function()
	beforeAll(function()
		jest.mock(BridgeModule, mockBridge)
		MockedBridge = require(BridgeModule) :: mockBridge.MockedBridge

		player = mockPlayers.createPlayer()
		clientBridge = MockedBridge.new("client", player)

		serverBridge = MockedBridge.new("server")
	end)

	test("Should work across the Client-Server Boundary", function()
		MockedBridge = require(BridgeModule) :: mockBridge.MockedBridge

		queueFakePackets(serverBridge, player)

		serverBridge:endFrame()
		clientBridge:endFrame()
		serverBridge:beginFrame()
		clientBridge:beginFrame()

		local snapshot = clientBridge:snapshot()

		expect(snapshot["Reliable"][1]).toEqual({
			data = { 1, 2, 3 },
			identifier = globalIdentifier,
			sender = "NET_SERVER",
		})
	end)

	test(
		"When called, `_processOutgoingQueue` should properly package Client/Server payloads",
		function()
			MockedBridge = require(BridgeModule) :: mockBridge.MockedBridge

			local identifier = Identifier.new()

			local fakeClientPacket = {
				channel = "Reliable",
				identifier = identifier,
				recipient = serverBridge.server,
				data = { "a", "b", "c" },
			}

			-- Fill the outgoingQueue with fake packets
			queueFakePackets(serverBridge, player, fakeClientPacket)

			local clientPayloads: { [any]: any }, serverPayloads =
				serverBridge:_processOutgoingQueue()

			-- Check client payloads
			expect(clientPayloads[player].Reliable).toEqual(expect.objectContaining({
				[globalIdentifier] = {
					{ 1, 2, 3 },
				},
			}))

			-- Check server payloads
			expect(serverPayloads.Reliable).toEqual(expect.objectContaining({
				[fakeClientPacket.identifier] = { fakeClientPacket.data },
			}))
		end
	)

	describe(
		"Should send payloads to their appropriate Channel and Recipient on the Client",
		function()
			test("Client -> Server", function()
				local identifier = Identifier.new()

				local packet = {
					channel = "Reliable",
					identifier = identifier,
					recipient = Bridge.server,
					data = { "a", "b", "c" },
				} :: Bridge.OutgoingPacket

				-- Add to the queue
				table.insert(clientBridge._outgoingQueue.Reliable, packet)

				-- Process and send payloads
				local clientPayloads, serverPayloads = clientBridge:_processOutgoingQueue()
				clientBridge:_sendPayloads(clientPayloads, serverPayloads)

				-- Find the packet in the queue
				expect(serverBridge._incomingQueue.Reliable).toEqual(expect.arrayContaining({
					{
						identifier = packet.identifier,
						sender = player,
						data = packet.data,
					},
				}))
			end)

			test("Server -> Client", function()
				local identifier = Identifier.new()

				local packet = {
					channel = "Reliable",
					identifier = identifier,
					recipient = player,
					data = { "a", "b", "c" },
				} :: Bridge.OutgoingPacket

				-- Add to the queue
				table.insert(serverBridge._outgoingQueue.Reliable, packet)

				-- Process and send payloads
				local clientPayloads, serverPayloads = serverBridge:_processOutgoingQueue()
				serverBridge:_sendPayloads(clientPayloads, serverPayloads)

				-- Find the packet in the queue
				expect(clientBridge._incomingQueue.Reliable).toEqual(expect.arrayContaining({
					{
						identifier = packet.identifier,
						sender = serverBridge.server,
						data = packet.data,
					},
				}))
			end)
		end
	)

	test("Should not send empty payloads", function()
		-- Clear queue
		clientBridge:endFrame()
		clientBridge:beginFrame()

		-- Track whether or not the RemoteEvent was fired
		local wasFired = false
		clientBridge.reliableChannel.OnClientEvent:Connect(function()
			wasFired = true
		end)

		-- Send empty payload
		local clientPayloads, serverPayloads = serverBridge:_processOutgoingQueue()
		serverBridge:_sendPayloads(clientPayloads, serverPayloads)

		expect(clientBridge._incomingQueue.Reliable).toEqual({})
		expect(wasFired).toBe(false)
	end)

	describe("Ser/Des Integration", function()
		test("Should provide an Instance map when serializing Instances", function()
			MockedBridge = require(BridgeModule) :: mockBridge.MockedBridge

			local identifier = Identifier.new()

			local instance = Instance.new("Part")

			table.insert(serverBridge._outgoingQueue["Reliable"], {
				channel = "Reliable",
				identifier = identifier,
				recipient = player,
				data = { instance },
			})

			serverBridge:endFrame()
			clientBridge:endFrame()
			serverBridge:beginFrame()
			clientBridge:beginFrame()

			expect(clientBridge._instanceMap).toContain(instance)

			local snapshot = clientBridge:snapshot()

			expect(snapshot["Reliable"][1]).toEqual({
				identifier = identifier,
				sender = "NET_SERVER",
				data = { instance },
			})
		end)
	end)
end)


local JestGlobals = require("@dev-packages/JestGlobals")
local describe = JestGlobals.describe
local expect = JestGlobals.expect
local it = JestGlobals.it

local Identifier = require("@yetanothernet/Identifier")

describe("Identifier", function()
	it("Should return unique strings", function()
		local identifierOne = Identifier.new()
		local identifierTwo = Identifier.new()

		expect(identifierOne).never.toEqual(identifierTwo)
	end)

	it("Should have the correct position when a position is given", function()
		local identifierThree = Identifier.new(1)
		local _identifierFour = Identifier.new(2)
		local identifierFive = Identifier.new(1)

		expect(identifierThree).toEqual(identifierFive)
	end)
end)


local Net = require("@yetanothernet")
local JestGlobals = require("@dev-packages/JestGlobals")
local beforeAll = JestGlobals.beforeAll
local afterEach = JestGlobals.afterEach
local beforeEach = JestGlobals.beforeEach
local describe = JestGlobals.describe
local expect = JestGlobals.expect
local test = JestGlobals.test
local jest = JestGlobals.jest

local root = script.Parent.Parent
local BridgeModule = root.Bridge

local mockPlayers = require("@yetanothernet/__utils__/mockPlayers")
local mockBridge = require("@yetanothernet/__mocks__/mockBridge")

local MockedBridge

local serverRoute
local serverBeginFrame
local serverEndFrame

local clientRoute
local clientBeginFrame
local clientEndFrame
local mockPlayer

local beginFrame
local endFrame

describe("Route", function()
	beforeAll(function()
		jest.mock(BridgeModule, mockBridge)

		MockedBridge = require(BridgeModule) :: mockBridge.MockedBridge

		local function createHook(
			routes: { Net.Route<...any> },
			context: "server" | "client",
			player: any?
		)
			local bridge = MockedBridge.new(context, player)

			local function beginFrameFn()
				bridge:beginFrame()

				for _, route in routes do
					route:_updateSnapshot(bridge)
				end
			end

			local function endFrameFn()
				for _, route in routes do
					route:_queuePackets(bridge)
				end

				bridge:endFrame()
			end

			return beginFrameFn, endFrameFn
		end

		-- Setup server

		serverRoute = Net.Route.new({
			Channel = "Reliable",
		}) :: Net.Route<number, boolean, string>

		serverBeginFrame, serverEndFrame = createHook({ serverRoute }, "server")

		-- Setup client

		mockPlayer = mockPlayers.createPlayer()

		clientRoute = Net.Route.new({
			Channel = "Reliable",
		}) :: Net.Route<number, boolean, string>
		clientRoute["_identifier"] = serverRoute["_identifier"]

		clientBeginFrame, clientEndFrame = createHook({ clientRoute }, "client", mockPlayer)

		beginFrame = function()
			serverBeginFrame()
			clientBeginFrame()
		end

		endFrame = function()
			serverEndFrame()
			clientEndFrame()
		end
	end)

	-- Schedule cleanup
	beforeEach(function()
		beginFrame()
	end)

	-- Schedule cleanup
	afterEach(function()
		endFrame()
	end)

	test("When calling `:send()`, it should properly queue the packets to it's bridge", function()
		local sendRequest = serverRoute:send(1, true, "Hello, world")
		sendRequest:to(mockPlayer)

		expect(sendRequest._outgoingQueue).toEqual(serverRoute._outgoingQueue)
		expect(sendRequest._outgoingQueue[sendRequest._position]).toEqual({
			recipient = mockPlayer,
			data = { 1, true, "Hello, world" } :: { any },
		})
	end)

	test("When calling `:query()`, it should return a valid QueryResult", function()
		local sendRequest = serverRoute:send(1, true, "Hello, world")
		sendRequest:to(mockPlayer)

		endFrame()
		beginFrame()

		local queryResult = clientRoute:query()

		expect(queryResult._identifier).toEqual(serverRoute._identifier)
		expect(queryResult._snapshot).toContainEqual({
			identifier = serverRoute._identifier,
			sender = Net.server,
			data = { 1, true, "Hello, world" } :: { any },
		})
	end)

	test("Incoming Middleware should overwrite each packet's data", function()
		clientRoute:addIncomingMiddleware(function(num, bool, str)
			local returnValues = {} :: { any }

			table.insert(returnValues, if typeof(num) ~= "number" then 1 else num)
			table.insert(returnValues, if typeof(bool) ~= "boolean" then true else bool)
			table.insert(returnValues, if typeof(str) ~= "string" then "Hello, world" else str)

			return table.unpack(returnValues)
		end)

		-- Send incorrect values
		local sendRequest = serverRoute:send(false :: any, "Oops" :: any, 1 :: any)
		sendRequest:to(mockPlayer)

		endFrame()
		beginFrame()

		local queryResult = clientRoute:query()

		expect(queryResult._identifier).toEqual(serverRoute._identifier)
		expect(queryResult._snapshot).toContainEqual({
			identifier = serverRoute._identifier,
			sender = Net.server,
			data = { 1, true, "Hello, world" } :: { any },
		})
	end)

	test("Outgoing Middleware should overwrite each packet's data", function()
		serverRoute:addOutgoingMiddleware(function(num, bool, str)
			local returnValues = {} :: { any }

			table.insert(returnValues, if typeof(num) ~= "number" then 1 else num)
			table.insert(returnValues, if typeof(bool) ~= "boolean" then true else bool)
			table.insert(returnValues, if typeof(str) ~= "string" then "Hello, world" else str)

			return table.unpack(returnValues)
		end)

		-- Send incorrect values
		local sendRequest = serverRoute:send(false :: any, "Oops" :: any, 1 :: any)
		sendRequest:to(mockPlayer)

		endFrame()
		beginFrame()

		local queryResult = clientRoute:query()

		expect(queryResult._identifier).toEqual(serverRoute._identifier)
		expect(queryResult._snapshot).toContainEqual({
			identifier = serverRoute._identifier,
			sender = Net.server,
			data = { 1, true, "Hello, world" } :: { any },
		})
	end)

	test("Should process connections", function()
		local seenConnection = false

		Net.createConnection(clientRoute, function(i, sender, num, bool, str)
			expect(i).toEqual(1)
			expect(sender).toEqual("NET_SERVER")
			expect(num).toEqual(1)
			expect(bool).toEqual(true)
			expect(str).toEqual("Hello, world")

			seenConnection = true
		end)

		local sendRequest = serverRoute:send(1, true, "Hello, world")
		sendRequest:to(mockPlayer)

		endFrame()
		beginFrame()

		clientRoute:_processConnections()
		expect(seenConnection).toEqual(true)
	end)
end)


local JestGlobals = require("@dev-packages/JestGlobals")
local describe = JestGlobals.describe
local test = JestGlobals.test

local Identifier = require("@yetanothernet/Identifier")
local SendRequest = require("@yetanothernet/SendRequest")

describe("SendRequest", function()
	test("When calling `to()`, should modify the recipient", function()
		local outgoingQueue = {}

		local identifier = Identifier.new()

		local packet = {
			channel = "Reliable",
			data = { 1, 2, 3 },
			identifier = identifier,
			recipient = nil,
		}
		table.insert(outgoingQueue, packet)

		local sendRequest = {
			_outgoingQueue = outgoingQueue,
			_position = #outgoingQueue,
		}
		setmetatable(sendRequest, SendRequest)
	end)
end)


local Workspace = game:GetService("Workspace")

local JestGlobals = require("@dev-packages/JestGlobals")
local expect = JestGlobals.expect
local describe = JestGlobals.describe
local test = JestGlobals.test

local root = script.Parent.Parent

local typeToValue = {
	array = { 1, 2, 3 },
	dictionary = {
		a = 1,
		b = 2,
		c = 3,
	},
	identifier = 1,
	boolean = true,
	number = 22,
	string = "Hello, world!",
	BrickColor = BrickColor.Yellow(),
	CFrame = CFrame.new(Vector3.new(20, 40, 60))
		* CFrame.fromEulerAnglesXYZ(math.rad(45), math.rad(90), math.rad(180)),
	Color3 = Color3.fromHex("54bbff"),
	DateTime = DateTime.fromUnixTimestampMillis(1686894720000),
	EnumItem = Enum.Genre.Scary,
	Instance = Instance.new("Part"),
	Rect = Rect.new(Vector2.new(10, 20), Vector2.new(40, 80)),
	Region3 = Region3.new(Vector3.zero, Vector3.new(30, 30, 30)),
	Region3int16 = Region3int16.new(Vector3int16.new(0, 0, 0), Vector3int16.new(30, 30, 30)),
	TweenInfo = TweenInfo.new(1, Enum.EasingStyle.Quad, Enum.EasingDirection.InOut, -1, true, 5),
	UDim = UDim.new(0.5, 10),
	UDim2 = UDim2.new(UDim.new(0.1, 10), UDim.new(0.7, 30)),
	Vector2 = Vector2.new(10, 20),
	Vector2int16 = Vector2int16.new(30, 60),
	Vector3 = Vector3.new(20, 40, 60),
	Vector3int16 = Vector3int16.new(40, 80, 120),
}

local customTests = {
	"buffer",
}

describe("Ser/Des", function()
	describe("Serialization and Deserialization should return exact value", function()
		for _, module in root.serdes.datatypes:GetDescendants() do
			if not module:IsA("ModuleScript") then
				continue
			end

			if not typeToValue[module.Name] then
				if table.find(customTests, module.Name) then
					continue
				end

				test.todo(module.Name)
				continue
			end

			test(module.Name, function()
				local serdes = require(module) :: any

				local _buffer = serdes.serialize(typeToValue[module.Name])
				expect(_buffer).toEqual(expect.any("buffer"))

				local value = serdes.deserialize(_buffer)
				expect(value).toEqual(typeToValue[module.Name])
			end)
		end
	end)

	test("Array should return nil when failing serialization", function()
		local part = Instance.new("Part")
		part.Anchored = true
		part.Position = Vector3.new(0, 3, 0)
		part.Parent = Workspace

		-- RaycastResults have no constructor, meaning we can't deserialize it
		local raycastResult = Workspace:Raycast(Vector3.zero, Vector3.new(0, 5, 0))

		local serdes = require("@serdes/datatypes/custom/array")
		local array: { any } = {
			1,
			true,
			raycastResult,
		}

		part:Destroy()

		local _buffer = serdes.serialize(array)
		expect(_buffer).toBeNil()
	end)

	test("Array should be able to serialize/deserialize empty arrays", function()
		local serdes = require("@serdes/datatypes/custom/array")
		local array = {}

		local _buffer = serdes.serialize(array)
		expect(_buffer).toEqual(expect.any("buffer"))

		local value = serdes.deserialize(_buffer)
		expect(value).toEqual(array)
	end)

	test("Dictionary should return nil when failing serialization", function()
		local part = Instance.new("Part")
		part.Anchored = true
		part.Position = Vector3.new(0, 3, 0)
		part.Parent = Workspace

		-- RaycastResults have no constructor, meaning we can't deserialize it
		local raycastResult = Workspace:Raycast(Vector3.zero, Vector3.new(0, 5, 0))

		local serdes = require("@serdes/datatypes/custom/dictionary")
		local dictionary = {
			a = 1,
			b = true,
			c = raycastResult,
		}

		part:Destroy()

		local _buffer = serdes.serialize(dictionary)
		expect(_buffer).toBeNil()
	end)
end)


local mockedPlayers = {}

function createPlayer()
	local n = #mockedPlayers + 1
	table.insert(mockedPlayers, {
		__type = "Player",
		Name = `MockPlayer{n}`,
		UserId = -n,
	})

	return mockedPlayers[n]
end

function getAllPlayers()
	return mockedPlayers
end

return {
	createPlayer = createPlayer,
	getAllPlayers = getAllPlayers,
}


--!nocheck

local Signal = require("@dev-packages/Signal")

local clientRemotes = {}
local serverRemotes = {}

local ClientRemoteEvent = {}
ClientRemoteEvent.__index = ClientRemoteEvent

local mockPlayers = require("@yetanothernet/__utils__/mockPlayers")

function ClientRemoteEvent:FireServer(...: any)
	local container = serverRemotes[self._identifier]
	if not container then
		return
	end

	for _, remote in container do
		if remote._channel ~= self._channel then
			return
		end
		remote.OnServerEvent:Fire(self._owner, ...)
	end
end

function ClientRemoteEvent.new(player, identifier: string, channel: "Reliable" | "Unreliable")
	local remote = {}
	setmetatable(remote, ClientRemoteEvent)

	remote._owner = player
	remote._channel = channel
	remote._identifier = identifier
	remote.OnClientEvent = Signal.new()

	local container = clientRemotes[remote._identifier]
	if not container then
		container = {}
		clientRemotes[remote._identifier] = container
	end

	table.insert(container, remote)

	return remote
end

local ServerRemoteEvent = {}
ServerRemoteEvent.__index = ServerRemoteEvent

function ServerRemoteEvent:FireAllClient(...: any)
	for _, player in mockPlayers.getAllPlayers() do
		self:FireClient(player, ...)
	end
end

function ServerRemoteEvent:FireClient(player, ...: any)
	local container = clientRemotes[self._identifier]
	if not container then
		return
	end

	for _, remote in container do
		if remote._owner ~= player or remote._channel ~= self._channel then
			continue
		end

		remote.OnClientEvent:Fire(...)
	end
end

function ServerRemoteEvent.new(identifier: string, channel: "Reliable" | "Unreliable")
	local remote = {}
	setmetatable(remote, ServerRemoteEvent)

	remote._channel = channel
	remote._identifier = identifier
	remote.OnServerEvent = Signal.new()

	local container = serverRemotes[remote._identifier]
	if not container then
		container = {}
		serverRemotes[remote._identifier] = container
	end

	table.insert(container, remote)

	return remote
end

function getAllPlayersRemotes(identifier: string)
	for _, player in mockPlayers.getAllPlayers() do
		getPlayerRemotes(player, identifier)
	end
end

function getPlayerRemotes(player, identifier: string)
	local container = clientRemotes[identifier]
	if not container then
		return {}
	end

	local remotes = {}

	for _, remote in container do
		if remote._owner ~= player then
			continue
		end

		table.insert(remotes, player)
	end

	return remotes
end

function getServerRemotes(identifier: string)
	local container = serverRemotes[identifier]
	if not container then
		return {}
	end

	return container
end

return {
	ClientRemoteEvent = ClientRemoteEvent,
	ServerRemoteEvent = ServerRemoteEvent,

	getPlayerRemotes = getPlayerRemotes,
	getAllPlayersRemotes = getAllPlayersRemotes,
	getServerRemotes = getServerRemotes,
}


--!native

local BUFFER_SIZE = 2

local nextIdentifier = 1

local function serialize(position: number?): buffer
	if not position then
		position = nextIdentifier
		nextIdentifier += 1
	end

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writeu16(_buffer, 0, position :: number)

	return _buffer
end

local function deserialize(_buffer: buffer): number
	local position = buffer.readu16(_buffer, 0)
	return position
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 50,
}


--!native

local BUFFER_SIZE = 1

local _true = buffer.create(BUFFER_SIZE)
buffer.writei8(_true, 0, 1)

local _false = buffer.create(BUFFER_SIZE)
buffer.writei8(_false, 0, 0)

local _nil = buffer.create(BUFFER_SIZE)
buffer.writei8(_nil, 0, -1)

local function serialize(bool: boolean?): buffer
	if bool == true then
		return _true
	elseif bool == false then
		return _false
	else
		return _nil
	end
end

local function deserialize(_buffer: buffer): boolean?
	local number = buffer.readi8(_buffer, 0)
	return if number == 1 then true elseif number == 0 then false else nil
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,

	id = 3,
}


--!native

--[[
    This exists purely to add support for buffers into
    Arrays, Dictionaries, and other custom types which may
    contain a buffer.

    Instead of defining unique behavior for buffers, this
    module allows us to use the prexisting framework for
    serializing/deserializing datatypes for buffers.
]]

local function serialize(_buffer: buffer): buffer
	return _buffer
end

local function deserialize(_buffer: buffer): buffer
	return _buffer
end

return {
	serialize = serialize,
	deserialize = deserialize,

	id = 49,
}


--!native

local BUFFER_SIZE = 4

local function serialize(number: number): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef32(_buffer, 0, number)
	return _buffer
end

local function deserialize(_buffer: buffer): number
	return buffer.readf32(_buffer, 0)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 4,
}


--!native

local function serialize(_string: string): buffer
	return buffer.fromstring(_string)
end

local function deserialize(_buffer: buffer): string
	return buffer.tostring(_buffer)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	id = 5,
}


--!native

local BUFFER_SIZE = 2

local brickColorCache = {}

local function serialize(color: BrickColor): buffer
	if brickColorCache[color.Number] then
		return brickColorCache[color.Number]
	end

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writeu16(_buffer, 0, color.Number)

	brickColorCache[color.Number] = _buffer

	return _buffer
end

local function deserialize(_buffer: buffer): BrickColor
	local colorNumber = buffer.readu16(_buffer, 0)
	return BrickColor.new(colorNumber)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 7,
}


--!native

local BUFFER_SIZE = 48

local function serialize(cframe: CFrame): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	local x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22 = cframe:GetComponents()
	buffer.writef32(_buffer, 0, x)
	buffer.writef32(_buffer, 4, y)
	buffer.writef32(_buffer, 8, z)
	buffer.writef32(_buffer, 12, R00)
	buffer.writef32(_buffer, 16, R01)
	buffer.writef32(_buffer, 20, R02)
	buffer.writef32(_buffer, 24, R10)
	buffer.writef32(_buffer, 28, R11)
	buffer.writef32(_buffer, 32, R12)
	buffer.writef32(_buffer, 36, R20)
	buffer.writef32(_buffer, 40, R21)
	buffer.writef32(_buffer, 44, R22)

	return _buffer
end

local function deserialize(_buffer: buffer): CFrame
	local x = buffer.readf32(_buffer, 0)
	local y = buffer.readf32(_buffer, 4)
	local z = buffer.readf32(_buffer, 8)
	local R00 = buffer.readf32(_buffer, 12)
	local R01 = buffer.readf32(_buffer, 16)
	local R02 = buffer.readf32(_buffer, 20)
	local R10 = buffer.readf32(_buffer, 24)
	local R11 = buffer.readf32(_buffer, 28)
	local R12 = buffer.readf32(_buffer, 32)
	local R20 = buffer.readf32(_buffer, 36)
	local R21 = buffer.readf32(_buffer, 40)
	local R22 = buffer.readf32(_buffer, 44)

	return CFrame.new(x, y, z, R00, R01, R02, R10, R11, R12, R20, R21, R22)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 9,
}


--!native

local BUFFER_SIZE = 12

local function serialize(color: Color3): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef32(_buffer, 0, color.R)
	buffer.writef32(_buffer, 4, color.G)
	buffer.writef32(_buffer, 8, color.B)

	return _buffer
end

local function deserialize(_buffer: buffer): Color3
	local r = buffer.readf32(_buffer, 0)
	local g = buffer.readf32(_buffer, 4)
	local b = buffer.readf32(_buffer, 8)

	return Color3.new(r, g, b)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 10,
}


--!native

local BUFFER_SIZE = 8

local function serialize(dateTime: DateTime): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef64(_buffer, 0, dateTime.UnixTimestampMillis)
	return _buffer
end

local function deserialize(_buffer: buffer): DateTime
	return DateTime.fromUnixTimestampMillis(buffer.readf64(_buffer, 0))
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 14,
}


--!native

local enumMap = {}
for _, enum in Enum:GetEnums() do
	enumMap[tostring(enum)] = {}

	for _, enumItem in enum:GetEnumItems() do
		enumMap[tostring(enum)][enumItem.Value] = enumItem
	end
end

local enumItemCache = {}

local function serialize(enumItem: EnumItem): buffer
	if enumItemCache[enumItem] then
		return enumItemCache[enumItem]
	end

	local typeBuffer = buffer.fromstring(tostring(enumItem.EnumType))

	local _buffer = buffer.create(1 + buffer.len(typeBuffer))
	buffer.writei8(_buffer, 0, enumItem.Value)
	buffer.copy(_buffer, 1, typeBuffer)

	enumItemCache[enumItem] = _buffer

	return _buffer
end

local function deserialize(_buffer: buffer): EnumItem
	local typeBuffer = buffer.create(buffer.len(_buffer) - 1)
	buffer.copy(typeBuffer, 0, _buffer, 1)

	local _type = buffer.tostring(typeBuffer)
	local value = buffer.readi8(_buffer, 0)

	return enumMap[_type][value]
end

return {
	serialize = serialize,
	deserialize = deserialize,

	id = 17,
}


--!native

local BUFFER_SIZE = 4

local nextIdentifer = 1
local instanceMap: { [number]: Instance } = {}

local function setIdentifier(identifier: number, instance: Instance)
	instanceMap[tonumber(identifier) :: number] = instance
end

local function getInstanceMap()
	return table.freeze(table.clone(instanceMap))
end

local function serialize(instance: Instance): (buffer, number)
	local identifier = table.find(instanceMap, instance) or nextIdentifer
	if identifier == nextIdentifer then
		setIdentifier(identifier, instance)
		nextIdentifer += 1
	end

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writeu32(_buffer, 0, identifier)

	return _buffer, identifier
end

local function deserialize(_buffer: buffer): Instance
	local identifier = buffer.readu32(_buffer, 0)
	return instanceMap[identifier]
end

return {
	serialize = serialize,
	deserialize = deserialize,
	setIdentifier = setIdentifier,
	getInstanceMap = getInstanceMap,

	size = BUFFER_SIZE,
	id = 22,
}


--!native

local vector2 = require("@serdes/datatypes/userdata/Vector2")

local BUFFER_SIZE = vector2.size * 2

local function serialize(rect: Rect): buffer
	local min = vector2.serialize(rect.Min)
	local max = vector2.serialize(rect.Max)

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.copy(_buffer, 0, min, 0, vector2.size)
	buffer.copy(_buffer, vector2.size, max, 0, vector2.size)

	return _buffer
end

local function deserialize(_buffer: buffer): Rect
	local minBuffer = buffer.create(vector2.size)
	local maxBuffer = buffer.create(vector2.size)

	buffer.copy(minBuffer, 0, _buffer, 0, vector2.size)
	buffer.copy(maxBuffer, 0, _buffer, vector2.size, vector2.size)

	local min = vector2.deserialize(minBuffer)
	local max = vector2.deserialize(maxBuffer)

	return Rect.new(min, max)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 36,
}


--!native

local cframe = require("@serdes/datatypes/userdata/CFrame")
local vector3 = require("@serdes/datatypes/userdata/Vector3")

local BUFFER_SIZE = cframe.size + vector3.size

local function serialize(region: Region3): buffer
	local _cframe = cframe.serialize(region.CFrame)
	local size = vector3.serialize(region.Size)

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.copy(_buffer, 0, _cframe, 0, cframe.size)
	buffer.copy(_buffer, cframe.size, size, 0, vector3.size)

	return _buffer
end

local function deserialize(_buffer: buffer): Region3
	local cframeBuffer = buffer.create(cframe.size)
	local sizeBuffer = buffer.create(vector3.size)

	buffer.copy(cframeBuffer, 0, _buffer, 0, cframe.size)
	buffer.copy(sizeBuffer, 0, _buffer, cframe.size, vector3.size)

	local _cframe = cframe.deserialize(cframeBuffer)
	local size = vector3.deserialize(sizeBuffer)

	local min = _cframe.Position - (size / 2)
	local max = _cframe.Position + (size / 2)

	return Region3.new(min, max)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 37,
}


--!native

local vector3int16 = require("@serdes/datatypes/userdata/Vector3int16")

local BUFFER_SIZE = vector3int16.size * 2

local function serialize(region: Region3int16): buffer
	local min = vector3int16.serialize(region.Min)
	local max = vector3int16.serialize(region.Max)

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.copy(_buffer, 0, min, 0, vector3int16.size)
	buffer.copy(_buffer, 6, max, 0, vector3int16.size)

	return _buffer
end

local function deserialize(_buffer: buffer): Region3int16
	local minBuffer = buffer.create(vector3int16.size)
	local maxBuffer = buffer.create(vector3int16.size)

	buffer.copy(minBuffer, 0, _buffer, 0, vector3int16.size)
	buffer.copy(maxBuffer, 0, _buffer, vector3int16.size, vector3int16.size)

	local min = vector3int16.deserialize(minBuffer)
	local max = vector3int16.deserialize(maxBuffer)

	return Region3int16.new(min, max)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 38,
}


--!native

local enumItem = require("@serdes/datatypes/userdata/EnumItem")
local number = require("@serdes/datatypes/primitive/number")
local boolean = require("@serdes/datatypes/primitive/boolean")
local _string = require("@serdes/datatypes/primitive/string")

local PARTIAL_BUFFER_SIZE = (number.size * 3) + boolean.size

local function serialize(tweenInfo: TweenInfo): buffer
	local time = number.serialize(tweenInfo.Time)
	local delayTime = number.serialize(tweenInfo.DelayTime)
	local repeatCount = number.serialize(tweenInfo.RepeatCount)
	local reverses = boolean.serialize(tweenInfo.Reverses)
	local easingDirection = enumItem.serialize(tweenInfo.EasingDirection)
	local easingStyle = enumItem.serialize(tweenInfo.EasingStyle)

	local _buffer = buffer.create(
		PARTIAL_BUFFER_SIZE + 8 + buffer.len(easingDirection) + buffer.len(easingStyle)
	)

	buffer.copy(_buffer, 0, time, 0, number.size)
	buffer.copy(_buffer, number.size, delayTime, 0, number.size)
	buffer.copy(_buffer, number.size * 2, repeatCount, 0, number.size)
	buffer.copy(_buffer, number.size * 3, reverses, 0, boolean.size)

	local cursor = PARTIAL_BUFFER_SIZE

	local easingDirectionSizeBuffer = buffer.create(4)
	buffer.writeu32(easingDirectionSizeBuffer, 0, buffer.len(easingDirection))

	buffer.copy(_buffer, cursor, easingDirectionSizeBuffer)
	cursor += buffer.len(easingDirectionSizeBuffer)

	buffer.copy(_buffer, cursor, easingDirection)
	cursor += buffer.len(easingDirection)

	local easingStyleSizeBuffer = buffer.create(4)
	buffer.writeu32(easingStyleSizeBuffer, 0, buffer.len(easingStyle))

	buffer.copy(_buffer, cursor, easingStyleSizeBuffer)
	cursor += buffer.len(easingStyleSizeBuffer)

	buffer.copy(_buffer, cursor, easingStyle)
	cursor += buffer.len(easingStyle)

	return _buffer
end

local function deserialize(_buffer: buffer): TweenInfo
	local timeBuffer = buffer.create(number.size)
	local delayTimeBuffer = buffer.create(number.size)
	local repeatCountBuffer = buffer.create(number.size)
	local reversesBuffer = buffer.create(boolean.size)

	buffer.copy(timeBuffer, 0, _buffer, 0, number.size)
	buffer.copy(delayTimeBuffer, 0, _buffer, number.size, number.size)
	buffer.copy(repeatCountBuffer, 0, _buffer, number.size * 2, number.size)
	buffer.copy(reversesBuffer, 0, _buffer, number.size * 3, boolean.size)

	local cursor = PARTIAL_BUFFER_SIZE

	local easingDirectionSizeBuffer = buffer.create(4)
	buffer.copy(easingDirectionSizeBuffer, 0, _buffer, cursor, 4)
	cursor += 4

	local easingDirectionSize = buffer.readu32(easingDirectionSizeBuffer, 0)

	local easingDirectionBuffer = buffer.create(easingDirectionSize)
	buffer.copy(easingDirectionBuffer, 0, _buffer, cursor, easingDirectionSize)
	cursor += buffer.len(easingDirectionBuffer)

	local easingStyleSizeBuffer = buffer.create(4)
	buffer.copy(easingStyleSizeBuffer, 0, _buffer, cursor, 4)
	cursor += 4

	local easingStyleSize = buffer.readu32(easingStyleSizeBuffer, 0)

	local easingStyleBuffer = buffer.create(easingStyleSize)
	buffer.copy(easingStyleBuffer, 0, _buffer, cursor, easingStyleSize)
	cursor += buffer.len(easingStyleBuffer)

	local time = number.deserialize(timeBuffer)
	local delayTime = number.deserialize(delayTimeBuffer)
	local repeatCount = number.deserialize(repeatCountBuffer)
	local reverses = boolean.deserialize(reversesBuffer)
	local easingDirection = enumItem.deserialize(easingDirectionBuffer) :: Enum.EasingDirection
	local easingStyle = enumItem.deserialize(easingStyleBuffer) :: Enum.EasingStyle

	return TweenInfo.new(time, easingStyle, easingDirection, repeatCount, reverses, delayTime)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	id = 42,
}


--!native

local BUFFER_SIZE = 8

local function serialize(udim: UDim): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef32(_buffer, 0, udim.Scale)
	buffer.writeu32(_buffer, 4, udim.Offset)

	return _buffer
end

local function deserialize(_buffer: buffer): UDim
	local scale = buffer.readf32(_buffer, 0)
	local offset = buffer.readu32(_buffer, 4)

	return UDim.new(scale, offset)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 43,
}


--!native

local udim = require("@serdes/datatypes/userdata/UDim")

local BUFFER_SIZE = udim.size * 2

local function serialize(_udim: UDim2): buffer
	local x = udim.serialize(_udim.X)
	local y = udim.serialize(_udim.Y)

	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.copy(_buffer, 0, x)
	buffer.copy(_buffer, udim.size, y)

	return _buffer
end

local function deserialize(_buffer: buffer): UDim2
	local xBuffer = buffer.create(udim.size)
	local yBuffer = buffer.create(udim.size)
	buffer.copy(xBuffer, 0, _buffer, 0, udim.size)
	buffer.copy(yBuffer, 0, _buffer, udim.size, udim.size)

	local x = udim.deserialize(xBuffer)
	local y = udim.deserialize(yBuffer)

	return UDim2.new(x, y)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	id = 44,
}


--!native

local BUFFER_SIZE = 8

local function serialize(vector: Vector2): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef32(_buffer, 0, vector.X)
	buffer.writef32(_buffer, 4, vector.Y)

	return _buffer
end

local function deserialize(_buffer: buffer): Vector2
	local x = buffer.readf32(_buffer, 0)
	local y = buffer.readf32(_buffer, 4)

	return Vector2.new(x, y)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = 8,
	id = 45,
}


--!native

local BUFFER_SIZE = 4

local function serialize(vector: Vector2int16): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writei16(_buffer, 0, vector.X)
	buffer.writei16(_buffer, 2, vector.Y)

	return _buffer
end

local function deserialize(_buffer: buffer): Vector2int16
	local x = buffer.readi16(_buffer, 0)
	local y = buffer.readi16(_buffer, 2)

	return Vector2int16.new(x, y)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 46,
}


--!native

local BUFFER_SIZE = 12

local function serialize(vector: Vector3): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writef32(_buffer, 0, vector.X)
	buffer.writef32(_buffer, 4, vector.Y)
	buffer.writef32(_buffer, 8, vector.Z)

	return _buffer
end

local function deserialize(_buffer: buffer): Vector3
	local x = buffer.readf32(_buffer, 0)
	local y = buffer.readf32(_buffer, 4)
	local z = buffer.readf32(_buffer, 8)

	return Vector3.new(x, y, z)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 47,
}


--!native

local BUFFER_SIZE = 6

local function serialize(vector: Vector3int16): buffer
	local _buffer = buffer.create(BUFFER_SIZE)
	buffer.writei16(_buffer, 0, vector.X)
	buffer.writei16(_buffer, 2, vector.Y)
	buffer.writei16(_buffer, 4, vector.Z)

	return _buffer
end

local function deserialize(_buffer: buffer): Vector3int16
	local x = buffer.readi16(_buffer, 0)
	local y = buffer.readi16(_buffer, 2)
	local z = buffer.readi16(_buffer, 4)

	return Vector3int16.new(x, y, z)
end

return {
	serialize = serialize,
	deserialize = deserialize,

	size = BUFFER_SIZE,
	id = 48,
}


local array = require("@serdes/datatypes/custom/array")
local dictionary = require("@serdes/datatypes/custom/dictionary")

local boolean = require("@serdes/datatypes/primitive/boolean")
local number = require("@serdes/datatypes/primitive/number")
local _string = require("@serdes/datatypes/primitive/string")

local BrickColor = require("@serdes/datatypes/userdata/BrickColor")
local CFrame = require("@serdes/datatypes/userdata/CFrame")
local Color3 = require("@serdes/datatypes/userdata/Color3")
local DateTime = require("@serdes/datatypes/userdata/DateTime")
local EnumItem = require("@serdes/datatypes/userdata/EnumItem")
local Instance = require("@serdes/datatypes/userdata/Instance")
local Rect = require("@serdes/datatypes/userdata/Rect")
local Region3 = require("@serdes/datatypes/userdata/Region3")
local Region3int16 = require("@serdes/datatypes/userdata/Region3int16")
local TweenInfo = require("@serdes/datatypes/userdata/TweenInfo")
local UDim = require("@serdes/datatypes/userdata/UDim")
local UDim2 = require("@serdes/datatypes/userdata/UDim2")
local Vector2 = require("@serdes/datatypes/userdata/Vector2")
local Vector2int16 = require("@serdes/datatypes/userdata/Vector2int16")
local Vector3 = require("@serdes/datatypes/userdata/Vector3")
local Vector3int16 = require("@serdes/datatypes/userdata/Vector3int16")

return {
	array = array,
	dictionary = dictionary,

	boolean = boolean,
	number = number,
	string = _string,

	BrickColor = BrickColor,
	CFrame = CFrame,
	Color3 = Color3,
	DateTime = DateTime,
	EnumItem = EnumItem,
	Instance = Instance,
	Rect = Rect,
	Region3 = Region3,
	Region3int16 = Region3int16,
	TweenInfo = TweenInfo,
	UDim = UDim,
	UDim2 = UDim2,
	Vector2 = Vector2,
	Vector2int16 = Vector2int16,
	Vector3 = Vector3,
	Vector3int16 = Vector3int16,
}


local Workspace = script.Parent

-- In case we need to specify a custom testSetupFile for a project, we need to do that in in a separate jest.config.lua file that's in the project's root folder.
-- Therefore we specify the project here and provide it to the "projects" field in this config file.
-- We also need to add the project to the "testPathIgnorePatterns" field so that Jest doesn't try to run the project's tests again.
local projectsWithCustomJestConfig = {} :: { [string]: any }
local testPathIgnorePatterns = {
	"**/DevPackages/**",
}
local allProjects = { Workspace }

for _, project in projectsWithCustomJestConfig do
	table.insert(testPathIgnorePatterns, project)
	table.insert(allProjects, project)
end

return {
	setupFilesAfterEnv = {},
	projects = allProjects,
	testMatch = {
		"**/__tests__/*.(spec|test)",
	},
	testPathIgnorePatterns = testPathIgnorePatterns,
}


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Jest = require("@dev-packages/Jest")

Jest.runCLI(
	script,
	{
		testPathPattern = { "\\.test$" },
		testPathIgnorePatterns = { "DevPackages" },
		verbose = true,
	} :: any,
	{
		ReplicatedStorage,
	}
):await()


--!strict

local Settings = {}

Settings.Matches = {
	-- // MATCHMAKING

	MATCHMAKING_DURATION = 300, -- // How long the player is in matchmaking before removing them
	CHECK_MATCHMAKING_INTERVALS = 5, -- // How fast to check for matches
	CACHE_CLEANUP_INTERVAL = 300, -- // How fast cached data gets cleaned up
	CROSS_SERVER_DATA_TTL = 120, -- // The Lifetime for the cross server data
	TELEPORT_RETRIES = 3, -- // How many times the game tries to teleport you on fail

	-- // ELO SETTINGS

	ELO_CHANGE = 200, -- // How close ELO can be for matchmaking
	ELO_INCREASE_RATE = 50, -- // Higher ELO people may not be put in any matches. Finds more people in range every loop so they can be matchmaked

	-- // BATCHING

	BATCH_SIZE = 50, -- // The Batch of the amount of keys fetched from the MemoryStore
	MAX_QUEUE_STATUS_PLAYERS = 50, -- // The max amount of players are gotten and sent for each PublishAsync

	-- // LOCKING

	LOCK_TIMEOUT = 10 -- // How long 'Locking' lives before being removed. Locking is a state that tells servers that the player is in a match so the edge case where 2 servers try to match make someone at the same time doesn't happen
}

Settings.Information = {
	PLACE_ID = game.PlaceId, -- // The Place ID of where you want the player to go when they're matchmaked
	SERVER_ID = game.JobId
}

Settings.Modes = {"Casual", "Ranked"} -- // Modes act like parents to SubModes

Settings.SubModes = { -- // Names of mode, totalPlayers is how many players can be in that mode. All SubModes are in each Modes
	["1v1"] = { totalPlayers = 2 },
	["2v2"] = { totalPlayers = 4 },
	["3v3"] = { totalPlayers = 6 },
	["4v4"] = { totalPlayers = 8 }
}

table.freeze(Settings)

return Settings


--!strict

local Signal = {}

Signal.__index = Signal

--=========================
-- // TYPES
--=========================

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

export type SignalData = {
    _listeners: { [number]: (...any) -> () },
    _nextId: number
}

export type Signal = typeof(setmetatable({} :: SignalData, Signal))

--=========================
-- // PUBLIC API
--=========================

-- new(): Constructor for Signal
function Signal.new(): Signal
    local self = setmetatable({}, Signal)

    self._listeners = {}

    self._nextId = 0

    return self
end

-- Connect(): Connect a function
-- @param fn: The function to connect
-- @return Connection
function Signal:Connect(fn: (...any) -> ()): Connection
    if type(fn) ~= "function" then
        error("Connect(): Expected function", 2)
	end

	local selfTyped = self :: SignalData

	selfTyped._nextId += 1

	local id = selfTyped._nextId

	selfTyped._listeners[id] = fn

    local disconnected = false

    local connection: Connection = {} :: any

    function connection:Disconnect()
        if not disconnected then
			selfTyped._listeners[id] = nil
            disconnected = true
        end
    end

    function connection:IsConnected()
        return not disconnected
    end

    return connection
end

-- Once(): Connect a one-time listener
-- @param fn: The function to call once
-- @return Connection
function Signal:Once(fn: (...any) -> ()): Connection
    local conn: Connection

    conn = self:Connect(function(...)
        conn:Disconnect()
        fn(...)
    end)

    return conn
end

-- Fire(): Fire a signal
-- @param Variadic
function Signal:Fire(...: any): ()
    local snapshot = {}

    for _, fn in self._listeners do
        snapshot[#snapshot + 1] = fn
    end

    for _, fn in snapshot do
        task.spawn(fn, ...)
    end
end

-- Wait(): Yield until this Signal fires
-- @return ...any
function Signal:Wait(): ...any
    local co = coroutine.running()

    if not co then
        error("Wait(): Must be called from a coroutine", 2)
    end

    local result: { any } = {}
    local conn: Connection

    conn = self:Connect(function(...)
        result = { ... }
        conn:Disconnect()
        coroutine.resume(co)
    end)

    coroutine.yield()

    return table.unpack(result)
end

-- DisconnectAll(): Disconnect all listeners
function Signal:DisconnectAll(): ()
    for k in self._listeners do
        self._listeners[k] = nil
    end
end

return Signal


--!strict

local CrossServerManager = {}

--=========================
-- // SERVICES
--=========================

local Players = game:GetService("Players")
local MessagingService = game:GetService("MessagingService")
local HttpService = game:GetService("HttpService")

--=========================
-- // TYPES
--=========================

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

--=========================
-- // MODULES
--=========================

CrossServerManager.Modules = {
	Settings = require(script.Parent.Parent.helpers.Settings),
	SharedData = require(script.Parent.SharedData),
	TeleportHandler = require(script.Parent.TeleportHandler),
	QueueManager = require(script.Parent.QueueManager)
}

--=========================
-- // PUBLIC API
--=========================

-- SetupMessagingService(): Sets up the messaging service subscribers
function CrossServerManager.SetupMessagingService(): ()
	for _, c in CrossServerManager.Modules.SharedData.messagingConnections do
		c:Disconnect()
	end

	CrossServerManager.Modules.SharedData.messagingConnections = {}

	local function subscribe(channel: string, handlerKey: string, handler: ({ Data: any }) -> ()): ()
		local ok, conn = pcall(function()
			return MessagingService:SubscribeAsync(channel, handler)
		end)

		if ok then
			CrossServerManager.Modules.SharedData.messagingConnections[handlerKey] = conn
		end
	end

	local Settings = CrossServerManager.Modules.Settings
	local serverId = Settings.Information.SERVER_ID

	subscribe("CrossServerTeleport", "crossServerTeleport", function(msg)
		local data = msg.Data

		if data.PlayersForServer and data.PlayersForServer[serverId] then
			task.spawn(function()
				CrossServerManager.Modules.TeleportHandler.HandleTeleportRequest(data)
			end)
		end
	end)
end

-- NotifyServersOfMatch(): Notifies all servers about the new match
-- @param code: Reserved server code
-- @param playersByServer: Players organized by server ID
-- @param partyMapping: Party information for each player
-- @param queueKey: The key of the queue
-- @return boolean: True on success, false on failure
function CrossServerManager.NotifyServersOfMatch(code: string, playersByServer: { [string]: { number } }, partyMapping: Types.PartyMap, queueKey: Types.QueueKey): boolean
	local success, err = pcall(function()
		MessagingService:PublishAsync("CrossServerTeleport", {
			MatchId = CrossServerManager.Modules.Settings.Information.SERVER_ID,
			ServerCode = code,
			PlaceId = CrossServerManager.Modules.Settings.Information.PLACE_ID,
			PlayersForServer = playersByServer,
            PartyMapping = partyMapping,
			Mode = queueKey.Mode,
			SubMode = queueKey.SubMode,
			Timestamp = tick()
		})
	end)

	if not success then
		warn("NotifyServersOfMatch(): Failed to notify servers:", err)
		return false
	end

	return true
end

--=========================
-- // CLEANUP
--=========================

-- Cleanup(): Cleans up cross-server specific data
function CrossServerManager.Cleanup(): ()
	for _, connection in CrossServerManager.Modules.SharedData.messagingConnections do
		pcall(function()
			connection:Disconnect()
		end)
	end

	CrossServerManager.Modules.SharedData.messagingConnections = {}
end

--=========================
-- // INIT
--=========================

CrossServerManager.SetupMessagingService()

return CrossServerManager