--!nonstrict
-- Roblox character sound script

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

local Utils = require(ReplicatedStorage.Modules.Utils)
local AtomicBinding = require(script:WaitForChild("AtomicBinding"))

type Playable = Sound | AudioPlayer

local function loadFlag(flag: string)
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled(flag)
	end)
	return success and result
end

local FFlagUserSoundsUseRelativeVelocity = loadFlag('UserSoundsUseRelativeVelocity2')
local FFlagUserNewCharacterSoundsApi = loadFlag('UserNewCharacterSoundsApi3')

local SOUND_DATA : { [string]: {[string]: any}} = {
	Climbing = {
		SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looped = true,
	},
	Died = {
		SoundId = "rbxasset://sounds/uuhhh.mp3",
	},
	FreeFalling = {
		SoundId = "rbxasset://sounds/action_falling.ogg",
		Looped = true,
	},
	GettingUp = {
		SoundId = "rbxasset://sounds/action_get_up.mp3",
	},
	Jumping = {
		SoundId = "rbxasset://sounds/action_jump.mp3",
	},
	Landing = {
		SoundId = "rbxasset://sounds/action_jump_land.mp3",
	},
	Running = {
		SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looped = true,
		Pitch = 1.85,
	},
	Splash = {
		SoundId = "rbxasset://sounds/impact_water.mp3",
	},
	Swimming = {
		SoundId = "rbxasset://sounds/action_swim.mp3",
		Looped = true,
		Pitch = 1.6,
	},
}

local AUDIOPLAYER_DATA : { [string]: {[string]: any}} = {
	Climbing = {
		AssetId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looping = true,
	},
	Died = {
		AssetId = "rbxasset://sounds/uuhhh.mp3",
	},
	FreeFalling = {
		AssetId = "rbxasset://sounds/action_falling.ogg",
		Looping = true,
	},
	GettingUp = {
		AssetId = "rbxasset://sounds/action_get_up.mp3",
	},
	Jumping = {
		AssetId = "rbxasset://sounds/action_jump.mp3",
	},
	Landing = {
		AssetId = "rbxasset://sounds/action_jump_land.mp3",
	},
	Running = {
		AssetId = "rbxasset://sounds/action_footsteps_plastic.mp3",
		Looping = true,
		PlaybackSpeed = 1.85,
	},
	Splash = {
		AssetId = "rbxasset://sounds/impact_water.mp3",
	},
	Swimming = {
		AssetId = "rbxasset://sounds/action_swim.mp3",
		Looping = true,
		PlaybackSpeed = 1.6,
	},
}

-- map a value from one range to another
local function map(x: number, inMin: number, inMax: number, outMin: number, outMax: number): number
	return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
end

local function getRelativeVelocity(cm, velocity)
	if not cm then
		return velocity
	end
	local activeSensor = cm.ActiveController and
    (
        (cm.ActiveController:IsA("GroundController") and cm.GroundSensor) or
        (cm.ActiveController:IsA("ClimbController") and cm.ClimbSensor)
    )
	if activeSensor and activeSensor.SensedPart then
		-- Calculate the platform relative velocity by subtracting the velocity of the surface we're attached to or standing on.
		local platformVelocity = activeSensor.SensedPart:GetVelocityAtPosition(cm.RootPart.Position)
		return velocity - platformVelocity
	end
	return velocity
end

local function playSound(sound: Playable, continue: boolean?)
	if not continue then
		(sound :: any).TimePosition = 0
	end
	if FFlagUserNewCharacterSoundsApi and sound:IsA("AudioPlayer") then
		sound:Play()
	else
		(sound :: Sound).Playing = true
	end
end

local function stopSound(sound: Playable)
	if FFlagUserNewCharacterSoundsApi and sound:IsA("AudioPlayer") then
		sound:Stop()
	else
		(sound :: Sound).Playing = false
	end
end

local function playSoundIf(sound: Playable, condition: boolean)
	if FFlagUserNewCharacterSoundsApi and sound:IsA("AudioPlayer") then
		if sound.IsPlaying and not condition then
			sound:Stop()
		elseif not sound.IsPlaying and condition then
			sound:Play()
		end
	else
		(sound :: Sound).Playing = condition
	end
end

local function setSoundLooped(sound: Playable, isLooped: boolean)
	if FFlagUserNewCharacterSoundsApi and sound:IsA("AudioPlayer") then
		sound.Looping = isLooped
	else
		(sound :: Sound).Looped = isLooped
	end
end

local function shallowCopy(t)
	local out = table.clone(t)
	return out
end

local function initializeSoundSystem(instances: { [string]: Instance })
	local humanoid = instances.humanoid
	local rootPart = instances.rootPart
	local audioEmitter = nil
	local cm = nil
	if FFlagUserSoundsUseRelativeVelocity then
		local character = humanoid.Parent
		cm = character:FindFirstChild('ControllerManager')
	end

	local sounds: {[string]: Playable} = {}

	if FFlagUserNewCharacterSoundsApi and SoundService.CharacterSoundsUseNewApi == Enum.RolloutState.Enabled then
		-- initialize Audio Emitter
		local localPlayer = Players.LocalPlayer
		local character = localPlayer.Character
		local curve = {}
		local i : number = 5
		local step : number = 1.25 -- determines how fine-grained the curve gets sampled
		while i < 150 do
			curve[i] = 5 / i
			i *= step
		end
		curve[150] = 0
		audioEmitter = Instance.new("AudioEmitter")
        audioEmitter.Parent = character
		audioEmitter.Name = "RbxCharacterSoundsEmitter"
		audioEmitter:SetDistanceAttenuation(curve)
		-- initialize sounds
		for name: string, props: {[string]: any} in AUDIOPLAYER_DATA do
			local sound = Instance.new("AudioPlayer")
			local audioPlayerWire: Wire = Instance.new("Wire")
			sound.Name = name
			audioPlayerWire.Name = name .. "Wire"
			-- set default values
			sound.Archivable = false
			sound.Volume = 0.65
			for propName, propValue: any in props do
				(sound :: any)[propName] = propValue
			end
			sound.Parent = rootPart
			audioPlayerWire.Parent = sound
			audioPlayerWire.SourceInstance = sound
			audioPlayerWire.TargetInstance = audioEmitter
			sounds[name] = sound
		end
	else
		-- initialize sounds
		for name: string, props: {[string]: any} in SOUND_DATA do
			local sound = Instance.new("Sound")
			sound.Name = name
			-- set default values
			sound.Archivable = false
			sound.RollOffMinDistance = 5
			sound.RollOffMaxDistance = 150
			sound.Volume = 0.65
			for propName, propValue: any in props do
				(sound :: any)[propName] = propValue
			end
			sound.Parent = rootPart
			sounds[name] = sound
		end
	end

	local playingLoopedSounds: {[Playable]: boolean?} = {}

	local function stopPlayingLoopedSounds(except: Playable?)
		except = except or nil --default value
		for sound in shallowCopy(playingLoopedSounds) do
			if sound ~= except then
				stopSound(sound)
				playingLoopedSounds[sound] = nil
			end
		end
	end

	-- state transition callbacks.
	local stateTransitions: {[Enum.HumanoidStateType]: () -> ()} = {
		[Enum.HumanoidStateType.FallingDown] = function()
			stopPlayingLoopedSounds()
		end,

		[Enum.HumanoidStateType.GettingUp] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.GettingUp)
		end,

		[Enum.HumanoidStateType.Jumping] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.Jumping)
		end,

		[Enum.HumanoidStateType.Swimming] = function()
			local verticalSpeed = math.abs(rootPart.AssemblyLinearVelocity.Y)
			if verticalSpeed > 0.1 then
				(sounds.Splash :: any).Volume = math.clamp(map(verticalSpeed, 100, 350, 0.28, 1), 0, 1)
				playSound(sounds.Splash)
			end
			stopPlayingLoopedSounds(sounds.Swimming)
			playSound(sounds.Swimming, true)
			playingLoopedSounds[sounds.Swimming] = true
		end,

		[Enum.HumanoidStateType.Freefall] = function()
			(sounds.FreeFalling :: any).Volume = 0
			stopPlayingLoopedSounds(sounds.FreeFalling)

			setSoundLooped(sounds.FreeFalling, true)
			if sounds.FreeFalling:IsA("Sound") then
				sounds.FreeFalling.PlaybackRegionsEnabled = true
			end
			(sounds.FreeFalling :: any).LoopRegion = NumberRange.new(2, 9)
			playSound(sounds.FreeFalling)

			playingLoopedSounds[sounds.FreeFalling] = true
		end,

		[Enum.HumanoidStateType.Landed] = function()
			stopPlayingLoopedSounds()
			local verticalSpeed = math.abs(rootPart.AssemblyLinearVelocity.Y)
			if verticalSpeed > 75 then
				(sounds.Landing :: any).Volume = math.clamp(map(verticalSpeed, 50, 100, 0, 1), 0, 1)
				playSound(sounds.Landing)
			end
		end,

		--[[[Enum.HumanoidStateType.Running] = function()
			stopPlayingLoopedSounds(sounds.Running)
			playSound(sounds.Running, true)
			playingLoopedSounds[sounds.Running] = true
		end,]]

		[Enum.HumanoidStateType.Climbing] = function()
			local sound = sounds.Climbing
			local partVelocity = rootPart.AssemblyLinearVelocity
			local velocity = if FFlagUserSoundsUseRelativeVelocity then getRelativeVelocity(cm, partVelocity) else partVelocity
			if math.abs(velocity.Y) > 0.1 then
				playSound(sound, true)
				stopPlayingLoopedSounds(sound)
			else
				stopPlayingLoopedSounds()
			end
			playingLoopedSounds[sound] = true
		end,

		[Enum.HumanoidStateType.Seated] = function()
			stopPlayingLoopedSounds()
		end,

		[Enum.HumanoidStateType.Dead] = function()
			stopPlayingLoopedSounds()
			playSound(sounds.Died)
		end,
	}

	-- updaters for looped sounds
	local loopedSoundUpdaters: {[Playable]: (number, Playable, Vector3) -> ()} = {
		[sounds.Climbing] = function(_dt: number, sound: Playable, vel: Vector3)
			local velocity = if FFlagUserSoundsUseRelativeVelocity then getRelativeVelocity(cm, vel) else vel
			playSoundIf(sound, velocity.Magnitude > 0.1)
		end,

		[sounds.FreeFalling] = function(dt: number, sound: Playable, vel: Vector3): ()
			if vel.Magnitude > 75 then
				(sound :: any).Volume = math.clamp((sound :: any).Volume + 0.9*dt, 0, 1)
			else
				(sound :: any).Volume = 0
			end
		end,

		[sounds.Running] = function(_dt: number, sound: Playable, vel: Vector3)
			playSoundIf(sound, vel.Magnitude > 0.5 and humanoid.MoveDirection.Magnitude > 0.5)
		end,
	}

	-- state substitutions to avoid duplicating entries in the state table
	local stateRemap: {[Enum.HumanoidStateType]: Enum.HumanoidStateType} = {
		[Enum.HumanoidStateType.RunningNoPhysics] = Enum.HumanoidStateType.Running,
	}

	local activeState: Enum.HumanoidStateType = stateRemap[humanoid:GetState()] or humanoid:GetState()

	local function transitionTo(state)
		local transitionFunc: () -> () = stateTransitions[state]

		if transitionFunc then
			transitionFunc()
		end

		activeState = state
	end

	transitionTo(activeState)

	local stateChangedConn = humanoid.StateChanged:Connect(function(_, state)
		state = stateRemap[state] or state

		if state ~= activeState then
			transitionTo(state)
		end
	end)

	local steppedConn = RunService.PreSimulation:Connect(function(worldDt: number)
		-- update looped sounds on stepped
		for sound in playingLoopedSounds do
			local updater: (number, Playable, Vector3) -> () = loopedSoundUpdaters[sound]

			if updater then
				updater(worldDt, sound, rootPart.AssemblyLinearVelocity)
			end
		end
	end)

	local function terminate()
		stateChangedConn:Disconnect()
		steppedConn:Disconnect()

		-- Unparent all sounds and empty sounds table
		-- This is needed in order to support the case where initializeSoundSystem might be called more than once for the same player,
		-- which might happen in case player character is unparented and parented back on server and reset-children mechanism is active.
		for _name: string, sound: Playable in sounds do
			sound:Destroy()
		end
		table.clear(sounds)
	end

	return terminate
