local a = {}
a.__index = a
local typeof = typeof
local assert = assert
function a:CreatePlugin(b, c)
	local self = setmetatable({}, a)
	self.PluginName = b
	self.PluginDescription = c
	self.Commands = {}
	return self
end
function a:AddCommand(d, e, c, f, g)
	assert(typeof(d) == "string", "CommandName must be a string.")
	assert(typeof(e) == "string", "List must be a string.")
	assert(typeof(c) == "string", "Description must be a string.")
	assert(typeof(g) == "function", "Func must be a function.")
	assert(typeof(f) == "table", "Alias must be a table.")
	self.Commands[d] = {
		ListName = e,
		Description = c,
		Aliases = f,
		Function = function(h, i)
			g(h, i)
		end,
	}
end

local flash = a:CreatePlugin("Flashlight", "LOL GIVES U A FLASHLIGHT")
flash:AddCommand("flashlight", "flashlight", "gives you a- what? a flashlight?!", { "fl" }, function(Args, Speaker)
	Brightness1 = 1
	Brightness2 = 0.75
	Range1 = 32
	Range2 = 60

	Players = game:GetService("Players")
	LocalPlayer = Players.LocalPlayer
	Backpack = LocalPlayer.Backpack
	StarterGui = game:GetService("StarterGui")

	local Flashlight = Instance.new("Tool", Backpack)
	Flashlight.GripPos = Vector3.new(0.1, -0.4, 0)
	Flashlight.Name = "Flashlight"
	Flashlight.TextureId = "http://www.roblox.com/asset/?id=115955232"

	local Handle = Instance.new("Part", Flashlight)
	Handle.BrickColor = BrickColor.new("Bright yellow")
	Handle.Color = Color3.fromRGB(245, 205, 48)
	Handle.Name = "Handle"
	Handle.Locked = true
	Handle.Size = Vector3.new(0.5, 0.5, 2)
	Handle.CanCollide = true

	local LightPart = Instance.new("Part", Flashlight)
	LightPart.BrickColor = BrickColor.new("Mid gray")
	LightPart.Color = Color3.fromRGB(205, 205, 205)
	LightPart.Transparency = 1
	LightPart.Name = "LightPart"
	LightPart.CanCollide = false
	LightPart.Locked = true
	LightPart.Size = Vector3.new(0.2, 0.2, 0.2)

	local Motor = Instance.new("Motor", Flashlight)
	Motor.Part0 = Handle
	Motor.Part1 = LightPart

	local Sound = Instance.new("Sound", Handle)
	Sound.SoundId = "http://www.roblox.com/asset/?id=115959318"
	Sound.Volume = 1

	local Mesh = Instance.new("SpecialMesh", Handle)
	Mesh.Name = "Mesh"
	Mesh.MeshId = "http://www.roblox.com/asset/?id=115955313"
	Mesh.MeshType = "FileMesh"
	Mesh.Scale = Vector3.new(0.7, 0.7, 0.7)
	Mesh.TextureId = "http://www.roblox.com/asset?id=115955343"

	local SpotLight = Instance.new("SpotLight", LightPart)
	SpotLight.Name = "SpotLight"
	SpotLight.Angle = 70
	SpotLight.Brightness = Brightness1
	SpotLight.Color = Color3.fromRGB(244, 255, 233)
	SpotLight.Enabled = false
	SpotLight.Face = "Front"
	SpotLight.Range = Range1

	local SpotLight2 = Instance.new("SpotLight", LightPart)
	SpotLight2.Name = "SpotLight2"
	SpotLight2.Angle = 70
	SpotLight2.Brightness = Brightness2
	SpotLight2.Color = Color3.fromRGB(244, 255, 233)
	SpotLight2.Enabled = false
	SpotLight2.Range = Range2

	sp = Flashlight

	cooldown = 0.35

	lightontexture = 115984370
	lightofftexture = 115955343

	local weld = sp:WaitForChild("Motor")
	local lp = sp:WaitForChild("LightPart")
	local handle = sp:WaitForChild("Handle")
	local spotlight = lp:WaitForChild("SpotLight")
	local spotlight2 = lp:WaitForChild("SpotLight2")
	local mesh = handle:WaitForChild("Mesh")
	local sound = handle:WaitForChild("Sound")
	lastclick = 0

	sp.Equipped:connect(function(mouse)
		equipped = true
		if mouse ~= nil then
			themouse = mouse
			mouse.Button1Down:connect(function()
				if lastclick + cooldown < tick() then
					spotlight.Enabled = not spotlight.Enabled
					spotlight2.Enabled = spotlight.Enabled
					mesh.TextureId = "http://www.roblox.com/asset?id="
						.. tostring((spotlight.Enabled and lightontexture) or lightofftexture)
					sound:Play()
					lastclick = tick()
				end
			end)
		end
		if weld ~= nil then
			weld.Parent = sp
		end
		while equipped and sp.Parent ~= nil do
			local head = sp.Parent:FindFirstChild("Head")
			if spotlight.Enabled and themouse and handle and weld and head then
				local vec = themouse.Hit.p - head.Position
				local pos = (handle.CFrame * CFrame.new(0, 0, -1)).p
				local cf = CFrame.new(pos, pos + vec)
				--lp.CFrame=cf
				weld.C0 = handle.CFrame:toObjectSpace(cf)
			end
			wait()
		end
	end)

	sp.Unequipped:connect(function()
		if weld ~= nil then
			weld.Parent = sp
		end
		equipped = false
	end)

	local Flashlight2 = Flashlight:clone()
	Flashlight2.Parent = StarterGui
end)

return flash


return {
	["PluginName"] = "Client sided gear",
	["PluginDescription"] = "Made by NoobSploit#0001",
	["Commands"] = {
		["gear"] = {
			["ListName"] = "gear [id]",
			["Description"] = "This is client sided and will probably not work",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				local id = getstring(1)
				local a = game:GetObjects("rbxassetid://" .. id)[1]
				a.Parent = speaker.Backpack
			end,
		},
	},
}


return {
	["PluginName"] = "Anime Hit",
	["PluginDescription"] = "Hit animation",
	["Commands"] = {
		["hit"] = {
			["ListName"] = "hit [1-3]",
			["Description"] = "Hit animation",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				for i, track in pairs(game.Players.LocalPlayer.Character.Humanoid:GetPlayingAnimationTracks()) do
					track:Stop()
				end
				if args[1] == "1" then
					hitAnim = Instance.new("Animation")
					hitAnim.AnimationId = "rbxassetid://3334832150"
					hit = speaker.Character:FindFirstChildOfClass("Humanoid"):LoadAnimation(hitAnim)
					hit:Play(0.1, 1, 2)
					wait(0.5)
					hit:AdjustSpeed(0)
					wait(0.3)
					execCmd("refreshanim")
				elseif args[1] == "2" then
					hitAnim = Instance.new("Animation")
					hitAnim.AnimationId = "rbxassetid://3334832150"
					hit = speaker.Character:FindFirstChildOfClass("Humanoid"):LoadAnimation(hitAnim)
					hit:Play(0.1, 1, 10)
					wait(0.1)
					hit:AdjustSpeed(0)
					wait(0.3)
					execCmd("refreshanim")
				elseif args[1] == "3" then
					hit2Anim = Instance.new("Animation")
					hit2Anim.AnimationId = "rbxassetid://3334968680"
					hit2 = speaker.Character:FindFirstChildOfClass("Humanoid"):LoadAnimation(hit2Anim)
					hit2:Play(0.1, 1, 12)
					wait(0.1)
					hit2:AdjustSpeed(1)
					wait(0.4)
					hit2:AdjustSpeed(0)
					wait(0.3)
					execCmd("refreshanim")
				end
			end,
		},
	},
}


local Plugin = {
	["PluginName"] = "G.I.P.",
	["PluginDescription"] = "Generates random IPv4 Address",
	["Commands"] = {
		["getip"] = {
			["ListName"] = "GetIP [plr] / GIP [plr]",
			["Description"] = "Generates IP",
			["Aliases"] = { "gip" },
			["Function"] = function(args, speaker)
				for i, plr in pairs(getPlayer(args[1], speaker)) do
					game:GetService("ReplicatedStorage").DefaultChatSystemChatEvents.SayMessageRequest:FireServer(
						game:GetService("Players")[plr].Name
							.. "'s Ip is "
							.. math.random(1, 255)
							.. "#"
							.. math.random(1, 255)
							.. "#"
							.. math.random(1, 255)
							.. "#"
							.. math.random(1, 255),
						"All"
					)
				end
			end,
		},
	},
}

return Plugin


local Plugin = {
	["PluginName"] = "Random place generator",
	["PluginDescription"] = "join random place",
	["Commands"] = {
		["joinplace"] = {
			["ListName"] = "joinplace [min] [max]",
			["Description"] = "join random place ([min] [max] is placeid)",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				local minimum = args[1]
				local maximum = args[2]
				notify("Joining random place...")
				wait(1.5)

				local ran = math.random(minimum, maximum)
				notify("Joining " .. ran)
				game:GetService("TeleportService"):Teleport(ran)
				wait(2)
				notify("Failed to join: " .. ran)
				wait(2)
			end,
		},
	},
}

return Plugin


local Plugin = {
	["PluginName"] = "Loop Lighting",
	["PluginDescription"] = "Adds loops for lighting commands.",
	["Commands"] = {
		["loopday"] = {
			["ListName"] = "loopday / lday / ld",
			["Description"] = "Sunshiiiine!",
			["Aliases"] = { "lday", "ld" },
			["Function"] = function(args, speaker)
				if dayLoop then
					dayLoop:Disconnect()
				end
				local function dayFunc()
					game:GetService("Lighting").ClockTime = 14
				end

				dayLoop = game:GetService("RunService").RenderStepped:Connect(dayFunc)
			end,
		},
		["unloopday"] = {
			["ListName"] = "unloopday / unlday / unld",
			["Description"] = "No more sunshine",
			["Aliases"] = { "unlday", "unld" },
			["Function"] = function(args, speaker)
				if dayLoop then
					dayLoop:Disconnect()
				end
			end,
		},
		["loopnight"] = {
			["ListName"] = "loopnight / lnight / ln",
			["Description"] = "Moonlight.",
			["Aliases"] = { "lnight", "ln" },
			["Function"] = function(args, speaker)
				if nightLoop then
					nightLoop:Disconnect()
				end
				local function nightFunc()
					game:GetService("Lighting").ClockTime = 0
				end

				nightLoop = game:GetService("RunService").RenderStepped:Connect(nightFunc)
			end,
		},
		["unloopnight"] = {
			["ListName"] = "unloopnight / unlnight/ unln",
			["Description"] = "No more moonlight.",
			["Aliases"] = { "unlnight", "unln" },
			["Function"] = function(args, speaker)
				if nightLoop then
					nightLoop:Disconnect()
				end
			end,
		},
		["loopnofog"] = {
			["ListName"] = "loopnofog / lnofog / lnf / loopnf",
			["Description"] = "See clearly forever!.",
			["Aliases"] = { "lnofog", "lnf", "loopnf" },
			["Function"] = function(args, speaker)
				if fogLoop then
					fogLoop:Disconnect()
				end
				local function fogFunc()
					game:GetService("Lighting").FogEnd = math.huge
				end

				fogLoop = game:GetService("RunService").RenderStepped:Connect(fogFunc)
			end,
		},
		["unloopnofog"] = {
			["ListName"] = "unloopnofog / unlnofog / unlnf / unloopnf",
			["Description"] = "No more sight.",
			["Aliases"] = { "unlnofog", "unlnf", "unloopnf" },
			["Function"] = function(args, speaker)
				if fogLoop then
					fogLoop:Disconnect()
				end
			end,
		},
	},
}

return Plugin


local Plugin = {
	["PluginName"] = "Marble",
	["PluginDescription"] = "Turn into a marble lmao",
	["Commands"] = {
		["marble"] = {
			["ListName"] = "marble [size]",
			["Description"] = "Turns you into a marble",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				local UserInputService = game:GetService("UserInputService")
				local RunService = game:GetService("RunService")
				local Camera = workspace.CurrentCamera

				local SPEED_MULTIPLIER = 30
				local JUMP_POWER = 60
				local JUMP_GAP = 0.3

				local character = game.Players.LocalPlayer.Character

				for i, v in ipairs(character:GetDescendants()) do
					if v:IsA("BasePart") then
						v.CanCollide = false
					end
				end

				local ball = character.HumanoidRootPart
				ball.Shape = Enum.PartType.Ball
				local size = tonumber(args[1])
				ball.Size = Vector3.new(size, size, size)
				local humanoid = character:WaitForChild("Humanoid")
				local params = RaycastParams.new()
				params.FilterType = Enum.RaycastFilterType.Blacklist
				params.FilterDescendantsInstances = { character }

				local tc = RunService.RenderStepped:Connect(function(delta)
					ball.CanCollide = true
					humanoid.PlatformStand = true
					if UserInputService:GetFocusedTextBox() then
						return
					end
					if UserInputService:IsKeyDown("W") then
						ball.RotVelocity -= Camera.CFrame.RightVector * delta * SPEED_MULTIPLIER
					end
					if UserInputService:IsKeyDown("A") then
						ball.RotVelocity -= Camera.CFrame.LookVector * delta * SPEED_MULTIPLIER
					end
					if UserInputService:IsKeyDown("S") then
						ball.RotVelocity += Camera.CFrame.RightVector * delta * SPEED_MULTIPLIER
					end
					if UserInputService:IsKeyDown("D") then
						ball.RotVelocity += Camera.CFrame.LookVector * delta * SPEED_MULTIPLIER
					end
					--ball.RotVelocity = ball.RotVelocity - Vector3.new(0,ball.RotVelocity.Y/50,0)
				end)

				UserInputService.JumpRequest:Connect(function()
					local result =
						workspace:Raycast(ball.Position, Vector3.new(0, -((ball.Size.Y / 2) + JUMP_GAP), 0), params)
					if result then
						ball.Velocity = ball.Velocity + Vector3.new(0, JUMP_POWER, 0)
					end
				end)

				Camera.CameraSubject = ball
				humanoid.Died:Connect(function()
					tc:Disconnect()
				end)
			end,
		},
	},
}

