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

local Network = require(ReplicatedStorage.Modules.Network)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Janitor = require(ReplicatedStorage.Packages.Janitor)

local ItemGUI = {}

local PrefabFolder = script:FindFirstChild("Prefabs")
local Prefabs = {
    Container = PrefabFolder:FindFirstChild("ItemIconContainer"),
    Item = PrefabFolder:FindFirstChild("ItemContainer"),
}

local PossibleKeys = {
	Zero = 10,
	One = 1,
	Two = 2,
	Three = 3,
	Four = 4,
	Five = 5,
	Six = 6,
	Seven = 7,
	Eight = 8,
	Nine = 9
}

function ItemGUI.InitGUI()
    local Character = Players.LocalPlayer.Character
    local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")

    local Container = Prefabs.Container:Clone()
    Container.Name = "Backpack"
    Container.Parent = Players.LocalPlayer.PlayerGui.CharacterGUI

    local JanitorInstance = Janitor.new()
    JanitorInstance:LinkToInstances(Container, Character)

    local Tools: {Tool} = {}

    JanitorInstance:Add(Players.LocalPlayer.Backpack.ChildAdded:Connect(function(newChild: Instance)
        if not newChild:IsA("Tool") or table.find(Tools, newChild) then
            return
        end

        table.insert(Tools, newChild)
        ItemGUI._InitTool(Container, newChild, Humanoid)
    end))
    JanitorInstance:Add(Character.ChildAdded:Connect(function(newChild: Instance)
        if not newChild:IsA("Tool") or table.find(Tools, newChild) then
            return
        end

        table.insert(Tools, newChild)
        ItemGUI._InitTool(Container, newChild, Humanoid)
    end))
    JanitorInstance:Add(Players.LocalPlayer.Backpack.ChildRemoved:Connect(function(child: Instance)
        if not child:IsA("Tool") or child.Parent then
            return
        end

        local Index = table.find(Tools, child)
        if Index then
            table.remove(Tools, Index)
        end
    end))
    JanitorInstance:Add(Character.ChildRemoved:Connect(function(child: Instance)
        if not child:IsA("Tool") or child.Parent then
            return
        end

        local Index = table.find(Tools, child)
        if Index then
            table.remove(Tools, Index)
        end
    end))

    JanitorInstance:Add(UserInputService.InputBegan:Connect(function(input: InputObject, gameProcessedEvent: boolean)
        if gameProcessedEvent then
            return
        end

        local Index = PossibleKeys[input.KeyCode.Name]
        if not Index then
            return
        end

        local Tool = Tools[Index]
        if not Tool then
            return
        end

        if Tool.Parent == Character then
            Humanoid:UnequipTools()

            return
        end

        Humanoid:EquipTool(Tool)
    end))

    do
        local ExistingTools: {Tool} = {}
        local ToolInCharacter: Tool = Character:FindFirstChildWhichIsA("Tool")
        if ToolInCharacter then
            table.insert(ExistingTools, ToolInCharacter)
        end

        for _, Item: Instance in ipairs(Players.LocalPlayer.Backpack:GetChildren()) do
            if not Item:IsA("Tool") then
                continue
            end

            table.insert(ExistingTools, Item)
        end

        for _, Item: Tool in ipairs(ExistingTools) do
            table.insert(Tools, Item)
            ItemGUI._InitTool(Container, Item)
        end
    end

    JanitorInstance:Add(function()
        table.clear(Tools)
    end, true)
end

function ItemGUI._InitTool(Container: Frame, Tool: Tool, Humanoid: Humanoid)
    local FrameJanitor = Janitor.new()
    FrameJanitor:LinkToInstance(Tool)

    local ItemContainer: Frame = Prefabs.Item:Clone()
    ItemContainer.Name = Tool.Name
    ItemContainer.Parent = Container
    local Image = ItemContainer:FindFirstChildWhichIsA("ImageLabel")

    local function ReloadEquipped(Instant: boolean?)
        if Instant == nil then
            Instant = false
        end

        local Values = {
            Equipped =  {
                Color = Color3.fromRGB(255, 255, 100),
                Size = UDim2.fromScale(0.6, 0.6),
            },
            Unequipped = {
                Color = Color3.fromRGB(255, 255, 255),
                Size = UDim2.fromScale(0.55, 0.55),
            },
        }

        local Group = Tool.Parent == Humanoid.Parent and "Equipped" or "Unequipped"

        if Instant then
            Image.ImageColor3 = Values[Group].Color
            ItemContainer.Size = Values[Group].Size

            return
        end

        local Info = TweenInfo.new(0.4, Enum.EasingStyle.Elastic, Enum.EasingDirection.Out)
        TweenService:Create(Image, Info, {ImageColor3 = Values[Group].Color}):Play()
        TweenService:Create(ItemContainer, Info, {Size = Values[Group].Size}):Play()
    end

    FrameJanitor:Add(Tool.Equipped:Connect(function()
        ReloadEquipped()
    end))
    FrameJanitor:Add(Tool.Unequipped:Connect(function()
        ReloadEquipped()
    end))

    local CanDrop = false
    FrameJanitor:Add((ItemContainer:FindFirstChild("Interactor") :: ImageButton).MouseButton1Click:Connect(function()
        if CanDrop then
            Network:FireServerConnection("DropItem", "REMOTE_EVENT", Tool)

            return
        end

        if Tool.Parent == Humanoid.Parent then
            Humanoid:UnequipTools()
        else
            Humanoid:EquipTool(Tool)
        end

        CanDrop = true
        task.delay(0.225, function()
            CanDrop = false
        end)

        ReloadEquipped()
    end))

    FrameJanitor:Add(function()
        ItemContainer:Destroy()
    end, true)

    task.spawn(function()
        local ItemEquivalent = ReplicatedStorage.Assets.Items:FindFirstChild(Tool.Name)
        if not ItemEquivalent then
            return
        end

        ItemEquivalent = Utils.Type.CopyTable(require(ItemEquivalent))

        ItemContainer.Label.Text = ItemEquivalent.Name
        if ItemEquivalent.Icon then
            Image.Image = ItemEquivalent.Icon
        end

        ItemEquivalent:Init(Players.LocalPlayer, Tool)
    end)

    ReloadEquipped(true)
end

return ItemGUI


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Types = require(ReplicatedStorage.Classes.Types)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Janitor = require(ReplicatedStorage.Packages.Janitor)
local CommonFunctions = RunService:IsServer() and require(game:GetService("ServerScriptService").System.CommonFunctions) or nil

local PickableItem = {}
PickableItem.__index = PickableItem

function PickableItem.GetDefaultSettings(): Types.PickableItem
    return setmetatable({
        ItemEquivalent = nil,
        Description = "cool item!",
        Model = nil,
        Janitor = nil,
    }, PickableItem)
end

function PickableItem.New(Props: Types.PickableItem?): Types.PickableItem
    Props = Props or {} :: any
    local Final = PickableItem.GetDefaultSettings()

    Utils.Type.DeepTableOverwrite(Final, Props :: any)

    return Final
end

function PickableItem:Init()
    print("initting uhhh ig")

    if not RunService:IsServer() or not self.Model then
        warn(`Can't initialize item {self.ItemEquivalent and self.ItemEquivalent.Name or self.DisplayName or "UNKNOWN"}!`, debug.traceback())
        return
    end

    self.ModelInstance = self.Model:Clone()

    self.Janitor = Janitor.new()
    self.Janitor:LinkToInstance(self.ModelInstance)

    do
        local Prompt = self.ModelInstance:FindFirstChildWhichIsA("ProximityPrompt", true)
        if not Prompt then
            Prompt = Instance.new("ProximityPrompt")
            Prompt.Parent = self.ModelInstance.PrimaryPart or self.ModelInstance
        end

        self:AddConnection(Prompt.Triggered:Connect(function(Player: Player)
            local Role = Player.Character:FindFirstChild("Role")
            if not Role or Role.Value ~= "Survivor" then
                return
            end

            task.spawn(CommonFunctions.GivePhysicalItemToPlayer, Player, self.ItemEquivalent)

            self.ModelInstance:Destroy()
        end))
    end

    self.ModelInstance.Parent = workspace:FindFirstChild("Map") and workspace.Map:FindFirstChild("Items") or workspace.TempObjectFolders
end

function PickableItem:AddConnection<T>(Connection: T & (RBXScriptConnection | thread)): T & (RBXScriptConnection | thread)
    self.Janitor:Add(Connection,
        if typeof(Connection) == "thread" then
            true
        else
            "Disconnect"
    )

    return Connection
end

return PickableItem


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Sounds = require(ReplicatedStorage.Modules.Sounds)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Janitor = require(ReplicatedStorage.Packages.Janitor)
local Types = require(script.Parent.Types)

local Item = {}
Item.__index = Item

function Item.GetDefaultSettings(): Types.Item
    return setmetatable({
        Name = "Item",

        OnEquip = nil,
        OnUnequip = nil,

        Behaviour = nil :: any,

        AnimationIDs = {},
        SoundIDs = {},

        AnimationTracks = {},

        UseConnections = {},

        Owner = nil,
        OwnerProperties = {} :: any,
        Janitor = nil,
    }, Item)
end

function Item.New(Props: Types.Item?): Types.Item
    Props = Props or {} :: any
    local Final = Item.GetDefaultSettings()

    Utils.Type.DeepTableOverwrite(Final, Props :: any)

    return Final
end

function Item:Init(Owner: Player, ExistingTool: Tool?)
    self.Owner = Owner

    if not self.Owner.Character then
        return
    end

    if not self.ToolPrefab then
        warn(`[Item:Init()] No tool prefab provided for tool {self.Name}!`, debug.traceback())

        return
    end

    self.Owner = Owner
    local Character = self.Owner.Character :: Model

    task.defer(function()
        Utils.Misc.PreloadAssets(self.SoundIDs)
    end)

    local Tool = ExistingTool
    if not Tool then
        Tool = self.ToolPrefab:Clone()
        Tool.Parent = self.Owner.Backpack
    end
    self.ToolInstance = Tool

    self.Janitor = Janitor.new()
    self.Janitor:LinkToInstances(Character, Tool)

    self:AddConnection(Tool.Equipped:Connect(function()
        self:Equip()
    end))
    self:AddConnection(Tool.Unequipped:Connect(function()
        self:Unequip()
    end))

    if RunService:IsServer() then
        self.OwnerProperties = {
            Character = Character,
            Humanoid = Character:FindFirstChildWhichIsA("Humanoid"),
            HRP = Utils.Character.GetRootPart(Character),
            InputManager = require(self.Owner.PlayerScripts.InputManager),
        }

        self:AddConnection(Tool.Activated:Connect(function()
            local UseSound = self.SoundIDs.Use
            if UseSound then
                Sounds.PlaySound(UseSound, {
                    Parent = self.OwnerProperties.HRP,
                })
            end

            if self.Behaviour then
                self:Behaviour()
            end
        end))

        return
    end

    task.defer(function()
        Utils.Misc.PreloadAssets(self.AnimationIDs)
    end)

    self.OwnerProperties = {
        Character = Character,
        Humanoid = Character:FindFirstChildWhichIsA("Humanoid"),
        HRP = Utils.Character.GetRootPart(Character),
        InputManager = require(self.Owner.PlayerScripts.InputManager),
        FOVManager = require(Character.PlayerAttributeScripts.FOVManager),
        EffectManager = require(Character.PlayerAttributeScripts.EffectManager),
        EmoteManager = require(Character.Miscellaneous.EmoteManager),
        TurnToMoveDirection = require(self.Owner.PlayerScripts.Miscellaneous.TurnToMoveDirection),
        AnimationManager = require(Character.AnimationManager),
    }

    for Name, ID in self.AnimationIDs do
        self.OwnerProperties.AnimationManager:LoadAnimation(`{self.Name}.{Name}`, ID)
    end

    self:AddConnection(Tool.Activated:Connect(function()
        if self.Behaviour then
            self:Behaviour()
        end
    end))