end

local binding = AtomicBinding.new({
	humanoid = "Humanoid",
	rootPart = "HumanoidRootPart",
}, initializeSoundSystem)

local playerConnections = {}

local function characterAdded(character)
	binding:bindRoot(character)
end

local function characterRemoving(character)
	binding:unbindRoot(character)
end

local function playerAdded(player: Player)
	local connections = playerConnections[player]
	if not connections then
		connections = {}
		playerConnections[player] = connections
	end

	if player.Character then
		characterAdded(player.Character)
	end
	table.insert(connections, player.CharacterAdded:Connect(characterAdded))
	table.insert(connections, player.CharacterRemoving:Connect(characterRemoving))
end

local function playerRemoving(player: Player)
	local connections = playerConnections[player]
	if connections then
		for _, conn in ipairs(connections) do
			conn:Disconnect()
		end
		playerConnections[player] = nil
	end

	if player.Character then
		characterRemoving(player.Character)
	end
end

Utils.Player.ObservePlayers(playerAdded)
Players.PlayerRemoving:Connect(playerRemoving)


local ReplicatedStorage = game:GetService("ReplicatedStorage")

local Network = require(ReplicatedStorage.Modules.Network)

local Hitmarker = require(ReplicatedStorage.Classes.Hitmarker)

return function()
	Network:SetConnection("DisplayHitmarker", "REMOTE_EVENT", function(position: Vector3, amount: number)
		Hitmarker.new(position, amount)
	end)
end


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

return function()
    local function Execute(path: string, ...)
		local module: ModuleScript
		if path and #path > 0 then
			local steps = path:split(".")
			for index, step in steps do
				if index == 1 then
					module = game:GetService(step)
				else
					module = module:FindFirstChild(step)
				end
			end
		end
		if module:IsA("ModuleScript") then
			require(module)(...)
		end
	end

	Network:SetConnection("ExecuteReplicatedModule", "UREMOTE_EVENT", function(path: string, ...)
		Execute(path, ...)
	end)

	Network:SetConnection("ExecuteReplicatedModule", "REMOTE_EVENT", function(path: string, ...)
		Execute(path, ...)
	end)
end


local ReplicatedStorage = game:GetService("ReplicatedStorage")
local Network = require(ReplicatedStorage.Modules.Network)
return function()
    Network:SetConnection("Print", "UREMOTE_EVENT", function(messageType: Enum.MessageType, ...)
        if workspace:GetAttribute("DebugAllowed") ~= true then
            return
        end
        if messageType == Enum.MessageType.MessageError then
            error(...)
        elseif messageType == Enum.MessageType.MessageWarning then
            warn(...)
        else
            print(...)
        end
    end)
end


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

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

local LocalPlayer = Players.LocalPlayer

local Images = {
    Victim = {
        Killer = {
            "rbxassetid://125952385779817",
        },
        Survivor = {
            "rbxassetid://125952385779817",
        },
    },
    Source = {
        Killer = {
            "rbxassetid://122348374983136",
        },
        Survivor = {
            "rbxassetid://122348374983136",
        },
    },
}

local SoundsTable = {
    Victim = {
        Killer = {
            "rbxassetid://7188420724",
        },
        Survivor = {
            "rbxassetid://7188420724",
        },
    },
    Source = {
        Killer = {
            "rbxassetid://7188420724",
        },
        Survivor = {
            "rbxassetid://7188420724",
        },
    },
}

return function()
    Utils.Misc.PreloadAssets({Images, SoundsTable})

    local ImagePreset = Utils.Instance.FindFirstChild(script, "ImageLabel")

    Network:SetConnection("ShowJokeSettingImage", "REMOTE_EVENT", function(isVictim: boolean, role: string)
        if not role then
            return
        end

        local UsedImageTable = if isVictim then Images.Victim else Images.Source
        UsedImageTable = UsedImageTable[role] or UsedImageTable.Survivor

        local UsedSoundTable = if isVictim then SoundsTable.Victim else SoundsTable.Source
        UsedSoundTable = UsedSoundTable[role] or UsedSoundTable.Survivor

        local Image = ImagePreset:Clone()
        Image.Image = UsedImageTable[math.random(1, #UsedImageTable)]
        Image.Parent = LocalPlayer.PlayerGui.TemporaryUI

        TweenService:Create(Image, TweenInfo.new(1.2), {
            ImageTransparency = 1
        }):Play()
        Sounds.PlaySound(UsedSoundTable)

        Debris:AddItem(Image, 1.5)
    end)
end


return {
	Init = function(_self)
		for _, func in script:GetChildren() do
			if not func:IsA("ModuleScript") then
				continue
			end
			require(func)()
		end
	end
}


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

local Achievements = require(ReplicatedStorage.Assets.Achievements)
local Types = require(ReplicatedStorage.Classes.Types)
local Sounds = require(ReplicatedStorage.Modules.Sounds)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Janitor = require(ReplicatedStorage.Packages.Janitor)

local SideBar = require(script.Parent.SideBar)

local LocalPlayer = Players.LocalPlayer

local AchievementsMenu = {
    Enabled = true,
    UIInstance = nil,
    Opened = false,
    Janitor = nil,
    Defaults = {
        Shown = {
            Title = "Achievement Name",
            Description = "Achievement Description",
            Icon = "rbxasset://textures/ui/GuiImagePlaceholder.png",
        },
        Hidden = {
            Title = "?????",
            Description = "????????????????????",
            Icon = "rbxassetid://15117261700",
        },
    },
}

local Prefabs = Utils.Instance.FindFirstChild(script, "Prefabs")
local AchievementPrefab = Utils.Instance.FindFirstChild(Prefabs, "Achievement")
local CategoryPrefab = Utils.Instance.FindFirstChild(Prefabs, "Category")
local CategoryButtonPrefab = Utils.Instance.FindFirstChild(Prefabs, "CategoryButton")

local UIParent = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "Menus")
function AchievementsMenu:Init()
    if AchievementsMenu.UIInstance then
        AchievementsMenu.UIInstance.Parent = UIParent
        AchievementsMenu.UIInstance.Size = Utils.UDim.Zero
        AchievementsMenu.UIInstance.Visible = false
        return
    end

    AchievementsMenu.Janitor = Janitor.new()

    local UI = Utils.Instance.FindFirstChild(script, "Achievements")
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    AchievementsMenu.UIInstance = UI
    UI.Parent = UIParent

    AchievementsMenu.Janitor:LinkToInstance(UI)

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

    AchievementsMenu._SetupAchievements()
end

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

    if Time > 0 then
        TweenService:Create(AchievementsMenu.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.5, 0.7)}):Play()
    else
        AchievementsMenu.UIInstance.Size = UDim2.fromScale(0.4, 0.7)
    end
    AchievementsMenu.Opened = true

    return
end

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

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