return Plugin


local Plugin = {}

Plugin.PluginName = "More Sex"
Plugin.PluginDescription = "made by prisj"
Plugin.Commands = {}

local Players = game:GetService("Players")
local gbangLoop = nil
local GBANGANIM = nil
local GBANGtrack = nil
local gbangDied = nil
local SuckLoop = nil
local SuckAnim = nil
local Sucktrack = nil
local SuckDied = nil

Plugin.Commands["getbang"] = {
	["ListName"] = "getbang / gbang [plr]",
	["Description"] = "Get banged by a player.",
	["Aliases"] = { "gbang" },
	["Function"] = function(args, speaker)
		if r15(speaker) then
			execCmd("ungetbang")
			local players = getPlayer(args[1], speaker)
			for i, plr in pairs(players) do
				Players.LocalPlayer.Character.Animate.Disabled = true
				GBANGAnim = Instance.new("Animation")
				GBANGAnim.AnimationId = "rbxassetid://4686925579"
				GBANGtrack = Players.LocalPlayer.Character.Humanoid:LoadAnimation(GBANGAnim)
				GBANGtrack:Play()
				if args[2] then
					GBANGtrack:AdjustSpeed(args[2])
				else
					GBANGtrack:AdjustSpeed(8)
				end
				gbangDied = Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid").Died:Connect(function()
					GBANGAnim:Destroy()
					GBANGtrack:Stop()
					gbangLoop:Disconnect()
					gbangDied:Disconnect()
				end)
				gbangLoop = game:GetService("RunService").Stepped:Connect(function()
					pcall(function()
						getRoot(Players.LocalPlayer.Character).CFrame = Players[plr].Character.HumanoidRootPart.CFrame
							* CFrame.new(0, 0, -1)
					end)
				end)
			end
		else
			notify("R15 Required", "This command requires the r15 rig type")
		end
	end,
}

Plugin.Commands["ungetbang"] = {
	["ListName"] = "ungetbang / ungbang",
	["Description"] = "Get unbanged by a player.",
	["Aliases"] = { "ungbang" },
	["Function"] = function(args, speaker)
		if gbangLoop ~= nil then
			gbangLoop:Disconnect()
			GBANGAnim:Destroy()
			GBANGtrack:Stop()
			Players.LocalPlayer.Character.Animate.Disabled = false
			gbangDied:Disconnect()
		end
	end,
}

Plugin.Commands["suck"] = {
	["ListName"] = "suck [plr]",
	["Description"] = "suck a player.",
	["Aliases"] = {},
	["Function"] = function(args, speaker)
		if r15(speaker) then
			execCmd("unsuck")
			local players = getPlayer(args[1], speaker)
			for i, plr in pairs(players) do
				Players.LocalPlayer.Character.Animate.Disabled = true
				SuckAnim = Instance.new("Animation")
				SuckAnim.AnimationId = "rbxassetid://4686925579"
				Sucktrack = Players.LocalPlayer.Character.Humanoid:LoadAnimation(GBANGAnim)
				Sucktrack:Play()
				if args[2] then
					GBANGtrack:AdjustSpeed(args[2])
				else
					GBANGtrack:AdjustSpeed(8)
				end
				SuckDied = Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid").Died:Connect(function()
					SuckAnim:Destroy()
					Sucktrack:Stop()
					SuckLoop:Disconnect()
					SuckDied:Disconnect()
				end)
				SuckLoop = game:GetService("RunService").Stepped:Connect(function()
					pcall(function()
						getRoot(Players.LocalPlayer.Character).CFrame = Players[plr].Character.HumanoidRootPart.CFrame
							* CFrame.new(0, 0, -3.5)
							* CFrame.Angles(0, math.rad(180), 0)
					end)
				end)
			end
		else
			notify("R15 Required", "This command requires the r15 rig type")
		end
	end,
}

Plugin.Commands["unsuck"] = {
	["ListName"] = "unsuck [plr]",
	["Description"] = "unsuck a player.",
	["Aliases"] = {},
	["Function"] = function(args, speaker)
		if SuckLoop ~= nil then
			SuckLoop:Disconnect()
			SuckAnim:Destroy()
			Sucktrack:Stop()
			Players.LocalPlayer.Character.Animate.Disabled = false
			SuckDied:Disconnect()
		end
	end,
}

return Plugin


local Plugin = {
	["PluginName"] = "No Cooldown",
	["PluginDescription"] = "Removes Cooldown (Universal)",
	["Commands"] = {
		["nocooldown"] = {
			["ListName"] = "nocooldown / noc",
			["Description"] = "Removes cooldown, is universal. Might cause some lag.",
			["Aliases"] = { "noc" },
			["Function"] = function()
				local a
				a = hookfunc(wait, function(b)
					b = nil
					return a(b)
				end)
			end,
		},
	},
}
return Plugin


local Plugin = {
	["PluginName"] = "No Sound",
	["PluginDescription"] = "Scripted by NoobSploit#0001 Sarge#0196 doesnt have a dick",
	["Commands"] = {
		["nosound"] = {
			["ListName"] = "nosound",
			["Description"] = "Deletes sounds",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				for _, sound in next, workspace:GetDescendants() do
					if sound:IsA("Sound") then
						sound:Destroy()
					end
				end
			end,
		},
	},
}
return Plugin


local Plugin = {
	["PluginName"] = "Part Flashback",
	["PluginDescription"] = "Creates a part that you can teleport to",
	["Commands"] = {
		["partflashback"] = {
			["ListName"] = "partflashback / pflash",
			["Description"] = "Allows you to teleport to a part",
			["Aliases"] = { "pflash" },
			["Function"] = function(args, speaker)
				mouse = game.Players.LocalPlayer:GetMouse()

				sound = Instance.new("Sound")
				sound.SoundId = "rbxassetid://690607937"
				sound.Volume = 1

				player = game.Players.LocalPlayer
				character = player.Character

				local tppart = Instance.new("Part")
				sound.Parent = tppart
				tppart.Parent = workspace
				tppart.Anchored = true

				tppart.Material = "ForceField"

				active = false

				mouse.KeyDown:Connect(function(key)
					tppart.Size = Vector3.new(1, 1, 1)
					tppart.CanCollide = false
					if key == "h" then
						tppart.CFrame = character.HumanoidRootPart.CFrame
					end

					if key == "g" then
						character:MoveTo(tppart.Position)
						sound:Play()
						active = false
					end
				end)

				character.Humanoid.Died:Connect(function()
					tppart:Destroy()
					script:Destroy()
				end)
			end,
		},
	},
}

return Plugin


local Plugin = {
	["PluginName"] = "Plugin Info",
	["PluginDescription"] = "Displays information about your plugin usage.",
	["Commands"] = {
		["plugininfo"] = {
			["ListName"] = "plugininfo / plugins",
			["Description"] = "Displays information about your plugin usage.",
			["Aliases"] = { "plugins" },
			["Function"] = function(args, speaker)
				local pluginInfo = "Loaded plugins: "
				for i, pluginName in pairs(PluginsTable) do
					if i ~= 1 then
						pluginInfo = pluginInfo .. ", "
					end
					pluginInfo = pluginInfo .. pluginName:sub(1, -4)
				end

				if listfiles == nil then
					return notify(pluginInfo)
				end

				local pluginInfo = pluginInfo .. "\nUnloaded plugins: "
				local unloaded = {} -- for formatting with commas properly
				for i, fileName in pairs(listfiles("")) do
					if
						fileName:sub(-3) == ".iy"
						and (
							fileName:lower() ~= "plugin file name"
							and fileName:lower() ~= "iy_fe.iy"
							and fileName ~= "iy_fe"
						)
					then
						if not FindInTable(PluginsTable, fileName) then
							table.insert(unloaded, fileName:sub(1, -4))
						end
					end
				end
				for i, pluginName in pairs(unloaded) do
					if i ~= 1 then
						pluginInfo = pluginInfo .. ", "
					end
					pluginInfo = pluginInfo .. pluginName
				end

				notify(pluginInfo)
			end,
		},
	},
}

return Plugin


local CmdSuggestion = Instance.new("TextLabel")
CmdSuggestion.Name = "CmdSuggestion"
CmdSuggestion.Parent = Cmdbar
CmdSuggestion.BackgroundTransparency = 1
CmdSuggestion.BorderSizePixel = 0
CmdSuggestion.Position = UDim2.new(0, 0, 0, 0)
CmdSuggestion.Size = UDim2.new(0, 240, 0, 25)
CmdSuggestion.Font = Enum.Font.SourceSans
CmdSuggestion.TextSize = 18
CmdSuggestion.TextXAlignment = Enum.TextXAlignment.Left
CmdSuggestion.TextColor3 = Color3.fromRGB(171, 171, 171)
CmdSuggestion.Text = ""
CmdSuggestion.ZIndex = 10