end

function Item:Equip()
    if not RunService:IsServer() then
        --playing equip & idle tracks if existent
        local EquipTrack = self.OwnerProperties.AnimationManager:GetAnimationTrack(`{self.Name}.Equip`)
        if EquipTrack then
            self.OwnerProperties.AnimationManager:PlayAnimation(`{self.Name}.Equip`)
            self:AddUseConnection(task.delay(EquipTrack.Length - 0.02, function()
                self.OwnerProperties.AnimationManager:PlayAnimation(`{self.Name}.Idle`)
            end))
        end
    else
        local EquipSound = self.SoundIDs.Equip
        if EquipSound then
            Sounds.PlaySound(EquipSound, {
                Parent = self.OwnerProperties.HRP,
            })
        end
    end

    if self.OnEquip then
        self:OnEquip()
    end
end

function Item:Unequip()
    if not RunService:IsServer() then
        for _, Name in {"Idle", "Equip", "Use"} do
            self.OwnerProperties.AnimationManager:StopAnimation(`{self.Name}.{Name}`)
        end
    else
        local UnequipSound = self.SoundIDs.Unequip
        if UnequipSound then
            Sounds.PlaySound(UnequipSound, {
                Parent = self.OwnerProperties.HRP,
            })
        end
    end

    for _, Connection in self.UseConnections do
        if typeof(Connection) == "thread" then
            if coroutine.running() ~= Connection then
                task.cancel(Connection)
            end
        else
            Connection:Disconnect()
        end
    end

    if self.OnUnequip then
        self:OnUnequip()
    end
end

function Item:AddConnection<T>(Connection: T & (RBXScriptConnection | thread)): T & (RBXScriptConnection | thread)
    self.Janitor:Add(Connection,
        if typeof(Connection) == "thread" then
            true
        else
            "Disconnect"
    )

    return Connection
end

function Item:AddUseConnection<T>(Connection: T & (RBXScriptConnection | thread)): T & (RBXScriptConnection | thread)
    self:AddConnection(Connection)
    table.insert(self.UseConnections, Connection)

    return Connection
end

return Item


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Types = require(script.Parent.Types)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Sounds = require(ReplicatedStorage.Modules.Sounds)

local KillerIntroClass = {}

--- The settings of a killer intro. Determines which intro to use and what names to display.
export type IntroSettings = {
    --- The killer's Roblox name.
    PlayerName: string,
    --- The killer's name.
    KillerName: string,
    --- The killer's skin name.
    SkinName: string?,
}

--- Creates a new 2D killer intro.
--- See `IntroSettings`.
function KillerIntroClass.New2DIntro(Config: IntroSettings, IntroModule: ModuleScript): Types.UiIntro
    return {
        Connections = {},
        Disposables = {},

        Init = function(self: Types.UiIntro)
            if RunService:IsServer() then
                return
            end

            local VideoPlayer = require(Players.LocalPlayer.PlayerScripts.UI.VideoPlayer)

            local Intro = require(IntroModule)
            local Module = require(Utils.Instance.GetCharacterModule("Killer", Config.KillerName, Config.SkinName))

            if Intro.Frames then
                VideoPlayer:LoadVideo(Intro.Frames)
            end
            if Intro.Sound then
                VideoPlayer:SetAudio(Intro.Sound)
            end

            self.VideoPlayer = VideoPlayer
            self.Module = Module

            if Module.Config.OnIntroInit then
                Module.Config:OnIntroInit(self)
            end
        end,

        Play = function(self: Types.UiIntro): (any)
            if RunService:IsServer() then
                return
            end

            local Intro = require(IntroModule)
            table.insert(self.Connections, task.spawn(function()
                if Intro.Frames then
                    self.VideoPlayer:Play(self.Module.Config.IntroFPS) --will default to 30 if nil
                elseif Intro.Sound then
                    Sounds.PlaySound(Intro.Sound or self.Module.Config.Sounds.IntroSound, {Name = "IntroSound"})
                end
            end))

            if Intro.Behaviour then
                table.insert(self.Connections, task.spawn(function()
                    Intro:Behaviour(self)
                end))
            end

            if self.Module.Config.OnIntroPlay then
                table.insert(self.Connections, task.spawn(function()
                    self.Module.Config:OnIntroPlay(self.VideoPlayer)
                end))
            end

            return self.VideoPlayer
        end,

        Destroy = function(self: Types.UiIntro)
            self.VideoPlayer:Reset()

            for _, connection in self.Connections do
                if typeof(connection) == "thread" then
                    if coroutine.status(connection) ~= "running" then
                        task.cancel(connection)
                    end
                else
                    connection:Disconnect()
                end
            end

            for _, disposable in self.Disposables do
                disposable:Destroy()
            end
        end,
    }
end

--- Creates a new 3D killer intro.
--- See `IntroSettings`.
function KillerIntroClass.New3DIntro(Config: IntroSettings): Types.AnimatedIntro
    return {
        Connections = {},
        Disposables = {},
        Animations = {},

        Init = function(self: Types.AnimatedIntro)
            if RunService:IsServer() then
                return
            end

            local Module = Utils.Instance.GetCharacterModule("Killer", Config.KillerName, Config.SkinName)

            local PlayerRig = workspace.Players:FindFirstChild(Config.Name)
            --yield until the char loads properly
            local HRP = Utils.Instance.FindFirstChild(PlayerRig, "HumanoidRootPart")

            local KillerRig = PlayerRig:Clone()
            HRP = Utils.Character.GetRootPart(KillerRig)
            KillerRig.Parent = workspace.TempObjectFolders.Intro
            HRP.Anchored = true
            KillerRig:PivotTo(CFrame.new(0, 10000, 0))
            self.Disposables.KillerRig = KillerRig

            local CameraRig = Module:FindFirstChild("CameraRig") or ReplicatedStorage.Objects.CameraRig
            CameraRig = CameraRig:Clone()
            CameraRig:PivotTo(CFrame.new(0, 10000, 0))
            CameraRig.Parent = workspace.TempObjectFolders.Intro
            self.Disposables.CameraRig = CameraRig

            Module = require(Module)
            self.Module = Module

            if not Module.Config.AnimationIDs["KillerRig"] and not Module.Config.AnimationIDs["CameraRig"] then
                self:Destroy()
                return
            end

            if Module.Config.OnIntroInit then
                Module.Config:OnIntroInit(self, KillerRig, CameraRig)
            end

            for _, Rig in {
                "KillerRig",
                "CameraRig",
            } do
                if Module.Config.AnimationIDs[Rig] then
                    self.Animations[Rig] = Utils.Character.LoadAnimationFromID(Rig:find("Killer") and KillerRig or CameraRig, Module.Config.AnimationIDs[Rig])
                end
            end
        end,

        Play = function(self: Types.AnimatedIntro): (Model, Model, number)
            if RunService:IsServer() then
                return
            end

            if not self.Module.Config.AnimationIDs["KillerRig"] and not self.Module.Config.AnimationIDs["CameraRig"] then
                return
            end

            local Camera = workspace.CurrentCamera
            local CamPart = self.Disposables.CameraRig:FindFirstChild(self.Disposables.CameraRig:GetAttribute("CameraName") or "CameraPart")

            table.insert(self.Connections, task.spawn(function()
                if CamPart then
                    while self.Disposables.Camera do
                        Camera.CameraType = Enum.CameraType.Scriptable
                        Camera.CFrame = CamPart.CFrame
                        task.wait()
                    end
                end

                Camera.CameraType = Enum.CameraType.Custom
                Camera.CameraSubject = Players.LocalPlayer.Character:FindFirstChildWhichIsA("Humanoid")
            end))

            for _, Anim in self.Animations do
                Anim:Play(0)
            end

            if self.Module.Config.Sounds.IntroSound then
                Sounds.PlaySound(self.Module.Config.Sounds.IntroSound)
            end

            if self.Module.Config.OnIntroPlay then
                table.insert(self.Connections, task.spawn(function()
                    self.Module.Config:OnIntroPlay(self.Disposables.KillerRig, self.Disposables.CameraRig, self.Animations.CameraRig.Length)
                end))
            end

            return self.Disposables.KillerRig, self.Disposables.CameraRig, self.Animations.CameraRig.Length
        end,

        Destroy = function(self: Types.AnimatedIntro)
            for _, connection in self.Connections do
                if typeof(connection) == "thread" then
                    task.cancel(connection)
                else
                    connection:Disconnect()
                end
            end

            for _, disposable in self.Disposables do
                disposable:Destroy()
            end
        end,
    }
end

return KillerIntroClass


local Debris = game:GetService("Debris")
local GuiService = game:GetService("GuiService")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local TweenService = game:GetService("TweenService")
local UserInputService = game:GetService("UserInputService")

local Sounds = require(ReplicatedStorage.Modules.Sounds)
local Utils = require(ReplicatedStorage.Modules.Utils)

--- Class used to create mouse tooltips.
--- Only one can be created at a time and this class should only be used internally / in the `More Info` section for characters.
local TooltipClass = {}

local Tooltip = nil
local Prefab = script:FindFirstChild("Tooltip")
local UI = Utils.Instance.FindFirstChild(Players.LocalPlayer.PlayerGui, "Menus")

--- Creates a new Tooltip.
--- If there's an existing one, it'll just change the text if it's different.
--- If `Text` is `nil`, it'll hide the existing tooltip and destroy it.
function TooltipClass.New(Text: string)
    if Tooltip and (not Text or #Text <= 0) and Tooltip.Parent then
        TweenService:Create(Tooltip.Title, TweenInfo.new(0.15), {
            TextSize = 0,
            TextTransparency = 1,
        }):Play()
        TweenService:Create(Tooltip.Background, TweenInfo.new(0.15), {
            Size = Utils.UDim.Zero,
            ImageTransparency = 1,
            BackgroundTransparency = 1,
        }):Play()
        Debris:AddItem(Tooltip, 0.15)
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})

        Tooltip = nil
        return
    end

    if not Text or #Text <= 0 then
        return
    end

    if Tooltip and Text then
        if Text ~= Tooltip.Title.Text then
            Tooltip.Title.Text = Text
        end
        return Tooltip
    end

    if not Tooltip then
        local InitialMousePos = UserInputService:GetMouseLocation() - GuiService:GetGuiInset()
        Tooltip = Prefab:Clone()
        Tooltip.Title.Text = Text
        Tooltip.Title.Position = UDim2.fromOffset(InitialMousePos.X, InitialMousePos.Y - 30)
        Tooltip.Background.Position = UDim2.fromOffset(InitialMousePos.X, InitialMousePos.Y - 30 - Tooltip.Title.AbsoluteSize.Y + 40)
        Tooltip.Parent = UI
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
        task.spawn(function()
            while task.wait() and Tooltip do
                local MousePos = UserInputService:GetMouseLocation() - GuiService:GetGuiInset()
                Tooltip.Title.Position = UDim2.fromOffset(MousePos.X, MousePos.Y - 30)
                Tooltip.Background.Position = UDim2.fromOffset(MousePos.X, MousePos.Y - 30 - Tooltip.Title.AbsoluteSize.Y + 40)
                Tooltip.Background.Size = UDim2.fromOffset(Tooltip.Title.AbsoluteSize.X + 64, Tooltip.Title.AbsoluteSize.Y + 50)
            end
        end)

        return Tooltip
    end

    return Tooltip
end

return TooltipClass


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

local Effect = require(ReplicatedStorage.Classes.Effect)
local Types = require(ReplicatedStorage.Classes.Types)
local Utils = require(ReplicatedStorage.Modules.Utils)

local DisappearThread