function AchievementsMenu._SetupAchievements()
    local Content = AchievementsMenu.UIInstance.Content

    local AchievementGroupLayoutOrder = 0

    local CategoryButtons = AchievementsMenu.UIInstance.CategoryButtons

    local function IsAchievementUnlocked(AchievementValue: NumberValue | BoolValue, AchievementEquivalent: Types.Achievement): boolean
        return if AchievementEquivalent.Requirement ~= nil then AchievementValue.Value >= AchievementEquivalent.Requirement else (AchievementValue.Value :: boolean)
    end

    local Counter = AchievementsMenu.UIInstance.Label.TextLabel
    local function ReloadCounter()
        local Total = 0
        local Unlocked = 0

        for _, Group in Content:GetChildren() do
            if not Group:IsA("Frame") then
                continue
            end

            local AchievementGroupEquivalent = Achievements[Group.Name]

            for _, Achievement in Group:GetChildren() do
                if not Achievement:IsA("Frame") then
                    continue
                end

                Total += 1

                local AchievementEquivalent: Types.Achievement = AchievementGroupEquivalent.Achievements[Achievement.Name]
                local AchievementValue = Utils.Instance.FindFirstChild(LocalPlayer, `PlayerData.Achievements.{Group.Name}.{Achievement.Name}`, 1)
                if IsAchievementUnlocked(AchievementValue, AchievementEquivalent) then
                    Unlocked += 1
                end
            end
        end

        Counter.Text = `Achievements ({Unlocked}/{Total} Unlocked)`
    end

    local function MakeCategoryButton(Name: string, Title: string?, Category: Frame?)
        local CategoryButton = CategoryButtonPrefab:Clone()
        CategoryButton.Name = Name
        CategoryButton.Label.Text = Title or Name
        CategoryButton.LayoutOrder = AchievementGroupLayoutOrder
        CategoryButton.MouseEnter:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
        end)
        CategoryButton.MouseLeave:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
        end)
        CategoryButton.MouseButton1Click:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
            for _, OtherCategory: Frame in Content:GetChildren() do
                if not OtherCategory:IsA("Frame") then
                    continue
                end

                OtherCategory.Visible = if Category ~= nil then OtherCategory == Category else true
            end
        end)
        CategoryButton.Parent = CategoryButtons
    end

    MakeCategoryButton("All")

    local AchievementGroups = Utils.Instance.FindFirstChild(LocalPlayer, "PlayerData.Achievements", 5):GetChildren()
    table.sort(AchievementGroups, function(a: Folder, b: Folder)
        local aEquivalent = Achievements[a.Name]
        local bEquivalent = Achievements[b.Name]

        return aEquivalent.LayoutOrder < bEquivalent.LayoutOrder
    end)

    for _, AchievementGroup in AchievementGroups do
        local AchievementGroupEquivalent = Achievements[AchievementGroup.Name]

        AchievementGroupLayoutOrder += 1

        local Category = CategoryPrefab:Clone()
        Category.Name = AchievementGroup.Name
        Category.CategoryLabel.Label.Text = AchievementGroupEquivalent.Title
        Category.LayoutOrder = AchievementGroupLayoutOrder
        Category.Parent = Content

        MakeCategoryButton(AchievementGroup.Name, AchievementGroupEquivalent.Title, Category)

        local AchievementsInThisGroup = AchievementGroup:GetChildren()
        table.sort(AchievementsInThisGroup, function(a: NumberValue | BoolValue, b: NumberValue | BoolValue)
            local aEquivalent = AchievementGroupEquivalent.Achievements[a.Name]
            local bEquivalent = AchievementGroupEquivalent.Achievements[b.Name]

            return aEquivalent.LayoutOrder < bEquivalent.LayoutOrder
        end)

        local AchievementLayoutOrder = 0

        for _, AchievementValue in AchievementGroup:GetChildren() do
            local AchievementEquivalent: Types.Achievement = AchievementGroupEquivalent.Achievements[AchievementValue.Name]

            AchievementLayoutOrder += 1

            local Achievement = AchievementPrefab:Clone()
            Achievement.Name = AchievementValue.Name
            Achievement.Reward.Visible = false
            Achievement.LayoutOrder = AchievementLayoutOrder

            local function Reload()
                if not IsAchievementUnlocked(AchievementValue, AchievementEquivalent) then
                    Achievement.LockedOverlay.Visible = true

                    if AchievementEquivalent.HideTitleIfLocked then
                        Achievement.Content.DisplayName.Text = AchievementEquivalent.HiddenTitle or AchievementsMenu.Defaults.Hidden.Title
                    else
                        Achievement.Content.DisplayName.Text = AchievementEquivalent.Title or AchievementsMenu.Defaults.Shown.Title
                    end

                    if AchievementEquivalent.HideDescriptionIfLocked then
                        Achievement.Content.Description.Text = AchievementEquivalent.HiddenDescription or AchievementsMenu.Defaults.Hidden.Description
                    else
                        Achievement.Content.Description.Text = AchievementEquivalent.Description or AchievementsMenu.Defaults.Shown.Description
                    end

                    if AchievementEquivalent.HideIconIfLocked then
                        Achievement.Icon.Image = AchievementEquivalent.LockIcon or AchievementsMenu.Defaults.Hidden.Icon
                    else
                        Achievement.Icon.Image = AchievementEquivalent.Icon or AchievementsMenu.Defaults.Shown.Icon
                    end

                    if AchievementEquivalent.Hide then
                        Achievement.Visible = false
                    end
                else
                    Achievement.LockedOverlay.Visible = false
                    Achievement.Visible = true

                    Achievement.Content.DisplayName.Text = AchievementEquivalent.Title or AchievementsMenu.Defaults.Shown.Title
                    Achievement.Content.Description.Text = AchievementEquivalent.Description or AchievementsMenu.Defaults.Shown.Description
                    Achievement.Icon.Image = AchievementEquivalent.Icon or AchievementsMenu.Defaults.Shown.Icon
                end

                if AchievementEquivalent.Requirement then
                    Achievement.Content.Requirement.Text = tostring(AchievementValue.Value).."/"..tostring(AchievementEquivalent.Requirement)
                end

                ReloadCounter()
            end

            AchievementValue.Changed:Connect(function()
                Reload()
            end)
            Reload()

            if not AchievementEquivalent.Requirement then
                Achievement.Content.Requirement:Destroy()
            end

            if AchievementEquivalent.RewardType then
                local Reward = AchievementEquivalent.RewardType == "Currency" and
                    AchievementEquivalent.Amount or
                    (AchievementEquivalent.RewardType == "Skin" and
                        AchievementEquivalent.Skin or
                        AchievementEquivalent.Item
                        )

                local RewardText = AchievementEquivalent.RewardType == "Currency" and "$"..Reward or Reward
                if AchievementEquivalent.RewardType ~= "Currency" then
                    local Suffix = " ("..(AchievementEquivalent.RewardType == "Skin" and tostring(AchievementEquivalent.Item).." Skin" or AchievementEquivalent.RewardType)..")"
                    RewardText = RewardText..Suffix
                end

                Achievement.Reward.Text = "Reward: "..RewardText

                Achievement.MouseEnter:Connect(function()
                    if AchievementEquivalent.HideRewardIfLocked and not IsAchievementUnlocked(AchievementValue, AchievementEquivalent) then
                        return
                    end

                    Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
                    Achievement.Reward.Visible = true
                end)
                Achievement.MouseLeave:Connect(function()
                    if AchievementEquivalent.HideRewardIfLocked and not IsAchievementUnlocked(AchievementValue, AchievementEquivalent) then
                        return
                    end

                    Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
                    Achievement.Reward.Visible = false
                end)
            end

            Achievement.Parent = Category
        end
    end

    ReloadCounter()
end

return AchievementsMenu


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

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

local LocalPlayer = Players.LocalPlayer

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

--- The format to follow in `CurrentChangelog`'s
type ChangelogFormat = {
    --- The version of the place containing the update.
    --- Can be found by printing `game.PlaceVersion`.
    PlaceVersion: number,
    --- The text that'll be shown in the menu's title.
    MenuTitle: string,
    --- The title of the update for the beginning of the changelog.
    UpdateTitle: string,
    --- The ID of the update's thumbnail's image.
    --- May also be a banner if you modify the UI yourself.
    UpdateThumbnailID: string,
    --- The date of the update.
    --- Can be anything, but preferably the date.
    --- Will show under the thumbnail.
    UpdateDate: string,
    --- The entries of the changelog.
    Entries: {{
        Type: "Separator" | "SectionEntry" | "Text",
        Text: string,
    }},
}
local CurrentChangelog: ChangelogFormat = {
    PlaceVersion = 0,
    MenuTitle = "Changelog",
    UpdateTitle = "GAME RELEASE",
    UpdateThumbnailID = "rbxasset://textures/ui/GuiImagePlaceholder.png",
    UpdateDate = "01/01/2099",
    Entries = {
        {
            Type = "Separator",
            Text = "Section N.º 1",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 1!",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 2!",
        },
        {
            Type = "Text",
            Text = "Quick note N.º 1!",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 3!",
        },
        {
            Type = "Text",
            Text = "Quick note N.º 2!",
        },
        {
            Type = "Separator",
            Text = "Section N.º 2",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 1!",
        },
        {
            Type = "Text",
            Text = "Quick note N.º 1!",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 2!",
        },
        {
            Type = "SectionEntry",
            Text = "Pretty cool feature N.º 3!",
        },
        {
            Type = "Text",
            Text = "Quick note N.º 2!",
        },
    },
}

local PrefabFolder = Utils.Instance.FindFirstChild(script, "Prefabs")
local Prefabs = {
    Separator = Utils.Instance.FindFirstChild(PrefabFolder, "Separator"),
    SectionEntry = Utils.Instance.FindFirstChild(PrefabFolder, "SectionEntry"),
    Text = Utils.Instance.FindFirstChild(PrefabFolder, "PlainText"),
}

local UIParent = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "Menus")
function Changelog:Init()
    if Changelog.UIInstance then
        Changelog.UIInstance.Parent = UIParent
        Changelog.UIInstance.Size = Utils.UDim.Zero
        Changelog.UIInstance.Visible = false
        return
    end

    local UI = Utils.Instance.FindFirstChild(script, "Changelog")
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    Changelog.UIInstance = UI
    UI.Parent = UIParent

    local Content = UI.Content
    local Top = Content.Top
    Top.Title.Text = CurrentChangelog.UpdateTitle
    Top.Thumbnail.Image = CurrentChangelog.UpdateThumbnailID
    Top.Date.Text = CurrentChangelog.UpdateDate

    UI.Label.TextLabel.Text = CurrentChangelog.MenuTitle

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

    for order, entry in CurrentChangelog.Entries do
        local Inst = Prefabs[entry.Type]:Clone()
        Inst.Title.Text = entry.Text
        Inst.LayoutOrder = order
        Inst.Parent = Content
    end

    local Log = Utils.Instance.FindFirstChild(LocalPlayer, "PlayerData.Misc.LastSeenLog")
    if Log.Value < CurrentChangelog.PlaceVersion and Log.Value < ReplicatedStorage.PlaceVersion.Value and CurrentChangelog.PlaceVersion <= ReplicatedStorage.PlaceVersion.Value then
        SideBar.OpenMenu(Changelog)
    end
end

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

    if Time > 0 then
        TweenService:Create(Changelog.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.4, 0.7)}):Play()
    else
        Changelog.UIInstance.Size = UDim2.fromScale(0.4, 0.7)
    end
    Changelog.Opened = true

    return
end

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

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

return Changelog


local CharacterStatsManager = {
    UI = nil,
    Connection = nil,
}

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

function CharacterStatsManager:Init()
    local UIPrefab = Utils.Instance.FindFirstChild(script, "Stats")

    Utils.Character.ObserveCharacter(LocalPlayer, function(char: Model)
        CharacterStatsManager._InitUI(char, UIPrefab:Clone())
    end)
end