Match = function(str1, str2)
	return str2 == string.sub(str1, 1, #str2)
end
local matchSearch = function(str1, str2)
	return str1 == string.sub(str2, 1, #str1)
end

local stringFind = function(tbl, str)
	if tbl == nil then
		return
	end
	if type(tbl) == "table" then
		for _, v in ipairs(tbl) do
			if matchSearch(str, v) then
				return v
			end
		end
	end
end

local getPlrArguments = function(argument)
	local arg = string.lower(argument)
	local specialcases = {
		"all",
		"others",
		"random",
		"me",
		"nearest",
		"farthest",
		"allies",
		"enemies",
		"team",
		"nonteam",
		"friends",
		"nonfriends",
		"bacons",
		"nearest",
		"farthest",
		"alive",
		"dead",
		"cursor",
	}
	return stringFind(specialcases, arg)
		or (function()
			for _, v in ipairs(Players:GetPlayers()) do
				local Name = string.lower(v.Name)
				if matchSearch(arg, Name) then
					return Name
				end
			end
		end)()
end

Cmdbar:GetPropertyChangedSignal("Text"):Connect(function()
	if Cmdbar:IsFocused() then
		CmdSuggestion.Text = ""
		local InputText = Cmdbar.Text
		local Args = string.split(InputText, " ")
		local CmdArgs = cargs or {}
		if InputText == "" then
			return
		end
		for _, v in next, cmds do
			local Name = v.NAME
			local Aliases = v.ALIAS
			local FoundAlias = false
			if matchSearch(InputText, Name) then
				CmdSuggestion.Text = Name
				break
			end
			for _, v2 in next, Aliases do
				if matchSearch(InputText, v2) then
					FoundAlias = true
					CmdSuggestion.Text = v2
					break
				end
				if FoundAlias then
					break
				end
			end
		end
		for i, v in next, Args do
			if i > 1 and v ~= "" then
				local Predict = ""
				if #CmdArgs >= 1 then
					Predict = getPlrArguments(v) or Predict
				else
					Predict = getPlrArguments(v) or Predict
				end
				CmdSuggestion.Text = string.sub(InputText, 1, #InputText - #Args[#Args]) .. Predict
				local split = v:split(",")
				if next(split) then
					for i2, v2 in next, split do
						if i2 > 1 and v2 ~= "" then
							local PlayerName = getPlrArguments(v2)
							CmdSuggestion.Text = string.sub(InputText, 1, #InputText - #split[#split])
								.. (PlayerName or "")
						end
					end
				end
			end
		end
	end
end)

Cmdbar.FocusLost:Connect(function()
	if not Cmdbar:IsFocused() then
		CmdSuggestion.Text = ""
	end
end)

tabAllowed = false
local newTabComplete = nil
Cmdbar.Focused:Connect(function()
	newTabComplete = UserInputService.InputBegan:Connect(function(input, gameProcessed)
		if Cmdbar:IsFocused() then
			if input.KeyCode == Enum.KeyCode.Tab then
				if CmdSuggestion.Text == ("" or " ") then
				else
					if string.match(CmdSuggestion.Text, " ") then
						Cmdbar.Text = CmdSuggestion.Text
						wait()
						Cmdbar.Text = Cmdbar.Text:gsub("\t", "")
						Cmdbar.CursorPosition = #Cmdbar.Text + 1
					else
						Cmdbar.Text = CmdSuggestion.Text .. " "
						wait()
						Cmdbar.Text = Cmdbar.Text:gsub("\t", "")
						Cmdbar.CursorPosition = #Cmdbar.Text + 1
					end
				end
			end
		else
			newTabComplete:Disconnect()
			newTabComplete = nil
		end
	end)
end)

return {
	["PluginName"] = "Prediction",
	["PluginDescription"] = "Add back the super hot autocomplete from version 5.3.2",
	["Commands"] = {},
}


return {
	["PluginName"] = "rename",
	["PluginDescription"] = "Scripted by NoobSploit#0001",
	["Commands"] = {
		["rename"] = {
			["ListName"] = "rename [text]",
			["Description"] = "why",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				if args[1] ~= nil then
					Title.Text = getstring(1)
				end
			end,
		},
	},
}


local Plugin = {
	["PluginName"] = "schat",
	["PluginDescription"] = "Scripted by NoobSploit#0001",
	["Commands"] = {
		["schat"] = {
			["ListName"] = "schat [text]",
			["Description"] = "Chats in game.Players:Chat and can only be seen though chat logs",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				if not args[1] then
					return
				end
				game:GetService("Players"):Chat(tostring(getstring(1)))
			end,
		},
	},
}
return Plugin


local Plugin = {
	["PluginName"] = "Showhealth",
	["PluginDescription"] = "Made by YuKii#4662",
	["Commands"] = {
		["health"] = {
			["ListName"] = "health / hp [plr]",
			["Description"] = "Show Player Health",
			["Aliases"] = { "hp" },
			["Function"] = function(args, speaker)
				local players = getPlayer(args[1], speaker)
				for k, v in pairs(players) do
					if Players[v].Character:FindFirstChildWhichIsA("Humanoid") then
						local HP = Players[v].Character:FindFirstChildWhichIsA("Humanoid").Health
						notify(Players[v].name .. "'s Health", tostring(HP))
					end
				end
			end,
		},
	},
}
return Plugin


local Plugin = {
	["PluginName"] = "System chat",
	["PluginDescription"] = "Fake chat as a system",
	["Commands"] = {
		["systemchat"] = {
			["ListName"] = "systemchat / schat [msg]",
			["Description"] = "chat as a system",
			["Aliases"] = { "schat" },
			["Function"] = function(args, speaker)
				local msg = "                                                                                {System} "
					.. getstring(1)
				game:GetService("ReplicatedStorage").DefaultChatSystemChatEvents.SayMessageRequest
					:FireServer(msg, "All")
			end,
		},
	},
}

return Plugin


TFlyEnabled = false

local Plugin = {
	["PluginName"] = "Tween Fly",
	["PluginDescription"] = "Basically smooth flying",
	["Commands"] = {
		["tfly"] = {
			["ListName"] = "tfly [speed]",
			["Description"] = "Enables tween fly",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				TFlyEnabled = true

				local speed, e1, e2
				local Hum, mouse = speaker.Character:FindFirstChildOfClass("Humanoid"), speaker:GetMouse()

				local Core = Instance.new("Part", workspace)
				Core.Size, Core.CanCollide = Vector3.new(0.05, 0.05, 0.05), false
				local Trs = Core

				local keys = { a = false, d = false, w = false, s = false }
				e1 = mouse.KeyDown:Connect(function(key)
					if not Trs or not Trs.Parent then
						e1:disconnect()
						e2:disconnect()
						return
					end
					if key == "w" then
						keys.w = true
					elseif key == "s" then
						keys.s = true
					elseif key == "a" then
						keys.a = true
					elseif key == "d" then
						keys.d = true
					end
				end)
				e2 = mouse.KeyUp:Connect(function(key)
					if key == "w" then
						keys.w = false
					elseif key == "s" then
						keys.s = false
					elseif key == "a" then
						keys.a = false
					elseif key == "d" then
						keys.d = false
					end
				end)

				local Weld = Instance.new("Weld", Core)
				Weld.Part0, Weld.Part1, Weld.C0 = Core, Hum.RootPart, CFrame.new(0, 0, 0)

				local pos, gyro = Instance.new("BodyPosition", Trs), Instance.new("BodyGyro", Trs)
				pos.maxForce, pos.position = Vector3.new(math.huge, math.huge, math.huge), Trs.Position
				gyro.maxTorque, gyro.cframe = Vector3.new(9e9, 9e9, 9e9), Trs.CFrame

				repeat
					wait()
					Hum.PlatformStand = true
					local new = gyro.cframe - gyro.cframe.p + pos.position
					if not keys.w and not keys.s and not keys.a and not keys.d then
						if not args[1] then
							speed = 2
						else
							speed = args[1]
						end
					end
					if keys.w then
						new = new + workspace.CurrentCamera.CoordinateFrame.lookVector * speed
						speed = speed + 0
					end
					if keys.s then
						new = new - workspace.CurrentCamera.CoordinateFrame.lookVector * speed
						speed = speed + 0
					end
					if keys.d then
						new = new * CFrame.new(speed, 0, 0)
						speed = speed + 0
					end
					if keys.a then
						new = new * CFrame.new(-speed, 0, 0)
						speed = speed + 0
					end

					pos.position = new.p
					if keys.w then
						gyro.cframe = workspace.CurrentCamera.CoordinateFrame
							* CFrame.Angles(-math.rad(speed * 0), 0, 0)
					elseif keys.s then
						gyro.cframe = workspace.CurrentCamera.CoordinateFrame * CFrame.Angles(math.rad(speed * 0), 0, 0)
					else
						gyro.cframe = workspace.CurrentCamera.CoordinateFrame
					end
				until TFlyEnabled == false
				if gyro then
					gyro:Destroy()
				end
				if pos then
					pos:Destroy()
				end
				Hum.PlatformStand = false
				speed = 10
			end,
		},

		["untfly"] = {
			["ListName"] = "untfly",
			["Description"] = "Disables tween fly",
			["Aliases"] = {},
			["Function"] = function(args, speaker)
				TFlyEnabled = false
				for i, v in pairs(Core:GetChildren()) do
					v:Destroy()
				end
			end,
		},
	},
}

return Plugin


local Wishes = {
	["PluginName"] = "Activate Tool",
	["PluginDescription"] = "Activates tool thats in players hand (loops) ",
	["Commands"] = {
		["activate"] = {
			["ListName"] = "activate / acti",
			["Description"] = "Begins to loop activate tool",
			["Aliases"] = { "acti" },
			["Function"] = function(args, speaker)
				_G.Stop = false

				game:GetService("RunService").RenderStepped:Connect(function()
					if _G.Stop == true then
						return
					end
					for _, v in pairs(game.Players.LocalPlayer.Character:GetChildren()) do
						if v:IsA("Tool") then
							v:Activate()
						end
					end
				end)
			end,
		},
		["unactivate"] = {
			["ListName"] = "unactivate / unacti",
			["Description"] = "Stops activating tools",
			["Aliases"] = { "unacti" },
			["Function"] = function(args, speaker)
				_G.Stop = true
			end,
		},
	},
}
return Wishes


local process = require("@lune/process")
local stdio = require("@lune/stdio")

local branches = require("./branches")

for _, branch in ipairs(branches) do
	if table.find(process.args, branch) then
		return branch
	end
end

local index = stdio.prompt(
	"select",
	"What branch to build for? (Pass it in as argument when running command to skip this)",
	branches
)
return branches[index]


local fs = require("@lune/fs")
local serde = require("@lune/serde")
local process = require("@lune/process")

local branch = require("./branch")
local defaultConfig = serde.decode("toml", fs.readFile(".lune/build/config/default.toml")) -- ./default.toml would've been nice :sob:

local config = { branch = branch }
local function writeToConfig(dictionary: { [string]: any }?)
	for k, v in dictionary or {} do
		config[k] = v
	end
end

writeToConfig(defaultConfig.global)
writeToConfig(defaultConfig[branch])

for k, v in process.env do
	-- Filter out any env variable not present in the default config
	if config[k] == nil then
		continue
	end

	config[k] = v
end

if fs.isFile(".config.toml") then
	local customConfig = serde.decode("toml", fs.readFile(".config.toml"))
	writeToConfig(customConfig.global)
	writeToConfig(customConfig[branch])
end

for _, v in ipairs(require("./branches")) do
	config[v] = config.branch == v
end

return config


local MLDSA87 = require("@shared/crypto/MlDSA").ML_DSA_87
local MLKEM1024 = require("@shared/crypto/MlKEM").MLKEM_1024
local CSPRNG = require("@shared/crypto/CSPRNG")
local Base64 = require("@shared/crypto/base64")

return function(config)
	local rules = table.clone(config.darkluaRules)

	local inject = table.clone(config)
	inject.darkluaRules = nil

	do
		local function set(global: string, value: any)
			if inject[global] ~= true then
				return
			end

			inject[global] = value
		end

		local dsaPublicKey, dsaPrivateKey = MLDSA87.GenerateKeys()
		set("dsaServerPublicKey", buffer.tostring(Base64.Encode(dsaPublicKey)))
		set("dsaServerPrivateKey", buffer.tostring(Base64.Encode(dsaPrivateKey)))

		local kemPublicKey, kemPrivateKey = MLKEM1024.GenerateKeys()
		set("kemServerPublicKey", buffer.tostring(Base64.Encode(kemPublicKey)))
		set("kemServerPrivateKey", buffer.tostring(Base64.Encode(kemPrivateKey)))

		for _, global in ipairs({ "scriptHostKey", "localScriptHostKey", "moduleScriptHostKey" }) do
			set(global, CSPRNG.RandomString(256))
		end
	end

	for key, value in inject do
		table.insert(rules, 1, {
			rule = "inject_global_value",
			identifier = key,
			value = value,
		})
	end

	return {
		generator = "readable",
		bundle = {
			modules_identifier = "__DARKLUA_BUNDLE_MODULES",
			require_mode = {
				name = "luau",
				aliases = {
					-- Use ../ to jump out of the "build" folder
					["@client"] = "../modules/client",
					["@server"] = "../modules/server",
					["@shared"] = "../modules/shared",
				},

				use_luau_configuration = false,
			},
		},

		rules = rules,
	}
end


--!nonstrict
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local Players = game:GetService("Players")
local RunService = game:GetService("RunService")
local UserGameSettings = UserSettings():GetService("UserGameSettings")
local VRService = game:GetService("VRService")
local StarterGui = game:GetService("StarterGui")

local player = Players.LocalPlayer

local CAMERA_INPUT_PRIORITY = Enum.ContextActionPriority.Medium.Value
local MB_TAP_LENGTH = 0.3 -- (s) length of time for a short mouse button tap to be registered

local ROTATION_SPEED_KEYS = math.rad(120) -- (rad/s)
local ROTATION_SPEED_MOUSE = Vector2.new(1, 0.77)*math.rad(0.5) -- (rad/s)
local ROTATION_SPEED_POINTERACTION = Vector2.new(1, 0.77)*math.rad(7) -- (rad/s)
local ROTATION_SPEED_TOUCH = Vector2.new(1, 0.66)*math.rad(1) -- (rad/s)
local ROTATION_SPEED_GAMEPAD = Vector2.new(1, 0.77)*math.rad(4) -- (rad/s)

local ZOOM_SPEED_MOUSE = 1 -- (scaled studs/wheel click)
local ZOOM_SPEED_KEYS = 0.1 -- (studs/s)
local ZOOM_SPEED_TOUCH = 0.04 -- (scaled studs/DIP %)

local MIN_TOUCH_SENSITIVITY_FRACTION = 0.25 -- 25% sensitivity at 90°

local FFlagUserResetTouchStateOnMenuOpen
do
	local success, result = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserResetTouchStateOnMenuOpen")
	end)
	FFlagUserResetTouchStateOnMenuOpen = success and result
end

-- right mouse button up & down events
local rmbDown, rmbUp do
	local rmbDownBindable = Instance.new("BindableEvent")
	local rmbUpBindable = Instance.new("BindableEvent")

	rmbDown = rmbDownBindable.Event
	rmbUp = rmbUpBindable.Event

	UserInputService.InputBegan:Connect(function(input, gpe)
		if not gpe and input.UserInputType == Enum.UserInputType.MouseButton2 then
			rmbDownBindable:Fire()
		end
	end)

	UserInputService.InputEnded:Connect(function(input, gpe)
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			rmbUpBindable:Fire()
		end
	end)
end

local thumbstickCurve do
	local K_CURVATURE = 2 -- amount of upwards curvature (0 is flat)
	local K_DEADZONE = 0.1 -- deadzone

	function thumbstickCurve(x)
		-- remove sign, apply linear deadzone
		local fDeadzone = (math.abs(x) - K_DEADZONE)/(1 - K_DEADZONE)

		-- apply exponential curve and scale to fit in [0, 1]
		local fCurve = (math.exp(K_CURVATURE*fDeadzone) - 1)/(math.exp(K_CURVATURE) - 1)

		-- reapply sign and clamp
		return math.sign(x)*math.clamp(fCurve, 0, 1)
	end
end

-- Adjust the touch sensitivity so that sensitivity is reduced when swiping up
-- or down, but stays the same when swiping towards the middle of the screen
local function adjustTouchPitchSensitivity(delta: Vector2): Vector2
	local camera = workspace.CurrentCamera

	if not camera then
		return delta
	end

	-- get the camera pitch in world space
	local pitch = camera.CFrame:ToEulerAnglesYXZ()

	if delta.Y*pitch >= 0 then
		-- do not reduce sensitivity when pitching towards the horizon
		return delta
	end

	-- set up a line to fit:
	-- 1 = f(0)
	-- 0 = f(±pi/2)
	local curveY = 1 - (2*math.abs(pitch)/math.pi)^0.75

	-- remap curveY from [0, 1] -> [MIN_TOUCH_SENSITIVITY_FRACTION, 1]
	local sensitivity = curveY*(1 - MIN_TOUCH_SENSITIVITY_FRACTION) + MIN_TOUCH_SENSITIVITY_FRACTION

	return Vector2.new(1, sensitivity)*delta
end

local function isInDynamicThumbstickArea(pos: Vector3): boolean
	local playerGui = player:FindFirstChildOfClass("PlayerGui")
	local touchGui = playerGui and playerGui:FindFirstChild("TouchGui")
	local touchFrame = touchGui and touchGui:FindFirstChild("TouchControlFrame")
	local thumbstickFrame = touchFrame and touchFrame:FindFirstChild("DynamicThumbstickFrame")

	if not thumbstickFrame then
		return false
	end

	if not touchGui.Enabled then
		return false
	end

	local posTopLeft = thumbstickFrame.AbsolutePosition
	local posBottomRight = posTopLeft + thumbstickFrame.AbsoluteSize

	return
		pos.X >= posTopLeft.X and
		pos.Y >= posTopLeft.Y and
		pos.X <= posBottomRight.X and
		pos.Y <= posBottomRight.Y
end

local worldDt = 1/60
RunService.Stepped:Connect(function(_, _worldDt)
	worldDt = _worldDt
end)

local CameraInput = {}

do
	local connectionList = {}
	local panInputCount = 0

	local function incPanInputCount()
		panInputCount = math.max(0, panInputCount + 1)
	end

	local function decPanInputCount()
		panInputCount = math.max(0, panInputCount - 1)
	end

	local function resetPanInputCount()
		panInputCount = 0
	end

	local touchPitchSensitivity = 1
	local gamepadState = {
		Thumbstick2 = Vector2.new(),
	}
	local keyboardState = {
		Left = 0,
		Right = 0,
		I = 0,
		O = 0
	}
	local mouseState = {
		Movement = Vector2.new(),
		Wheel = 0, -- PointerAction
		Pan = Vector2.new(), -- PointerAction
		Pinch = 0, -- PointerAction
	}
	local touchState = {
		Move = Vector2.new(),
		Pinch = 0,
	}

	local gamepadZoomPressBindable = Instance.new("BindableEvent")
	CameraInput.gamepadZoomPress = gamepadZoomPressBindable.Event

	local gamepadResetBindable = VRService.VREnabled and Instance.new("BindableEvent") or nil
	if VRService.VREnabled then
		CameraInput.gamepadReset = gamepadResetBindable.Event
	end

	function CameraInput.getRotationActivated(): boolean
		return panInputCount > 0 or gamepadState.Thumbstick2.Magnitude > 0
	end

	function CameraInput.getRotation(disableKeyboardRotation: boolean?): Vector2
		local inversionVector = Vector2.new(1, UserGameSettings:GetCameraYInvertValue())

		-- keyboard input is non-coalesced, so must account for time delta
		local kKeyboard = Vector2.new(keyboardState.Right - keyboardState.Left, 0)*worldDt
		local kGamepad = gamepadState.Thumbstick2
		local kMouse = mouseState.Movement
		local kPointerAction = mouseState.Pan
		local kTouch = adjustTouchPitchSensitivity(touchState.Move)

		if disableKeyboardRotation then
			kKeyboard = Vector2.new()
		end

		local result =
			kKeyboard*ROTATION_SPEED_KEYS +
			kGamepad*ROTATION_SPEED_GAMEPAD +
			kMouse*ROTATION_SPEED_MOUSE +
			kPointerAction*ROTATION_SPEED_POINTERACTION +
			kTouch*ROTATION_SPEED_TOUCH

		return result*inversionVector
	end

	function CameraInput.getZoomDelta(): number
		local kKeyboard = keyboardState.O - keyboardState.I
		local kMouse = -mouseState.Wheel + mouseState.Pinch
		local kTouch = -touchState.Pinch
		return kKeyboard*ZOOM_SPEED_KEYS + kMouse*ZOOM_SPEED_MOUSE + kTouch*ZOOM_SPEED_TOUCH
	end

	do
		local function thumbstick(action, state, input)
			local position = input.Position
			gamepadState[input.KeyCode.Name] = Vector2.new(thumbstickCurve(position.X), -thumbstickCurve(position.Y))
			return Enum.ContextActionResult.Pass
		end

		local function mouseMovement(input)
			local delta = input.Delta
			mouseState.Movement = Vector2.new(delta.X, delta.Y)
		end

		local function mouseWheel(action, state, input)
			mouseState.Wheel = input.Position.Z
			return Enum.ContextActionResult.Pass
		end

		local function keypress(action, state, input)
			keyboardState[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
		end

		local function gamepadZoomPress(action, state, input)
			if state == Enum.UserInputState.Begin then
				gamepadZoomPressBindable:Fire()
			end
		end

		local function gamepadReset(action, state, input)
			if state == Enum.UserInputState.Begin then
				gamepadResetBindable:Fire()
			end
		end

		local function resetInputDevices()
			for _, device in pairs({
				gamepadState,
				keyboardState,
				mouseState,
				touchState,
			}) do
				for k, v in pairs(device) do
					if type(v) == "boolean" then
						device[k] = false
					else
						device[k] *= 0 -- Mul by zero to preserve vector types
					end
				end
			end
		end

		local touchBegan, touchChanged, touchEnded, resetTouchState do
			-- Use TouchPan & TouchPinch when they work in the Studio emulator

			local touches: {[InputObject]: boolean?} = {} -- {[InputObject] = sunk}
			local dynamicThumbstickInput: InputObject? -- Special-cased
			local lastPinchDiameter: number?

			function touchBegan(input: InputObject, sunk: boolean)
				assert(input.UserInputType == Enum.UserInputType.Touch)
				assert(input.UserInputState == Enum.UserInputState.Begin)

				if dynamicThumbstickInput == nil and isInDynamicThumbstickArea(input.Position) and not sunk then
					-- any finger down starting in the dynamic thumbstick area should always be
					-- ignored for camera purposes. these must be handled specially from all other
					-- inputs, as the DT does not sink inputs by itself
					dynamicThumbstickInput = input
					return
				end

				if not sunk then
					incPanInputCount()
				end

				-- register the finger
				touches[input] = sunk
			end

			function touchEnded(input: InputObject, sunk: boolean)
				assert(input.UserInputType == Enum.UserInputType.Touch)
				assert(input.UserInputState == Enum.UserInputState.End)

				-- reset the DT input
				if input == dynamicThumbstickInput then
					dynamicThumbstickInput = nil
				end

				-- reset pinch state if one unsunk finger lifts
				if touches[input] == false then
					lastPinchDiameter = nil
					decPanInputCount()
				end

				-- unregister input
				touches[input] = nil
			end

			function touchChanged(input, sunk)
				assert(input.UserInputType == Enum.UserInputType.Touch)
				assert(input.UserInputState == Enum.UserInputState.Change)

				-- ignore movement from the DT finger
				if input == dynamicThumbstickInput then
					return
				end

				-- fixup unknown touches
				if touches[input] == nil then
					touches[input] = sunk
				end

				-- collect unsunk touches
				local unsunkTouches = {}
				for touch, sunk in pairs(touches) do
					if not sunk then
						table.insert(unsunkTouches, touch)
					end
				end

				-- 1 finger: pan
				if #unsunkTouches == 1 then
					if touches[input] == false then
						local delta = input.Delta
						touchState.Move += Vector2.new(delta.X, delta.Y) -- total touch pan movement (reset at end of frame)
					end
				end

				-- 2 fingers: pinch
				if #unsunkTouches == 2 then
					local pinchDiameter = (unsunkTouches[1].Position - unsunkTouches[2].Position).Magnitude

					if lastPinchDiameter then
						touchState.Pinch += pinchDiameter - lastPinchDiameter
					end

					lastPinchDiameter = pinchDiameter
				else
					lastPinchDiameter = nil
				end
			end

			function resetTouchState()
				touches = {}
				dynamicThumbstickInput = nil
				lastPinchDiameter = nil
				if FFlagUserResetTouchStateOnMenuOpen then
					resetPanInputCount()
				end
			end
		end

		local function pointerAction(wheel, pan, pinch, gpe)
			if not gpe then
				mouseState.Wheel = wheel
				mouseState.Pan = pan
				mouseState.Pinch = -pinch
			end
		end

		local function inputBegan(input, sunk)
			if input.UserInputType == Enum.UserInputType.Touch then
				touchBegan(input, sunk)

			elseif input.UserInputType == Enum.UserInputType.MouseButton2 and not sunk then
				incPanInputCount()
			end
		end

		local function inputChanged(input, sunk)
			if input.UserInputType == Enum.UserInputType.Touch then
				touchChanged(input, sunk)

			elseif input.UserInputType == Enum.UserInputType.MouseMovement then
				mouseMovement(input)
			end
		end

		local function inputEnded(input, sunk)
			if input.UserInputType == Enum.UserInputType.Touch then
				touchEnded(input, sunk)

			elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
				decPanInputCount()
			end
		end

		local inputEnabled = false

		function CameraInput.setInputEnabled(_inputEnabled)
			if inputEnabled == _inputEnabled then
				return
			end
			inputEnabled = _inputEnabled

			resetInputDevices()
			resetTouchState()

			if inputEnabled then -- enable
				ContextActionService:BindActionAtPriority(
					"RbxCameraThumbstick",
					thumbstick,
					false,
					CAMERA_INPUT_PRIORITY,
					Enum.KeyCode.Thumbstick2
				)

				ContextActionService:BindActionAtPriority(
					"RbxCameraKeypress",
					keypress,
					false,
					CAMERA_INPUT_PRIORITY,
					Enum.KeyCode.Left,
					Enum.KeyCode.Right,
					Enum.KeyCode.I,
					Enum.KeyCode.O
				)

				if VRService.VREnabled then
					ContextActionService:BindAction(
						"RbxCameraGamepadReset",
						gamepadReset,
						false,
						Enum.KeyCode.ButtonL3
					)
				end

				ContextActionService:BindAction(
					"RbxCameraGamepadZoom",
					gamepadZoomPress,
					false,
					Enum.KeyCode.ButtonR3
				)

				table.insert(connectionList, UserInputService.InputBegan:Connect(inputBegan))
				table.insert(connectionList, UserInputService.InputChanged:Connect(inputChanged))
				table.insert(connectionList, UserInputService.InputEnded:Connect(inputEnded))
				table.insert(connectionList, UserInputService.PointerAction:Connect(pointerAction))
				if FFlagUserResetTouchStateOnMenuOpen then
					local GuiService = game:GetService("GuiService")
					table.insert(connectionList, GuiService.MenuOpened:connect(resetTouchState))
				end

			else -- disable
				ContextActionService:UnbindAction("RbxCameraThumbstick")
				ContextActionService:UnbindAction("RbxCameraMouseMove")
				ContextActionService:UnbindAction("RbxCameraMouseWheel")
				ContextActionService:UnbindAction("RbxCameraKeypress")

				ContextActionService:UnbindAction("RbxCameraGamepadZoom")
				if VRService.VREnabled then
					ContextActionService:UnbindAction("RbxCameraGamepadReset")
				end

				for _, conn in pairs(connectionList) do
					conn:Disconnect()
				end
				connectionList = {}
			end
		end

		function CameraInput.getInputEnabled()
			return inputEnabled
		end

		function CameraInput.resetInputForFrameEnd()
			mouseState.Movement = Vector2.new()
			touchState.Move = Vector2.new()
			touchState.Pinch = 0

			mouseState.Wheel = 0 -- PointerAction
			mouseState.Pan = Vector2.new() -- PointerAction
			mouseState.Pinch = 0 -- PointerAction
		end

		UserInputService.WindowFocused:Connect(resetInputDevices)
		UserInputService.WindowFocusReleased:Connect(resetInputDevices)
	end
end

-- Toggle pan
do
	local holdPan = false
	local togglePan = false
	local lastRmbDown = 0 -- tick() timestamp of the last right mouse button down event

	function CameraInput.getHoldPan(): boolean
		return holdPan
	end

	function CameraInput.getTogglePan(): boolean
		return togglePan
	end

	function CameraInput.getPanning(): boolean
		return togglePan or holdPan
	end

	function CameraInput.setTogglePan(value: boolean)
		togglePan = value
	end

	local cameraToggleInputEnabled = false
	local rmbDownConnection
	local rmbUpConnection

	function CameraInput.enableCameraToggleInput()
		if cameraToggleInputEnabled then
			return
		end
		cameraToggleInputEnabled = true

		holdPan = false
		togglePan = false

		if rmbDownConnection then
			rmbDownConnection:Disconnect()
		end

		if rmbUpConnection then
			rmbUpConnection:Disconnect()
		end

		rmbDownConnection = rmbDown:Connect(function()
			holdPan = true
			lastRmbDown = tick()
		end)

		rmbUpConnection = rmbUp:Connect(function()
			holdPan = false
			if tick() - lastRmbDown < MB_TAP_LENGTH and (togglePan or UserInputService:GetMouseDelta().Magnitude < 2) then
				togglePan = not togglePan
			end
		end)
	end

	function CameraInput.disableCameraToggleInput()
		if not cameraToggleInputEnabled then
			return
		end
		cameraToggleInputEnabled = false

		if rmbDownConnection then
			rmbDownConnection:Disconnect()
			rmbDownConnection = nil
		end

		if rmbUpConnection then
			rmbUpConnection:Disconnect()
			rmbUpConnection = nil
		end
	end
end

return CameraInput


local Players = game:GetService("Players")
local UserInputService = game:GetService("UserInputService")
local GameSettings = UserSettings():GetService("UserGameSettings")

local Input = require(script.Parent:WaitForChild("CameraInput"))
local CameraUI = require(script.Parent:WaitForChild("CameraUI"))
local CameraUtils = require(script.Parent:WaitForChild("CameraUtils"))

local lastTogglePan = false
local lastTogglePanChange = tick()

local CROSS_MOUSE_ICON = "rbxasset://textures/Cursors/CrossMouseIcon.png"

local lockStateDirty = false
local wasTogglePanOnTheLastTimeYouWentIntoFirstPerson = false
local lastFirstPerson = false

CameraUI.setCameraModeToastEnabled(false)

return function(isFirstPerson: boolean)
	local togglePan = Input.getTogglePan()
	local toastTimeout = 3

	if isFirstPerson and togglePan ~= lastTogglePan then
		lockStateDirty = true
	end

	if lastTogglePan ~= togglePan or tick() - lastTogglePanChange > toastTimeout then
		local doShow = togglePan and tick() - lastTogglePanChange < toastTimeout

		CameraUI.setCameraModeToastOpen(doShow)

		if togglePan then
			lockStateDirty = false
		end
		lastTogglePanChange = tick()
		lastTogglePan = togglePan
	end

	if isFirstPerson ~= lastFirstPerson then
		if isFirstPerson then
			wasTogglePanOnTheLastTimeYouWentIntoFirstPerson = Input.getTogglePan()
			Input.setTogglePan(true)
		elseif not lockStateDirty then
			Input.setTogglePan(wasTogglePanOnTheLastTimeYouWentIntoFirstPerson)
		end
	end

	if isFirstPerson then
		if Input.getTogglePan() then
			CameraUtils.setMouseIconOverride(CROSS_MOUSE_ICON)
			CameraUtils.setMouseBehaviorOverride(Enum.MouseBehavior.LockCenter)
			CameraUtils.setRotationTypeOverride(Enum.RotationType.CameraRelative)
		else
			CameraUtils.restoreMouseIcon()
			CameraUtils.restoreMouseBehavior()
			CameraUtils.setRotationTypeOverride(Enum.RotationType.CameraRelative)
		end

	elseif Input.getTogglePan() then
		CameraUtils.setMouseIconOverride(CROSS_MOUSE_ICON)
		CameraUtils.setMouseBehaviorOverride(Enum.MouseBehavior.LockCenter)
		CameraUtils.setRotationTypeOverride(Enum.RotationType.MovementRelative)

	elseif Input.getHoldPan() then
		CameraUtils.restoreMouseIcon()
		CameraUtils.setMouseBehaviorOverride(Enum.MouseBehavior.LockCurrentPosition)
		CameraUtils.setRotationTypeOverride(Enum.RotationType.MovementRelative)

	else
		CameraUtils.restoreMouseIcon()
		CameraUtils.restoreMouseBehavior()
		CameraUtils.restoreRotationType()
	end

	lastFirstPerson = isFirstPerson
end


--!nonstrict
local StarterGui = game:GetService("StarterGui")

local initialized = false

local CameraUI: any = {}

do
	-- Instantaneously disable the toast or enable for opening later on. Used when switching camera modes.
	function CameraUI.setCameraModeToastEnabled(enabled: boolean)
		if not enabled and not initialized then
			return
		end

		if not initialized then
			initialized = true
		end

		if not enabled then
			CameraUI.setCameraModeToastOpen(false)
		end
	end

	function CameraUI.setCameraModeToastOpen(open: boolean)
		assert(initialized)

		if open then
			StarterGui:SetCore("SendNotification", {
				Title = "Camera Control Enabled",
				Text = "Right click to toggle",
				Duration = 3,
			})
		end
	end
end

return CameraUI


--!nonstrict
--[[
	Invisicam - Occlusion module that makes objects occluding character view semi-transparent
	2018 Camera Update - AllYourBlox
--]]

--[[ Top Level Roblox Services ]]--
local PlayersService = game:GetService("Players")

--[[ Constants ]]--
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local USE_STACKING_TRANSPARENCY = true	-- Multiple items between the subject and camera get transparency values that add up to TARGET_TRANSPARENCY
local TARGET_TRANSPARENCY = 0.75 -- Classic Invisicam's Value, also used by new invisicam for parts hit by head and torso rays
local TARGET_TRANSPARENCY_PERIPHERAL = 0.5 -- Used by new SMART_CIRCLE mode for items not hit by head and torso rays

local MODE = {
	--CUSTOM = 1, 		-- Retired, unused
	LIMBS = 2, 			-- Track limbs
	MOVEMENT = 3, 		-- Track movement
	CORNERS = 4, 		-- Char model corners
	CIRCLE1 = 5, 		-- Circle of casts around character
	CIRCLE2 = 6, 		-- Circle of casts around character, camera relative
	LIMBMOVE = 7, 		-- LIMBS mode + MOVEMENT mode
	SMART_CIRCLE = 8, 	-- More sample points on and around character
	CHAR_OUTLINE = 9,	-- Dynamic outline around the character
}

local LIMB_TRACKING_SET = {
	-- Body parts common to R15 and R6
	['Head'] = true,

	-- Body parts unique to R6
	['Left Arm'] = true,
	['Right Arm'] = true,
	['Left Leg'] = true,
	['Right Leg'] = true,

	-- Body parts unique to R15
	['LeftLowerArm'] = true,
	['RightLowerArm'] = true,
	['LeftUpperLeg'] = true,
	['RightUpperLeg'] = true
}

local CORNER_FACTORS = {
	Vector3.new(1,1,-1),
	Vector3.new(1,-1,-1),
	Vector3.new(-1,-1,-1),
	Vector3.new(-1,1,-1)
}

local CIRCLE_CASTS = 10
local MOVE_CASTS = 3
local SMART_CIRCLE_CASTS = 24
local SMART_CIRCLE_INCREMENT = 2.0 * math.pi / SMART_CIRCLE_CASTS
local CHAR_OUTLINE_CASTS = 24

-- Used to sanitize user-supplied functions
local function AssertTypes(param, ...)
	local allowedTypes = {}
	local typeString = ''
	for _, typeName in pairs({...}) do
		allowedTypes[typeName] = true
		typeString = typeString .. (typeString == '' and '' or ' or ') .. typeName
	end
	local theType = type(param)
	assert(allowedTypes[theType], typeString .. " type expected, got: " .. theType)
end

-- Helper function for Determinant of 3x3, not in CameraUtils for performance reasons
local function Det3x3(a: number,b: number,c: number,d: number,e: number,f: number,g: number,h: number,i: number): number
	return (a*(e*i-f*h)-b*(d*i-f*g)+c*(d*h-e*g))
end

-- Smart Circle mode needs the intersection of 2 rays that are known to be in the same plane
-- because they are generated from cross products with a common vector. This function is computing
-- that intersection, but it's actually the general solution for the point halfway between where
-- two skew lines come nearest to each other, which is more forgiving.
local function RayIntersection(p0: Vector3, v0: Vector3, p1: Vector3, v1: Vector3): Vector3
	local v2 = v0:Cross(v1)
	local d1 = p1.X - p0.X
	local d2 = p1.Y - p0.Y
	local d3 = p1.Z - p0.Z
	local denom = Det3x3(v0.X,-v1.X,v2.X,v0.Y,-v1.Y,v2.Y,v0.Z,-v1.Z,v2.Z)

	if (denom == 0) then
		return ZERO_VECTOR3 -- No solution (rays are parallel)
	end

	local t0 = Det3x3(d1,-v1.X,v2.X,d2,-v1.Y,v2.Y,d3,-v1.Z,v2.Z) / denom
	local t1 = Det3x3(v0.X,d1,v2.X,v0.Y,d2,v2.Y,v0.Z,d3,v2.Z) / denom
	local s0 = p0 + t0 * v0
	local s1 = p1 + t1 * v1
	local s = s0 + 0.5 * ( s1 - s0 )

	-- 0.25 studs is a threshold for deciding if the rays are
	-- close enough to be considered intersecting, found through testing
	if (s1-s0).Magnitude < 0.25 then
		return s
	else
		return ZERO_VECTOR3
	end
end

--[[ The Module ]]--
local BaseOcclusion = require(script.Parent:WaitForChild("BaseOcclusion"))
local Invisicam = setmetatable({}, BaseOcclusion)
Invisicam.__index = Invisicam

function Invisicam.new()
	local self = setmetatable(BaseOcclusion.new(), Invisicam)

	self.char = nil
	self.humanoidRootPart = nil
	self.torsoPart = nil
	self.headPart = nil

	self.childAddedConn = nil
	self.childRemovedConn = nil

	self.behaviors = {} 	-- Map of modes to behavior fns
	self.behaviors[MODE.LIMBS] = self.LimbBehavior
	self.behaviors[MODE.MOVEMENT] = self.MoveBehavior
	self.behaviors[MODE.CORNERS] = self.CornerBehavior
	self.behaviors[MODE.CIRCLE1] = self.CircleBehavior
	self.behaviors[MODE.CIRCLE2] = self.CircleBehavior
	self.behaviors[MODE.LIMBMOVE] = self.LimbMoveBehavior
	self.behaviors[MODE.SMART_CIRCLE] = self.SmartCircleBehavior
	self.behaviors[MODE.CHAR_OUTLINE] = self.CharacterOutlineBehavior

	self.mode = MODE.SMART_CIRCLE
	self.behaviorFunction = self.SmartCircleBehavior

	self.savedHits = {} 	-- Objects currently being faded in/out
	self.trackedLimbs = {}	-- Used in limb-tracking casting modes

	self.camera = game.Workspace.CurrentCamera

	self.enabled = false
	return self
end

function Invisicam:Enable(enable)
	self.enabled = enable

	if not enable then
		self:Cleanup()
	end
end

function Invisicam:GetOcclusionMode()
	return Enum.DevCameraOcclusionMode.Invisicam
end

--[[ Module functions ]]--
function Invisicam:LimbBehavior(castPoints)
	for limb, _ in pairs(self.trackedLimbs) do
		castPoints[#castPoints + 1] = limb.Position
	end
end

function Invisicam:MoveBehavior(castPoints)
	for i = 1, MOVE_CASTS do
		local position: Vector3, velocity: Vector3 = self.humanoidRootPart.Position, self.humanoidRootPart.Velocity
		local horizontalSpeed: number = Vector3.new(velocity.X, 0, velocity.Z).Magnitude / 2
		local offsetVector: Vector3 = (i - 1) * self.humanoidRootPart.CFrame.lookVector :: Vector3 * horizontalSpeed
		castPoints[#castPoints + 1] = position + offsetVector
	end
end

function Invisicam:CornerBehavior(castPoints)
	local cframe: CFrame = self.humanoidRootPart.CFrame
	local centerPoint: Vector3 = cframe.Position
	local rotation = cframe - centerPoint
	local halfSize = self.char:GetExtentsSize() / 2 --NOTE: Doesn't update w/ limb animations
	castPoints[#castPoints + 1] = centerPoint
	for i = 1, #CORNER_FACTORS do
		castPoints[#castPoints + 1] = centerPoint + (rotation * (halfSize * CORNER_FACTORS[i]))
	end
end

function Invisicam:CircleBehavior(castPoints)
	local cframe: CFrame
	if self.mode == MODE.CIRCLE1 then
		cframe = self.humanoidRootPart.CFrame
	else
		local camCFrame: CFrame = self.camera.CoordinateFrame
		cframe = camCFrame - camCFrame.Position + self.humanoidRootPart.Position
	end
	castPoints[#castPoints + 1] = cframe.Position
	for i = 0, CIRCLE_CASTS - 1 do
		local angle = (2 * math.pi / CIRCLE_CASTS) * i
		local offset = 3 * Vector3.new(math.cos(angle), math.sin(angle), 0)
		castPoints[#castPoints + 1] = cframe * offset
	end
end

function Invisicam:LimbMoveBehavior(castPoints)
	self:LimbBehavior(castPoints)
	self:MoveBehavior(castPoints)
end

function Invisicam:CharacterOutlineBehavior(castPoints)
	local torsoUp = self.torsoPart.CFrame.upVector.unit
	local torsoRight = self.torsoPart.CFrame.rightVector.unit

	-- Torso cross of points for interior coverage
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
	if self.headPart then
		castPoints[#castPoints + 1] = self.headPart.CFrame.p
	end

	local cframe = CFrame.new(ZERO_VECTOR3,Vector3.new(self.camera.CoordinateFrame.lookVector.X,0,self.camera.CoordinateFrame.lookVector.Z))
	local centerPoint = (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)

	local partsWhitelist = {self.torsoPart}
	if self.headPart then
		partsWhitelist[#partsWhitelist + 1] = self.headPart
	end

	for i = 1, CHAR_OUTLINE_CASTS do
		local angle = (2 * math.pi * i / CHAR_OUTLINE_CASTS)
		local offset = cframe * (3 * Vector3.new(math.cos(angle), math.sin(angle), 0))

		offset = Vector3.new(offset.X, math.max(offset.Y, -2.25), offset.Z)

		local ray = Ray.new(centerPoint + offset, -3 * offset)
		local hit, hitPoint = game.Workspace:FindPartOnRayWithWhitelist(ray, partsWhitelist, false)

		if hit then
			-- Use hit point as the cast point, but nudge it slightly inside the character so that bumping up against
			-- walls is less likely to cause a transparency glitch
			castPoints[#castPoints + 1] = hitPoint + 0.2 * (centerPoint - hitPoint).unit
		end
	end
end

function Invisicam:SmartCircleBehavior(castPoints)
	local torsoUp = self.torsoPart.CFrame.upVector.unit
	local torsoRight = self.torsoPart.CFrame.rightVector.unit

	-- SMART_CIRCLE mode includes rays to head and 5 to the torso.
	-- Hands, arms, legs and feet are not included since they
	-- are not canCollide and can therefore go inside of parts
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoUp
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p + torsoRight
	castPoints[#castPoints + 1] = self.torsoPart.CFrame.p - torsoRight
	if self.headPart then
		castPoints[#castPoints + 1] = self.headPart.CFrame.p
	end

	local cameraOrientation = self.camera.CFrame - self.camera.CFrame.p
	local torsoPoint = Vector3.new(0,0.5,0) + (self.torsoPart and self.torsoPart.Position or self.humanoidRootPart.Position)
	local radius = 2.5

	-- This loop first calculates points in a circle of radius 2.5 around the torso of the character, in the
	-- plane orthogonal to the camera's lookVector. Each point is then raycast to, to determine if it is within
	-- the free space surrounding the player (not inside anything). Two iterations are done to adjust points that
	-- are inside parts, to try to move them to valid locations that are still on their camera ray, so that the
	-- circle remains circular from the camera's perspective, but does not cast rays into walls or parts that are
	-- behind, below or beside the character and not really obstructing view of the character. This minimizes
	-- the undesirable situation where the character walks up to an exterior wall and it is made invisible even
	-- though it is behind the character.
	for i = 1, SMART_CIRCLE_CASTS do
		local angle = SMART_CIRCLE_INCREMENT * i - 0.5 * math.pi
		local offset = radius * Vector3.new(math.cos(angle), math.sin(angle), 0)
		local circlePoint = torsoPoint + cameraOrientation * offset

		-- Vector from camera to point on the circle being tested
		local vp = circlePoint - self.camera.CFrame.p

		local ray = Ray.new(torsoPoint, circlePoint - torsoPoint)
		local hit, hp, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )
		local castPoint = circlePoint

		if hit then
			local hprime = hp + 0.1 * hitNormal.unit -- Slightly offset hit point from the hit surface
			local v0 = hprime - torsoPoint -- Vector from torso to offset hit point

			local perp = (v0:Cross(vp)).unit

			-- Vector from the offset hit point, along the hit surface
			local v1 = (perp:Cross(hitNormal)).unit

			-- Vector from camera to offset hit
			local vprime = (hprime - self.camera.CFrame.p).unit

			-- This dot product checks to see if the vector along the hit surface would hit the correct
			-- side of the invisicam cone, or if it would cross the camera look vector and hit the wrong side
			if ( v0.unit:Dot(-v1) < v0.unit:Dot(vprime)) then
				castPoint = RayIntersection(hprime, v1, circlePoint, vp)

				if castPoint.Magnitude > 0 then
					local ray = Ray.new(hprime, castPoint - hprime)
					local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )

					if hit then
						local hprime2 = hitPoint + 0.1 * hitNormal.unit
						castPoint = hprime2
					end
				else
					castPoint = hprime
				end
			else
				castPoint = hprime
			end

			local ray = Ray.new(torsoPoint, (castPoint - torsoPoint))
			local hit, hitPoint, hitNormal = game.Workspace:FindPartOnRayWithIgnoreList(ray, {self.char}, false, false )

			if hit then
				local castPoint2 = hitPoint - 0.1 * (castPoint - torsoPoint).unit
				castPoint = castPoint2
			end
		end

		castPoints[#castPoints + 1] = castPoint
	end
end

function Invisicam:CheckTorsoReference()
	if self.char then
		self.torsoPart = self.char:FindFirstChild("Torso")
		if not self.torsoPart then
			self.torsoPart = self.char:FindFirstChild("UpperTorso")
			if not self.torsoPart then
				self.torsoPart = self.char:FindFirstChild("HumanoidRootPart")
			end
		end

		self.headPart = self.char:FindFirstChild("Head")
	end
end

function Invisicam:CharacterAdded(char: Model, player: Player)
	-- We only want the LocalPlayer's character
	if player~=PlayersService.LocalPlayer then return end

	if self.childAddedConn then
		self.childAddedConn:Disconnect()
		self.childAddedConn = nil
	end
	if self.childRemovedConn then
		self.childRemovedConn:Disconnect()
		self.childRemovedConn = nil
	end

	self.char = char

	self.trackedLimbs = {}
	local function childAdded(child)
		if child:IsA("BasePart") then
			if LIMB_TRACKING_SET[child.Name] then
				self.trackedLimbs[child] = true
			end

			if child.Name == "Torso" or child.Name == "UpperTorso" then
				self.torsoPart = child
			end

			if child.Name == "Head" then
				self.headPart = child
			end
		end
	end

	local function childRemoved(child)
		self.trackedLimbs[child] = nil

		-- If removed/replaced part is 'Torso' or 'UpperTorso' double check that we still have a TorsoPart to use
		self:CheckTorsoReference()
	end

	self.childAddedConn = char.ChildAdded:Connect(childAdded)
	self.childRemovedConn = char.ChildRemoved:Connect(childRemoved)
	for _, child in pairs(self.char:GetChildren()) do
		childAdded(child)
	end
end

function Invisicam:SetMode(newMode: number)
	AssertTypes(newMode, 'number')
	for _, modeNum in pairs(MODE) do
		if modeNum == newMode then
			self.mode = newMode
			self.behaviorFunction = self.behaviors[self.mode]
			return
		end
	end
	error("Invalid mode number")
end

function Invisicam:GetObscuredParts()
	return self.savedHits
end

-- Want to turn off Invisicam? Be sure to call this after.
function Invisicam:Cleanup()
	for hit, originalFade in pairs(self.savedHits) do
		hit.LocalTransparencyModifier = originalFade
	end
end

function Invisicam:Update(dt: number, desiredCameraCFrame: CFrame, desiredCameraFocus: CFrame): (CFrame, CFrame)
	-- Bail if there is no Character
	if not self.enabled or not self.char then
		return desiredCameraCFrame, desiredCameraFocus
	end

	self.camera = game.Workspace.CurrentCamera

	-- TODO: Move this to a GetHumanoidRootPart helper, probably combine with CheckTorsoReference
	-- Make sure we still have a HumanoidRootPart
	if not self.humanoidRootPart then
		local humanoid = self.char:FindFirstChildOfClass("Humanoid")
		if humanoid and humanoid.RootPart then
			self.humanoidRootPart = humanoid.RootPart
		else
			-- Not set up with Humanoid? Try and see if there's one in the Character at all:
			self.humanoidRootPart = self.char:FindFirstChild("HumanoidRootPart")
			if not self.humanoidRootPart then
				-- Bail out, since we're relying on HumanoidRootPart existing
				return desiredCameraCFrame, desiredCameraFocus
			end
		end

		-- TODO: Replace this with something more sensible
		local ancestryChangedConn
		ancestryChangedConn = self.humanoidRootPart.AncestryChanged:Connect(function(child, parent)
			if child == self.humanoidRootPart and not parent then
				self.humanoidRootPart = nil
				if ancestryChangedConn and ancestryChangedConn.Connected then
					ancestryChangedConn:Disconnect()
					ancestryChangedConn = nil
				end
			end
		end)
	end

	if not self.torsoPart then
		self:CheckTorsoReference()
		if not self.torsoPart then
			-- Bail out, since we're relying on Torso existing, should never happen since we fall back to using HumanoidRootPart as torso
			return desiredCameraCFrame, desiredCameraFocus
		end
	end

	-- Make a list of world points to raycast to
	local castPoints = {}
	self.behaviorFunction(self, castPoints)

	-- Cast to get a list of objects between the camera and the cast points
	local currentHits = {}
	local ignoreList = {self.char}
	local function add(hit)
		currentHits[hit] = true
		if not self.savedHits[hit] then
			self.savedHits[hit] = hit.LocalTransparencyModifier
		end
	end

	local hitParts
	local hitPartCount = 0

	-- Hash table to treat head-ray-hit parts differently than the rest of the hit parts hit by other rays
	-- head/torso ray hit parts will be more transparent than peripheral parts when USE_STACKING_TRANSPARENCY is enabled
	local headTorsoRayHitParts = {}

	local perPartTransparencyHeadTorsoHits = TARGET_TRANSPARENCY
	local perPartTransparencyOtherHits = TARGET_TRANSPARENCY

	if USE_STACKING_TRANSPARENCY then

		-- This first call uses head and torso rays to find out how many parts are stacked up
		-- for the purpose of calculating required per-part transparency
		local headPoint = self.headPart and self.headPart.CFrame.p or castPoints[1]
		local torsoPoint = self.torsoPart and self.torsoPart.CFrame.p or castPoints[2]
		hitParts = self.camera:GetPartsObscuringTarget({headPoint, torsoPoint}, ignoreList)

		-- Count how many things the sample rays passed through, including decals. This should only
		-- count decals facing the camera, but GetPartsObscuringTarget does not return surface normals,
		-- so my compromise for now is to just let any decal increase the part count by 1. Only one
		-- decal per part will be considered.
		for i = 1, #hitParts do
			local hitPart = hitParts[i]
			hitPartCount = hitPartCount + 1 -- count the part itself
			headTorsoRayHitParts[hitPart] = true
			for _, child in pairs(hitPart:GetChildren()) do
				if child:IsA('Decal') or child:IsA('Texture') then
					hitPartCount = hitPartCount + 1 -- count first decal hit, then break
					break
				end
			end
		end

		if (hitPartCount > 0) then
			perPartTransparencyHeadTorsoHits = math.pow( ((0.5 * TARGET_TRANSPARENCY) + (0.5 * TARGET_TRANSPARENCY / hitPartCount)), 1 / hitPartCount )
			perPartTransparencyOtherHits = math.pow( ((0.5 * TARGET_TRANSPARENCY_PERIPHERAL) + (0.5 * TARGET_TRANSPARENCY_PERIPHERAL / hitPartCount)), 1 / hitPartCount )
		end
	end

	-- Now get all the parts hit by all the rays
	hitParts = self.camera:GetPartsObscuringTarget(castPoints, ignoreList)

	local partTargetTransparency = {}

	-- Include decals and textures
	for i = 1, #hitParts do
		local hitPart = hitParts[i]

		partTargetTransparency[hitPart] =headTorsoRayHitParts[hitPart] and perPartTransparencyHeadTorsoHits or perPartTransparencyOtherHits

		-- If the part is not already as transparent or more transparent than what invisicam requires, add it to the list of
		-- parts to be modified by invisicam
		if hitPart.Transparency < partTargetTransparency[hitPart] then
			add(hitPart)
		end

		-- Check all decals and textures on the part
		for _, child in pairs(hitPart:GetChildren()) do
			if child:IsA('Decal') or child:IsA('Texture') then
				if (child.Transparency < partTargetTransparency[hitPart]) then
					partTargetTransparency[child] = partTargetTransparency[hitPart]
					add(child)
				end
			end
		end
	end

	-- Invisibilize objects that are in the way, restore those that aren't anymore
	for hitPart, originalLTM in pairs(self.savedHits) do
		if currentHits[hitPart] then
			-- LocalTransparencyModifier gets whatever value is required to print the part's total transparency to equal perPartTransparency
			hitPart.LocalTransparencyModifier = (hitPart.Transparency < 1) and ((partTargetTransparency[hitPart] - hitPart.Transparency) / (1.0 - hitPart.Transparency)) or 0
		else -- Restore original pre-invisicam value of LTM
			hitPart.LocalTransparencyModifier = originalLTM
			self.savedHits[hitPart] = nil
		end
	end

	-- Invisicam does not change the camera values
	return desiredCameraCFrame, desiredCameraFocus
end

return Invisicam


--!nonstrict
--[[
	LegacyCamera - Implements legacy controller types: Attach, Fixed, Watch
	2018 Camera Update - AllYourBlox
--]]

local ZERO_VECTOR2 = Vector2.new()
local PITCH_LIMIT = math.rad(80)

local Util = require(script.Parent:WaitForChild("CameraUtils"))
local CameraInput = require(script.Parent:WaitForChild("CameraInput"))

--[[ Services ]]--
local PlayersService = game:GetService('Players')

--[[ The Module ]]--
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
local LegacyCamera = setmetatable({}, BaseCamera)
LegacyCamera.__index = LegacyCamera

function LegacyCamera.new()
	local self = setmetatable(BaseCamera.new(), LegacyCamera)

	self.cameraType = Enum.CameraType.Fixed
	self.lastUpdate = tick()
	self.lastDistanceToSubject = nil

	return self
end

function LegacyCamera:GetModuleName()
	return "LegacyCamera"
end

--[[ Functions overridden from BaseCamera ]]--
function LegacyCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
	return BaseCamera.SetCameraToSubjectDistance(self,desiredSubjectDistance)
end

function LegacyCamera:Update(dt: number): (CFrame?, CFrame?)

	-- Cannot update until cameraType has been set
	if not self.cameraType then
		return nil, nil
	end

	local now = tick()
	local timeDelta = (now - self.lastUpdate)
	local camera = 	workspace.CurrentCamera
	local newCameraCFrame = camera.CFrame
	local newCameraFocus = camera.Focus
	local player = PlayersService.LocalPlayer

	if self.lastUpdate == nil or timeDelta > 1 then
		self.lastDistanceToSubject = nil
	end
	local subjectPosition: Vector3 = self:GetSubjectPosition()

	if self.cameraType == Enum.CameraType.Fixed then
		if subjectPosition and player and camera then
			local distanceToSubject = self:GetCameraToSubjectDistance()
			local newLookVector = self:CalculateNewLookVectorFromArg(nil, CameraInput.getRotation())

			newCameraFocus = camera.Focus -- Fixed camera does not change focus
			newCameraCFrame = CFrame.new(camera.CFrame.p, camera.CFrame.p + (distanceToSubject * newLookVector))
		end

	elseif self.cameraType == Enum.CameraType.Attach then
		local subjectCFrame = self:GetSubjectCFrame()
		local cameraPitch = camera.CFrame:ToEulerAnglesYXZ()
		local _, subjectYaw = subjectCFrame:ToEulerAnglesYXZ()

		cameraPitch = math.clamp(cameraPitch - CameraInput.getRotation().Y, -PITCH_LIMIT, PITCH_LIMIT)

		newCameraFocus = CFrame.new(subjectCFrame.p)*CFrame.fromEulerAnglesYXZ(cameraPitch, subjectYaw, 0)
		newCameraCFrame = newCameraFocus*CFrame.new(0, 0, self:StepZoom())

	elseif self.cameraType == Enum.CameraType.Watch then
		if subjectPosition and player and camera then
			local cameraLook = nil

			if subjectPosition == camera.CFrame.p then
				warn("Camera cannot watch subject in same position as itself")
				return camera.CFrame, camera.Focus
			end

			local humanoid = self:GetHumanoid()
			if humanoid and humanoid.RootPart then
				local diffVector = subjectPosition - camera.CFrame.p
				cameraLook = diffVector.unit

				if self.lastDistanceToSubject and self.lastDistanceToSubject == self:GetCameraToSubjectDistance() then
					-- Don't clobber the zoom if they zoomed the camera
					local newDistanceToSubject = diffVector.magnitude
					self:SetCameraToSubjectDistance(newDistanceToSubject)
				end
			end

			local distanceToSubject: number = self:GetCameraToSubjectDistance()
			local newLookVector: Vector3 = self:CalculateNewLookVectorFromArg(cameraLook, CameraInput.getRotation())

			newCameraFocus = CFrame.new(subjectPosition)
			newCameraCFrame = CFrame.new(subjectPosition - (distanceToSubject * newLookVector), subjectPosition)

			self.lastDistanceToSubject = distanceToSubject
		end
	else
		-- Unsupported type, return current values unchanged
		return camera.CFrame, camera.Focus
	end

	self.lastUpdate = now
	return newCameraCFrame, newCameraFocus
end

return LegacyCamera


--!nonstrict
--[[
	MouseLockController - Replacement for ShiftLockController, manages use of mouse-locked mode
	2018 Camera Update - AllYourBlox
--]]

--[[ Constants ]]--
local DEFAULT_MOUSE_LOCK_CURSOR = "rbxasset://textures/MouseLockedCursor.png"

local CONTEXT_ACTION_NAME = "MouseLockSwitchAction"
local MOUSELOCK_ACTION_PRIORITY = Enum.ContextActionPriority.Medium.Value

--[[ Services ]]--
local PlayersService = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local Settings = UserSettings()	-- ignore warning
local GameSettings = Settings.GameSettings

--[[ Imports ]]
local CameraUtils = require(script.Parent:WaitForChild("CameraUtils"))

--[[ The Module ]]--
local MouseLockController = {}
MouseLockController.__index = MouseLockController

function MouseLockController.new()
	local self = setmetatable({}, MouseLockController)

	self.isMouseLocked = false
	self.savedMouseCursor = nil
	self.boundKeys = {Enum.KeyCode.LeftShift, Enum.KeyCode.RightShift} -- defaults

	self.mouseLockToggledEvent = Instance.new("BindableEvent")

	local boundKeysObj = script:FindFirstChild("BoundKeys")
	if (not boundKeysObj) or (not boundKeysObj:IsA("StringValue")) then
		-- If object with correct name was found, but it's not a StringValue, destroy and replace
		if boundKeysObj then
			boundKeysObj:Destroy()
		end

		boundKeysObj = Instance.new("StringValue")
		-- Luau FIXME: should be able to infer from assignment above that boundKeysObj is not nil
		assert(boundKeysObj, "")
		boundKeysObj.Name = "BoundKeys"
		boundKeysObj.Value = "LeftShift,RightShift"
		boundKeysObj.Parent = script
	end

	if boundKeysObj then
		boundKeysObj.Changed:Connect(function(value)
			self:OnBoundKeysObjectChanged(value)
		end)
		self:OnBoundKeysObjectChanged(boundKeysObj.Value) -- Initial setup call
	end

	-- Watch for changes to user's ControlMode and ComputerMovementMode settings and update the feature availability accordingly
	GameSettings.Changed:Connect(function(property)
		if property == "ControlMode" or property == "ComputerMovementMode" then
			self:UpdateMouseLockAvailability()
		end
	end)

	-- Watch for changes to DevEnableMouseLock and update the feature availability accordingly
	PlayersService.LocalPlayer:GetPropertyChangedSignal("DevEnableMouseLock"):Connect(function()
		self:UpdateMouseLockAvailability()
	end)

	-- Watch for changes to DevEnableMouseLock and update the feature availability accordingly
	PlayersService.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(function()
		self:UpdateMouseLockAvailability()
	end)

	self:UpdateMouseLockAvailability()

	return self
end

function MouseLockController:GetIsMouseLocked()
	return self.isMouseLocked
end

function MouseLockController:GetBindableToggleEvent()
	return self.mouseLockToggledEvent.Event
end

function MouseLockController:GetMouseLockOffset()
	local offsetValueObj: Vector3Value = script:FindFirstChild("CameraOffset") :: Vector3Value
	if offsetValueObj and offsetValueObj:IsA("Vector3Value") then
		return offsetValueObj.Value
	else
		-- If CameraOffset object was found but not correct type, destroy
		if offsetValueObj then
			offsetValueObj:Destroy()
		end
		offsetValueObj = Instance.new("Vector3Value")
		assert(offsetValueObj, "")
		offsetValueObj.Name = "CameraOffset"
		offsetValueObj.Value = Vector3.new(1.75,0,0) -- Legacy Default Value
		offsetValueObj.Parent = script
	end

	if offsetValueObj and offsetValueObj.Value then
		return offsetValueObj.Value
	end

	return Vector3.new(1.75,0,0)
end

function MouseLockController:UpdateMouseLockAvailability()
	local devAllowsMouseLock = PlayersService.LocalPlayer.DevEnableMouseLock
	local devMovementModeIsScriptable = PlayersService.LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.Scriptable
	local userHasMouseLockModeEnabled = GameSettings.ControlMode == Enum.ControlMode.MouseLockSwitch
	local userHasClickToMoveEnabled =  GameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove
	local MouseLockAvailable = devAllowsMouseLock and userHasMouseLockModeEnabled and not userHasClickToMoveEnabled and not devMovementModeIsScriptable

	if MouseLockAvailable~=self.enabled then
		self:EnableMouseLock(MouseLockAvailable)
	end
end

function MouseLockController:OnBoundKeysObjectChanged(newValue: string)
	self.boundKeys = {} -- Overriding defaults, note: possibly with nothing at all if boundKeysObj.Value is "" or contains invalid values
	for token in string.gmatch(newValue,"[^%s,]+") do
		for _, keyEnum in pairs(Enum.KeyCode:GetEnumItems()) do
			if token == keyEnum.Name then
				self.boundKeys[#self.boundKeys+1] = keyEnum :: Enum.KeyCode
				break
			end
		end
	end
	self:UnbindContextActions()
	self:BindContextActions()
end

--[[ Local Functions ]]--
function MouseLockController:OnMouseLockToggled()
	self.isMouseLocked = not self.isMouseLocked

	if self.isMouseLocked then
		local cursorImageValueObj: StringValue? = script:FindFirstChild("CursorImage") :: StringValue?
		if cursorImageValueObj and cursorImageValueObj:IsA("StringValue") and cursorImageValueObj.Value then
			CameraUtils.setMouseIconOverride(cursorImageValueObj.Value)
		else
			if cursorImageValueObj then
				cursorImageValueObj:Destroy()
			end
			cursorImageValueObj = Instance.new("StringValue")
			assert(cursorImageValueObj, "")
			cursorImageValueObj.Name = "CursorImage"
			cursorImageValueObj.Value = DEFAULT_MOUSE_LOCK_CURSOR
			cursorImageValueObj.Parent = script
			CameraUtils.setMouseIconOverride(DEFAULT_MOUSE_LOCK_CURSOR)
		end
	else
		CameraUtils.restoreMouseIcon()
	end

	self.mouseLockToggledEvent:Fire()
end

function MouseLockController:DoMouseLockSwitch(name, state, input)
	if state == Enum.UserInputState.Begin then
		self:OnMouseLockToggled()
		return Enum.ContextActionResult.Sink
	end
	return Enum.ContextActionResult.Pass
end

function MouseLockController:BindContextActions()
	ContextActionService:BindActionAtPriority(CONTEXT_ACTION_NAME, function(name, state, input)
		return self:DoMouseLockSwitch(name, state, input)
	end, false, MOUSELOCK_ACTION_PRIORITY, unpack(self.boundKeys))
end

function MouseLockController:UnbindContextActions()
	ContextActionService:UnbindAction(CONTEXT_ACTION_NAME)
end

function MouseLockController:IsMouseLocked(): boolean
	return self.enabled and self.isMouseLocked
end

function MouseLockController:EnableMouseLock(enable: boolean)
	if enable ~= self.enabled then

		self.enabled = enable

		if self.enabled then
			-- Enabling the mode
			self:BindContextActions()
		else
			-- Disabling
			-- Restore mouse cursor
			CameraUtils.restoreMouseIcon()

			self:UnbindContextActions()

			-- If the mode is disabled while being used, fire the event to toggle it off
			if self.isMouseLocked then
				self.mouseLockToggledEvent:Fire()
			end

			self.isMouseLocked = false
		end

	end
end

return MouseLockController


--!nonstrict
--[[
	OrbitalCamera - Spherical coordinates control camera for top-down games
	2018 Camera Update - AllYourBlox
--]]

-- Local private variables and constants
local UNIT_Z = Vector3.new(0,0,1)
local X1_Y0_Z1 = Vector3.new(1,0,1)	--Note: not a unit vector, used for projecting onto XZ plane
local ZERO_VECTOR3 = Vector3.new(0,0,0)
local TAU = 2 * math.pi

-- Do not edit these values, they are not the developer-set limits, they are limits
-- to the values the camera system equations can correctly handle
local MIN_ALLOWED_ELEVATION_DEG = -80
local MAX_ALLOWED_ELEVATION_DEG = 80

local externalProperties = {}
externalProperties["InitialDistance"]  = 25
externalProperties["MinDistance"]      = 10
externalProperties["MaxDistance"]      = 100
externalProperties["InitialElevation"] = 35
externalProperties["MinElevation"]     = 35
externalProperties["MaxElevation"]     = 35
externalProperties["ReferenceAzimuth"] = -45	-- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
externalProperties["CWAzimuthTravel"]  = 90	-- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
externalProperties["CCWAzimuthTravel"] = 90	-- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
externalProperties["UseAzimuthLimits"] = false -- Full rotation around Y axis available by default

local Util = require(script.Parent:WaitForChild("CameraUtils"))
local CameraInput = require(script.Parent:WaitForChild("CameraInput"))

--[[ Services ]]--
local PlayersService = game:GetService('Players')
local VRService = game:GetService("VRService")

--[[ The Module ]]--
local BaseCamera = require(script.Parent:WaitForChild("BaseCamera"))
local OrbitalCamera = setmetatable({}, BaseCamera)
OrbitalCamera.__index = OrbitalCamera

function OrbitalCamera.new()
	local self = setmetatable(BaseCamera.new(), OrbitalCamera)

	self.lastUpdate = tick()

	-- OrbitalCamera-specific members
	self.changedSignalConnections = {}
	self.refAzimuthRad = nil
	self.curAzimuthRad = nil
	self.minAzimuthAbsoluteRad = nil
	self.maxAzimuthAbsoluteRad = nil
	self.useAzimuthLimits = nil
	self.curElevationRad = nil
	self.minElevationRad = nil
	self.maxElevationRad = nil
	self.curDistance = nil
	self.minDistance = nil
	self.maxDistance = nil

	self.gamepadDollySpeedMultiplier = 1

	self.lastUserPanCamera = tick()

	self.externalProperties = {}
	self.externalProperties["InitialDistance"] 	= 25
	self.externalProperties["MinDistance"] 		= 10
	self.externalProperties["MaxDistance"] 		= 100
	self.externalProperties["InitialElevation"] 	= 35
	self.externalProperties["MinElevation"] 		= 35
	self.externalProperties["MaxElevation"] 		= 35
	self.externalProperties["ReferenceAzimuth"] 	= -45	-- Angle around the Y axis where the camera starts. -45 offsets the camera in the -X and +Z directions equally
	self.externalProperties["CWAzimuthTravel"] 	= 90	-- How many degrees the camera is allowed to rotate from the reference position, CW as seen from above
	self.externalProperties["CCWAzimuthTravel"] 	= 90	-- How many degrees the camera is allowed to rotate from the reference position, CCW as seen from above
	self.externalProperties["UseAzimuthLimits"] 	= false -- Full rotation around Y axis available by default
	self:LoadNumberValueParameters()

	return self
end

function OrbitalCamera:LoadOrCreateNumberValueParameter(name: string, valueType, updateFunction)
	local valueObj = script:FindFirstChild(name)

	if valueObj and valueObj:isA(valueType) then
		-- Value object exists and is the correct type, use its value
		self.externalProperties[name] = valueObj.Value
	elseif self.externalProperties[name] ~= nil then
		-- Create missing (or replace incorrectly-typed) valueObject with default value
		valueObj = Instance.new(valueType)
		valueObj.Name = name
		valueObj.Parent = script
		valueObj.Value = self.externalProperties[name]
	else
		return
	end

	if updateFunction then
		if self.changedSignalConnections[name] then
			self.changedSignalConnections[name]:Disconnect()
		end
		self.changedSignalConnections[name] = valueObj.Changed:Connect(function(newValue)
			self.externalProperties[name] = newValue
			updateFunction(self)
		end)
	end
end

function OrbitalCamera:SetAndBoundsCheckAzimuthValues()
	self.minAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"]) - math.abs(math.rad(self.externalProperties["CWAzimuthTravel"]))
	self.maxAzimuthAbsoluteRad = math.rad(self.externalProperties["ReferenceAzimuth"]) + math.abs(math.rad(self.externalProperties["CCWAzimuthTravel"]))
	self.useAzimuthLimits = self.externalProperties["UseAzimuthLimits"]
	if self.useAzimuthLimits then
		self.curAzimuthRad = math.max(self.curAzimuthRad, self.minAzimuthAbsoluteRad)
		self.curAzimuthRad = math.min(self.curAzimuthRad, self.maxAzimuthAbsoluteRad)
	end
end

function OrbitalCamera:SetAndBoundsCheckElevationValues()
	-- These degree values are the direct user input values. It is deliberate that they are
	-- ranged checked only against the extremes, and not against each other. Any time one
	-- is changed, both of the internal values in radians are recalculated. This allows for
	-- A developer to change the values in any order and for the end results to be that the
	-- internal values adjust to match intent as best as possible.
	local minElevationDeg = math.max(self.externalProperties["MinElevation"], MIN_ALLOWED_ELEVATION_DEG)
	local maxElevationDeg = math.min(self.externalProperties["MaxElevation"], MAX_ALLOWED_ELEVATION_DEG)

	-- Set internal values in radians
	self.minElevationRad = math.rad(math.min(minElevationDeg, maxElevationDeg))
	self.maxElevationRad = math.rad(math.max(minElevationDeg, maxElevationDeg))
	self.curElevationRad = math.max(self.curElevationRad, self.minElevationRad)
	self.curElevationRad = math.min(self.curElevationRad, self.maxElevationRad)
end

function OrbitalCamera:SetAndBoundsCheckDistanceValues()
	self.minDistance = self.externalProperties["MinDistance"]
	self.maxDistance = self.externalProperties["MaxDistance"]
	self.curDistance = math.max(self.curDistance, self.minDistance)
	self.curDistance = math.min(self.curDistance, self.maxDistance)
end

-- This loads from, or lazily creates, NumberValue objects for exposed parameters
function OrbitalCamera:LoadNumberValueParameters()
	-- These initial values do not require change listeners since they are read only once
	self:LoadOrCreateNumberValueParameter("InitialElevation", "NumberValue", nil)
	self:LoadOrCreateNumberValueParameter("InitialDistance", "NumberValue", nil)

	-- Note: ReferenceAzimuth is also used as an initial value, but needs a change listener because it is used in the calculation of the limits
	self:LoadOrCreateNumberValueParameter("ReferenceAzimuth", "NumberValue", self.SetAndBoundsCheckAzimuthValue)
	self:LoadOrCreateNumberValueParameter("CWAzimuthTravel", "NumberValue", self.SetAndBoundsCheckAzimuthValues)
	self:LoadOrCreateNumberValueParameter("CCWAzimuthTravel", "NumberValue", self.SetAndBoundsCheckAzimuthValues)
	self:LoadOrCreateNumberValueParameter("MinElevation", "NumberValue", self.SetAndBoundsCheckElevationValues)
	self:LoadOrCreateNumberValueParameter("MaxElevation", "NumberValue", self.SetAndBoundsCheckElevationValues)
	self:LoadOrCreateNumberValueParameter("MinDistance", "NumberValue", self.SetAndBoundsCheckDistanceValues)
	self:LoadOrCreateNumberValueParameter("MaxDistance", "NumberValue", self.SetAndBoundsCheckDistanceValues)
	self:LoadOrCreateNumberValueParameter("UseAzimuthLimits", "BoolValue", self.SetAndBoundsCheckAzimuthValues)

	-- Internal values set (in radians, from degrees), plus sanitization
	self.curAzimuthRad = math.rad(self.externalProperties["ReferenceAzimuth"])
	self.curElevationRad = math.rad(self.externalProperties["InitialElevation"])
	self.curDistance = self.externalProperties["InitialDistance"]

	self:SetAndBoundsCheckAzimuthValues()
	self:SetAndBoundsCheckElevationValues()
	self:SetAndBoundsCheckDistanceValues()
end

function OrbitalCamera:GetModuleName()
	return "OrbitalCamera"
end

function OrbitalCamera:SetInitialOrientation(humanoid: Humanoid)
	if not humanoid or not humanoid.RootPart then
		warn("OrbitalCamera could not set initial orientation due to missing humanoid")
		return
	end
	assert(humanoid.RootPart, "")
	local newDesiredLook = (humanoid.RootPart.CFrame.LookVector - Vector3.new(0,0.23,0)).Unit
	local horizontalShift = Util.GetAngleBetweenXZVectors(newDesiredLook, self:GetCameraLookVector())
	local vertShift = math.asin(self:GetCameraLookVector().Y) - math.asin(newDesiredLook.Y)
	if not Util.IsFinite(horizontalShift) then
		horizontalShift = 0
	end
	if not Util.IsFinite(vertShift) then
		vertShift = 0
	end
end

--[[ Functions of BaseCamera that are overridden by OrbitalCamera ]]--
function OrbitalCamera:GetCameraToSubjectDistance()
	return self.curDistance
end

function OrbitalCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
	local player = PlayersService.LocalPlayer
	if player then
		self.currentSubjectDistance = math.clamp(desiredSubjectDistance, self.minDistance, self.maxDistance)

		-- OrbitalCamera is not allowed to go into the first-person range
		self.currentSubjectDistance = math.max(self.currentSubjectDistance, self.FIRST_PERSON_DISTANCE_THRESHOLD)
	end
	self.inFirstPerson = false
	self:UpdateMouseBehavior()
	return self.currentSubjectDistance
end

function OrbitalCamera:CalculateNewLookVector(suppliedLookVector: Vector3, xyRotateVector: Vector2): Vector3
	local currLookVector: Vector3 = suppliedLookVector or self:GetCameraLookVector()
	local currPitchAngle: number = math.asin(currLookVector.Y)
	local yTheta: number = math.clamp(xyRotateVector.Y, currPitchAngle - math.rad(MAX_ALLOWED_ELEVATION_DEG), currPitchAngle - math.rad(MIN_ALLOWED_ELEVATION_DEG))
	local constrainedRotateInput: Vector2 = Vector2.new(xyRotateVector.X, yTheta)
	local startCFrame: CFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
	local newLookVector: Vector3 = (CFrame.Angles(0, -constrainedRotateInput.X, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.Y,0,0)).LookVector
	return newLookVector
end

-- [[ Update ]]--
function OrbitalCamera:Update(dt: number): (CFrame, CFrame)
	local now = tick()
	local timeDelta = (now - self.lastUpdate)
	local userPanningTheCamera = CameraInput.getRotation() ~= Vector2.new()
	local camera = 	workspace.CurrentCamera
	local newCameraCFrame = camera.CFrame
	local newCameraFocus = camera.Focus
	local player = PlayersService.LocalPlayer
	local cameraSubject = camera and camera.CameraSubject
	local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
	local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')

	if self.lastUpdate == nil or timeDelta > 1 then
		self.lastCameraTransform = nil
	end

	-- Reset tween speed if user is panning
	if userPanningTheCamera then
		self.lastUserPanCamera = tick()
	end

	local subjectPosition = self:GetSubjectPosition()

	if subjectPosition and player and camera then

		-- Process any dollying being done by gamepad
		-- TODO: Move this
		if self.gamepadDollySpeedMultiplier ~= 1 then
			self:SetCameraToSubjectDistance(self.currentSubjectDistance * self.gamepadDollySpeedMultiplier)
		end

		local VREnabled = VRService.VREnabled
		newCameraFocus = VREnabled and self:GetVRFocus(subjectPosition, timeDelta) or CFrame.new(subjectPosition)

		local flaggedRotateInput = CameraInput.getRotation()

		local cameraFocusP = newCameraFocus.p
		if VREnabled and not self:IsInFirstPerson() then
			local cameraHeight = self:GetCameraHeight()
			local vecToSubject: Vector3 = (subjectPosition - camera.CFrame.p)
			local distToSubject: number = vecToSubject.Magnitude

			-- Only move the camera if it exceeded a maximum distance to the subject in VR
			if distToSubject > self.currentSubjectDistance or flaggedRotateInput.X ~= 0 then
				local desiredDist = math.min(distToSubject, self.currentSubjectDistance)

				-- Note that CalculateNewLookVector is overridden from BaseCamera
				vecToSubject = self:CalculateNewLookVector(vecToSubject.Unit * X1_Y0_Z1, Vector2.new(flaggedRotateInput.X, 0)) * desiredDist

				local newPos = cameraFocusP - vecToSubject
				local desiredLookDir = camera.CFrame.LookVector
				if flaggedRotateInput.X ~= 0 then
					desiredLookDir = vecToSubject
				end
				local lookAt = Vector3.new(newPos.X + desiredLookDir.X, newPos.Y, newPos.Z + desiredLookDir.Z)
				newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
			end
		else
			-- rotateInput is a Vector2 of mouse movement deltas since last update
			self.curAzimuthRad = self.curAzimuthRad - flaggedRotateInput.X

			if self.useAzimuthLimits then
				self.curAzimuthRad = math.clamp(self.curAzimuthRad, self.minAzimuthAbsoluteRad, self.maxAzimuthAbsoluteRad)
			else
				self.curAzimuthRad = (self.curAzimuthRad ~= 0) and (math.sign(self.curAzimuthRad) * (math.abs(self.curAzimuthRad) % TAU)) or 0
			end

			self.curElevationRad = math.clamp(self.curElevationRad + flaggedRotateInput.Y, self.minElevationRad, self.maxElevationRad)

			local cameraPosVector = self.currentSubjectDistance * ( CFrame.fromEulerAnglesYXZ( -self.curElevationRad, self.curAzimuthRad, 0 ) * UNIT_Z )
			local camPos = subjectPosition + cameraPosVector

			newCameraCFrame = CFrame.new(camPos, subjectPosition)
		end

		self.lastCameraTransform = newCameraCFrame
		self.lastCameraFocus = newCameraFocus
		if (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
			self.lastSubjectCFrame = cameraSubject.CFrame
		else
			self.lastSubjectCFrame = nil
		end
	end

	self.lastUpdate = now
	return newCameraCFrame, newCameraFocus
end

return OrbitalCamera