return Effect.New({
    Name = "Blindness",
    Description = "Blinds the affected player for a set time. This effect will flash white if the Photosensitive setting is off, and black if it's on.",

    ApplyEffect = function(own: Types.Effect, char: Model, level: number, duration: number)
        if not RunService:IsServer() then
            if DisappearThread then
                task.cancel(DisappearThread)
            end

            local CC: ColorCorrectionEffect = Lighting:FindFirstChild("BlindnessColorCorrection")
            if CC then
                CC:Destroy()
            end

            CC = Instance.new("ColorCorrectionEffect")
            CC.Name = "BlindnessColorCorrection"
            CC.Brightness = Utils.PlayerData.GetPlayerSetting(Players.LocalPlayer, "Accessibility.EpilepsyMode") and 0.99 or -0.24
            CC.Saturation = 1
            CC.Parent = Lighting

            local Q = duration / 4
            DisappearThread = task.delay(Q * 3, function()
                TweenService:Create(CC, TweenInfo.new(Q, Enum.EasingStyle.Sine, Enum.EasingDirection.In), {Brightness = 0, Saturation = 0}):Play()
            end)
        end
    end,
    RemoveEffect = function(own: Types.Effect, char: Model)
        if not RunService:IsServer() then
            if DisappearThread then
                task.cancel(DisappearThread)
            end

            local CC = Lighting:FindFirstChild("BlindnessColorCorrection")
            if CC then
                CC:Destroy()
            end
        end
    end,
})


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

local Effect = require(ReplicatedStorage.Classes.Effect)

local PlayerModule = not RunService:IsServer() and require(Players.LocalPlayer.PlayerScripts.PlayerModule) or nil

return Effect.New({
    Name = "Disoriented",
    Description = "Inverts the player's contorls for the duration of the effect.",
    Duration = 4.2,
    ShowInGUI = false,
    ApplyEffect = function()
        if not RunService:IsServer() then
            PlayerModule.controls.disoriented = true
        end
    end,
    RemoveEffect = function()
        if not RunService:IsServer() then
            PlayerModule.controls.disoriented = false
        end
    end,
})


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Effect = require(ReplicatedStorage.Classes.Effect)

return Effect.New({
    Name = "Helpless",
    Description = "Disables a character's abilities during its presence.",
    Duration = 1,
})


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local Effect = require(ReplicatedStorage.Classes.Effect)
local Types = require(ReplicatedStorage.Classes.Types)

local function Work(apply: boolean, instances: {BasePart & Decal}, level: number?)
    if apply then
        for _, values in instances do
            if values.Instance:HasTag("Transparent") then
                continue
            end
            values.Instance.Transparency = math.clamp(0.7 + 0.07 * level, 0, 0.975)
            if values.Instance.Transparency > 0.8 and values.Instance:IsA("BasePart") then
                values.Instance.CastShadow = false
            end
        end
    else
        for _, values in instances do
            if values.Instance:HasTag("Transparent") then
                continue
            end
            values.Instance.Transparency = values.Transparency
            if values.Instance:IsA("BasePart") then
                values.Instance.CastShadow = values.CastShadow
            end
        end
    end
end

return Effect.New({
    Name = "Invisibility",
    Description = "Makes the player fairly transparent (but never fully invisible) the higher the level.",
    Duration = 10,
    TransparentInstances = {},

    ApplyEffect = function(own: Types.Effect, level: number, char: Model)
        if not RunService:IsServer() then
            return
        end

        table.clear(own.TransparentInstances)
        local handleIndex = 1
        for _, Part in char:GetDescendants() do
            if Part:IsA("BasePart") then
                local nameToUse = Part.Name
                if Part.Name == "Handle" then
                    nameToUse = "Handle"..tostring(handleIndex)
                    handleIndex += 1
                end
                own.TransparentInstances[nameToUse] = {
                    Instance = Part,
                    Transparency = Part.Transparency,
                    CastShadow = Part.CastShadow,
                }
            elseif Part:IsA("Decal") then
                own.TransparentInstances[Part.Name] = {
                    Instance = Part,
                    Transparency = Part.Transparency,
                }
            end
        end

        Work(true, own.TransparentInstances, level)
    end,
    RemoveEffect = function(own: Types.Effect)
        if not RunService:IsServer() then
            return
        end

        Work(false, own.TransparentInstances)
        table.clear(own.TransparentInstances)
    end
})


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Utils = require(ReplicatedStorage.Modules.Utils)
local Effect = require(ReplicatedStorage.Classes.Effect)
local Types = require(ReplicatedStorage.Classes.Types)

return Effect.New({
    Name = "Spawn Protection",
    Description = "Prevents a killer from being stunned for a set time, generally 12 seconds.",
    Duration = 12,
    ShowInGUI = false,
    ApplyEffect = function(self: Types.Effect, level: number, char: Model, duration: number)
        local HRP = Utils.Character.GetRootPart(char)
        local UI = HRP:FindFirstChild("StunSpawnProtection")
        if UI then
            UI:FindFirstChildWhichIsA("ParticleEmitter").Enabled = true
        end
    end,
    RemoveEffect = function(self: Types.Effect, char: Model)
        local HRP = Utils.Character.GetRootPart(char)
        local UI = HRP:FindFirstChild("StunSpawnProtection")
        if UI then
            UI:FindFirstChildWhichIsA("ParticleEmitter").Enabled = false
        end
    end,
})


local Effect = require(game:GetService("ReplicatedStorage").Classes.Effect)

return Effect.New({
    Name = "Resistance",
    Description = "Reduces the damage inflicted to the player (produced by the killer, not by the environment or himself).",
    Duration = 15,
})


local Effect = require(game:GetService("ReplicatedStorage").Classes.Effect)

return Effect.New({
    Name = "Undetectable",
    Description = "Makes the player's aura invisible for the duration of the effect.",
    Duration = 15,
})


local Initter = {
    CurrentlyInitting = nil,
    Loaded = false,
}

function Initter:Init()
    debug.setmemorycategory("DysNetworking")
    self.CurrentlyInitting = "DysNetworking"
    require(script.Parent.Modules.Network):Init()
    for _, Module in ipairs(script.Parent.Modules:GetDescendants()) do
        if Module.Name == "Network" then
            continue
        end
        if Module:IsA("ModuleScript") and not Module:HasTag("PreventInit") then
            debug.setmemorycategory(Module.Name)
            self.CurrentlyInitting = Module.Name
            local Code = require(Module)
            if Code.Init ~= nil and typeof(Code.Init) == "function" then
                Code:Init()
            end
        end
    end

    debug.setmemorycategory("Default")

    Initter.Loaded = true
end

return Initter


-- TODO: intro lighting support

local Debris = game:GetService("Debris")
local Lighting = game:GetService("Lighting")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Network = require(ReplicatedStorage.Modules.Network)

local LightingModule = {
    Properties = {
        Ambient = Color3.fromRGB(0, 0, 0),
        Brightness = 3,
        ColorShift_Bottom = Color3.fromRGB(0, 0, 0),
        ColorShift_Top = Color3.fromRGB(0, 0, 0),
        EnvironmentDiffuseScale = 1,
        EnvironmentSpecularScale = 1,
        GlobalShadows = true,
        OutdoorAmbient = Color3.fromRGB(70, 70, 70),
        ShadowSoftness = 0.2,
        ClockTime = 5.4,
        GeographicLatitude = 0,
        ExposureCompensation = 1.24,
        FogColor = Color3.fromRGB(192, 192, 192),
        FogEnd = 100000,
        FogStart = 0,
    },
    CurrentLighting = nil,
}

function LightingModule:Init()
    if RunService:IsServer() then
        Network:SetConnection("GetCurrentLighting", "REMOTE_FUNCTION", function(player: Player)
            if LightingModule.CurrentLighting then
                local Props = LightingModule.CurrentLighting:Clone()
                Props.Parent = player.PlayerGui
                Debris:AddItem(Props, 20)
                return Props
            end
            return {}
        end)
    else
        Network:SetConnection("SetCustomLighting", "REMOTE_EVENT", function(Properties)
            LightingModule.SetCustomLighting(Properties)
        end)
        Network:SetConnection("SetDefaultLighting", "REMOTE_EVENT", function()
            LightingModule.SetDefaultLighting()
        end)
        LightingModule.SetDefaultLighting()
    end
end

function LightingModule.SetAsCurrentLighting()
    if RunService:IsServer() then
        return
    end

    LightingModule.SetDefaultLighting()
    local Current = Network:FireServerConnection("GetCurrentLighting", "REMOTE_FUNCTION")
    if Current then
        LightingModule.SetCustomLighting(Current)
    end
end

function LightingModule.SetCustomLighting(Properties: {[any]: any} | Instance)
    if RunService:IsServer() then
        if typeof(Properties) == "Instance" and Properties:IsA("Folder") then
            if ReplicatedStorage:FindFirstChild("CurrentReplicatedLighting") then
                ReplicatedStorage.CurrentReplicatedLighting:Destroy()
            end
            Properties = Properties:Clone()
            Properties.Name = "CurrentReplicatedLighting"
            Properties.Parent = ReplicatedStorage
        end
        LightingModule.CurrentLighting = Properties
        Network:FireAllClientConnection("SetCustomLighting", "REMOTE_EVENT", Properties)
    elseif typeof(Properties) == "Instance" then
        if Properties:IsA("Folder") then
            Lighting:ClearAllChildren()
            for _, Child in Properties:GetChildren() do
                if Child:IsA("ModuleScript") then
                    LightingModule.SetCustomLighting(require(Child))
                else
                    Child:Clone().Parent = Lighting
                end
            end
        elseif Properties:IsA("ModuleScript") then
            if #Properties:GetChildren() > 0 then
                Lighting:ClearAllChildren()
                for _, Child in Properties:GetChildren() do
                    Child:Clone().Parent = Lighting
                end
            end
            LightingModule.SetCustomLighting(require(Properties))
        end
    else
        local Props = Properties or {}
        for name, value in Props do
            Lighting[name] = value
        end
    end
end

function LightingModule.SetDefaultLighting()
    if RunService:IsServer() then
        if typeof(LightingModule.CurrentLighting) == "Instance" then
            LightingModule.CurrentLighting:Destroy()
        end
        LightingModule.CurrentLighting = nil
        Network:FireAllClientConnection("SetDefaultLighting", "REMOTE_EVENT")
    else
        LightingModule.SetCustomLighting(LightingModule.Properties)
        Lighting:ClearAllChildren()
        for _, Child in script:GetChildren() do
            Child:Clone().Parent = Lighting
        end
    end
end

return LightingModule


--[[
	Thanks for using DysNetworking!
	Every bit of documentation is scattered through this same module.
	I tried to document it as well as I could to make it as understandable as possible.
	You'll still have to know some coding to work properly with this.
	Anyway, have fun making your games!

	To access this module's functions, just require it from anywhere, from any kind of script.
	For example:

		---CLIENT---
		local DysNetworking = require(game:GetService("ReplicatedStorage").DysNetworking)
		DysNetworking:SetConnection("EXAMPLE_1", "REMOTE_EVENT", function(param1: string)
			print(param1)
		end)

		DysNetworking:FireServerConnection("EXAMPLE_2", "REMOTE_EVENT", "printing from server!")

		---SERVER---
		local DysNetworking = require(game:GetService("ReplicatedStorage").DysNetworking)
		DysNetworking:FireAllClients("EXAMPLE_1", "REMOTE_EVENT", "printing!") --where `"printing!"` is `param1` in the client.
		--If you do `FireClient` instead, you'll have to specify the target player before anything.

		--When connecting from the server to a remote, the first parameter **MUST** always be the player that called the event, just like with Remote instances.
		DysNetworking:SetConnection("EXAMPLE_2", "REMOTE_EVENT", function(sourcePlayer: Player, param1: string)
			print(sourcePlayer.Name.." called this event!")
			print(param1)
		end)

	That's it, pretty simple.
	Everything works like your traditional Remote & Bindable instances.

	Though, this module will have to be initted in both sides before it can be properly used.
	For that, just create a script in `ServerScriptService` and another one in `StarterPlayerScripts` that requires it and calls `DysNetworking:Init()`.
	In the template there are two scripts that already do this and destroy themselves when they do so, so you can just use those. :p

	That's it! :>
]]