function CharacterStatsManager._InitUI(Char: Model, UI: Frame)
    if CharacterStatsManager.Connection then
        CharacterStatsManager.Connection:Disconnect()
    end
    if CharacterStatsManager.UI then
        CharacterStatsManager.UI:Destroy()
    end

    if Char:FindFirstChild("Role").Value == "Spectator" then return end

    CharacterStatsManager.UI = UI
    UI.Parent = LocalPlayer.PlayerGui.PlayerStats

    local Stamina = UI.Stamina
    local StaminaText = Stamina.TextLabel
    local StaminaBar = Stamina.BarBackground.Bar

    local Health = UI.Health
    local HealthText = Health.TextLabel
    local HealthBar = Health.BarBackground.Bar

    local Humanoid = Char:FindFirstChildWhichIsA("Humanoid")
    local StaminaAttributes = {
    	Stamina = Char.PlayerAttributes.Stamina,
    	MaxStamina = Char.PlayerAttributes.MaxStamina
    }

    CharacterStatsManager.Connection = RunService.PreRender:Connect(function(delta: number)
        if not CharacterStatsManager.UI then
            CharacterStatsManager.Connection:Disconnect()
            return
        end

	    HealthBar.Size = UDim2.fromScale(math.lerp(HealthBar.Size.X.Scale, Humanoid.Health / Humanoid.MaxHealth, delta * 9), 1)
	    --
	    StaminaBar.Size = UDim2.fromScale(math.lerp(StaminaBar.Size.X.Scale, StaminaAttributes.Stamina.Value / StaminaAttributes.MaxStamina.Value, delta * 9), 1)
    end)

    Utils.Instance.ObserveProperty(Humanoid, "Health", function(value: number)
        HealthText.Text = tostring(math.round(Humanoid.Health)).."/"..tostring(Humanoid.MaxHealth)
    end)

    local function UpdateStaminaText()
        StaminaText.Text = tostring(math.abs(math.round(StaminaAttributes.Stamina.Value))).."/"..tostring(StaminaAttributes.MaxStamina.Value)
    end
    Utils.Instance.ObserveProperty(StaminaAttributes.Stamina, "Value", UpdateStaminaText)
    Utils.Instance.ObserveProperty(StaminaAttributes.MaxStamina, "Value", UpdateStaminaText)
end

return CharacterStatsManager


local Players = game:GetService("Players")
local ReplicatedStorage = game:GetService("ReplicatedStorage")
local StarterGui = game:GetService("StarterGui")
local TextChatService = game:GetService("TextChatService")

local Utils = require(ReplicatedStorage.Modules.Utils)

repeat
	local success = pcall(function()
		StarterGui:SetCore("ResetButtonCallback", false)
	end)
	task.wait()
until success --this means that it has initialized

return {
    Init = function(_)
        Utils.Character.ObserveCharacter(Players.LocalPlayer, function(Char: Model)
            local IsSpectator = Char:FindFirstChild("Role").Value == "Spectator"
            TextChatService:FindFirstChildWhichIsA("ChatWindowConfiguration").Enabled = IsSpectator
            StarterGui:SetCore("ResetButtonCallback", IsSpectator)
        end)
    end,
}


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

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

local LocalPlayer = Players.LocalPlayer

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

type Credit = {
    Render: string,
    DisplayName: string,
    Username: string,
    Description: string,
}
local Credits: {{ Name: string, Credits: {Credit} }} = {
    {
        Name = "Dysymmetrical",
        Credits = {
            {
                Render = "rbxassetid://112215528635901",
                DisplayName = "Dyscarn",
                Username = "@LrgeAlvaro8882",
                Description = "Made the engine used in this game: Dysymmetrical.",
            },
            {
                Render = "",
                DisplayName = "heromax149807",
                Username = "@heromax149807",
                Description = "Made the original engine art like the sidebar icons, the buttons, etc.",
            },
        },
    },
}

local UIParent = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "Menus")
function CreditsMenu:Init()
    if CreditsMenu.UIInstance then
        CreditsMenu.UIInstance.Parent = UIParent
        CreditsMenu.UIInstance.Size = Utils.UDim.Zero
        CreditsMenu.UIInstance.Visible = false
        return
    end

    local UI = Utils.Instance.FindFirstChild(script, "CreditsMenu")
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    CreditsMenu.UIInstance = UI
    UI.Parent = UIParent

    local Prefabs = {
        Section = Utils.Instance.FindFirstChild(script, "SectionLabel"),
        Credit = Utils.Instance.FindFirstChild(script, "Credit"),
    }

    local LO = 0
    --using ipairs instead of nothing to make sure that it's ordered
    for _, CreditsSection: {Name: string, Credits: {Credit}} in ipairs(Credits) do
        LO += 1

        local SectionPrefab = Prefabs.Section:Clone()
        SectionPrefab.Name = CreditsSection.Name.."Section"
        SectionPrefab.SectionName.Text = CreditsSection.Name
        SectionPrefab.LayoutOrder = LO
        SectionPrefab.Parent = UI.Content

        for _, Credit: Credit in ipairs(CreditsSection.Credits) do
            LO += 1

            local CreditPrefab = Prefabs.Credit:Clone()
            CreditPrefab.Name = Credit.Username.."Credit"
            CreditPrefab.DisplayName.Text = Credit.DisplayName
            CreditPrefab.Username.Text = Credit.Username
            CreditPrefab.Portrait.Image = Credit.Render or "rbxasset://textures/ui/GuiImagePlaceholder.png"
            CreditPrefab.Description.Text = Credit.Description

            CreditPrefab.LayoutOrder = LO
            CreditPrefab.Parent = UI.Content
        end
    end

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

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

    TweenService:Create(CreditsMenu.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.475, 0.599)}):Play()
    CreditsMenu.Opened = true

    return
end

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

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

return CreditsMenu


local EffectViewer = {}

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

local Utils = require(ReplicatedStorage.Modules.Utils)

local EffectDisplay: Frame

function EffectViewer:Init()
    EffectDisplay = Utils.Instance.FindFirstChild(script, "EffectDisplay")
    local UIPrefab = Utils.Instance.FindFirstChild(script, "StatusContainer")

    Utils.Character.ObserveCharacter(LocalPlayer, function(Char: Model)
        EffectViewer._SetupUI(UIPrefab:Clone(), Char)
    end)
end

function EffectViewer._SetupUI(UI: Frame, char: Model)
    if char:FindFirstChild("Role").Value == "Spectator" then
        UI:Destroy()
        return
    end

    UI.Parent = LocalPlayer.PlayerGui.Effects

    local EffectManager = require(char.PlayerAttributeScripts.EffectManager)

    local Connection: RBXScriptConnection = RunService.PreRender:Connect(function()
        local Effects: {} = EffectManager.Effects

	    for name, effect in Effects do
	    	if not effect.ShowInGUI then continue end
	    	local Display = UI:FindFirstChild(name)
	    	if not Display then
	    		Display = EffectDisplay:Clone()
	    		Display.Name = name
	    		Display.Title.Text = effect.Name
                if effect.Level > 1 then
                    local RomanLevel = Utils.Math.IntToRoman(effect.Level)
                    Display.Title.Text = Display.Title.Text.." "..RomanLevel
                end
	    		Display.Parent = UI
	    	end
	    	Display.Timer.Text = Utils.Math.ConvertToMinSec(effect.TimeLeft + 1)
	    end

	    for _, display in UI:GetChildren() do
	    	if display:IsA("Frame") and not Effects[display.Name] then
	    		display:Destroy()
	    	end
	    end
    end)

    UI.Destroying:Connect(function()
        Connection:Disconnect()
    end)
end

return EffectViewer


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

local LocalPlayer = Players.LocalPlayer

local Mouse = LocalPlayer:GetMouse()
local InputManager = require(LocalPlayer.PlayerScripts.InputManager)
local Utils = require(ReplicatedStorage.Modules.Utils)
local TopbarPlus = require(ReplicatedStorage.Packages.TopbarPlus)

local EmotePanel = {
    Open = false,
    TopbarButton = nil,
}

local UI
local UISize = UDim2.fromScale(0.3, 0.533)

function EmotePanel:Init()
    local ScreenGui = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "EmotePanel")

    UI = Utils.Instance.FindFirstChild(script, "EmotePanel")
    UI.Parent = ScreenGui

    local Selector = Utils.Instance.FindFirstChild(UI, "Selector")
    local EmoteButtonContainer = Utils.Instance.FindFirstChild(UI, "EmoteContainer")

    local SelectedEmote = ""

    local function CheckEmotePlay()
        if not EmotePanel.Open then
            return
        end
        EmotePanel._Close()

        local EmoteToPlay = EmoteButtonContainer[SelectedEmote]:GetAttribute("Name")
        if LocalPlayer.Character and EmoteToPlay and #EmoteToPlay > 0 then
            local E = Utils.Instance.FindFirstChild(LocalPlayer.Character, "Miscellaneous.EmoteManager", 0)
            if not E then
                return
            end
            local EmoteManager = require(E)
            if EmoteManager.CurrentlyPlayingEmote and EmoteManager.CurrentlyPlayingEmote.Name == EmoteToPlay then return end
            EmoteManager:PlayEmote(EmoteToPlay)
        end
    end

    local function ReloadContent()
        for _, EmoteSlot in EmoteButtonContainer:GetChildren() do
            local EquippedEmote = Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Emotes."..EmoteSlot.Name)
            local EmoteModule = EquippedEmote and #EquippedEmote > 0 and Utils.Instance.GetEmoteModule(EquippedEmote)
            if EmoteModule then
                EmoteSlot:SetAttribute("Name", EquippedEmote)
                EmoteModule = require(EmoteModule)
                EmoteSlot:SetAttribute("DisplayName", EmoteModule.Config.Name)

                EmoteSlot.Render.Image = EmoteModule.Config.Render
                EmoteSlot.NameLabel.Text = EmoteModule.Config.Name

                EmoteSlot.Render.ImageTransparency = 0
                EmoteSlot.NameLabel.TextTransparency = 0
            else
                EmoteSlot:SetAttribute("Name", "")
                EmoteSlot:SetAttribute("DisplayName", "")
                EmoteSlot.Render.ImageTransparency = 1
                EmoteSlot.NameLabel.TextTransparency = 1
            end
        end
    end

    ReloadContent()
    for _, Emote in Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Emotes", false):GetChildren() do
        Emote.Changed:Connect(ReloadContent)
    end

    RunService.PreRender:Connect(function()
        UI.Visible = UI.Size ~= Utils.UDim.Zero
        if not UI.Visible then
            return
        end

        local CenterOfScreen = ScreenGui.AbsoluteSize / 2
        local MousePosition = Vector2.new(Mouse.X, Mouse.Y) - CenterOfScreen

        local CurrentRotation = math.deg(math.atan2(MousePosition.Y, MousePosition.X)) + 180

        Selector.Rotation = CurrentRotation - 90

        SelectedEmote = "Emote"..tostring(math.clamp(math.floor((CurrentRotation) / 45) + 1, 1, 8))
        UI.Selected.Text = EmoteButtonContainer[SelectedEmote]:GetAttribute("DisplayName")
    end)

    UserInputService.InputBegan:Connect(function(Input: InputObject, GPE: boolean)
        if GPE then
            return
        end

        if InputManager.CurrentControlScheme == "Gamepad" then
            if Input.KeyCode == Enum.KeyCode.ButtonR2 then
                CheckEmotePlay()
            end
        elseif Input.UserInputType == Enum.UserInputType.MouseButton1 or Input.UserInputType == Enum.UserInputType.Touch then
            CheckEmotePlay()
        end
    end)

    InputManager:GetInputAction("Miscellaneous.EmotePanel").Pressed:Connect(function()
        EmotePanel.Toggle()
    end)

    local function CheckMobileButton(ControlScheme: "Keyboard" | "Gamepad" | "Touch")
        if ControlScheme == "Touch" and not EmotePanel.TopbarButton then
            EmotePanel.TopbarButton = TopbarPlus.new()
            EmotePanel.TopbarButton:setOrder(10)
            EmotePanel.TopbarButton:oneClick(true)
            EmotePanel.TopbarButton:setLabel("Emote")
            EmotePanel.TopbarButton:setTextFont(Enum.Font.Bodoni)
            EmotePanel.TopbarButton:bindEvent("deselected", function()
                if not LocalPlayer.Character then
                    EmotePanel.Toggle()
                    return
                end

                local E = Utils.Instance.FindFirstChild(LocalPlayer.Character, "Miscellaneous.EmoteManager", 0)
                if not E then
                    return
                end
                local EmoteManager = require(E)

                if EmoteManager.CurrentlyPlayingEmote then
                    EmoteManager:StopEmote(EmoteManager.CurrentlyPlayingEmote)
                else
                    EmotePanel.Toggle()
                end
            end)
        elseif EmotePanel.TopbarButton then
            EmotePanel.TopbarButton:Destroy()
        end
    end

    InputManager.SchemeChanged:Connect(CheckMobileButton)
    CheckMobileButton(InputManager.CurrentControlScheme)
end

function EmotePanel.Toggle()
    if not EmotePanel.Open then
        EmotePanel._Open()
    else
        EmotePanel._Close()
    end
end

local Info = TweenInfo.new(0.3, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out)

function EmotePanel._Open()
    TweenService:Create(UI, Info, {Size = UISize}):Play()
    EmotePanel.Open = true
end

function EmotePanel._Close()
    EmotePanel.Open = false
    TweenService:Create(UI, Info, {Size = Utils.UDim.Zero}):Play()
end

return EmotePanel


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

local Utils = require(ReplicatedStorage.Modules.Utils)
local KillerIntro = require(ReplicatedStorage.Classes.KillerIntro)

local IntroFolder = ReplicatedStorage.Assets.KillerIntros
local function GetIntroModule(name: string, skin: string?)
	local Intro

	if skin ~= nil and #skin > 0 then
		Intro = IntroFolder.Skins:FindFirstChild(name)
		if Intro then
			Intro = Intro:FindFirstChild(skin)
		end
	end

	if not Intro then
		Intro = IntroFolder:FindFirstChild(name)
	end

	return Intro
end

local IntroContainer = Utils.Instance.FindFirstChild(Players.LocalPlayer.PlayerGui, "KillerIntros")
local ToastPrefab = GetIntroModule("KillerNameToast")

return {
    Init = function(_)
        local KillerPlayerNames = {}
		local CurrentIntro

        local function SetupCharacter(Char: Model)
            if not workspace:GetAttribute("ClientLoaded") then
                return
            end

            local Role = Char:FindFirstChild("Role")
            if Role.Value ~= "Killer" then
                return
            end

            local KillerName = Char:GetAttribute("CharacterName")
            local KillerSkin = Char:GetAttribute("CharacterSkinName")

            --TODO: multi-killer toast support
            KillerPlayerNames[1] = Char.Name

		    if Utils.PlayerData.GetPlayerSetting(Players.LocalPlayer, "Miscellaneous.DisableKillerIntros") then
		    	task.spawn(require(ToastPrefab), IntroContainer, KillerPlayerNames)

		    	Utils.Player.Fade(Players.LocalPlayer, "Out", 0.6)
		    	return
		    end

		    local IntroInfo: KillerIntro.IntroSettings = {
		    	KillerName = KillerName,
		    	SkinName = KillerSkin,
		    	PlayerName = KillerPlayerNames[1],
		    }

            task.delay(7.5, function()
                table.clear(KillerPlayerNames)
            end)

		    local Intro2D = GetIntroModule(KillerName, KillerSkin)

		    local Intro
			if Intro2D then
				Intro = KillerIntro.New2DIntro(IntroInfo, Intro2D)
			else
				local Module = Utils.Instance.GetCharacterModule("Killer", KillerName, KillerSkin)
				if Module then
					Module = require(Module)
					if Module.Config.AnimationIDs["KillerRig"] and Module.Config.AnimationIDs["CameraRig"] then
						Intro = KillerIntro.New3DIntro(IntroInfo)
					end
				end
			end

			if not Intro then
		    	task.spawn(require(ToastPrefab), IntroContainer, KillerPlayerNames)

		    	Utils.Player.Fade(Players.LocalPlayer, "Out", 0.6)
				return
			end

			if CurrentIntro then
				CurrentIntro:Destroy()
				CurrentIntro = nil
			end
			CurrentIntro = Intro

		    Intro:Init()

		    task.spawn(require(ToastPrefab), IntroContainer, KillerPlayerNames)

		    Utils.Player.Fade(Players.LocalPlayer, "Out", 0.6)

		    Intro:Play()
		    task.wait(2.5)

		    Utils.Player.Fade(Players.LocalPlayer, "In", 0.6, true)
		    Intro:Destroy()
		    Utils.Player.Fade(Players.LocalPlayer, "Out", 0.6)
        end

		Utils.Player.ObservePlayers(function(plr: Player)
			Utils.Character.ObserveCharacter(plr, SetupCharacter)
		end)
    end,
}


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

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

local ButtonPrefab = Utils.Instance.FindFirstChild(script, "EmoteButton")

local EmoteEquipWindow = {}
EmoteEquipWindow.__index = EmoteEquipWindow

function EmoteEquipWindow.New(Slot: number, Parent: Instance)
    local Window = setmetatable({
        Instance = Utils.Instance.FindFirstChild(script, "EmoteEquipWindow"):Clone(),
        Connections = {},
        Equipped = Signal.new(),
        Removed = Signal.new(),
        Buttons = {},
    }, EmoteEquipWindow)
    Window.Instance.Parent = Parent
    local EmotesToInstantiate = {}
    for _, Emote in Utils.PlayerData.GetPlayerOwned(Players.LocalPlayer, "Emotes", false):GetChildren() do
        table.insert(EmotesToInstantiate, Emote.Name)
    end
    table.sort(EmotesToInstantiate, function(a, b)
        return a < b
    end)
    for _, Emote in EmotesToInstantiate do
        local EmoteInfo = require(Utils.Instance.GetEmoteModule(Emote))
        local Button = ButtonPrefab:Clone()
        Button.Name = Emote
        Button.Render.Image = EmoteInfo.Config.Render
        Button.NameLabel.Text = EmoteInfo.Config.Name
        table.insert(Window.Buttons, Button)
        Button.Parent = Window.Instance.Content
    end

    for _, Button in Window.Buttons do
        table.insert(Window.Connections, Button.MouseButton1Click:Connect(function()
            Window.Equipped:Fire(Network:FireServerConnection("EquipEmote", "REMOTE_FUNCTION", Slot, Button.Name))
            Window:Hide()
        end))
    end
    table.insert(Window.Connections, Window.Instance.Cancel.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end))
    table.insert(Window.Connections, Window.Instance.Cancel.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end))
    table.insert(Window.Connections, Window.Instance.Cancel.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
        Window:Hide()
    end))

    table.insert(Window.Connections, Window.Instance.Unequip.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end))
    table.insert(Window.Connections, Window.Instance.Unequip.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end))
    table.insert(Window.Connections, Window.Instance.Unequip.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
        Window.Equipped:Fire(Network:FireServerConnection("EquipEmote", "REMOTE_FUNCTION", Slot, ""))
        Window:Hide()
    end))
    Window:Show()
    return Window
end

function EmoteEquipWindow:Show()
    TweenService:Create(self.Instance, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.855, 0.962)}):Play()
end

function EmoteEquipWindow:Hide(instant: boolean?)
    if instant == nil then
        instant = false
    end
    self.Removed:Fire()
    self.Removed:DisconnectAll()
    self.Equipped:DisconnectAll()
    for _, connection in self.Connections do
        if typeof(connection) == "thread" then
            if coroutine.status(connection :: thread) ~= "running" then
                task.cancel(connection)
            end
        else
            connection:Disconnect()
        end
    end
    if not instant then
        TweenService:Create(self.Instance, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.Zero}):Play()
    else
        self.Instance.Size = Utils.UDim.Zero
    end
    Debris:AddItem(self.Instance, 0.5)
end

return EmoteEquipWindow


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

local EmoteEquipWindow = require(script.EmoteEquipWindow)
local SideBar = require(script.Parent.SideBar)
local Network = require(ReplicatedStorage.Modules.Network)
local Types = require(ReplicatedStorage.Classes.Types)
local Tooltip = require(ReplicatedStorage.Classes.Tooltip)
local Utils = require(ReplicatedStorage.Modules.Utils)
local Sounds = require(ReplicatedStorage.Modules.Sounds)
local Janitor = require(ReplicatedStorage.Packages.Janitor)

local LocalPlayer = Players.LocalPlayer

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

local Inventory = {
    Opened = false,
    Enabled = true,
    SelectedSection = "Survivors",
    SkinMenu = {
        Open = false,
        ShowingCharacter = "",
    },
    InfoMenu = {
        Open = false,
        ShowingCharacter = "",
    },
    LoadedItems = {
        Killers = {},
        Survivors = {},
        Skins = {},
    },
    ItemModules = {},
    SelectedItem = {
        Name = "",
        Type = "",
    },
    UIInstance = nil,
    EmoteWindow = nil,
}

local Sizes = {
    SideBar = UDim2.fromScale(0.3, 1),
    ClosedSideBar = Utils.UDim.FullY,
}

local UI
local InventoryContainer
local KillerContainer
local SurvivorContainer
local EmoteContainer

local SideContainer
local SkinContainer
local InfoContainer
local PreviewContainer
local ButtonsContainer

local Buttons = {
    Equip = nil,
    Info = nil,
    Skins = nil,
}

local Equipped = {
    EquippedKiller = nil,
    EquippedSurvivor = nil,
}

local PreviewInstances = {
    Render = nil,
    Title = nil,
}

local Templates = {
    CardTemplate = nil,
    Header = nil,
    Text = nil,
    Quote = nil,
    Separator = nil,
    InfoSection = nil,
}