local RunService = game:GetService("RunService")

--- Class used to connect the server and the clients more efficiently by encapsulating everything in a single place.
local Network = {
    Folders = {
        REMOTE_EVENT = nil,
        REMOTE_FUNCTION = nil,
        UREMOTE_EVENT = nil,
        BINDABLE_EVENT = nil,
    },
    _FolderNames = {
        "REMOTE_EVENT",
        "REMOTE_FUNCTION",
        "UREMOTE_EVENT",
        "BINDABLE_EVENT",
    },
    InstanceCreator = nil,
    ServiceLive = false,
}

--- Initializes the networking system in the current environment.
function Network:Init()
    if RunService:IsServer() then
        game:BindToClose(function()
            self:Destroy()
        end)

        for _, name in self._FolderNames do
            local Folder = Instance.new("Folder")
            Folder.Name = name
            self.Folders[name] = Folder
            Folder.Parent = script
        end

        self.InstanceCreator = Instance.new("RemoteFunction")
        self.InstanceCreator.Name = "InstanceCreator"
        self.InstanceCreator.Parent = script

        self.InstanceCreator.OnServerInvoke = function(_player: Player, name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION" | "BINDABLE_EVENT")
            return self:_CreateInstance(name, Type)
        end
    else
        for _, name in self._FolderNames do
            self.Folders[name] = script:FindFirstChild(name) or script:WaitForChild(name)
        end

        if not script:FindFirstChild("InstanceCreator") then
            while not script.InstanceCreator do
                task.wait()
            end
        end

        self.InstanceCreator = script.InstanceCreator
    end

    self.ServiceLive = true
end

--- Creates an instance of a specific type for later usage.
--- @param name                                                                     (`string`): The name of the instance.
--- @param Type                                                                     (`string`): The desired type of the instance.
--- @return `RemoteEvent | UnreliableRemoteEvent | RemoteFunction | BindableEvent`              The instance that has been created.
function Network:_CreateInstance(name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION" | "BINDABLE_EVENT"): RemoteEvent | UnreliableRemoteEvent | RemoteFunction | BindableEvent
    if not RunService:IsServer() then return end
    local instance
    if Type == "REMOTE_EVENT" then
        instance = Instance.new("RemoteEvent")
    elseif Type == "UREMOTE_EVENT" then
        instance = Instance.new("UnreliableRemoteEvent")
    elseif Type == "REMOTE_FUNCTION" then
        instance = Instance.new("RemoteFunction")
    elseif Type == "BINDABLE_EVENT" then
        instance = Instance.new("BindableEvent")
    end

    instance.Name = name
    instance.Parent = self.Folders[Type]
    return instance
end

--- Sets up a connection for an instance depending on the type.
--- @param instance (`RemoteEvent | UnreliableRemoteEvent | RemoteFunction | BindableEvent`):   The instance to set up the connection of.
--- @param Callback (`function`):                                                               The code that will execute when the connection fires.
function Network:_SetupConnection(instance: RemoteEvent | UnreliableRemoteEvent | RemoteFunction | BindableEvent, Callback: (any) -> (any)): RBXScriptConnection | nil
    if instance:IsA("BindableEvent") then
        return instance.Event:Connect(Callback)
    else
        if RunService:IsServer() then
            if instance:IsA("RemoteEvent") or instance:IsA("UnreliableRemoteEvent") then
                return instance.OnServerEvent:Connect(Callback)
            elseif instance:IsA("RemoteFunction") then
                instance.OnServerInvoke = Callback
            end
            return
        else
            if instance:IsA("RemoteEvent") or instance:IsA("UnreliableRemoteEvent") then
                return instance.OnClientEvent:Connect(Callback)
            elseif instance:IsA("RemoteFunction") then
                instance.OnClientInvoke = Callback
            end
            return
        end
    end
end

--- Adds a connection to a specific event / function instance.
--- @param name                    (`string`):      The name of the instance.
--- @param Type                    (`string`):      The type of the instance.
--- @param Callback                (`function`):    The code that will run when the connection is fired.
--- @return `RBXScriptConnection`                   The set connection for further usage in the script from where this is called.
function Network:SetConnection(name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION" | "BINDABLE_EVENT", Callback: (...any) -> (...any)): RBXScriptConnection
    while not self:IsLive() do
        task.wait()
    end
    local instance
    if RunService:IsServer() then
        if not self.Folders[Type]:FindFirstChild(name) then
            instance = self:_CreateInstance(name, Type)
        else
            instance = self.Folders[Type]:FindFirstChild(name)
        end
    else
        if not self.Folders[Type]:FindFirstChild(name) then
            instance = self.InstanceCreator:InvokeServer(name, Type)
        else
            instance = self.Folders[Type]:FindFirstChild(name)
        end
    end
    return self:_SetupConnection(instance, Callback)
end

--- Destroys the Network instance. Only called when the server closes.
function Network:Destroy()
    for _, RemoteFunction in self.Folders.REMOTE_FUNCTION:GetChildren() do
        if not RemoteFunction:IsA("RemoteFunction") then continue end
        RemoteFunction.OnServerInvoke = function() end
    end
end

--- Fires a specific client's event / function connections.
--- @param target  (`Player`):  The target client whose connections will be fired.
--- @param name    (`string`):  The name of the desired event / function
--- @param Type    (`string`):  The type of instance to fire the connections of
--- @param ...                  Any parameters to pass to the connection
function Network:FireClientConnection(target: Player | {Player}, name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION", ...): any | nil
    while not self:IsLive() do
        task.wait()
    end
    if not RunService:IsServer() or not self.Folders[Type]:FindFirstChild(name) then return end

    local event = self.Folders[Type]:FindFirstChild(name)

    local function Fire(player: Player, ...)
        if event:IsA("RemoteFunction") then
            return event:InvokeClient(player, ...)
        else
            event:FireClient(player, ...)
        end
        return
    end

    if typeof(target) == "table" then
        local results = {}
        for _, player: Player in target do
            results[player.UserId] = Fire(player, ...)
        end
        return results
    else
        return Fire(target, ...)
    end
end

--- Fires an event's connections to every single connected client. If the type of the object is a RemoteFunction, it won't do anything.
--- @param name    (`string`):  The name of the desired event
--- @param Type    (`string`):  The type of the event (Reliable / Unreliable)
--- @param ...                  Any parameters to pass to the connection
function Network:FireAllClientConnection(name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION", ...)
    while not self:IsLive() do
        task.wait()
    end
    if not RunService:IsServer() or Type == "REMOTE_FUNCTION" or not self.Folders[Type]:FindFirstChild(name) then return end

    self.Folders[Type]:FindFirstChild(name):FireAllClients(...)
end

--- Fires a server's event / function connections.
--- @param name    (`string`):  The name of the desired event / function
--- @param Type    (`string`):  The type of instance to fire the connections of
--- @param ...                  Any parameters to pass to the connection
function Network:FireServerConnection(name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION", ...): any | nil
    while not self:IsLive() do
        task.wait()
    end
    if not RunService:IsClient() or not self.Folders[Type]:FindFirstChild(name) then return end

    local instance = self.Folders[Type]:FindFirstChild(name)
    if instance:IsA("RemoteFunction") then
        return instance:InvokeServer(...)
    else
        instance:FireServer(...)
    end
    return
end

--- Fires a BindableEvent's connections
--- @param name (`string`): The name of the desired BindableEvent
--- @param ...              Any parameters to pass to the connection
function Network:FireConnection(name: string, ...)
    while not self:IsLive() do
        task.wait()
    end
    if not self.Folders["BINDABLE_EVENT"]:FindFirstChild(name) then return end

    self.Folders["BINDABLE_EVENT"]:FindFirstChild(name):Fire(...)
end

--- Returns an object instance
--- @param name    (`string`):    The name of the desired instance
--- @param Type    (`string`):    The type of the desired instance inside of the script (e. g. "REMOTE_EVENT")
--- @return `any`                 May return the desired instance
function Network:GetInstance(name: string, Type: "REMOTE_EVENT" | "UREMOTE_EVENT" | "REMOTE_FUNCTION" | "BINDABLE_EVENT")
    while not self:IsLive() do
        task.wait()
    end
    if not self.Folders[Type]:FindFirstChild(name) then return end
    return self.Folders[Type]:FindFirstChild(name)
end

--- Checks if the Network service is available in the current environment
function Network:IsLive()
    return self.ServiceLive and self:_FoldersAvailable()
end

--- Checks if every folder is available
function Network:_FoldersAvailable()
    local availableIndex = 0
    for _, folder in self.Folders do
        if folder ~= nil then
            availableIndex += 1
        end
    end
    return availableIndex == #self._FolderNames
end

return Network


local Debris = game:GetService("Debris")
local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Ragdoll = {
	HandledHumanoids = {},
}

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

function Ragdoll:Init()
	if RunService:IsServer() then
		local function AddHumRagdoll(Desc: Instance)
			local Parent = Desc.Parent
			if Parent and Parent:IsA("Model") and Parent.PrimaryPart and Desc:IsA("Humanoid") then
				if table.find(self.HandledHumanoids, Desc) then
					return
				end

				Parent.Archivable = true

				table.insert(self.HandledHumanoids, Desc)
				Desc.Died:Connect(function()
					Debris:AddItem(Parent, 30)
				end)
			end
		end

		workspace.DescendantAdded:Connect(AddHumRagdoll)
		for _, Descendant in workspace:GetDescendants() do
			AddHumRagdoll(Descendant)
		end
		Utils.Player.ObservePlayers(function(Player: Player)
			Utils.Character.ObserveCharacter(Player, function(Char: Model)
				AddHumRagdoll(Char:FindFirstChildWhichIsA("Humanoid"))
			end)
		end)
	else
		Network:SetConnection("Ragdoll", "REMOTE_EVENT", function(Character: Model, WaitTime: number?, Force: string, Position: CFrame?)
			if WaitTime then
				while time() < WaitTime do
					task.wait()
				end
			end
			local rag = Ragdoll.Enable(Character, true)
			if rag and rag.PrimaryPart then
				if Position then
					rag:PivotTo(Position)
				end
				if Force then
					local Silly = Utils.PlayerData.GetPlayerSetting(Players.LocalPlayer, "Advanced.SillyRagdolls")
					local ForceArr = Force:split("|")
					rag.PrimaryPart.Velocity = Vector3.new(ForceArr[1], ForceArr[2], ForceArr[3]) * (Silly and 10 or 1)
				end
			end
		end)

		local function AddHumRagdoll(Desc: Instance, Primary: BasePart?)
			local Parent = Desc.Parent
			Primary = Primary or (Parent and Parent:IsA("Model") and Utils.Character.GetRootPart(Parent))
			if Primary and Desc:IsA("Humanoid") then
				if table.find(self.HandledHumanoids, Desc) then
					return
				end

				Parent.Archivable = true

				table.insert(self.HandledHumanoids, Desc)
				Desc.BreakJointsOnDeath = false
				Desc.Died:Connect(function()
					task.wait()
					if not Primary.Anchored then
						Ragdoll.Enable(Parent, true)
					end
				end)
			end
		end
		workspace.DescendantAdded:Connect(AddHumRagdoll)
		for _, Descendant in workspace:GetDescendants() do
			AddHumRagdoll(Descendant)
		end
		Utils.Player.ObservePlayers(function(Player: Player)
			Utils.Character.ObserveCharacter(Player, function(Char: Model)
				local Hum = Char:FindFirstChildWhichIsA("Humanoid")
				AddHumRagdoll(Hum, Hum.RootPart)
			end)
		end)
	end
end

--- Makes a character ragdoll.
--- If `Dupe` is true, it'll make a dupe of the character and use that as a ragdoll, useful to DEATH.
--- Used when people die. Of death. I'm funny.
function Ragdoll.Enable(Character: Model, Dupe: boolean)
	if Character:GetAttribute("CantRagdoll") or Character:GetAttribute("Ragdolling") then
		return
	end

	Character:SetAttribute("Ragdolling", true)

	local Char = Character
	local Humanoid = Char:FindFirstChildWhichIsA("Humanoid")
	if Dupe then
		local RagdollFolder = workspace.Ragdolls
		if not RagdollFolder then
			return
		end

		local Silly = Utils.PlayerData.GetPlayerSetting(Players.LocalPlayer or Players:GetPlayerFromCharacter(Char), "Advanced.SillyRagdolls")

		--handle ragdoll limit option
		Char = Character:Clone()
		Char.Parent = RagdollFolder
		if Char.PrimaryPart then
			Char.PrimaryPart.Anchored = false
		end

		for _, Descendant in Character:GetDescendants() do
			local isPart = Descendant:IsA("BasePart")

			if Descendant:IsA("Decal") or isPart then
				if isPart then
					Descendant.CollisionGroup = "DeadPlayers"
				end
				Descendant.Transparency = 1
			end
		end

		for _, Descendant in Char:GetDescendants() do
			if Descendant:IsA("Sound") or Descendant:IsA("ParticleEmitter") or Descendant:IsA("Light") or Descendant:IsA("Beam") or Descendant:IsA("Highlight") then
                Descendant:Destroy()
            end
		end

		for index, blacklisted in {
			"LinearVelocity",
			"Highlight"
		} do
			local First = Char:FindFirstChild(blacklisted, true)
			while First and (index > 1 or not Silly) do
				First.Name = "ded"
				First:Destroy()
				First = Char:FindFirstChild(blacklisted, true)
			end
		end

		task.spawn(function()
			if Humanoid then
				Humanoid.Health = 1
				Humanoid.HealthDisplayType = Enum.HumanoidHealthDisplayType.AlwaysOff

				if RunService:IsClient() and Players.LocalPlayer.Character == Character then
					workspace.CurrentCamera.CameraSubject = not Humanoid.BreakJointsOnDeath and (Char:FindFirstChild("Head") or Humanoid) or Humanoid
				end
			end
		end)

		task.delay(30, function()
			if Char.Parent then
				Debris:AddItem(Char, 10)
				for _, Descendant in Char:GetDescendants() do
					if Descendant:IsA("BasePart") or Descendant:IsA("Decal") then
                        game.TweenService:Create(Descendant, TweenInfo.new(10, Enum.EasingStyle.Linear), {
                            Transparency = 1
                        }):Play()
                    end
				end
			end
		end)
	end

	for _, Joint in Char:GetDescendants() do
		if Joint:IsA("Motor6D") and Joint.Parent.Name ~= "HumanoidRootPart" and Joint.Parent.Name ~= "Head" then
        	local BallSocketConstraint = Instance.new("BallSocketConstraint")
        	BallSocketConstraint.Name = "TemporaryRagdollInstance"
        	local Attachment = Instance.new("Attachment")
        	Attachment.Name = "TemporaryRagdollInstance"
        	local Attachment1 = Instance.new("Attachment")
        	Attachment1.Name = "TemporaryRagdollInstance"
        	Attachment.Parent = Joint.Part0
        	Attachment1.Parent = Joint.Part1
        	BallSocketConstraint.Parent = Joint.Parent
        	BallSocketConstraint.Attachment0 = Attachment
        	BallSocketConstraint.Attachment1 = Attachment1
        	Attachment.CFrame = Joint.C0
        	Attachment1.CFrame = Joint.C1
        	BallSocketConstraint.LimitsEnabled = true
        	BallSocketConstraint.TwistLimitsEnabled = true
        	Joint.Enabled = false
        end
	end

	if Humanoid then
        Char:SetAttribute("OriginalJumpInfo", ("%*|%*"):format(Humanoid.JumpPower, Humanoid.JumpHeight))
        Humanoid.RequiresNeck = false
        Humanoid.PlatformStand = true
        Humanoid.JumpPower = 0
        Humanoid.JumpHeight = 0
	end

	for _, Part in Char:GetDescendants() do
        if Part:IsA("BasePart") then
            if not Part:GetAttribute("OriginalCollision") then
                Part:SetAttribute("OriginalCollision", Part.CanCollide)
            end

            Part.CanCollide = false
        end
    end

	local function MakeFakeLimb(Limb, Y)
        local Part = Instance.new("Part")
        Part.Transparency = 1
        Part.CFrame = Limb.CFrame * CFrame.new(0, Y, 0)
        Part.Size = Limb.Size - Vector3.new(0.05, 0.05 + Y, 0.05)
        Part.Name = "RagdollPart"
        Part.CollisionGroup = "Ragdolls"
        Part.Shape = Enum.PartType.Ball
        Part.Parent = Char
        local WeldConstraint = Instance.new("WeldConstraint")
    	WeldConstraint.Parent = Part
    	WeldConstraint.Part1 = Part
    	WeldConstraint.Part0 = Limb
    end

	local Limbs = {
        "Head",
        "Torso",
        "Left Arm",
        "Left Leg",
        "Right Arm",
        "Right Leg",
    }
	for _, Part in Char:GetChildren() do
        if Part:IsA("BasePart") and (table.find(Limbs, Part.Name) or (Part.Transparency < 0.25 and Part.Name ~= "HumanoidRootPart" and Part.Name ~= "CollisionHitbox")) then
            task.delay(0.125, function()
                Part.CanCollide = false
            end)

            if Part.Name == "Head" then
                MakeFakeLimb(Part, 0)
            else
                MakeFakeLimb(Part, -0.3)
                MakeFakeLimb(Part, 0.3)
            end
        end
    end

	return Char
end

--- Unragdolls a specific character if they're ragdolling.
function Ragdoll.Disable(Character: Model)
	if Character:GetAttribute("CantRagdoll") or not Character.Parent or not Character:FindFirstChildWhichIsA("Humanoid") then
		return
	end

	Character:SetAttribute("Ragdolling", false)
	for _, Descendant in Character:GetDescendants() do
		if Descendant.Name == "TemporaryRagdollInstance" then
            Descendant:Destroy()
        elseif Descendant:IsA("Motor6D") then
            Descendant.Enabled = true
        end
	end

	for _, Descendant in Character:GetChildren() do
		if Descendant.Name == "RagdollPart" then
			Descendant:Destroy()
		else
			Descendant.CanCollide = Descendant:GetAttribute("OriginalCollision")
			Descendant:SetAttribute("OriginalCollision", nil)
		end
	end

	local Humanoid = Character:FindFirstChildWhichIsA("Humanoid")
	local Attr = Character:GetAttribute("OriginalJumpInfo")
	if Attr then
        Attr = string.split(Attr, "|")
        Humanoid.JumpPower = Attr[1]
        Humanoid.JumpHeight = Attr[2]
    elseif not Character:FindFirstChild("Role") or Character.Role.Value == "Spectator" then
        Humanoid.JumpPower = game.StarterPlayer.CharacterJumpPower
        Humanoid.JumpHeight = game.StarterPlayer.CharacterJumpHeight
    else
        Humanoid.JumpPower = 0
        Humanoid.JumpHeight = 0
    end
    Humanoid.PlatformStand = false
end

return Ragdoll


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Achievements = require(ReplicatedStorage.Assets.Achievements)
local Types = require(ReplicatedStorage.Classes.Types)
local TypeUtils = require(script.Parent.TypeUtils)
local Janitor = require(ReplicatedStorage.Packages.Janitor)

local RunService = game:GetService("RunService")
local Debris = game:GetService("Debris")
local TweenService = game:GetService("TweenService")
local Network = require(ReplicatedStorage.Modules.Network)

local InstanceUtils = {}

--- Equivalent of `Instance.ChildAdded:Connect()` but managed by Janitor.
function InstanceUtils.ObserveChildren(
	parent: Instance,
	callback: (child: Instance, childJanitor: Types.Janitor) -> (),
	predicate: (child: Instance) -> boolean?
): Types.Janitor
	local mainJanitor = Janitor.new()
	mainJanitor:LinkToInstance(parent)

	local function onChildAdded(child: Instance)
		if predicate and not predicate(child) then
			return
		end
		callback(child, mainJanitor:AddObject(Janitor, nil, child))
	end

	for _, child in parent:GetChildren() do
		task.spawn(onChildAdded, child)
	end
	mainJanitor:Add(parent.ChildAdded:Connect(onChildAdded))
	mainJanitor:Add(parent.ChildRemoved:Connect(function(child: Instance)
		mainJanitor:Remove(child)
	end))

	return mainJanitor
end

--- Equivalent of `Instance.DescendantAdded:Connect()` but managed by Janitor.
function InstanceUtils.ObserveDescendants(
	parent: Instance,
	callback: (descendant: Instance, descendantJanitor: Types.Janitor) -> (),
	paredicate: (descendant: Instance) -> boolean?
): Types.Janitor
	local mainJanitor = Janitor.new()
	mainJanitor:LinkToInstance(parent)

	local function onDescendantAdded(descendant: Instance)
		if paredicate and not paredicate(descendant) then
			return
		end
		callback(descendant, mainJanitor:AddObject(Janitor, nil, descendant))
	end

	for _, descendant in parent:GetDescendants() do
		task.spawn(onDescendantAdded, descendant)
	end
	mainJanitor:Add(parent.DescendantAdded:Connect(onDescendantAdded))
	mainJanitor:Add(parent.DescendantRemoving:Connect(function(descendant: Instance)
		mainJanitor:Remove(descendant)
	end))

	return mainJanitor
end

--- Equivalent of `Instance.ChildAdded:Connect()` but only for children of a specific class and managed by Janitor.
function InstanceUtils.ObserveChildrenWhichIsA(
	parent: Instance,
	className: string,
	callback: (child: Instance, childJanitor: Types.Janitor) -> ()
): Types.Janitor
	return InstanceUtils.ObserveChildren(parent, callback, function(child: Instance)
		return child:IsA(className)
	end)
end

--- Equivalent of `Instance.DescendantAdded:Connect()` but only for descendants of a specific class and managed by Janitor.
function InstanceUtils.ObserveDescendantsWhichIsA(
	parent: Instance,
	className: string,
	callback: (descendant: Instance, descendantJanitor: Types.Janitor) -> ()
): Types.Janitor
	return InstanceUtils.ObserveDescendants(parent, callback, function(descendant: Instance)
		return descendant:IsA(className)
	end)
end

--- Equivalent of `Instance.ChildAdded:Connect()` but only for children of a specific name and managed by Janitor.
function InstanceUtils.ObserveChildrenOfName(
	parent: Instance,
	name: string,
	callback: (child: Instance, childJanitor: Types.Janitor) -> ()
): Types.Janitor
	return InstanceUtils.ObserveChildren(parent, callback, function(child: Instance)
		return child.Name == name
	end)
end

--- Equivalent of `Instance.DescendantAdded:Connect()` but only for descendants of a specific name and managed by Janitor.
function InstanceUtils.ObserveDescendantsOfName(
	parent: Instance,
	name: Instance,
	callback: (descendant: Instance, descendantJanitor: Types.Janitor) -> ()
): Types.Janitor
	return InstanceUtils.ObserveDescendants(parent, callback, function(descendant: Instance)
		return descendant.Name == name
	end)
end

--- Executes a callback when the `Parent` property of an `Instance` changes.
function InstanceUtils.ObserveParent(
	instance: Instance,
	callback: (parent: Instance, parentJanitor: Types.Janitor) -> (),
	predicate: (parent: Instance) -> boolean?
): Types.Janitor
	return InstanceUtils.ObserveProperty(instance, "Parent", callback, predicate)
end

--- Behaves like `WaitForChild()` but uses `...ChildWhichIsA()` which lets you look for a specific type of `Instance`.
---
--- ⚠ THIS FUNCTION YIELDS!
function InstanceUtils.WaitForChildWhichIsA(parent: Instance, className: string, timeout: number?): Instance
	-- local child = parent:FindFirstChildWhichIsA(className)
	-- if child then
	-- 	return child
	-- end

	-- local connection
	-- connection = parent.ChildAdded:Connect(function(newChild: Instance)
	-- 	if newChild:IsA(className) then
	-- 		child = newChild
	-- 	end
	-- end)

	-- local timer = 0
	-- while parent and parent.Parent and not child and (timeout == nil or timer < timeout) do
	-- 	timer += task.wait()
	-- end

	-- connection:Disconnect()

	-- return child

    local existing = parent:FindFirstChildWhichIsA(className)
    if existing then
        return existing
    end

    local thread = coroutine.running()
    local connection

    connection = parent.ChildAdded:Connect(function(child)
        if child:IsA(className) then
            connection:Disconnect()
            task.spawn(thread, child)
        end
    end)

    if timeout then
        task.delay(timeout, function()
            if connection then
                connection:Disconnect()
                task.spawn(thread)
            end
        end)
    end

    return coroutine.yield()
end

--- Executes a callback when a specific property of an `Instance` changes.
function InstanceUtils.ObserveProperty(
	instance: Instance,
	propertyName: string,
	callback: (value: any, propertyJanitor: Types.Janitor) -> (),
	predicate: ((value: any) -> boolean)?
): Types.Janitor
	local mainJanitor = Janitor.new()
	mainJanitor:LinkToInstance(instance)

	local function onPropertyChanged()
		if predicate and not predicate(instance[propertyName]) then
			return
		end

		callback(instance[propertyName], mainJanitor:AddObject(Janitor, nil, "LastValue"))
	end
	task.spawn(onPropertyChanged)
	mainJanitor:Add(instance:GetPropertyChangedSignal(propertyName):Connect(onPropertyChanged))

	return mainJanitor
end

--- Executes a callback when a specific attribute of an `Instance` changes.
function InstanceUtils.ObserveAttribute(
	instance: Instance,
	attributeName: string,
	callback: (value: any, propertyJanitor: Types.Janitor) -> (),
	predicate: ((value: any) -> boolean)?
): Types.Janitor
	local mainJanitor = Janitor.new()
	mainJanitor:LinkToInstance(instance)

	local function onAttributeChanged()
		local value = instance:GetAttribute(attributeName)
		if predicate and not predicate(value) then
			return
		end

		callback(value, mainJanitor:AddObject(Janitor, nil, "LastValue"))
	end
	task.spawn(onAttributeChanged)
	mainJanitor:Add(instance:GetAttributeChangedSignal(attributeName):Connect(onAttributeChanged))

	return mainJanitor
end

--- Equivalent to `Instance:WaitForChild()` but it uses `Instance:FindFirstChild()` first to check if it's already there and not have to do `Instance:WaitForChild()`.
function InstanceUtils.FindFirstChild(Parent: Instance, ChildPath: string, Timeout: number?): Instance
    if not Parent then
        warn("[Utils.Instance.FindFirstChild()] `Parent` is nil!")
        return
    end

    local SplitPath = TypeUtils.SplitStringPath(ChildPath)

    local Result = Parent

    for _, step in SplitPath do
        local P = Result

        Result = P:FindFirstChild(step) or ((Timeout or 3) > 0 and P:WaitForChild(step, Timeout or 3) or nil)

        if not Result then
            break
        end
    end

    if not Result or Result == Parent then
        -- warn("[Utils.Instance.FindFirstChild()] Couldn't find \""..Parent.Name.."."..ChildPath.."\"!", debug.traceback())
        return
    end

    return Result
end

--- Tries to get an instance from a set path with a string.
--- The syntax is exactly the same as in another language when looking for a variable (e. g. "ReplicatedStorage.Modules.Utils").
--- It's useful when sending an object's path between peers (S2C / C2S) to save bandwidth by using a string instead of an Instance reference.
function InstanceUtils.GetInstanceFromPath(path: string): Instance
    local Names = TypeUtils.SplitStringPath(path)

    local Inst
	for index, step in Names do
		if index == 1 then
			Inst = game:GetService(step)
		elseif Inst then
			Inst = Inst:FindFirstChild(step)
		end
	end

    if not Inst then
		if workspace:GetAttribute("DebugAllowed") then
    	    warn("[Utils:GetInstanceFromPath()] Instance not found for path: " .. path)
    	end
        return
    end

    return Inst
end

--- Gets a character's module from a specific type.
--- It also supports skins using `skinName` as an optional last parameter.
function InstanceUtils.GetCharacterModule(charType: "Survivor" | "Killer", charName: string, skinName: string?, doWarn: boolean?): ModuleScript
	if doWarn == nil then
		doWarn = true
	end

    local CharFolder = ReplicatedStorage.Characters

    local Module

    --checks if a skin exists and assigns it
    if skinName ~= nil and #skinName > 0 then
        Module = CharFolder["Skins"][charType.."s"]:FindFirstChild(charName)
        if Module then
            Module = Module:FindFirstChild(skinName)
        end
    end

    --replaces with the default character if it hasn't found the skin or if there wasn't one from the start
    if not Module then
        Module = CharFolder[charType.."s"]:FindFirstChild(charName)
    end

    --obv errors if there's none
    if not Module then

        -- Was erroring on that warn statement, as skins are optional– but you cannot concatenate a string with nil, so if no skin was specified, this error-handling message... Caused an error, lol.
        if not skinName then
            skinName = "[N/A]"
        end

		if doWarn then
        	warn("Can't find character module! ("..charType..", "..charName..", "..skinName..")")
        	warn(debug.traceback())
		end
        return
    end

    return Module
end

--- Gets an emote's module. Just used as a shortcut, and also serves use for tracing errors (it has a custom error message).
function InstanceUtils.GetEmoteModule(EmoteName: string): ModuleScript
    local EmoteFolder = ReplicatedStorage.Assets.Emotes

    local Module = EmoteFolder:FindFirstChild(EmoteName)

    --obv errors if there's none
    if not Module then
        -- error("Can't find emote module! ("..EmoteName..")")
        return
    end

    return Module
end

--- Gets an effect's module.
--- Also supports subfolders in `ReplicatedStorage.Effects`.
function InstanceUtils.GetEffectModule(name: string, subfolder: string?, module: boolean?): ModuleScript | Types.Effect
    if module == nil then
        module = false
    end

	local Effect: ModuleScript

	local path = (subfolder and #subfolder > 0) and subfolder.."."..name or name
	Effect = InstanceUtils.FindFirstChild(ReplicatedStorage.Effects, path, 0)

    if not Effect:IsA("ModuleScript") then
        return
    end

    return module and Effect or TypeUtils.CopyTable(require(Effect))
end

--- Returns an achievement's data table through an `AchievementGroup.AchievementName` path.
function InstanceUtils.GetAchievementData(path: string): Types.Achievement
    local steps = TypeUtils.SplitStringPath(path)

    return Achievements[steps[1]].Achievements[steps[2]]
end

--- Returns a completely invisible, uncollidable, undetectable, 1 cubic stud sized part in CFrame `cf` and parented to `parent`.
function InstanceUtils.GetInvisPart(cf: CFrame?, parent: Instance?): Part
    local Part = Instance.new("Part")
    Part.CanCollide = false
    Part.CanQuery = false
    Part.CanTouch = false
    Part.Transparency = 1
    Part.CastShadow = false
    Part.Anchored = true
    Part.Size = Vector3.one

    if cf then
        Part.CFrame = cf
    end
    if parent then
        Part.Parent = parent
    end

    return Part
end

--- CLIENT FUNCTION: Highlights an instance for the local player
function InstanceUtils.HighlightInstance(instance: Instance, duration: number?, color: Color3?)
	if RunService:IsServer() then return end
	if not instance or not instance:IsDescendantOf(workspace) then return end

	local existingHighlight = instance:FindFirstChild("InstanceHighlight")
	if existingHighlight then
		existingHighlight:Destroy()
	end

	local usedColor = color or Color3.fromRGB(255, 255, 255)
	local usedDuration = duration or 10

	local highlight = Instance.new("Highlight")
	highlight.Name = "InstanceHighlight"
	highlight.FillColor = usedColor
	highlight.OutlineColor = usedColor
	highlight.Parent = instance

	Debris:AddItem(highlight, usedDuration + 2)

	-- Fade in
	highlight.FillTransparency = 1
	highlight.OutlineTransparency = 1
	TweenService:Create(highlight, TweenInfo.new(1), {
		FillTransparency = 0.5,
		OutlineTransparency = 0
	}):Play()

	-- Fade out before destruction
	task.delay(usedDuration - 1, function()
		if highlight and highlight.Parent then
			highlight.Name = "FadingOut"
			TweenService:Create(highlight, TweenInfo.new(1), {
				FillTransparency = 1,
				OutlineTransparency = 1
			}):Play()
		end
	end)
end

--- SERVER FUNCTION: Highlights an instance for specific player(s)
function InstanceUtils.HighlightInstanceFor(players: Player | {Player}, instance: Instance, duration: number?, color: Color3?)
	if not RunService:IsServer() then return end

	if typeof(players) == "table" then
		for _, player in players do
			Network:FireClientConnection(player, "HighlightInstance", "REMOTE_EVENT", instance, duration, color)
		end
	else
		Network:FireClientConnection(players, "HighlightInstance", "REMOTE_EVENT", instance, duration, color)
	end
end

return InstanceUtils


local MathUtils = {}

--- ## DEPRECATED: use `math.lerp()` instead.
---
--- Linear interpolation: formula used to get a certain value between two specified values by using a magnitude from 0 to 1.
--- See [Lerping](https://rfcs.luau.org/function-math-lerp.html).
@deprecated
function MathUtils.Lerp(a: number, b: number, t: number): number
    return a + (b - a) * t
end

--- @return Returns a string containing the passed number to be 2 digits or more, filling the blank spaces with zeros.
function MathUtils.FormatInt(Int: number): string
	return string.format("%02i", Int)
end

--- Function that'll turn a seconds value to Days:Hours:Minutes:Seconds format in a string.
function MathUtils.ConvertToDHMS(Seconds: number): string
	local Minutes = (Seconds - Seconds % 60) / 60
	Seconds -= Minutes * 60
	local Hours = (Minutes - Minutes % 60) / 60
	Minutes -= Hours * 60

    local Days = 0
    if Hours > 23 then
        Days = (Hours - Hours % 24) / 24
        Hours -= Days * 24
    end

    local FinalText = MathUtils.FormatInt(Hours)..":"..MathUtils.FormatInt(Minutes)..":"..MathUtils.FormatInt(Seconds)

    if Days > 0 then
        FinalText = MathUtils.FormatInt(Days)..":"..FinalText
    end

	return FinalText
end

--- Function that works like `FormatInt` but is used to return seconds in Minutes:Seconds format.
function MathUtils.ConvertToMinSec(Seconds: number): string
	return string.format("%02i:%02i", Seconds / 60, Seconds % 60)
end

local RomanNumeralMap = {
    [1000] = 'M',
    [900] = 'CM',
    [500] = 'D',
    [400] = 'CD',
    [100] = 'C',
    [50] = 'L',
    [40] = 'XL',
    [10] = 'X',
    [9] = 'IX',
    [5] = 'V',
    [4] = 'IV',
    [1] = 'I',
}

--- Converts an `int` to roman numerals in a `string`.
function MathUtils.IntToRoman(num: number): string
    local roman = ""

    while num > 0 do
        for int, romanChar in RomanNumeralMap do
            while num >= int do
                roman = roman..romanChar
                num -= int
            end
        end
    end

    return roman
end

--- Calculates a character's level from an EXP value.
--- Will increase the requirement for the next level until level 100 and begins at 1.
function MathUtils.CalculateLevelFromEXP(EXP: number): (number, number, number)
	local Levels = 1
	local EXPForNextLevel = 100
	local EXPRemaining = EXP

	while EXPForNextLevel <= EXPRemaining do
		EXPRemaining -= EXPForNextLevel

		Levels += 1

		EXPForNextLevel = math.round(math.clamp(Levels, 1, 100) * 12 + 80)
	end

	return Levels, EXPRemaining, EXPForNextLevel
end

--- Maps a numeric range onto another and returns position `t`.
function MathUtils.MapToRange(t: number, a: number, b: number, c: number, d: number): number
	return c + ((d - c) / (b - a)) * (t - a)
end

--- Wraps a number between two values, meaning that if it's out of bounds, it turns to the contrary bound.
--- For example, if the value is less than the minimum, it'll turn into the maximum and viceversa.
function MathUtils.WrapNum(Value: number, Minimum: number, Maximum: number): number
    if Value < Minimum then
        Value = Maximum
    elseif Value > Maximum then
        Value = Minimum
    end

    return Value
end

return MathUtils


local ContentProvider = game:GetService("ContentProvider")
local Debris = game:GetService("Debris")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Network = require(ReplicatedStorage.Modules.Network)

local MiscellaneousUtils = {}

--- Loads any assets in the function's parameter for proper use without loading them right when used.
function MiscellaneousUtils.PreloadAssets(assets: string | {string})
    task.defer(function()
        local loadedAssets = {}

        local function Fetch(assetsToFetch: any)
            if typeof(assetsToFetch) == "table" then
                for _, asset in assetsToFetch do
                    pcall(Fetch, asset)
                end
                return
            end

            if typeof(assetsToFetch) ~= "string" and typeof(assetsToFetch) ~= "Instance" then
                return
            end

            if typeof(assetsToFetch) == "string" and assetsToFetch:find("rbxassetid://") then
                if assetsToFetch:lower() == "soundids" and RunService:IsClient() then
                    local Sound = Instance.new("Sound")
                    Sound.Name = assetsToFetch
                    Sound.SoundId = assetsToFetch
                    Sound.Volume = 0
                    Sound.Parent = workspace.Sounds
                    Sound:Play()

                    Debris:AddItem(Sound, 1)
                end
            end
            table.insert(loadedAssets, assetsToFetch)
        end

        pcall(Fetch, assets)
        ContentProvider:PreloadAsync(loadedAssets)
    end)
end

--- Function used instead of the built-in print to trace for bugs in public playtesting.
--- If called from Server, it'll also print to all clients to trace server values.
function MiscellaneousUtils.Print(...)
    if RunService:IsServer() then
        print(...)
        if workspace:GetAttribute("DebugAllowed") == true and not RunService:IsStudio() then
            Network:FireAllClientConnection("Print", "UREMOTE_EVENT", Enum.MessageType.MessageInfo, ...)
        end
    elseif workspace:GetAttribute("DebugAllowed") == true then
        print(...)
    end
end

return MiscellaneousUtils


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")

local Achievements = require(ReplicatedStorage.Assets.Achievements)
local Network = require(ReplicatedStorage.Modules.Network)
local InstanceUtils = require(script.Parent.InstanceUtils)
local TypeUtils = require(script.Parent.TypeUtils)

local PlayerDataUtils = {}

--- Retrieves a player's equipped item of a type through a string path.
--- Also works for emotes.
--- If `value` is false, it'll return the `StringValue` object instead of the value itself.
function PlayerDataUtils.GetPlayerEquipped(Player: Player, path: string, value: boolean?): StringValue | string
    if value == nil then
        value = true
    end

    local splitPath = TypeUtils.SplitStringPath(path)
    local Equipped = InstanceUtils.FindFirstChild(Player, "PlayerData.Equipped", 5)
    if #splitPath == 1 then
        local Child = Equipped:FindFirstChild(splitPath[1])
        return value and Child.Value or Child
    else
        local Child = Equipped
        for _, step in splitPath do
            if not Child then
                warn("[Utils:GetPlayerEquipped()] Child \""..step.."\" not found! Returning nil!")
                -- actually returning nil but preventing selene from crying
                return Child
            end

            if Child.Name == "Skins" then
                Child = Child:FindFirstChild(step)

                if not Child then
                    if RunService:IsServer() then
                        Child = PlayerDataUtils.CreateMissingSkinValue(Player, step)
                    else
                        Child = Network:FireServerConnection("CreateMissingSkinValue", "REMOTE_FUNCTION", step)
                    end
                end

                continue
            end

            Child = Child:FindFirstChild(step)
        end
        return value and Child.Value or Child
    end
end

--- Retrieves a player's purchased item of a type through a string path.
---
--- Also works for emotes.
---
--- If `value` is false, it'll return the `StringValue` object instead of the value itself.
function PlayerDataUtils.GetPlayerOwned(Player: Player, path: string, value: boolean?): StringValue | string
    if value == nil then
        value = true
    end

    local PurchasedFolder = InstanceUtils.FindFirstChild(Player, "PlayerData.Purchased", 5)
    local Child = InstanceUtils.FindFirstChild(PurchasedFolder, path, 0)
    return value and (Child and Child.Value or false) or Child
end

--- Retrieves a player's stat through a string path.
---
--- If `value` is false, it'll return the `ValueBase` object instead of the value itself to modify it or to get a property from it (e.g. attributes).
function PlayerDataUtils.GetPlayerStat(Player: Player, path: string, value: boolean?): ValueBase | any
    if value == nil then
        value = true
    end

    local Stats = InstanceUtils.FindFirstChild(Player, "PlayerData.Stats", 5)
    local Stat = InstanceUtils.FindFirstChild(Stats, path, 0)
    return value and Stat.Value or Stat
end

--- Retrieves a player's setting value through a string path.
function PlayerDataUtils.GetPlayerSetting(Player: Player, path: string): any
    local splitPath = TypeUtils.SplitStringPath(path)
    local PlayerSettings = InstanceUtils.FindFirstChild(Player, "PlayerData.Settings", 5)
    if #splitPath == 1 then
        return PlayerSettings:FindFirstChild(splitPath[1]).Value
    else
        local Setting = PlayerSettings
        for _, step in splitPath do
            if not Setting then
                warn("[Utils:GetPlayerSetting()] Child \""..step.."\" not found! Returning nil!")
                return
            end
            Setting = Setting:FindFirstChild(step)
        end
        return Setting.Value
    end
end

--- INTERNAL | SERVER FUNCTION: Used to create a character's equipped skin value for a player if it's missing.
function PlayerDataUtils.CreateMissingSkinValue(SourcePlayer: Player, name: string): StringValue
    if not RunService:IsServer() then
        return InstanceUtils.FindFirstChild(SourcePlayer, "PlayerData.Equipped.Skins."..name, 5) :: StringValue | any
    end

    local EquippedSkins = InstanceUtils.FindFirstChild(SourcePlayer, "PlayerData.Equipped.Skins", 5)
    local SkinValue = EquippedSkins:FindFirstChild(name)
    if SkinValue then
        return SkinValue
    end

    SkinValue = Instance.new("StringValue")
    SkinValue.Name = name
    SkinValue.Value = ""
    SkinValue.Parent = EquippedSkins

    return SkinValue
end

--- INTERNAL | SERVER FUNCTION: Used to create a character's purchased skins folder for a player if it's missing.
function PlayerDataUtils.CreateMissingPurchasedSkinValue(SourcePlayer: Player, name: string): Folder
    if not RunService:IsServer() then
        return InstanceUtils.FindFirstChild(SourcePlayer, "PlayerData.Purchased.Skins."..name, 5) :: Folder | any
    end

    local PurchasedSkins = InstanceUtils.FindFirstChild(SourcePlayer, "PlayerData.Purchased.Skins", 5)
    local SkinFolder = InstanceUtils.FindFirstChild(PurchasedSkins, name, 0)
    if SkinFolder then
        return SkinFolder
    end

    SkinFolder = Instance.new("Folder")
    SkinFolder.Name = name
    SkinFolder.Parent = PurchasedSkins

    return SkinFolder
end

function PlayerDataUtils.GetAchievementObject(SourcePlayer: Player, path: string): NumberValue | BoolValue
    local AchievementFolder = InstanceUtils.FindFirstChild(SourcePlayer, "PlayerData.Achievements", 5)
    local AchievementObject = InstanceUtils.FindFirstChild(AchievementFolder, path, 5)

    if not AchievementObject then
        local steps = TypeUtils.SplitStringPath(path)
        local Achievement = Achievements[steps[1]].Achievements[steps[2]]
        if not Achievement then
            return nil :: any
        end

        if Achievement.Requirement then
            AchievementObject = Instance.new("NumberValue")
            AchievementObject.Value = 0
        else
            AchievementObject = Instance.new("BoolValue")
            AchievementObject = false
        end
        AchievementObject.Name = steps[2]
        AchievementObject.Parent = AchievementFolder:FindFirstChild(steps[1])
    end

    return AchievementObject
end

return PlayerDataUtils


local TypeUtils = {}

local Rand = Random.new()

--- Splits a path into multiple strings.
function TypeUtils.SplitStringPath(path: string): {string}
    return path:split(".")
end

--- Makes a copy of an entire table.
function TypeUtils.CopyTable<T>(CloneThis: T & {}) : T & {}
    local NewTable = {}

	for Key, Value in CloneThis do
		if typeof(Value) == "table" then
			NewTable[Key] = TypeUtils.CopyTable(Value)
            local MetaTable = getmetatable(Value)
            if MetaTable then
                setmetatable(NewTable[Key], MetaTable)
            end
		else
			NewTable[Key] = Value
		end
	end

	local MetaTable = getmetatable(CloneThis)
    if MetaTable then
        setmetatable(NewTable, MetaTable)
    end

	return NewTable
end

--- Checks if a table is either a dictionary or a numeric table.
---
--- Mixed tables (containing numbers and others as keys) will be treated as dictionaries as well.
---
--- This util is useless if the table is empty for obvious reasons.
function TypeUtils.IsTableADictionary<T>(Target: T & {}): boolean
    for key, _ in Target do
        if typeof(key) ~= "number" then
            return true
        end
    end

    return false
end

--- Shuffles a table to make it have a random order.
---
--- Dictionaries won't be accepted in this function as they already don't have a specific order.
function TypeUtils.ShuffleTable<T>(TableToShuffle: T & {}): T & {}
    if TypeUtils.IsTableADictionary(TableToShuffle) then
        warn("[TypeUtils.ShuffleTable()]: Can't shuffle table! A dictionary, which can't be reordered, has been passed!")

        return TableToShuffle
    end

    local currentShuffledIndex, shufflingItem

	for index = #TableToShuffle, 1, -1 do
		currentShuffledIndex = Rand:NextInteger(1, index)
		shufflingItem = TableToShuffle[index]
		TableToShuffle[index] = TableToShuffle[currentShuffledIndex]
		TableToShuffle[currentShuffledIndex] = shufflingItem
	end

	return TableToShuffle
end

--- Turns a dictionary into a table, removing the keys and making them numbers.
---
--- Primarily used for randomness as it isn't ordered in any way.
function TypeUtils.DictToTable<T>(Dictionary: {[any]: T}): {T}
    if TypeUtils.IsTableADictionary(Dictionary) then
        return Dictionary
    end

    local NewTable = {}

    for _, item in Dictionary do
        table.insert(NewTable, item)
    end

    return NewTable
end

--- Returns the amount of items there are in a dictionary.
---
--- Also works with tables but you can just do `#TableName` instead.
function TypeUtils.GetCountOfDict(dictionary: {[any]: any}): number
    local Count = 0

    if dictionary then
        for _, _ in dictionary do
            Count += 1
        end
    end

    return Count
end

--- Attempts to find a certain key or value in a dictionary.
---
--- May also work with numeric tables.
function TypeUtils.FindInDictionary<K, V>(Dictionary: {[K]: V}, Item: K | V, Type: "Key" | "Value"): (K, V)
    if Type == "Key" then
        for Key, Value in Dictionary do
            if Key == Item then
                return Key, Value
            end
        end
    elseif Type == "Value" then
        for Key, Value in Dictionary do
            if Value == Item then
                return Key, Value
            end
        end
    end

    return
end

--- Replaces or copies any values from `Source` into `Target`.
--- If you pass in a table variable, you don't have to get anything as it'll be replaced in that variable directly.
function TypeUtils.DeepTableOverwrite<T>(Target: T & {}, Source: {[any]: any}): T & {}
    if not Target or not Source then
        return
    end

	for key, value in Source do
		if typeof(value) == "table" then
			if typeof(Target[key]) ~= "table" then
                local MetaTable = getmetatable(value)
                if MetaTable then
                    Target[key] = setmetatable({}, MetaTable)
                else
                    Target[key] = {}
                end
			end
			TypeUtils.DeepTableOverwrite(Target[key], value)
            continue
		end
		Target[key] = value
	end

    return Target
end

--- Copies any values from `Source` into `Target`.
--- If you pass in a table variable, you don't have to get anything as it'll be replaced in that variable directly.
--- Differs from `Utils:DeepTableOverwrite` in that it doesn't overwrite existing values.
function TypeUtils.DeepTableWrite<T>(Target: T & {}, Source: {[any]: any}): T & {}
    if not Target or not Source then
        return
    end

	for key, value in Source do
		if typeof(value) == "table" then
			if typeof(Target[key]) ~= "table" then
                local MetaTable = getmetatable(value)
                if MetaTable then
                    Target[key] = setmetatable({}, MetaTable)
                else
                    Target[key] = {}
                end
			end
			TypeUtils.DeepTableWrite(Target[key], value)
            continue
		end
        if Target[key] == nil then
		    Target[key] = value
        end
	end

    return Target
end

--- Gets a random item from the provided table.
---
--- This also works with dictionaries.
function TypeUtils.GetRandomItemFromTable<T>(Target: T & {}): any
    -- turn into a table if it's a dictionary
    local UsedTable = TypeUtils.DictToTable(Target)

    -- if it's empty return nothing
    if #UsedTable <= 0 then
        return
    end

    -- if it only has one item just return the item
    if #UsedTable == 1 then
        return UsedTable[1]
    end

    -- return a random index from the table
    return UsedTable[Rand:NextInteger(1, #UsedTable)]
end

--- Splits a table into multiple chunks with size `ChunkSize`.
---
--- @return A table containing all chunks.
function TypeUtils.SplitTableIntoChunks<T>(TableToSplit: T & {}, ChunkSize: number): {T & {}}
    local Chunks = {}
    local CurrentChunk = {}

    for _, Item in ipairs(TypeUtils.DictToTable(TableToSplit)) do
        table.insert(CurrentChunk, Item)
        if #CurrentChunk > ChunkSize then
            table.insert(Chunks, CurrentChunk)
            CurrentChunk = {}
        end
    end

    if #CurrentChunk > 0 then
        table.insert(Chunks, CurrentChunk)
    end

    return Chunks
end

return TypeUtils


return {
    Name = "Maximum Killer Count",
    DisplayedAttribute = "KillersAllowed",
    Executed = function(value: number)
        workspace:SetAttribute("KillersAllowed", value)
    end,
}


return {
    Name = "Kill Player",
    Executable = true,
    Params = {
        {
            Title = "Chosen Player",
            Type = "String",
            Default = "...",
        },
    },
    Executed = function(self, Player: string)
        if typeof(Player) ~= "string" then
            return
        end

        if Player:lower() == "all" then
            for _, PlayerChar in workspace.Players:GetChildren() do
                if not PlayerChar:FindFirstChildWhichIsA("Humanoid") then
                    continue
                end

                PlayerChar.Humanoid.Health = 0
            end
            return
        end

        if not workspace.Players:FindFirstChild(Player) or not workspace.Players[Player]:FindFirstChildWhichIsA("Humanoid") then
            return
        end

        workspace.Players[Player].Humanoid.Health = 0
    end,
}


return {
    Name = "Toggle Ability Charges",
    DisplayedAttribute = "ChargesEnabled",
    Type = "Bool",
    Executed = function()
        local Enabled = workspace:GetAttribute("ChargesEnabled")
        workspace:SetAttribute("ChargesEnabled", not Enabled)

        return workspace:GetAttribute("ChargesEnabled")
    end,
}


return {
    Name = "Toggle Ability Cooldowns",
    DisplayedAttribute = "CooldownsEnabled",
    Type = "Bool",
    Executed = function()
        local Enabled = workspace:GetAttribute("CooldownsEnabled")
        workspace:SetAttribute("CooldownsEnabled", not Enabled)

        return workspace:GetAttribute("CooldownsEnabled")
    end,
}


local RunService = game:GetService("RunService")
local ServerScriptService = game:GetService("ServerScriptService")

local TimeManager = RunService:IsServer() and require(ServerScriptService.Managers.TimeManager) or nil

return {
    Name = "Freeze Timer",
    DisplayedAttribute = "FreezeTime",
    Type = "Bool",
    Executed = function()
        local Enabled = workspace:GetAttribute("FreezeTime")
        workspace:SetAttribute("FreezeTime", not Enabled)
        TimeManager.FreezeTime = not Enabled

        return workspace:GetAttribute("FreezeTime")
    end,
}


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local RunService = game:GetService("RunService")
local ServerScriptService = game:GetService("ServerScriptService")

local Utils = require(ReplicatedStorage.Modules.Utils)
local ServerCharacterManager = RunService:IsServer() and require(ServerScriptService.Managers.ServerCharacterManager) or nil

return {
    Name = "Turn Player Into Killer/Survivor",
    Executable = true,
    Params = {
        {
            Title = "Chosen Player",
            Type = "Player",
            Default = "...",
        },
        {
            Title = "Character Type",
            Default = "...",
            Cache = {
                Options = {
                    "Survivor",
                    "Killer",
                },
            },
        },
    },
    Executed = function(self, Player: string, Type: "Survivor" | "Killer")
        if typeof(Player) ~= "string" then
            return
        end

        if not table.find(self.Params[2].Cache.Options, Type) then
            return
        end

        if Player:lower() == "all" then
            for _, PlayerChar in Players:GetPlayers() do
                task.spawn(function()
                    ServerCharacterManager.SetupCharacter(PlayerChar, Utils.PlayerData.GetPlayerEquipped(PlayerChar, Type), Type)
                end)
            end
            return
        end

        Player = Players:FindFirstChild(Player)
        ServerCharacterManager.SetupCharacter(Players:FindFirstChild(Player), Utils.PlayerData.GetPlayerEquipped(Player, Type), Type)
    end,
}


local PFS = game:GetService("PathfindingService")

local module = {}

local Path = {}
Path.__index = Path

local JUMP = Enum.PathWaypointAction.Jump
local PARTIAL = Enum.PathStatus.ClosestNoPath

export type Route = {PathWaypoint}
export type AgentParams = {
	AgentRadius : number, --(The Radius Of The Object(Half Its width))
	AgentHeight : number, --(The Height Of The Object)
	AgentCanClimb : boolean, --(Whether or not it can climb)
	AgentCanJump : boolean, --(Whether or not it allows jumps)
	Costs : {}, --(What Material or Pathfinding Modifiers should be avoided(High = Avoid, Low = Okay))
	WaypointSpacing : number, --(Spacing between Waypoints)
	PathSettings : {SupportPartialPath : boolean}
}

function module.new(AgentParams: AgentParams)
	local self = setmetatable({
		Path = PFS:CreatePath(AgentParams),
		Visual = {},
		Destroying = false,
	}, Path)

	return self
end

-- Generate a Path from PointA to PointB, Return Route, IsPartialPath
function Path:Generate(PointA : Vector3, PointB : Vector3): (Route?, boolean?)
	local Success, Message = pcall(function()
		self.Path:ComputeAsync(PointA, PointB)
	end)
	if not Success then
		warn(Message)
		return
	end

	if self.Destroying ~= false then
		return
	end

	local Route : Route = self.Path:GetWaypoints()

	for i = #Route - 1, 1, -1 do
		local Waypoint = Route[i]
		local NextWaypoint = Route[i + 1]
		local Distance = (Waypoint.Position - NextWaypoint.Position).Magnitude

		if Waypoint.Action ~= JUMP and Distance < 2 then
			table.remove(Route, i)
		end
	end
	return Route, self.Path.Status == PARTIAL
end

local VisualFolder = workspace:FindFirstChild("VisualWaypoints")
if not VisualFolder then
	VisualFolder = Instance.new("Folder")
	VisualFolder.Name = "VisualWaypoints"
	VisualFolder.Parent = workspace
end

local NoobPath = script.Parent

local GREEN = BrickColor.new("Bright green").Color
local YELLOW = BrickColor.new("Bright yellow").Color
local BLUE = BrickColor.new("Bright blue").Color
local RED = BrickColor.new("Bright red").Color

local function CreatePoint(Position : Vector3)
	local Point = Instance.new("Part")
	Point.Color = GREEN
	Point.CastShadow = false
	Point.Material = Enum.Material.Neon
	Point.Size = Vector3.new(0.5, 0.5, 0.5)
	Point.Shape = Enum.PartType.Ball
	Point.CanCollide = false
	Point.CanTouch = false
	Point.CanQuery = false
	Point.Anchored = true
	Point.Position = Position
	Point.Parent = VisualFolder

	return Point
end

local function GraphLine(Position : Vector3, Next : Vector3)
	local Point = CreatePoint(Position)
	local Direction = Next - Position

	local Line = Instance.new("LineHandleAdornment")
	Line.Parent = Point
	Line.Adornee = Point
	Line.CFrame = CFrame.new(Vector3.zero, Direction)
	Line.Length = Direction.Magnitude
	Line.Color3 = BLUE

	Line.Thickness = 3

	return Point, Line
end

-- Visualize the given Route
function Path:Show(Route)
	self:Hide()
	local Visual = self.Visual
	local Length = #Route

	if NoobPath:GetAttribute("GraphPath") then
		for i = 1, Length - 1 do
			local Waypoint = Route[i]
			local Point = GraphLine(Waypoint.Position, Route[i + 1].Position)

			if Waypoint.Action == JUMP then
				Point.Color = YELLOW
			end

			Visual[i] = Point
		end
	else
		for i = 1, Length - 1 do
			local Waypoint = Route[i]
			local Point = CreatePoint(Waypoint.Position)

			if Waypoint.Action == JUMP then
				Point.Color = YELLOW
			end

			Visual[i] = Point
		end
	end

	local Point = CreatePoint(Route[Length].Position)
	Point.Color = RED

	Visual[Length] = Point
end

-- Hide all Path Visualizations
function Path:Hide()
	local Visual = self.Visual
	for i = 1, #Visual do
		Visual[i]:Destroy()
	end
end

-- Estimate time required to travel the given Route based on given Speed
function Path:Estimate(Route, Speed)
	local Estimate : {number} = {}

	for i = 1, #Route - 1 do
		local Waypoint = Route[i]
		local NextWaypoint = Route[i + 1]

		local Distance = (Waypoint.Position - NextWaypoint.Position).Magnitude
		local Time = Distance / Speed -- Estimated time

		Estimate[i] = Time
	end

	return Estimate
end

-- Get the current status of the Path
function Path:GetStatus() : Enum.PathStatus
	return self.Path.Status
end

-- Destroy the Object
function Path:Destroy()
	self.Destroying = true
	self:Hide()
	self.Path:Destroy()
	table.clear(self)
	setmetatable(self, nil)
	self = nil
end

return module