local Sections = {}

function Inventory.ReorderSkins()
    local Ordered = {}
    local LO = 0

    for _, Skin in Inventory.LoadedItems.Skins do
        table.insert(Ordered, Skin)
    end

    table.sort(Ordered, function(a, b)
        local Modules = {
            A = require(Utils.Instance.GetCharacterModule(Inventory.SelectedItem.Type, Inventory.SelectedItem.Name, a.Name)),
            B = require(Utils.Instance.GetCharacterModule(Inventory.SelectedItem.Type, Inventory.SelectedItem.Name, b.Name)),
        }

        return
            Modules.A.Config.Price == Modules.B.Config.Price and a.Name < b.Name
            or Modules.A.Config.Price < Modules.B.Config.Price
    end)

    for _, Skin in Ordered do
        LO += 1
        Skin.LayoutOrder = LO
    end
end

function Inventory.CheckEquipLabels()
    local function RemoveEquippedLabel(card)
        if card.Container.Equipped.ImageTransparency ~= 1 then
            TweenService:Create(card.Container.Equipped, TweenInfo.new(0.2), {ImageTransparency = 1}):Play()
        end
    end

    local function ShowEquippedLabel(card)
        if card.Container.Equipped.ImageTransparency ~= 0 then
            TweenService:Create(card.Container.Equipped, TweenInfo.new(0.2), {ImageTransparency = 0}):Play()
        end
    end

    for name, Type in Inventory.LoadedItems do
        if name == "Skins" then
            continue
        end
        for _, Item in Type do
            if Item.Name == Equipped["Equipped"..name:sub(1, #name - 1)].Value then
                ShowEquippedLabel(Item)
            else
                RemoveEquippedLabel(Item)
            end
        end
    end
end

function Inventory.EquipItem()
    Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
    if not Inventory.Opened or Equipped["Equipped"..Inventory.SelectedItem.Type].Value == Inventory.SelectedItem.Name then
        return
    end

    local lastEquipped = Equipped["Equipped"..Inventory.SelectedItem.Type].Value
    Buttons.Equip.Title.Text = "Equipped"
    Network:FireServerConnection("EquipItem", "REMOTE_EVENT", Inventory.SelectedItem.Name, Inventory.SelectedItem.Type)
    if Equipped["Equipped"..Inventory.SelectedItem.Type].Value == lastEquipped then
        Equipped["Equipped"..Inventory.SelectedItem.Type].Changed:Wait()
    end
    Inventory.CheckEquipLabels()
end

local function SwitchSection(sectionName: string)
    if not Inventory.Opened or Inventory.SelectedSection == sectionName then return end

    TweenService:Create(SideContainer, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0, 1)}):Play()
    if Inventory.EmoteWindow then
        Inventory.EmoteWindow:Hide()
        Inventory.EmoteWindow = nil
    end

    for _, Section in Sections do
        Section.Visible = Section.Name == sectionName
    end

    Inventory.SelectedSection = sectionName
end

function Inventory.ReloadEquippedEmotes()
    for _, EmoteSlot in EmoteContainer.EmoteContainer:GetChildren() do
        local Value = Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Emotes."..EmoteSlot.Name)
        if #Value <= 0 then
            EmoteSlot.Render.Image = "rbxasset://textures/ui/GuiImagePlaceholder.png"
            EmoteSlot.NameLabel.Text = ""
            continue
        end

        local EmoteInfo = require(Utils.Instance.GetEmoteModule(Value))
        EmoteSlot.Render.Image = EmoteInfo.Config.Render
        EmoteSlot.NameLabel.Text = EmoteInfo.Config.Name
    end
end

function Inventory.ToggleSkinsMenu(Open: boolean?)
    if Open == nil then
        Open = not Inventory.SkinMenu.Open
    end

    if Open then
        Inventory.ToggleInfoMenu(false)
    end

    if Open then
        for _, Skin in Inventory.LoadedItems.Skins do
            Skin:Destroy()
        end
        table.clear(Inventory.LoadedItems.Skins)
        Inventory.ItemModules.Skins = Inventory.ItemModules.Skins or {}
        table.clear(Inventory.ItemModules.Skins)

        local charName = Inventory.SelectedItem.Name
        local EquippedSkin = Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Skins."..charName, false)

        local function CheckSkinEquipLabels()
            for _, Skin in Inventory.LoadedItems.Skins do
                if #EquippedSkin.Value < 0 or EquippedSkin.Value ~= Skin.Name then
                    if Skin.Container.Equipped.ImageTransparency ~= 1 then
                        TweenService:Create(Skin.Container.Equipped, TweenInfo.new(0.2), {ImageTransparency = 1}):Play()
                    end
                else
                    if Skin.Container.Equipped.ImageTransparency ~= 0 then
                        TweenService:Create(Skin.Container.Equipped, TweenInfo.new(0.2), {ImageTransparency = 0}):Play()
                    end
                end
            end
        end

        for _, Skin in (LocalPlayer.PlayerData.Purchased.Skins:FindFirstChild(Inventory.SelectedItem.Name) or Network:FireServerConnection("CreateMissingPurchasedSkinValue", "REMOTE_FUNCTION", Inventory.SelectedItem.Name)):GetChildren() do
            local Module = Utils.Instance.GetCharacterModule(Inventory.SelectedItem.Type, Inventory.SelectedItem.Name, Skin.Name)
            local Info = require(Module)
            local name = Module.Name

            local Card = Templates.CardTemplate:Clone()
            Card.Name = name
            local Container = Card.Container
            Card.Parent = SkinContainer.ScrollingFrame
            Container.Title.Text = Info.Config.Name
            Container.CharacterRender.Image = Info.Config.Render

            if Info.Config.CardFrame then
                Container.Outline.Image = Info.Config.CardFrame.Image or Container.Outline.Image
                Container.Outline.Size = Info.Config.CardFrame.Size or Container.Outline.Size
            end

            Inventory.LoadedItems.Skins[name] = Card
            Inventory.ItemModules.Skins[name] = Info

            Container.Interact.MouseEnter:Connect(function()
                TweenService:Create(Container, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
                    Size = UDim2.fromScale(1.065, 1.065)
                }):Play()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
            end)
            Container.Interact.MouseLeave:Connect(function()
                TweenService:Create(Container, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
                    Size = Utils.UDim.Full
                }):Play()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
            end)
            Container.Interact.MouseButton1Click:Connect(function()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})

                local valueBefore = EquippedSkin.Value
                if EquippedSkin.Value ~= name then
                    Network:FireServerConnection("EquipItem", "REMOTE_EVENT", name, "Skin", "Skins."..charName)
                else
                    Network:FireServerConnection("EquipItem", "REMOTE_EVENT", "", "Skin", "Skins."..charName)
                end

                if EquippedSkin.Value == valueBefore then
                    EquippedSkin.Changed:Wait()
                end

                CheckSkinEquipLabels()
            end)

            if Info.Config.Origin and Info.Config.Origin.Icon then
                Container.Origin.Visible = true
                Container.Origin.Image = Info.Config.Origin.Icon
                Container.Origin.MouseEnter:Connect(function()
                    Tooltip.New(Info.Config.Origin.TooltipText)
                    Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
                end)
                Container.Origin.MouseLeave:Connect(function()
                    Tooltip.New()
                    Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
                end)
            else
                Container.Origin.Visible = false
            end
        end

        CheckSkinEquipLabels()

        Inventory.ReorderSkins()

        TweenService:Create(InventoryContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.FullY}):Play()
        TweenService:Create(SkinContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.7, 1)}):Play()

        Inventory.SkinMenu.Open = true
    else
        TweenService:Create(InventoryContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.7, 1)}):Play()
        TweenService:Create(SkinContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.FullY}):Play()

        Inventory.SkinMenu.Open = false
    end

    TweenService:Create(InfoContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.FullY}):Play()
end

function Inventory.ToggleInfoMenu(Open: boolean?)
    if Open == nil then
        Open = not Inventory.InfoMenu.Open
    end

    if Open then
        Inventory.ToggleSkinsMenu(false)
    end

    if Open then
        for _, Child in InfoContainer:GetChildren() do
            if not Child:IsA("UIComponent") then
                Child:Destroy()
            end
        end

        local CharacterInfo = require(Utils.Instance.GetCharacterModule(Inventory.SelectedItem.Type, Inventory.SelectedItem.Name))

        if not CharacterInfo.Config.Description then
            return
        end

        Inventory.InfoMenu.Open = true

        local TextsToCheck = {}

        for SectionIndex: number, Section: {
            Type: "Text" | "Header" | "Separator" | "Quote",
            Text: string | {string},
            Image: string?,
        } in CharacterInfo.Config.Description do
            local SectionContainer = Templates.InfoSection:Clone()
            SectionContainer.LayoutOrder = SectionIndex
            SectionContainer.Parent = InfoContainer

            local Text = ""
            if typeof(Section.Text) == "table" then
                for index, paragraph in ipairs(Section.Text) do
                    if index > 1 then
                        Text = Text.."\n"
                    end
                    Text = Text..paragraph
                end
            else
                Text = Section.Text
            end

            local Pref = Templates[Section.Type]:Clone()
            if Section.Type ~= "Text" then
                (Section.Type == "Separator" and Pref.Title or Pref).Text = Section.Text
            else
                Pref.Title.Text = Text
                Pref.Title.MaxVisibleGraphemes = 0
                if Section.Image then
                    Pref.Image.Image = Section.Image
                    Pref.Image.Visible = true
                end
                table.insert(TextsToCheck, Pref)
                TweenService:Create(Pref.Title, TweenInfo.new(0.03 * #Text, Enum.EasingStyle.Linear), {MaxVisibleGraphemes = #Text}):Play()
            end
            Pref.Parent = SectionContainer
        end

        task.spawn(function()
            local PossibleEffects = {}

            local function AddEffectToList(Inst: ModuleScript | Folder)
                for _, Effect in Inst:GetChildren() do
                    if Effect:IsA("Folder") then
                        AddEffectToList(Effect)
                        continue
                    end

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

                    PossibleEffects[Effect.Name] = require(Effect).Description or "UNKNOWN"
                end
            end

            AddEffectToList(ReplicatedStorage.Effects)

            local LocalPlayer = Players.LocalPlayer
            local CurrentTooltip

            local function GetSpaceWidth(font, textSize, maxX)
                return TextService:GetTextSize(" ", textSize, font, Vector2.new(maxX, 1e5)).X
            end

            while task.wait() and Inventory.InfoMenu.Open do
                local ui = Inventory.UIInstance
                local char = LocalPlayer.Character
                local role = char and char:FindFirstChild("Role")

                if not (ui and ui.Visible and role and role.Value == "Spectator") then
                    if CurrentTooltip then
                        CurrentTooltip = Tooltip.New()
                    end
                    continue
                end

                local mousePos = UserInputService:GetMouseLocation() - GuiService:GetGuiInset()
                local foundEffect = false

                for _, textLabel in TextsToCheck do
                    if foundEffect then break end

                    local text = textLabel.Title
                    local absPos, absSize = text.AbsolutePosition, text.AbsoluteSize
                    local font, textSize = text.Font, text.TextSize
                    local maxWidth = absSize.X
                    local spaceWidth = GetSpaceWidth(font, textSize, maxWidth)

                    -- bound check
                    if not (mousePos.X >= absPos.X and mousePos.X <= absPos.X + absSize.X and
                            mousePos.Y >= absPos.Y and mousePos.Y <= absPos.Y + absSize.Y) then
                        continue
                    end

                    local lineHeight = TextService:GetTextSize("A", textSize, font, Vector2.new(maxWidth, 1e5)).Y
                    local totalY = 0

                    for _, rawLine in ipairs(text.Text:gsub("<.->", ""):split("\n")) do
                        local currentLine = {}
                        local currentWidth = 0

                        local wrappedLines = {}
                        for _, word in ipairs(rawLine:split(" ")) do
                            local wordSize = TextService:GetTextSize(word, textSize, font, Vector2.new(maxWidth, 1e5)).X
                            if currentWidth + wordSize > maxWidth then
                                table.insert(wrappedLines, currentLine)
                                currentLine = {}
                                currentWidth = 0
                            end
                            table.insert(currentLine, word)
                            currentWidth += wordSize + spaceWidth
                        end
                        if #currentLine > 0 then
                            table.insert(wrappedLines, currentLine)
                        end

                        for _, lineWords in ipairs(wrappedLines) do
                            if foundEffect then break end

                            local minY = absPos.Y + totalY
                            local maxY = minY + lineHeight
                            if mousePos.Y >= minY and mousePos.Y <= maxY then
                                local lineX = absPos.X

                                for _, word in ipairs(lineWords) do
                                    local wSize = TextService:GetTextSize(word, textSize, font, Vector2.new(maxWidth, 1e5)).X
                                    local minX, maxX = lineX, lineX + wSize

                                    if mousePos.X >= minX and mousePos.X <= maxX then
                                        local desc = PossibleEffects[word]
                                        if desc then
                                            CurrentTooltip = Tooltip.New(desc)
                                            foundEffect = true
                                            break
                                        end
                                    end
                                    lineX += wSize + spaceWidth
                                end
                            end
                            totalY += lineHeight
                        end
                    end
                end

                if not foundEffect then
                    CurrentTooltip = Tooltip.New()
                end
            end
        end)

        TweenService:Create(InventoryContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.FullY}):Play()
        TweenService:Create(InfoContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.7, 1)}):Play()
    else
        TweenService:Create(InventoryContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.7, 1)}):Play()
        TweenService:Create(InfoContainer.Parent, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Utils.UDim.FullY}):Play()

        Inventory.InfoMenu.Open = false
    end
end

function Inventory:Init()
    UI = Utils.Instance.FindFirstChild(script, "InventoryUI"):Clone()
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    UI.Parent = UIParent
    Inventory.UIInstance = UI

    local Container = UI.Container
    InventoryContainer = Container.InventoryContainer.Content
    KillerContainer = InventoryContainer.Killers
    SurvivorContainer = InventoryContainer.Survivors
    SkinContainer = Container.SkinsContainer.Content
    EmoteContainer = InventoryContainer.Emotes
    InfoContainer = Container.InfoContainer.Contents

    Sections = {
        KillerContainer,
        SurvivorContainer,
        EmoteContainer,
    }

    for _, button in Container.InventoryContainer.Topbar:GetChildren() do
        if button:IsA("ImageButton") and button.Visible then
            button.MouseEnter:Connect(function()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
            end)
            button.MouseLeave:Connect(function()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
            end)
            button.MouseButton1Click:Connect(function()
                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
                SwitchSection(button.Name)
            end)
        end
    end

    local templates = Utils.Instance.FindFirstChild(script, "Templates")
    Templates.CardTemplate = Templates.CardTemplate or templates.CardTemplate
    Templates.Header = Templates.Header or templates.Header
    Templates.Text = Templates.Text or templates.TextContainer
    Templates.Quote = Templates.Quote or templates.Quote
    Templates.Separator = Templates.Separator or templates.Separator
    Templates.InfoSection = Templates.InfoSection or templates.InfoSection

    SideContainer = Container.SideContainer
    PreviewContainer = SideContainer.PreviewContainer

    PreviewInstances.Render = PreviewContainer.Render
    PreviewInstances.Render.Image = ""
    PreviewInstances.Title = PreviewContainer.Title

    ButtonsContainer = SideContainer.ButtonsContainer

    Buttons.Equip = ButtonsContainer.Equip
    Buttons.Skins = ButtonsContainer.Skins
    Buttons.Info = ButtonsContainer.MoreInfo

    Equipped.EquippedKiller = Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Killer", false)
    Equipped.EquippedSurvivor = Utils.PlayerData.GetPlayerEquipped(LocalPlayer, "Survivor", false)

    local JanitorInstance = Janitor.new()
    JanitorInstance:LinkToInstance(UI)

    local BoughtItems: Folder = LocalPlayer.PlayerData.Purchased
    JanitorInstance:Add(BoughtItems.DescendantAdded:Connect(function(_descendant: Instance)
        Inventory.ReloadContent()
    end))
    JanitorInstance:Add(BoughtItems.DescendantRemoving:Connect(function(_descendant: Instance)
        Inventory.ReloadContent()
    end))

    JanitorInstance:Add(Buttons.Equip.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Equip.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Equip.MouseButton1Click:Connect(function()
        Inventory.EquipItem()
    end))

    JanitorInstance:Add(Buttons.Skins.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Skins.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Skins.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})

        TweenService:Create(SideContainer, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Sizes.SideBar}):Play() --assuring the sidebar is open

        Inventory.ToggleSkinsMenu()
    end))

    JanitorInstance:Add(Buttons.Info.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Info.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end))
    JanitorInstance:Add(Buttons.Info.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})

        TweenService:Create(SideContainer, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Sizes.SideBar}):Play() --assuring the sidebar is open

        Inventory.ToggleInfoMenu()
    end))

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

    Utils.Instance.ObserveProperty(InfoContainer.Parent, "Size", function(value: UDim2)
        if not Inventory.InfoMenu.Open and value.X.Scale < 0.05 and #InfoContainer:GetChildren() > 0 then
            for _, Child in InfoContainer:GetChildren() do
                if not Child:IsA("UIComponent") then
                    Child:Destroy()
                end
            end
        end
    end)

    for _, EmoteSlot: Frame in EmoteContainer.EmoteContainer:GetChildren() do
        JanitorInstance:Add(EmoteSlot.Button.MouseEnter:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
        end))
        JanitorInstance:Add(EmoteSlot.Button.MouseLeave:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
        end))
        JanitorInstance:Add(EmoteSlot.Button.MouseButton1Click:Connect(function()
            Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})

            if Inventory.EmoteWindow then
                return
            end

            local Slot = EmoteSlot.Name:gsub("Emote", "")
            Inventory.EmoteWindow = EmoteEquipWindow.New(tonumber(Slot), InventoryContainer)
            Inventory.EmoteWindow.Equipped:Connect(function()
                Inventory.ReloadEquippedEmotes()
            end)
            Inventory.EmoteWindow.Removed:Connect(function()
                Inventory.ReloadEquippedEmotes()
                Inventory.EmoteWindow = nil
            end)
        end))
    end

    Inventory.ReloadEquippedEmotes()
    Inventory.ReloadContent()
end

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

    Inventory.ToggleSkinsMenu(false)
    Inventory.ToggleInfoMenu(false)
    PreviewInstances.Render.Image = ""
    SkinContainer.Parent.Size = Utils.UDim.FullY
    SideContainer.Size = Utils.UDim.FullY
    InfoContainer.Parent.Size = Utils.UDim.FullY
    InventoryContainer.Parent.Size = UDim2.fromScale(0.7, 1)
    Inventory.SkinMenu.Open = false
    if Inventory.EmoteWindow then
        Inventory.EmoteWindow:Hide(true)
        Inventory.EmoteWindow = nil
    end

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

    return
end

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

function Inventory.Toggle(toggle: boolean)
    if not Inventory.Enabled and toggle then
        Inventory.Init()
    end
    if not toggle and Inventory.Enabled then
        table.clear(Inventory.ItemModules)
        Inventory.LoadedItems = {
            Killers = {},
            Survivors = {},
            Skins = {},
        }
        Inventory.SelectedItem = {
            Name = "",
            Type = "",
        }
        Inventory.SelectedSection = "Survivors"
        Inventory.UIInstance:Destroy()
        Inventory.SkinMenu = {
            Open = false,
        }
        if Inventory.EmoteWindow then
            Inventory.EmoteWindow:Hide(true)
            Inventory.EmoteWindow = nil
        end
    end
    Inventory.Enabled = toggle
end

function Inventory.OnCharButtonPress(codeName: string, info: Types.Killer | Types.Survivor, Type: string)
    if not Inventory.Opened then return end

    PreviewInstances.Render.Image = info.Config.Render
    PreviewInstances.Title.Text = info.Config.Name

    if Equipped["Equipped"..Type].Value == codeName then
        Buttons.Equip.Title.Text = "Equipped"
    else
        Buttons.Equip.Title.Text = "Equip"
    end

    Inventory.SelectedItem.Name = codeName
    Inventory.SelectedItem.Type = Type

    TweenService:Create(SideContainer, TweenInfo.new(0.5, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = Sizes.SideBar}):Play()
end

function Inventory.ReloadContent()
    local BoughtItems = LocalPlayer.PlayerData.Purchased

    --Killer frame reloading

    local Ordered = {}
    local LO = 0

    local function SetupCard(module, Type: "Killer" | "Survivor")
        local name = module.Name

        if Inventory.LoadedItems[Type.."s"][name] then
            Inventory.LoadedItems[Type.."s"][name].LayoutOrder = LO
            return
        end

        local Module = Utils.Instance.GetCharacterModule(Type, name)
        if not Module then
            warn(`[Inventory.ReloadContent()]: Couldn't display card for {Type:lower()} "{name}", as their module doesn't exist! Ignoring for now...`)
            return
        end

        local Info = require(Module)

		local Card = Templates.CardTemplate:Clone()
		local Container = Card.Container
		Card.Name = name
		Card.LayoutOrder = LO
		Card.Parent = Type == "Killer" and KillerContainer or SurvivorContainer
		Container.Title.Text = Info.Config.Name
		Container.CharacterRender.Image = Info.Config.Render

        if Info.Config.CardFrame then
            Container.Outline.Image = Info.Config.CardFrame.Image or Container.Outline.Image
            Container.Outline.Size = Info.Config.CardFrame.Size or Container.Outline.Size
        end

        Inventory.ItemModules[Type.."s"] = Inventory.ItemModules[Type.."s"] or {}

		Inventory.LoadedItems[Type.."s"][name] = Card
		Inventory.ItemModules[Type.."s"][name] = Info

		Container.Interact.MouseEnter:Connect(function()
            TweenService:Create(Container, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
                Size = UDim2.fromScale(1.065, 1.065)
            }):Play()
			Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, { Volume = 0.2 })
		end)

		Container.Interact.MouseLeave:Connect(function()
            TweenService:Create(Container, TweenInfo.new(0.25, Enum.EasingStyle.Sine, Enum.EasingDirection.Out), {
                Size = Utils.UDim.Full
            }):Play()
			Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, { Volume = 0.2 })
		end)

        Container.Interact.MouseButton1Click:Connect(function()
			if Inventory.SkinMenu.Open then
				return
			end
			Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, { Volume = 0.8 })
			Inventory.OnCharButtonPress(name, Info, Type)
        end)

        if Info.Config.Origin and Info.Config.Origin.Icon then
            Container.Origin.Visible = true
            Container.Origin.Image = Info.Config.Origin.Icon

            local TooltipAvailable = Info.Config.Origin.TooltipText ~= nil

            Container.Origin.MouseEnter:Connect(function()
                if TooltipAvailable then
                    Tooltip.New(Info.Config.Origin.TooltipText)
                end

                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
            end)
            Container.Origin.MouseLeave:Connect(function()
                if TooltipAvailable then
                    Tooltip.New()
                end

                Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
            end)
        else
            Container.Origin.Visible = false
        end
    end

    local function SortTableByPriceAndName(TableToSort)
        table.sort(TableToSort, function(a, b)
            local Modules = {
                A = require(a),
                B = require(b),
            }

            return
                Modules.A.Config.Price == Modules.B.Config.Price and a.Name < b.Name
                or Modules.A.Config.Price < Modules.B.Config.Price
        end)
    end

    local function SetupBatch(Type: "Killer" | "Survivor")
        Ordered = {}
        LO = 0

        for _, module in BoughtItems[Type.."s"]:GetChildren() do
            table.insert(Ordered, Utils.Instance.GetCharacterModule(Type, module.Name))
        end

        SortTableByPriceAndName(Ordered)

        for _, module in ipairs(Ordered) do
            SetupCard(module, Type)
        end
    end

    SetupBatch("Survivor")
    SetupBatch("Killer")

    Inventory.CheckEquipLabels()
end

return Inventory


local ReplicatedStorage = game:GetService("ReplicatedStorage")

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

local BoolCommand = {}

function BoolCommand.New(CommandModule: ModuleScript, UI): ImageLabel
    local Config = require(CommandModule)

    local CommandFrame = script:FindFirstChildWhichIsA("ImageLabel"):Clone()
    CommandFrame.Parent = UI.Content

    CommandFrame.Label.Text = Config.Name
    CommandFrame.Value.Text = "Current value: "..tostring(workspace:GetAttribute(Config.DisplayedAttribute))
    CommandFrame.Execute.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end)
    CommandFrame.Execute.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end)
    CommandFrame.Execute.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
        local Value = Network:FireServerConnection("ExecuteCommand", "REMOTE_FUNCTION", CommandModule.Name)
        CommandFrame.Value.Text = "Current value: "..tostring(Value or workspace:GetAttribute(Config.DisplayedAttribute))
    end)

    return CommandFrame
end

return BoolCommand


local ReplicatedStorage = game:GetService("ReplicatedStorage")

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

local GlobalPrefab = Utils.Instance.FindFirstChild(script, "Enum", 0)
local ValuePrefab = Utils.Instance.FindFirstChild(script, "EnumValue", 0)
-- local OptionPrefab = Utils.Instance.FindFirstChild(script, "EnumOption", 0)

local Dropdown = {}

function Dropdown.New(CommandModule: ModuleScript, UI): ImageLabel
    local Config = require(CommandModule)

    local CommandFrame = GlobalPrefab:Clone()
    CommandFrame.Parent = UI.Content

    CommandFrame.Label.Text = Config.Name
    CommandFrame.Value.Text = "Current value: "..tostring(workspace:GetAttribute(Config.DisplayedAttribute))

    for lo, param in Config.Params do
        local Value = ValuePrefab:Clone()
        Value.Parent = CommandFrame.Values

    end

    CommandFrame.Execute.MouseEnter:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHover, {Volume = 0.2})
    end)
    CommandFrame.Execute.MouseLeave:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonHoverStop, {Volume = 0.2})
    end)
    CommandFrame.Execute.MouseButton1Click:Connect(function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
        local Value = Network:FireServerConnection("ExecuteCommand", "REMOTE_FUNCTION", CommandModule.Name)
        CommandFrame.Value.Text = "Current value: "..tostring(Value or workspace:GetAttribute(Config.DisplayedAttribute))
    end)

    return CommandFrame
end

return Dropdown


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

local SideBar = require(script.Parent.SideBar)
local Network = require(ReplicatedStorage.Modules.Network)
local Sounds = require(ReplicatedStorage.Modules.Sounds)
local Utils = require(ReplicatedStorage.Modules.Utils)
local TopbarPlus = require(ReplicatedStorage.Packages.TopbarPlus)

local LocalPlayer = Players.LocalPlayer

local ModPanel = {
    Rank = 9999,
    Enabled = true,
    CloseOnOtherOpen = true,
    CloseOthersOnOpen = true,
    UIInstance = nil,
    Opened = false,
    Initted = false,
}

local CommandOrder = {
    "ToggleFreezeTime",
    "ToggleAbilityCooldowns",
    "ToggleAbilityCharges",
}

local Prefabs = {
    Bool = require(Utils.Instance.FindFirstChild(script, "Bool")),
}

function ModPanel:Init()
    if ModPanel.Initted then
        return
    end

    local HasRank, Rank = Network:FireServerConnection("HasPermRank", "REMOTE_FUNCTION", "ServerOwner")
    if not HasRank then
        return
    end

    ModPanel.Rank = Rank

    ModPanel.Start()
    ModPanel.Initted = true
end

function ModPanel.Start()
    local UI = Utils.Instance.FindFirstChild(script, "ModPanel")
    UI.Parent = Utils.Instance.FindFirstChild(LocalPlayer.PlayerGui, "Menus")
    UI.Size = Utils.UDim.Zero
    UI.Visible = false
    ModPanel.UIInstance = UI

    local TopbarButton = TopbarPlus.new()
    TopbarButton:setOrder(0)
    TopbarButton:oneClick(true)
    TopbarButton:setLabel("Command Panel")
    TopbarButton:setTextFont(Enum.Font.Bodoni)
    TopbarButton:bindEvent("deselected", function()
        Sounds.PlaySound(Sounds.CommonlyUsedSounds.ButtonPress, {Volume = 0.8})
        if ModPanel.Opened then
            ModPanel.Close(SideBar.ToggleDuration)
        else
            ModPanel.Open(SideBar.ToggleDuration)
        end
    end)

    SideBar.MenuModules["ModPanel"] = ModPanel

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

    local LO = 0
    local function SetupCmd(Cmd)
        if not Cmd or not Cmd:IsA("ModuleScript") then
            return
        end

        local Command = require(Cmd)
        Command.RankRequired = Command.RankRequired or Utils.Ranks.ServerOwner

        if ModPanel.Rank > Command.RankRequired or not Command.Type or not Prefabs[Command.Type] then
            return
        end

        LO += 1

        local Content = Prefabs[Command.Type].New(Cmd, UI)
        Content.LayoutOrder = LO
    end

    for _, Command in CommandOrder do
        SetupCmd(ReplicatedStorage.Security.ModeratorCommands:FindFirstChild(Command))
    end

    for _, Command in ReplicatedStorage.Security.ModeratorCommands:GetChildren() do
        if table.find(CommandOrder, Command.Name) then
            continue
        end

        SetupCmd(Command)
    end
end

function ModPanel.Open(Time: number)
    if not ModPanel.Enabled then
        return "Prevented"
    end

    SideBar.CloseAll(ModPanel)

    if Time > 0 then
        TweenService:Create(ModPanel.UIInstance, TweenInfo.new(Time, Enum.EasingStyle.Cubic, Enum.EasingDirection.Out), {Size = UDim2.fromScale(0.542, 0.741)}):Play()
    else
        ModPanel.UIInstance.Size = UDim2.fromScale(0.4, 0.7)
    end
    ModPanel.Opened = true

    return
end

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

function ModPanel:Toggle()
end

return ModPanel


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

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

local MoneyXPRewards = {}

function MoneyXPRewards:Init()
    local UI = Utils.Instance.FindFirstChild(Players.LocalPlayer.PlayerGui, "MoneyXPRewards")
    local Container = Utils.Instance.FindFirstChild(script, "MoneyXPRewardsContainer")
    Container.Parent = UI
    local Prefab = Utils.Instance.FindFirstChild(script, "Reward")

    Network:SetConnection("GrantReward", "REMOTE_EVENT", function(Money: number?, EXP: number?, Reason: string?)
        if not Utils.PlayerData.GetPlayerSetting(Players.LocalPlayer, "Miscellaneous.ShowRewardNotifications") then
            return
        end

        local Notification = Prefab:Clone()
        local Size = Notification.Size
        Notification.Size = Utils.UDim.Zero

        local Text = "Granted "
        if Money and EXP then
            Text = Text..tostring(Money).."$ and "..tostring(EXP).." EXP"
        elseif Money then
            Text = Text..tostring(Money).."$"
        elseif EXP then
            Text = Text..tostring(EXP).." EXP"
        else
            Notification:Destroy()
            return
        end

        if Reason then
            Text = Text.." for "..Reason
        end
        Text = Text.."."

        Notification.Text = Text

        Notification.Parent = Container

        TweenService:Create(Notification, TweenInfo.new(0.4), {Size = Size}):Play()
        task.wait(5.4)
        TweenService:Create(Notification, TweenInfo.new(0.4), {Size = Utils.UDim.Zero}):Play()
        Debris:AddItem(Notification, 1)
    end)
end

return MoneyXPRewards