All content on our site is free and will always be free.

Please consider supporting us with disabling your AdBlock software and to gain access to thousands of free content!

Not sure how to disable AdBLock? Follow this tutorial: How to disable AdBlock

Get the Walk on walls script

The Walk on walls script code is below. Copy & inject it into the game. Enjoy!

Universal Script ? - This script makes you walk on walls, it is univesal, so you can use it in every game!
--[[
local _p = game:WaitForChild("Players")
local _plr = _p.ChildAdded:Wait()
if _plr == _p.LocalPlayer then
	_plr.ChildAdded:Connect(function(cccc)
		if c.Name == "PlayerScriptsLoader" then
			c.Disabled = true
		end
	end)
end
]]
repeat wait()
a = pcall(function()
	game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:Connect(function(c)
		if c.Name == "PlayerScriptsLoader"then
			c.Disabled = true
		end
	end)
	end)
	if a == true then break end
until true == false
game:WaitForChild("Players").LocalPlayer:WaitForChild("PlayerScripts").ChildAdded:Connect(function(c)
	if c.Name == "PlayerScriptsLoader"then
		c.Disabled = true
	end
end)


function _CameraUI()
	local Players = game:GetService("Players")
	local TweenService = game:GetService("TweenService")
	
	local LocalPlayer = Players.LocalPlayer
	if not LocalPlayer then
		Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
		LocalPlayer = Players.LocalPlayer
	end
	
	local function waitForChildOfClass(parent, class)
		local child = parent:FindFirstChildOfClass(class)
		while not child or child.ClassName ~= class do
			child = parent.ChildAdded:Wait()
		end
		return child
	end
	
	local PlayerGui = waitForChildOfClass(LocalPlayer, "PlayerGui")
	
	local TOAST_OPEN_SIZE = UDim2.new(0, 326, 0, 58)
	local TOAST_CLOSED_SIZE = UDim2.new(0, 80, 0, 58)
	local TOAST_BACKGROUND_COLOR = Color3.fromRGB(32, 32, 32)
	local TOAST_BACKGROUND_TRANS = 0.4
	local TOAST_FOREGROUND_COLOR = Color3.fromRGB(200, 200, 200)
	local TOAST_FOREGROUND_TRANS = 0
	
	-- Convenient syntax for creating a tree of instanes
	local function create(className)
		return function(props)
			local inst = Instance.new(className)
			local parent = props.Parent
			props.Parent = nil
			for name, val in pairs(props) do
				if type(name) == "string" then
					inst[name] = val
				else
					val.Parent = inst
				end
			end
			-- Only set parent after all other properties are initialized
			inst.Parent = parent
			return inst
		end
	end
	
	local initialized = false
	
	local uiRoot
	local toast
	local toastIcon
	local toastUpperText
	local toastLowerText
	
	local function initializeUI()
		assert(not initialized)
	
		uiRoot = create("ScreenGui"){
			Name = "RbxCameraUI",
			AutoLocalize = false,
			Enabled = true,
			DisplayOrder = -1, -- Appears behind default developer UI
			IgnoreGuiInset = false,
			ResetOnSpawn = false,
			ZIndexBehavior = Enum.ZIndexBehavior.Sibling,
	
			create("ImageLabel"){
				Name = "Toast",
				Visible = false,
				AnchorPoint = Vector2.new(0.5, 0),
				BackgroundTransparency = 1,
				BorderSizePixel = 0,
				Position = UDim2.new(0.5, 0, 0, 8),
				Size = TOAST_CLOSED_SIZE,
				Image = "rbxasset://textures/ui/Camera/CameraToast9Slice.png",
				ImageColor3 = TOAST_BACKGROUND_COLOR,
				ImageRectSize = Vector2.new(6, 6),
				ImageTransparency = 1,
				ScaleType = Enum.ScaleType.Slice,
				SliceCenter = Rect.new(3, 3, 3, 3),
				ClipsDescendants = true,
	
				create("Frame"){
					Name = "IconBuffer",
					BackgroundTransparency = 1,
					BorderSizePixel = 0,
					Position = UDim2.new(0, 0, 0, 0),
					Size = UDim2.new(0, 80, 1, 0),
	
					create("ImageLabel"){
						Name = "Icon",
						AnchorPoint = Vector2.new(0.5, 0.5),
						BackgroundTransparency = 1,
						Position = UDim2.new(0.5, 0, 0.5, 0),
						Size = UDim2.new(0, 48, 0, 48),
						ZIndex = 2,
						Image = "rbxasset://textures/ui/Camera/CameraToastIcon.png",
						ImageColor3 = TOAST_FOREGROUND_COLOR,
						ImageTransparency = 1,
					}
				},
	
				create("Frame"){
					Name = "TextBuffer",
					BackgroundTransparency = 1,
					BorderSizePixel = 0,
					Position = UDim2.new(0, 80, 0, 0),
					Size = UDim2.new(1, -80, 1, 0),
					ClipsDescendants = true,
	
					create("TextLabel"){
						Name = "Upper",
						AnchorPoint = Vector2.new(0, 1),
						BackgroundTransparency = 1,
						Position = UDim2.new(0, 0, 0.5, 0),
						Size = UDim2.new(1, 0, 0, 19),
						Font = Enum.Font.GothamSemibold,
						Text = "Camera control enabled",
						TextColor3 = TOAST_FOREGROUND_COLOR,
						TextTransparency = 1,
						TextSize = 19,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextYAlignment = Enum.TextYAlignment.Center,
					},
	
					create("TextLabel"){
						Name = "Lower",
						AnchorPoint = Vector2.new(0, 0),
						BackgroundTransparency = 1,
						Position = UDim2.new(0, 0, 0.5, 3),
						Size = UDim2.new(1, 0, 0, 15),
						Font = Enum.Font.Gotham,
						Text = "Right mouse button to toggle",
						TextColor3 = TOAST_FOREGROUND_COLOR,
						TextTransparency = 1,
						TextSize = 15,
						TextXAlignment = Enum.TextXAlignment.Left,
						TextYAlignment = Enum.TextYAlignment.Center,
					},
				},
			},
	
			Parent = PlayerGui,
		}
	
		toast = uiRoot.Toast
		toastIcon = toast.IconBuffer.Icon
		toastUpperText = toast.TextBuffer.Upper
		toastLowerText = toast.TextBuffer.Lower
	
		initialized = true
	end
	
	local CameraUI = {}
	
	do
		-- Instantaneously disable the toast or enable for opening later on. Used when switching camera modes.
		function CameraUI.setCameraModeToastEnabled(enabled)
			if not enabled and not initialized then
				return
			end
	
			if not initialized then
				initializeUI()
			end
	
			toast.Visible = enabled
			if not enabled then
				CameraUI.setCameraModeToastOpen(false)
			end
		end
	
		local tweenInfo = TweenInfo.new(0.25, Enum.EasingStyle.Quad, Enum.EasingDirection.Out)
	
		-- Tween the toast in or out. Toast must be enabled with setCameraModeToastEnabled.
		function CameraUI.setCameraModeToastOpen(open)
			assert(initialized)
	
			TweenService:Create(toast, tweenInfo, {
				Size = open and TOAST_OPEN_SIZE or TOAST_CLOSED_SIZE,
				ImageTransparency = open and TOAST_BACKGROUND_TRANS or 1,
			}):Play()
	
			TweenService:Create(toastIcon, tweenInfo, {
				ImageTransparency = open and TOAST_FOREGROUND_TRANS or 1,
			}):Play()
	
			TweenService:Create(toastUpperText, tweenInfo, {
				TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
			}):Play()
	
			TweenService:Create(toastLowerText, tweenInfo, {
				TextTransparency = open and TOAST_FOREGROUND_TRANS or 1,
			}):Play()
		end
	end
	
	return CameraUI
end

function _CameraToggleStateController()
	local Players = game:GetService("Players")
	local UserInputService = game:GetService("UserInputService")
	local GameSettings = UserSettings():GetService("UserGameSettings")
	
	local LocalPlayer = Players.LocalPlayer
	if not LocalPlayer then
		Players:GetPropertyChangedSignal("LocalPlayer"):Wait()
		LocalPlayer = Players.LocalPlayer
	end
	
	local Mouse = LocalPlayer:GetMouse()
	
	local Input = _CameraInput()
	local CameraUI = _CameraUI()
	
	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)
		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
				Mouse.Icon = CROSS_MOUSE_ICON
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
				--GameSettings.RotationType = Enum.RotationType.CameraRelative
			else
				Mouse.Icon = ""
				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
				--GameSettings.RotationType = Enum.RotationType.CameraRelative
			end
	
		elseif Input.getTogglePan() then
			Mouse.Icon = CROSS_MOUSE_ICON
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
			GameSettings.RotationType = Enum.RotationType.MovementRelative
	
		elseif Input.getHoldPan() then
			Mouse.Icon = ""
			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
			GameSettings.RotationType = Enum.RotationType.MovementRelative
	
		else
			Mouse.Icon = ""
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
			GameSettings.RotationType = Enum.RotationType.MovementRelative
		end
	
		lastFirstPerson = isFirstPerson
	end
end

function _CameraInput()
	local UserInputService = game:GetService("UserInputService")
	
	local MB_TAP_LENGTH = 0.3 -- length of time for a short mouse button tap to be registered
	
	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 holdPan = false
	local togglePan = false
	local lastRmbDown = 0 -- tick() timestamp of the last right mouse button down event
	
	local CameraInput = {}
	
	function CameraInput.getHoldPan()
		return holdPan
	end
	
	function CameraInput.getTogglePan()
		return togglePan
	end
	
	function CameraInput.getPanning()
		return togglePan or holdPan
	end
	
	function CameraInput.setTogglePan(value)
		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
	
	return CameraInput
end

function _BaseCamera()
	--[[
		BaseCamera - Abstract base class for camera control modules
		2018 Camera Update - AllYourBlox
	--]]
	
	--[[ Local 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 THUMBSTICK_DEADZONE = 0.2
	local DEFAULT_DISTANCE = 12.5	-- Studs
	local PORTRAIT_DEFAULT_DISTANCE = 25		-- Studs
	local FIRST_PERSON_DISTANCE_THRESHOLD = 1.0 -- Below this value, snap into first person
	
	local CAMERA_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
	
	-- Note: DotProduct check in CoordinateFrame::lookAt() prevents using values within about
	-- 8.11 degrees of the +/- Y axis, that's why these limits are currently 80 degrees
	local MIN_Y = math.rad(-80)
	local MAX_Y = math.rad(80)
	
	local TOUCH_ADJUST_AREA_UP = math.rad(30)
	local TOUCH_ADJUST_AREA_DOWN = math.rad(-15)
	
	local TOUCH_SENSITIVTY_ADJUST_MAX_Y = 2.1
	local TOUCH_SENSITIVTY_ADJUST_MIN_Y = 0.5
	
	local VR_ANGLE = math.rad(15)
	local VR_LOW_INTENSITY_ROTATION = Vector2.new(math.rad(15), 0)
	local VR_HIGH_INTENSITY_ROTATION = Vector2.new(math.rad(45), 0)
	local VR_LOW_INTENSITY_REPEAT = 0.1
	local VR_HIGH_INTENSITY_REPEAT = 0.4
	
	local ZERO_VECTOR2 = Vector2.new(0,0)
	local ZERO_VECTOR3 = Vector3.new(0,0,0)
	
	local TOUCH_SENSITIVTY = Vector2.new(0.00945 * math.pi, 0.003375 * math.pi)
	local MOUSE_SENSITIVITY = Vector2.new( 0.002 * math.pi, 0.0015 * math.pi )
	
	local SEAT_OFFSET = Vector3.new(0,5,0)
	local VR_SEAT_OFFSET = Vector3.new(0,4,0)
	local HEAD_OFFSET = Vector3.new(0,1.5,0)
	local R15_HEAD_OFFSET = Vector3.new(0, 1.5, 0)
	local R15_HEAD_OFFSET_NO_SCALING = Vector3.new(0, 2, 0)
	local HUMANOID_ROOT_PART_SIZE = Vector3.new(2, 2, 1)
	
	local GAMEPAD_ZOOM_STEP_1 = 0
	local GAMEPAD_ZOOM_STEP_2 = 10
	local GAMEPAD_ZOOM_STEP_3 = 20
	
	local PAN_SENSITIVITY = 20
	local ZOOM_SENSITIVITY_CURVATURE = 0.5
	
	local abs = math.abs
	local sign = math.sign
	
	local FFlagUserCameraToggle do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
		end)
		FFlagUserCameraToggle = success and result
	end
	
	local FFlagUserDontAdjustSensitvityForPortrait do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserDontAdjustSensitvityForPortrait")
		end)
		FFlagUserDontAdjustSensitvityForPortrait = success and result
	end
	
	local FFlagUserFixZoomInZoomOutDiscrepancy do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserFixZoomInZoomOutDiscrepancy")
		end)
		FFlagUserFixZoomInZoomOutDiscrepancy = success and result
	end
	
	local Util = _CameraUtils()
	local ZoomController = _ZoomController()
	local CameraToggleStateController = _CameraToggleStateController()
	local CameraInput = _CameraInput()
	local CameraUI = _CameraUI()
	
	--[[ Roblox Services ]]--
	local Players = game:GetService("Players")
	local UserInputService = game:GetService("UserInputService")
	local StarterGui = game:GetService("StarterGui")
	local GuiService = game:GetService("GuiService")
	local ContextActionService = game:GetService("ContextActionService")
	local VRService = game:GetService("VRService")
	local UserGameSettings = UserSettings():GetService("UserGameSettings")
	
	local player = Players.LocalPlayer 
	
	--[[ The Module ]]--
	local BaseCamera = {}
	BaseCamera.__index = BaseCamera
	
	function BaseCamera.new()
		local self = setmetatable({}, BaseCamera)
	
		-- So that derived classes have access to this
		self.FIRST_PERSON_DISTANCE_THRESHOLD = FIRST_PERSON_DISTANCE_THRESHOLD
	
		self.cameraType = nil
		self.cameraMovementMode = nil
	
		self.lastCameraTransform = nil
		self.rotateInput = ZERO_VECTOR2
		self.userPanningCamera = false
		self.lastUserPanCamera = tick()
	
		self.humanoidRootPart = nil
		self.humanoidCache = {}
	
		-- Subject and position on last update call
		self.lastSubject = nil
		self.lastSubjectPosition = Vector3.new(0,5,0)
	
		-- These subject distance members refer to the nominal camera-to-subject follow distance that the camera
		-- is trying to maintain, not the actual measured value.
		-- The default is updated when screen orientation or the min/max distances change,
		-- to be sure the default is always in range and appropriate for the orientation.
		self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
		self.currentSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
	
		self.inFirstPerson = false
		self.inMouseLockedMode = false
		self.portraitMode = false
		self.isSmallTouchScreen = false
	
		-- Used by modules which want to reset the camera angle on respawn.
		self.resetCameraAngle = true
	
		self.enabled = false
	
		-- Input Event Connections
		self.inputBeganConn = nil
		self.inputChangedConn = nil
		self.inputEndedConn = nil
	
		self.startPos = nil
		self.lastPos = nil
		self.panBeginLook = nil
	
		self.panEnabled = true
		self.keyPanEnabled = true
		self.distanceChangeEnabled = true
	
		self.PlayerGui = nil
	
		self.cameraChangedConn = nil
		self.viewportSizeChangedConn = nil
	
		self.boundContextActions = {}
	
		-- VR Support
		self.shouldUseVRRotation = false
		self.VRRotationIntensityAvailable = false
		self.lastVRRotationIntensityCheckTime = 0
		self.lastVRRotationTime = 0
		self.vrRotateKeyCooldown = {}
		self.cameraTranslationConstraints = Vector3.new(1, 1, 1)
		self.humanoidJumpOrigin = nil
		self.trackingHumanoid = nil
		self.cameraFrozen = false
		self.subjectStateChangedConn = nil
	
		-- Gamepad support
		self.activeGamepad = nil
		self.gamepadPanningCamera = false
		self.lastThumbstickRotate = nil
		self.numOfSeconds = 0.7
		self.currentSpeed = 0
		self.maxSpeed = 6
		self.vrMaxSpeed = 4
		self.lastThumbstickPos = Vector2.new(0,0)
		self.ySensitivity = 0.65
		self.lastVelocity = nil
		self.gamepadConnectedConn = nil
		self.gamepadDisconnectedConn = nil
		self.currentZoomSpeed = 1.0
		self.L3ButtonDown = false
		self.dpadLeftDown = false
		self.dpadRightDown = false
	
		-- Touch input support
		self.isDynamicThumbstickEnabled = false
		self.fingerTouches = {}
		self.dynamicTouchInput = nil
		self.numUnsunkTouches = 0
		self.inputStartPositions = {}
		self.inputStartTimes = {}
		self.startingDiff = nil
		self.pinchBeginZoom = nil
		self.userPanningTheCamera = false
		self.touchActivateConn = nil
	
		-- Mouse locked formerly known as shift lock mode
		self.mouseLockOffset = ZERO_VECTOR3
	
		-- [[ NOTICE ]] --
		-- Initialization things used to always execute at game load time, but now these camera modules are instantiated
		-- when needed, so the code here may run well after the start of the game
	
		if player.Character then
			self:OnCharacterAdded(player.Character)
		end
	
		player.CharacterAdded:Connect(function(char)
			self:OnCharacterAdded(char)
		end)
	
		if self.cameraChangedConn then self.cameraChangedConn:Disconnect() end
		self.cameraChangedConn = workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
			self:OnCurrentCameraChanged()
		end)
		self:OnCurrentCameraChanged()
	
		if self.playerCameraModeChangeConn then self.playerCameraModeChangeConn:Disconnect() end
		self.playerCameraModeChangeConn = player:GetPropertyChangedSignal("CameraMode"):Connect(function()
			self:OnPlayerCameraPropertyChange()
		end)
	
		if self.minDistanceChangeConn then self.minDistanceChangeConn:Disconnect() end
		self.minDistanceChangeConn = player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(function()
			self:OnPlayerCameraPropertyChange()
		end)
	
		if self.maxDistanceChangeConn then self.maxDistanceChangeConn:Disconnect() end
		self.maxDistanceChangeConn = player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(function()
			self:OnPlayerCameraPropertyChange()
		end)
	
		if self.playerDevTouchMoveModeChangeConn then self.playerDevTouchMoveModeChangeConn:Disconnect() end
		self.playerDevTouchMoveModeChangeConn = player:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
			self:OnDevTouchMovementModeChanged()
		end)
		self:OnDevTouchMovementModeChanged() -- Init
	
		if self.gameSettingsTouchMoveMoveChangeConn then self.gameSettingsTouchMoveMoveChangeConn:Disconnect() end
		self.gameSettingsTouchMoveMoveChangeConn = UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
			self:OnGameSettingsTouchMovementModeChanged()
		end)
		self:OnGameSettingsTouchMovementModeChanged() -- Init
	
		UserGameSettings:SetCameraYInvertVisible()
		UserGameSettings:SetGamepadCameraSensitivityVisible()
	
		self.hasGameLoaded = game:IsLoaded()
		if not self.hasGameLoaded then
			self.gameLoadedConn = game.Loaded:Connect(function()
				self.hasGameLoaded = true
				self.gameLoadedConn:Disconnect()
				self.gameLoadedConn = nil
			end)
		end
	
		self:OnPlayerCameraPropertyChange()
	
		return self
	end
	
	function BaseCamera:GetModuleName()
		return "BaseCamera"
	end
	
	function BaseCamera:OnCharacterAdded(char)
		self.resetCameraAngle = self.resetCameraAngle or self:GetEnabled()
		self.humanoidRootPart = nil
		if UserInputService.TouchEnabled then
			self.PlayerGui = player:WaitForChild("PlayerGui")
			for _, child in ipairs(char:GetChildren()) do
				if child:IsA("Tool") then
					self.isAToolEquipped = true
				end
			end
			char.ChildAdded:Connect(function(child)
				if child:IsA("Tool") then
					self.isAToolEquipped = true
				end
			end)
			char.ChildRemoved:Connect(function(child)
				if child:IsA("Tool") then
					self.isAToolEquipped = false
				end
			end)
		end
	end
	
	function BaseCamera:GetHumanoidRootPart()
		if not self.humanoidRootPart then
			if player.Character then
				local humanoid = player.Character:FindFirstChildOfClass("Humanoid")
				if humanoid then
					self.humanoidRootPart = humanoid.RootPart
				end
			end
		end
		return self.humanoidRootPart
	end
	
	function BaseCamera:GetBodyPartToFollow(humanoid, isDead)
		-- If the humanoid is dead, prefer the head part if one still exists as a sibling of the humanoid
		if humanoid:GetState() == Enum.HumanoidStateType.Dead then
			local character = humanoid.Parent
			if character and character:IsA("Model") then
				return character:FindFirstChild("Head") or humanoid.RootPart
			end
		end
	
		return humanoid.RootPart
	end
	
	function BaseCamera:GetSubjectPosition()
		local result = self.lastSubjectPosition
		local camera = game.Workspace.CurrentCamera
		local cameraSubject = camera and camera.CameraSubject
	
		if cameraSubject then
			if cameraSubject:IsA("Humanoid") then
				local humanoid = cameraSubject
				local humanoidIsDead = humanoid:GetState() == Enum.HumanoidStateType.Dead
	
				if VRService.VREnabled and humanoidIsDead and humanoid == self.lastSubject then
					result = self.lastSubjectPosition
				else
					local bodyPartToFollow = humanoid.RootPart
	
					-- If the humanoid is dead, prefer their head part as a follow target, if it exists
					if humanoidIsDead then
						if humanoid.Parent and humanoid.Parent:IsA("Model") then
							bodyPartToFollow = humanoid.Parent:FindFirstChild("Head") or bodyPartToFollow
						end
					end
	
					if bodyPartToFollow and bodyPartToFollow:IsA("BasePart") then
						local heightOffset
						if humanoid.RigType == Enum.HumanoidRigType.R15 then
							if humanoid.AutomaticScalingEnabled then
								heightOffset = R15_HEAD_OFFSET
								if bodyPartToFollow == humanoid.RootPart then
									local rootPartSizeOffset = (humanoid.RootPart.Size.Y/2) - (HUMANOID_ROOT_PART_SIZE.Y/2)
									heightOffset = heightOffset + Vector3.new(0, rootPartSizeOffset, 0)
								end
							else
								heightOffset = R15_HEAD_OFFSET_NO_SCALING
							end
						else
							heightOffset = HEAD_OFFSET
						end
	
						if humanoidIsDead then
							heightOffset = ZERO_VECTOR3
						end
	
						result = bodyPartToFollow.CFrame.p + bodyPartToFollow.CFrame:vectorToWorldSpace(heightOffset + humanoid.CameraOffset)
					end
				end
	
			elseif cameraSubject:IsA("VehicleSeat") then
				local offset = SEAT_OFFSET
				if VRService.VREnabled then
					offset = VR_SEAT_OFFSET
				end
				result = cameraSubject.CFrame.p + cameraSubject.CFrame:vectorToWorldSpace(offset)
			elseif cameraSubject:IsA("SkateboardPlatform") then
				result = cameraSubject.CFrame.p + SEAT_OFFSET
			elseif cameraSubject:IsA("BasePart") then
				result = cameraSubject.CFrame.p
			elseif cameraSubject:IsA("Model") then
				if cameraSubject.PrimaryPart then
					result = cameraSubject:GetPrimaryPartCFrame().p
				else
					result = cameraSubject:GetModelCFrame().p
				end
			end
		else
			-- cameraSubject is nil
			-- Note: Previous RootCamera did not have this else case and let self.lastSubject and self.lastSubjectPosition
			-- both get set to nil in the case of cameraSubject being nil. This function now exits here to preserve the
			-- last set valid values for these, as nil values are not handled cases
			return
		end
	
		self.lastSubject = cameraSubject
		self.lastSubjectPosition = result
	
		return result
	end
	
	function BaseCamera:UpdateDefaultSubjectDistance()
		if self.portraitMode then
			self.defaultSubjectDistance = math.clamp(PORTRAIT_DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
		else
			self.defaultSubjectDistance = math.clamp(DEFAULT_DISTANCE, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
		end
	end
	
	function BaseCamera:OnViewportSizeChanged()
		local camera = game.Workspace.CurrentCamera
		local size = camera.ViewportSize
		self.portraitMode = size.X < size.Y
		self.isSmallTouchScreen = UserInputService.TouchEnabled and (size.Y < 500 or size.X < 700)
	
		self:UpdateDefaultSubjectDistance()
	end
	
	-- Listener for changes to workspace.CurrentCamera
	function BaseCamera:OnCurrentCameraChanged()
		if UserInputService.TouchEnabled then
			if self.viewportSizeChangedConn then
				self.viewportSizeChangedConn:Disconnect()
				self.viewportSizeChangedConn = nil
			end
	
			local newCamera = game.Workspace.CurrentCamera
	
			if newCamera then
				self:OnViewportSizeChanged()
				self.viewportSizeChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(function()
					self:OnViewportSizeChanged()
				end)
			end
		end
	
		-- VR support additions
		if self.cameraSubjectChangedConn then
			self.cameraSubjectChangedConn:Disconnect()
			self.cameraSubjectChangedConn = nil
		end
	
		local camera = game.Workspace.CurrentCamera
		if camera then
			self.cameraSubjectChangedConn = camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
				self:OnNewCameraSubject()
			end)
			self:OnNewCameraSubject()
		end
	end
	
	function BaseCamera:OnDynamicThumbstickEnabled()
		if UserInputService.TouchEnabled then
			self.isDynamicThumbstickEnabled = true
		end
	end
	
	function BaseCamera:OnDynamicThumbstickDisabled()
		self.isDynamicThumbstickEnabled = false
	end
	
	function BaseCamera:OnGameSettingsTouchMovementModeChanged()
		if player.DevTouchMovementMode == Enum.DevTouchMovementMode.UserChoice then
			if (UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.DynamicThumbstick
				or UserGameSettings.TouchMovementMode == Enum.TouchMovementMode.Default) then
				self:OnDynamicThumbstickEnabled()
			else
				self:OnDynamicThumbstickDisabled()
			end
		end
	end
	
	function BaseCamera:OnDevTouchMovementModeChanged()
		if player.DevTouchMovementMode.Name == "DynamicThumbstick" then
			self:OnDynamicThumbstickEnabled()
		else
			self:OnGameSettingsTouchMovementModeChanged()
		end
	end
	
	function BaseCamera:OnPlayerCameraPropertyChange()
		-- This call forces re-evaluation of player.CameraMode and clamping to min/max distance which may have changed
		self:SetCameraToSubjectDistance(self.currentSubjectDistance)
	end
	
	function BaseCamera:GetCameraHeight()
		if VRService.VREnabled and not self.inFirstPerson then
			return math.sin(VR_ANGLE) * self.currentSubjectDistance
		end
		return 0
	end
	
	function BaseCamera:InputTranslationToCameraAngleChange(translationVector, sensitivity)
		if not FFlagUserDontAdjustSensitvityForPortrait then
			local camera = game.Workspace.CurrentCamera
			if camera and camera.ViewportSize.X > 0 and camera.ViewportSize.Y > 0 and (camera.ViewportSize.Y > camera.ViewportSize.X) then
				-- Screen has portrait orientation, swap X and Y sensitivity
				return translationVector * Vector2.new( sensitivity.Y, sensitivity.X)
			end
		end
		return translationVector * sensitivity
	end
	
	function BaseCamera:Enable(enable)
		if self.enabled ~= enable then
			self.enabled = enable
			if self.enabled then
				self:ConnectInputEvents()
				self:BindContextActions()
	
				if player.CameraMode == Enum.CameraMode.LockFirstPerson then
					self.currentSubjectDistance = 0.5
					if not self.inFirstPerson then
						self:EnterFirstPerson()
					end
				end
			else
				self:DisconnectInputEvents()
				self:UnbindContextActions()
				-- Clean up additional event listeners and reset a bunch of properties
				self:Cleanup()
			end
		end
	end
	
	function BaseCamera:GetEnabled()
		return self.enabled
	end
	
	function BaseCamera:OnInputBegan(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchBegan(input, processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			self:OnMouse2Down(input, processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
			self:OnMouse3Down(input, processed)
		end
	end
	
	function BaseCamera:OnInputChanged(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchChanged(input, processed)
		elseif input.UserInputType == Enum.UserInputType.MouseMovement then
			self:OnMouseMoved(input, processed)
		end
	end
	
	function BaseCamera:OnInputEnded(input, processed)
		if input.UserInputType == Enum.UserInputType.Touch then
			self:OnTouchEnded(input, processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton2 then
			self:OnMouse2Up(input, processed)
		elseif input.UserInputType == Enum.UserInputType.MouseButton3 then
			self:OnMouse3Up(input, processed)
		end
	end
	
	function BaseCamera:OnPointerAction(wheel, pan, pinch, processed)
		if processed then
			return
		end
	
		if pan.Magnitude > 0 then
			local inversionVector = Vector2.new(1, UserGameSettings:GetCameraYInvertValue())
			local rotateDelta = self:InputTranslationToCameraAngleChange(PAN_SENSITIVITY*pan, MOUSE_SENSITIVITY)*inversionVector
			self.rotateInput = self.rotateInput + rotateDelta
		end
	
		local zoom = self.currentSubjectDistance
		local zoomDelta = -(wheel + pinch)
	
		if abs(zoomDelta) > 0 then
			local newZoom
			if self.inFirstPerson and zoomDelta > 0 then
				newZoom = FIRST_PERSON_DISTANCE_THRESHOLD
			else
				if FFlagUserFixZoomInZoomOutDiscrepancy then
					if (zoomDelta > 0) then
						newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
					else
						newZoom = (zoom + zoomDelta) / (1 - zoomDelta*ZOOM_SENSITIVITY_CURVATURE)
					end
				else
					newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
				end
			end
	
			self:SetCameraToSubjectDistance(newZoom)
		end
	end
	
	function BaseCamera:ConnectInputEvents()
		self.pointerActionConn = UserInputService.PointerAction:Connect(function(wheel, pan, pinch, processed)
			self:OnPointerAction(wheel, pan, pinch, processed)
		end)
	
		self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
			self:OnInputBegan(input, processed)
		end)
	
		self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
			self:OnInputChanged(input, processed)
		end)
	
		self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
			self:OnInputEnded(input, processed)
		end)
	
		self.menuOpenedConn = GuiService.MenuOpened:connect(function()
			self:ResetInputStates()
		end)
	
		self.gamepadConnectedConn = UserInputService.GamepadDisconnected:connect(function(gamepadEnum)
			if self.activeGamepad ~= gamepadEnum then return end
			self.activeGamepad = nil
			self:AssignActivateGamepad()
		end)
	
		self.gamepadDisconnectedConn = UserInputService.GamepadConnected:connect(function(gamepadEnum)
			if self.activeGamepad == nil then
				self:AssignActivateGamepad()
			end
		end)
	
		self:AssignActivateGamepad()
		if not FFlagUserCameraToggle then
			self:UpdateMouseBehavior()
		end
	end
	
	function BaseCamera:BindContextActions()
		self:BindGamepadInputActions()
		self:BindKeyboardInputActions()
	end
	
	function BaseCamera:AssignActivateGamepad()
		local connectedGamepads = UserInputService:GetConnectedGamepads()
		if #connectedGamepads > 0 then
			for i = 1, #connectedGamepads do
				if self.activeGamepad == nil then
					self.activeGamepad = connectedGamepads[i]
				elseif connectedGamepads[i].Value < self.activeGamepad.Value then
					self.activeGamepad = connectedGamepads[i]
				end
			end
		end
	
		if self.activeGamepad == nil then -- nothing is connected, at least set up for gamepad1
			self.activeGamepad = Enum.UserInputType.Gamepad1
		end
	end
	
	function BaseCamera:DisconnectInputEvents()
		if self.inputBeganConn then
			self.inputBeganConn:Disconnect()
			self.inputBeganConn = nil
		end
		if self.inputChangedConn then
			self.inputChangedConn:Disconnect()
			self.inputChangedConn = nil
		end
		if self.inputEndedConn then
			self.inputEndedConn:Disconnect()
			self.inputEndedConn = nil
		end
	end
	
	function BaseCamera:UnbindContextActions()
		for i = 1, #self.boundContextActions do
			ContextActionService:UnbindAction(self.boundContextActions[i])
		end
		self.boundContextActions = {}
	end
	
	function BaseCamera:Cleanup()
		if self.pointerActionConn then
			self.pointerActionConn:Disconnect()
			self.pointerActionConn = nil
		end
		if self.menuOpenedConn then
			self.menuOpenedConn:Disconnect()
			self.menuOpenedConn = nil
		end
		if self.mouseLockToggleConn then
			self.mouseLockToggleConn:Disconnect()
			self.mouseLockToggleConn = nil
		end
		if self.gamepadConnectedConn then
			self.gamepadConnectedConn:Disconnect()
			self.gamepadConnectedConn = nil
		end
		if self.gamepadDisconnectedConn then
			self.gamepadDisconnectedConn:Disconnect()
			self.gamepadDisconnectedConn = nil
		end
		if self.subjectStateChangedConn then
			self.subjectStateChangedConn:Disconnect()
			self.subjectStateChangedConn = nil
		end
		if self.viewportSizeChangedConn then
			self.viewportSizeChangedConn:Disconnect()
			self.viewportSizeChangedConn = nil
		end
		if self.touchActivateConn then
			self.touchActivateConn:Disconnect()
			self.touchActivateConn = nil
		end
	
		self.turningLeft = false
		self.turningRight = false
		self.lastCameraTransform = nil
		self.lastSubjectCFrame = nil
		self.userPanningTheCamera = false
		self.rotateInput = Vector2.new()
		self.gamepadPanningCamera = Vector2.new(0,0)
	
		-- Reset input states
		self.startPos = nil
		self.lastPos = nil
		self.panBeginLook = nil
		self.isRightMouseDown = false
		self.isMiddleMouseDown = false
	
		self.fingerTouches = {}
		self.dynamicTouchInput = nil
		self.numUnsunkTouches = 0
	
		self.startingDiff = nil
		self.pinchBeginZoom = nil
	
		-- Unlock mouse for example if right mouse button was being held down
		if UserInputService.MouseBehavior ~= Enum.MouseBehavior.LockCenter then
			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
		end
	end
	
	-- This is called when settings menu is opened
	function BaseCamera:ResetInputStates()
		self.isRightMouseDown = false
		self.isMiddleMouseDown = false
		self:OnMousePanButtonReleased() -- this function doesn't seem to actually need parameters
	
		if UserInputService.TouchEnabled then
			--[[menu opening was causing serious touch issues
			this should disable all active touch events if
			they're active when menu opens.]]
			for inputObject in pairs(self.fingerTouches) do
				self.fingerTouches[inputObject] = nil
			end
			self.dynamicTouchInput = nil
			self.panBeginLook = nil
			self.startPos = nil
			self.lastPos = nil
			self.userPanningTheCamera = false
			self.startingDiff = nil
			self.pinchBeginZoom = nil
			self.numUnsunkTouches = 0
		end
	end
	
	function BaseCamera:GetGamepadPan(name, state, input)
		if input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
	--		if self.L3ButtonDown then
	--			-- L3 Thumbstick is depressed, right stick controls dolly in/out
	--			if (input.Position.Y > THUMBSTICK_DEADZONE) then
	--				self.currentZoomSpeed = 0.96
	--			elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
	--				self.currentZoomSpeed = 1.04
	--			else
	--				self.currentZoomSpeed = 1.00
	--			end
	--		else
				if state == Enum.UserInputState.Cancel then
					self.gamepadPanningCamera = ZERO_VECTOR2
					return
				end
	
				local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
				if inputVector.magnitude > THUMBSTICK_DEADZONE then
					self.gamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
				else
					self.gamepadPanningCamera = ZERO_VECTOR2
				end
			--end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	end
	
	function BaseCamera:DoKeyboardPanTurn(name, state, input)
		if not self.hasGameLoaded and VRService.VREnabled then
			return Enum.ContextActionResult.Pass
		end
	
		if state == Enum.UserInputState.Cancel then
			self.turningLeft = false
			self.turningRight = false
			return Enum.ContextActionResult.Sink
		end
	
		if self.panBeginLook == nil and self.keyPanEnabled then
			if input.KeyCode == Enum.KeyCode.Left then
				self.turningLeft = state == Enum.UserInputState.Begin
			elseif input.KeyCode == Enum.KeyCode.Right then
				self.turningRight = state == Enum.UserInputState.Begin
			end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	end
	
	function BaseCamera:DoPanRotateCamera(rotateAngle)
		local angle = Util.RotateVectorByAngleAndRound(self:GetCameraLookVector() * Vector3.new(1,0,1), rotateAngle, math.pi*0.25)
		if angle ~= 0 then
			self.rotateInput = self.rotateInput + Vector2.new(angle, 0)
			self.lastUserPanCamera = tick()
			self.lastCameraTransform = nil
		end
	end
	
	function BaseCamera:DoGamepadZoom(name, state, input)
		if input.UserInputType == self.activeGamepad then
			if input.KeyCode == Enum.KeyCode.ButtonR3 then
				if state == Enum.UserInputState.Begin then
					if self.distanceChangeEnabled then
						local dist = self:GetCameraToSubjectDistance()
	
						if dist > (GAMEPAD_ZOOM_STEP_2 + GAMEPAD_ZOOM_STEP_3)/2 then
							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_2)
						elseif dist > (GAMEPAD_ZOOM_STEP_1 + GAMEPAD_ZOOM_STEP_2)/2 then
							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_1)
						else
							self:SetCameraToSubjectDistance(GAMEPAD_ZOOM_STEP_3)
						end
					end
				end
			elseif input.KeyCode == Enum.KeyCode.DPadLeft then
				self.dpadLeftDown = (state == Enum.UserInputState.Begin)
			elseif input.KeyCode == Enum.KeyCode.DPadRight then
				self.dpadRightDown = (state == Enum.UserInputState.Begin)
			end
	
			if self.dpadLeftDown then
				self.currentZoomSpeed = 1.04
			elseif self.dpadRightDown then
				self.currentZoomSpeed = 0.96
			else
				self.currentZoomSpeed = 1.00
			end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	--	elseif input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.ButtonL3 then
	--		if (state == Enum.UserInputState.Begin) then
	--			self.L3ButtonDown = true
	--		elseif (state == Enum.UserInputState.End) then
	--			self.L3ButtonDown = false
	--			self.currentZoomSpeed = 1.00
	--		end
	--	end
	end
	
	function BaseCamera:DoKeyboardZoom(name, state, input)
		if not self.hasGameLoaded and VRService.VREnabled then
			return Enum.ContextActionResult.Pass
		end
	
		if state ~= Enum.UserInputState.Begin then
			return Enum.ContextActionResult.Pass
		end
	
		if self.distanceChangeEnabled and player.CameraMode ~= Enum.CameraMode.LockFirstPerson then
			if input.KeyCode == Enum.KeyCode.I then
				self:SetCameraToSubjectDistance( self.currentSubjectDistance - 5 )
			elseif input.KeyCode == Enum.KeyCode.O then
				self:SetCameraToSubjectDistance( self.currentSubjectDistance + 5 )
			end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	end
	
	function BaseCamera:BindAction(actionName, actionFunc, createTouchButton, ...)
		table.insert(self.boundContextActions, actionName)
		ContextActionService:BindActionAtPriority(actionName, actionFunc, createTouchButton,
			CAMERA_ACTION_PRIORITY, ...)
	end
	
	function BaseCamera:BindGamepadInputActions()
		self:BindAction("BaseCameraGamepadPan", function(name, state, input) return self:GetGamepadPan(name, state, input) end,
			false, Enum.KeyCode.Thumbstick2)
		self:BindAction("BaseCameraGamepadZoom", function(name, state, input) return self:DoGamepadZoom(name, state, input) end,
			false, Enum.KeyCode.DPadLeft, Enum.KeyCode.DPadRight, Enum.KeyCode.ButtonR3)
	end
	
	function BaseCamera:BindKeyboardInputActions()
		self:BindAction("BaseCameraKeyboardPanArrowKeys", function(name, state, input) return self:DoKeyboardPanTurn(name, state, input) end,
			false, Enum.KeyCode.Left, Enum.KeyCode.Right)
		self:BindAction("BaseCameraKeyboardZoom", function(name, state, input) return self:DoKeyboardZoom(name, state, input) end,
			false, Enum.KeyCode.I, Enum.KeyCode.O)
	end
	
	local function isInDynamicThumbstickArea(input)
		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
	
		local frameCornerTopLeft = thumbstickFrame.AbsolutePosition
		local frameCornerBottomRight = frameCornerTopLeft + thumbstickFrame.AbsoluteSize
		if input.Position.X >= frameCornerTopLeft.X and input.Position.Y >= frameCornerTopLeft.Y then
			if input.Position.X  0 then
			local fractionAdjust = (currPitchAngle - TOUCH_ADJUST_AREA_DOWN)/(MIN_Y - TOUCH_ADJUST_AREA_DOWN)
			fractionAdjust = 1 - (1 - fractionAdjust)^3
			multiplierY = TOUCH_SENSITIVTY_ADJUST_MAX_Y - fractionAdjust * (
				TOUCH_SENSITIVTY_ADJUST_MAX_Y - TOUCH_SENSITIVTY_ADJUST_MIN_Y)
		end
	
		return Vector2.new(
			sensitivity.X,
			sensitivity.Y * multiplierY
		)
	end
	
	function BaseCamera:OnTouchBegan(input, processed)
		local canUseDynamicTouch = self.isDynamicThumbstickEnabled and not processed
		if canUseDynamicTouch then
			if self.dynamicTouchInput == nil and isInDynamicThumbstickArea(input) then
				-- First input in the dynamic thumbstick area should always be ignored for camera purposes
				-- Even if the dynamic thumbstick does not process it immediately
				self.dynamicTouchInput = input
				return
			end
			self.fingerTouches[input] = processed
			self.inputStartPositions[input] = input.Position
			self.inputStartTimes[input] = tick()
			self.numUnsunkTouches = self.numUnsunkTouches + 1
		end
	end
	
	function BaseCamera:OnTouchChanged(input, processed)
		if self.fingerTouches[input] == nil then
			if self.isDynamicThumbstickEnabled then
				return
			end
			self.fingerTouches[input] = processed
			if not processed then
				self.numUnsunkTouches = self.numUnsunkTouches + 1
			end
		end
	
		if self.numUnsunkTouches == 1 then
			if self.fingerTouches[input] == false then
				self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
				self.startPos = self.startPos or input.Position
				self.lastPos = self.lastPos or self.startPos
				self.userPanningTheCamera = true
	
				local delta = input.Position - self.lastPos
				delta = Vector2.new(delta.X, delta.Y * UserGameSettings:GetCameraYInvertValue())
				if self.panEnabled then
					local adjustedTouchSensitivity = TOUCH_SENSITIVTY
					self:AdjustTouchSensitivity(delta, TOUCH_SENSITIVTY)
	
					local desiredXYVector = self:InputTranslationToCameraAngleChange(delta, adjustedTouchSensitivity)
					self.rotateInput = self.rotateInput + desiredXYVector
				end
				self.lastPos = input.Position
			end
		else
			self.panBeginLook = nil
			self.startPos = nil
			self.lastPos = nil
			self.userPanningTheCamera = false
		end
		if self.numUnsunkTouches == 2 then
			local unsunkTouches = {}
			for touch, wasSunk in pairs(self.fingerTouches) do
				if not wasSunk then
					table.insert(unsunkTouches, touch)
				end
			end
			if #unsunkTouches == 2 then
				local difference = (unsunkTouches[1].Position - unsunkTouches[2].Position).magnitude
				if self.startingDiff and self.pinchBeginZoom then
					local scale = difference / math.max(0.01, self.startingDiff)
					local clampedScale = math.clamp(scale, 0.1, 10)
					if self.distanceChangeEnabled then
						self:SetCameraToSubjectDistance(self.pinchBeginZoom / clampedScale)
					end
				else
					self.startingDiff = difference
					self.pinchBeginZoom = self:GetCameraToSubjectDistance()
				end
			end
		else
			self.startingDiff = nil
			self.pinchBeginZoom = nil
		end
	end
	
	function BaseCamera:OnTouchEnded(input, processed)
		if input == self.dynamicTouchInput then
			self.dynamicTouchInput = nil
			return
		end
	
		if self.fingerTouches[input] == false then
			if self.numUnsunkTouches == 1 then
				self.panBeginLook = nil
				self.startPos = nil
				self.lastPos = nil
				self.userPanningTheCamera = false
			elseif self.numUnsunkTouches == 2 then
				self.startingDiff = nil
				self.pinchBeginZoom = nil
			end
		end
	
		if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
			self.numUnsunkTouches = self.numUnsunkTouches - 1
		end
		self.fingerTouches[input] = nil
		self.inputStartPositions[input] = nil
		self.inputStartTimes[input] = nil
	end
	
	function BaseCamera:OnMouse2Down(input, processed)
		if processed then return end
	
		self.isRightMouseDown = true
		self:OnMousePanButtonPressed(input, processed)
	end
	
	function BaseCamera:OnMouse2Up(input, processed)
		self.isRightMouseDown = false
		self:OnMousePanButtonReleased(input, processed)
	end
	
	function BaseCamera:OnMouse3Down(input, processed)
		if processed then return end
	
		self.isMiddleMouseDown = true
		self:OnMousePanButtonPressed(input, processed)
	end
	
	function BaseCamera:OnMouse3Up(input, processed)
		self.isMiddleMouseDown = false
		self:OnMousePanButtonReleased(input, processed)
	end
	
	function BaseCamera:OnMouseMoved(input, processed)
		if not self.hasGameLoaded and VRService.VREnabled then
			return
		end
	
		local inputDelta = input.Delta
		inputDelta = Vector2.new(inputDelta.X, inputDelta.Y * UserGameSettings:GetCameraYInvertValue())
	
		local isInputPanning = FFlagUserCameraToggle and CameraInput.getPanning()
		local isBeginLook = self.startPos and self.lastPos and self.panBeginLook
		local isPanning = isBeginLook or self.inFirstPerson or self.inMouseLockedMode or isInputPanning
	
		if self.panEnabled and isPanning then
			local desiredXYVector = self:InputTranslationToCameraAngleChange(inputDelta, MOUSE_SENSITIVITY)
			self.rotateInput = self.rotateInput + desiredXYVector
		end
	
		if self.startPos and self.lastPos and self.panBeginLook then
			self.lastPos = self.lastPos + input.Delta
		end
	end
	
	function BaseCamera:OnMousePanButtonPressed(input, processed)
		if processed then return end
		if not FFlagUserCameraToggle then
			self:UpdateMouseBehavior()
		end
		self.panBeginLook = self.panBeginLook or self:GetCameraLookVector()
		self.startPos = self.startPos or input.Position
		self.lastPos = self.lastPos or self.startPos
		self.userPanningTheCamera = true
	end
	
	function BaseCamera:OnMousePanButtonReleased(input, processed)
		if not FFlagUserCameraToggle then
			self:UpdateMouseBehavior()
		end
		if not (self.isRightMouseDown or self.isMiddleMouseDown) then
			self.panBeginLook = nil
			self.startPos = nil
			self.lastPos = nil
			self.userPanningTheCamera = false
		end
	end
	
	function BaseCamera:UpdateMouseBehavior()
		if FFlagUserCameraToggle and self.isCameraToggle then
			CameraUI.setCameraModeToastEnabled(true)
			CameraInput.enableCameraToggleInput()
			CameraToggleStateController(self.inFirstPerson)
		else
			if FFlagUserCameraToggle then
				CameraUI.setCameraModeToastEnabled(false)
				CameraInput.disableCameraToggleInput()
			end
			-- first time transition to first person mode or mouse-locked third person
			if self.inFirstPerson or self.inMouseLockedMode then
				--UserGameSettings.RotationType = Enum.RotationType.CameraRelative
				UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
			else
				UserGameSettings.RotationType = Enum.RotationType.MovementRelative
				if self.isRightMouseDown or self.isMiddleMouseDown then
					UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
				else
					UserInputService.MouseBehavior = Enum.MouseBehavior.Default
				end
			end
		end
	end
	
	function BaseCamera:UpdateForDistancePropertyChange()
		-- Calling this setter with the current value will force checking that it is still
		-- in range after a change to the min/max distance limits
		self:SetCameraToSubjectDistance(self.currentSubjectDistance)
	end
	
	function BaseCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
		local lastSubjectDistance = self.currentSubjectDistance
	
		-- By default, camera modules will respect LockFirstPerson and override the currentSubjectDistance with 0
		-- regardless of what Player.CameraMinZoomDistance is set to, so that first person can be made
		-- available by the developer without needing to allow players to mousewheel dolly into first person.
		-- Some modules will override this function to remove or change first-person capability.
		if player.CameraMode == Enum.CameraMode.LockFirstPerson then
			self.currentSubjectDistance = 0.5
			if not self.inFirstPerson then
				self:EnterFirstPerson()
			end
		else
			local newSubjectDistance = math.clamp(desiredSubjectDistance, player.CameraMinZoomDistance, player.CameraMaxZoomDistance)
			if newSubjectDistance < FIRST_PERSON_DISTANCE_THRESHOLD then
				self.currentSubjectDistance = 0.5
				if not self.inFirstPerson then
					self:EnterFirstPerson()
				end
			else
				self.currentSubjectDistance = newSubjectDistance
				if self.inFirstPerson then
					self:LeaveFirstPerson()
				end
			end
		end
	
		-- Pass target distance and zoom direction to the zoom controller
		ZoomController.SetZoomParameters(self.currentSubjectDistance, math.sign(desiredSubjectDistance - lastSubjectDistance))
	
		-- Returned only for convenience to the caller to know the outcome
		return self.currentSubjectDistance
	end
	
	function BaseCamera:SetCameraType( cameraType )
		--Used by derived classes
		self.cameraType = cameraType
	end
	
	function BaseCamera:GetCameraType()
		return self.cameraType
	end
	
	-- Movement mode standardized to Enum.ComputerCameraMovementMode values
	function BaseCamera:SetCameraMovementMode( cameraMovementMode )
		self.cameraMovementMode = cameraMovementMode
	end
	
	function BaseCamera:GetCameraMovementMode()
		return self.cameraMovementMode
	end
	
	function BaseCamera:SetIsMouseLocked(mouseLocked)
		self.inMouseLockedMode = mouseLocked
		if not FFlagUserCameraToggle then
			self:UpdateMouseBehavior()
		end
	end
	
	function BaseCamera:GetIsMouseLocked()
		return self.inMouseLockedMode
	end
	
	function BaseCamera:SetMouseLockOffset(offsetVector)
		self.mouseLockOffset = offsetVector
	end
	
	function BaseCamera:GetMouseLockOffset()
		return self.mouseLockOffset
	end
	
	function BaseCamera:InFirstPerson()
		return self.inFirstPerson
	end
	
	function BaseCamera:EnterFirstPerson()
		-- Overridden in ClassicCamera, the only module which supports FirstPerson
	end
	
	function BaseCamera:LeaveFirstPerson()
		-- Overridden in ClassicCamera, the only module which supports FirstPerson
	end
	
	-- Nominal distance, set by dollying in and out with the mouse wheel or equivalent, not measured distance
	function BaseCamera:GetCameraToSubjectDistance()
		return self.currentSubjectDistance
	end
	
	-- Actual measured distance to the camera Focus point, which may be needed in special circumstances, but should
	-- never be used as the starting point for updating the nominal camera-to-subject distance (self.currentSubjectDistance)
	-- since that is a desired target value set only by mouse wheel (or equivalent) input, PopperCam, and clamped to min max camera distance
	function BaseCamera:GetMeasuredDistanceToFocus()
		local camera = game.Workspace.CurrentCamera
		if camera then
			return (camera.CoordinateFrame.p - camera.Focus.p).magnitude
		end
		return nil
	end
	
	function BaseCamera:GetCameraLookVector()
		return game.Workspace.CurrentCamera and game.Workspace.CurrentCamera.CFrame.lookVector or UNIT_Z
	end
	
	-- Replacements for RootCamera:RotateCamera() which did not actually rotate the camera
	-- suppliedLookVector is not normally passed in, it's used only by Watch camera
	function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
		local currPitchAngle = math.asin(currLookVector.y)
		local yTheta = math.clamp(self.rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
		local constrainedRotateInput = Vector2.new(self.rotateInput.x, yTheta)
		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
		local newLookCFrame = CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)
		return newLookCFrame
	end
	function BaseCamera:CalculateNewLookVector(suppliedLookVector)
		local newLookCFrame = self:CalculateNewLookCFrame(suppliedLookVector)
		return newLookCFrame.lookVector
	end
	
	function BaseCamera:CalculateNewLookVectorVR()
		local subjectPosition = self:GetSubjectPosition()
		local vecToSubject = (subjectPosition - game.Workspace.CurrentCamera.CFrame.p)
		local currLookVector = (vecToSubject * X1_Y0_Z1).unit
		local vrRotateInput = Vector2.new(self.rotateInput.x, 0)
		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
		local yawRotatedVector = (CFrame.Angles(0, -vrRotateInput.x, 0) * startCFrame * CFrame.Angles(-vrRotateInput.y,0,0)).lookVector
		return (yawRotatedVector * X1_Y0_Z1).unit
	end
	
	function BaseCamera:GetHumanoid()
		local character = player and player.Character
		if character then
			local resultHumanoid = self.humanoidCache[player]
			if resultHumanoid and resultHumanoid.Parent == character then
				return resultHumanoid
			else
				self.humanoidCache[player] = nil -- Bust Old Cache
				local humanoid = character:FindFirstChildOfClass("Humanoid")
				if humanoid then
					self.humanoidCache[player] = humanoid
				end
				return humanoid
			end
		end
		return nil
	end
	
	function BaseCamera:GetHumanoidPartToFollow(humanoid, humanoidStateType)
		if humanoidStateType == Enum.HumanoidStateType.Dead then
			local character = humanoid.Parent
			if character then
				return character:FindFirstChild("Head") or humanoid.Torso
			else
				return humanoid.Torso
			end
		else
			return humanoid.Torso
		end
	end
	
	function BaseCamera:UpdateGamepad()
		local gamepadPan = self.gamepadPanningCamera
		if gamepadPan and (self.hasGameLoaded or not VRService.VREnabled) then
			gamepadPan = Util.GamepadLinearToCurve(gamepadPan)
			local currentTime = tick()
			if gamepadPan.X ~= 0 or gamepadPan.Y ~= 0 then
				self.userPanningTheCamera = true
			elseif gamepadPan == ZERO_VECTOR2 then
				self.lastThumbstickRotate = nil
				if self.lastThumbstickPos == ZERO_VECTOR2 then
					self.currentSpeed = 0
				end
			end
	
			local finalConstant = 0
	
			if self.lastThumbstickRotate then
				if VRService.VREnabled then
					self.currentSpeed = self.vrMaxSpeed
				else
					local elapsedTime = (currentTime - self.lastThumbstickRotate) * 10
					self.currentSpeed = self.currentSpeed + (self.maxSpeed * ((elapsedTime*elapsedTime)/self.numOfSeconds))
	
					if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
	
					if self.lastVelocity then
						local velocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
						local velocityDeltaMag = (velocity - self.lastVelocity).magnitude
	
						if velocityDeltaMag > 12 then
							self.currentSpeed = self.currentSpeed * (20/velocityDeltaMag)
							if self.currentSpeed > self.maxSpeed then self.currentSpeed = self.maxSpeed end
						end
					end
				end
	
				finalConstant = UserGameSettings.GamepadCameraSensitivity * self.currentSpeed
				self.lastVelocity = (gamepadPan - self.lastThumbstickPos)/(currentTime - self.lastThumbstickRotate)
			end
	
			self.lastThumbstickPos = gamepadPan
			self.lastThumbstickRotate = currentTime
	
			return Vector2.new( gamepadPan.X * finalConstant, gamepadPan.Y * finalConstant * self.ySensitivity * UserGameSettings:GetCameraYInvertValue())
		end
	
		return ZERO_VECTOR2
	end
	
	-- [[ VR Support Section ]] --
	
	function BaseCamera:ApplyVRTransform()
		if not VRService.VREnabled then
			return
		end
	
		--we only want this to happen in first person VR
		local rootJoint = self.humanoidRootPart and self.humanoidRootPart:FindFirstChild("RootJoint")
		if not rootJoint then
			return
		end
	
		local cameraSubject = game.Workspace.CurrentCamera.CameraSubject
		local isInVehicle = cameraSubject and cameraSubject:IsA("VehicleSeat")
	
		if self.inFirstPerson and not isInVehicle then
			local vrFrame = VRService:GetUserCFrame(Enum.UserCFrame.Head)
			local vrRotation = vrFrame - vrFrame.p
			rootJoint.C0 = CFrame.new(vrRotation:vectorToObjectSpace(vrFrame.p)) * CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
		else
			rootJoint.C0 = CFrame.new(0, 0, 0, -1, 0, 0, 0, 0, 1, 0, 1, 0)
		end
	end
	
	function BaseCamera:IsInFirstPerson()
		return self.inFirstPerson
	end
	
	function BaseCamera:ShouldUseVRRotation()
		if not VRService.VREnabled then
			return false
		end
	
		if not self.VRRotationIntensityAvailable and tick() - self.lastVRRotationIntensityCheckTime < 1 then
			return false
		end
	
		local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
		self.VRRotationIntensityAvailable = success and vrRotationIntensity ~= nil
		self.lastVRRotationIntensityCheckTime = tick()
	
		self.shouldUseVRRotation = success and vrRotationIntensity ~= nil and vrRotationIntensity ~= "Smooth"
	
		return self.shouldUseVRRotation
	end
	
	function BaseCamera:GetVRRotationInput()
		local vrRotateSum = ZERO_VECTOR2
		local success, vrRotationIntensity = pcall(function() return StarterGui:GetCore("VRRotationIntensity") end)
	
		if not success then
			return
		end
	
		local vrGamepadRotation = self.GamepadPanningCamera or ZERO_VECTOR2
		local delayExpired = (tick() - self.lastVRRotationTime) >= self:GetRepeatDelayValue(vrRotationIntensity)
	
		if math.abs(vrGamepadRotation.x) >= self:GetActivateValue() then
			if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2]) then
				local sign = 1
				if vrGamepadRotation.x < 0 then
					sign = -1
				end
				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity) * sign
				self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = true
			end
		elseif math.abs(vrGamepadRotation.x) < self:GetActivateValue() - 0.1 then
			self.vrRotateKeyCooldown[Enum.KeyCode.Thumbstick2] = nil
		end
		if self.turningLeft then
			if delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Left] then
				vrRotateSum = vrRotateSum - self:GetRotateAmountValue(vrRotationIntensity)
				self.vrRotateKeyCooldown[Enum.KeyCode.Left] = true
			end
		else
			self.vrRotateKeyCooldown[Enum.KeyCode.Left] = nil
		end
		if self.turningRight then
			if (delayExpired or not self.vrRotateKeyCooldown[Enum.KeyCode.Right]) then
				vrRotateSum = vrRotateSum + self:GetRotateAmountValue(vrRotationIntensity)
				self.vrRotateKeyCooldown[Enum.KeyCode.Right] = true
			end
		else
			self.vrRotateKeyCooldown[Enum.KeyCode.Right] = nil
		end
	
		if vrRotateSum ~= ZERO_VECTOR2 then
			self.lastVRRotationTime = tick()
		end
	
		return vrRotateSum
	end
	
	function BaseCamera:CancelCameraFreeze(keepConstraints)
		if not keepConstraints then
			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 1, self.cameraTranslationConstraints.z)
		end
		if self.cameraFrozen then
			self.trackingHumanoid = nil
			self.cameraFrozen = false
		end
	end
	
	function BaseCamera:StartCameraFreeze(subjectPosition, humanoidToTrack)
		if not self.cameraFrozen then
			self.humanoidJumpOrigin = subjectPosition
			self.trackingHumanoid = humanoidToTrack
			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, 0, self.cameraTranslationConstraints.z)
			self.cameraFrozen = true
		end
	end
	
	function BaseCamera:OnNewCameraSubject()
		if self.subjectStateChangedConn then
			self.subjectStateChangedConn:Disconnect()
			self.subjectStateChangedConn = nil
		end
	
		local humanoid = workspace.CurrentCamera and workspace.CurrentCamera.CameraSubject
		if self.trackingHumanoid ~= humanoid then
			self:CancelCameraFreeze()
		end
		if humanoid and humanoid:IsA("Humanoid") then
			self.subjectStateChangedConn = humanoid.StateChanged:Connect(function(oldState, newState)
				if VRService.VREnabled and newState == Enum.HumanoidStateType.Jumping and not self.inFirstPerson then
					self:StartCameraFreeze(self:GetSubjectPosition(), humanoid)
				elseif newState ~= Enum.HumanoidStateType.Jumping and newState ~= Enum.HumanoidStateType.Freefall then
					self:CancelCameraFreeze(true)
				end
			end)
		end
	end
	
	function BaseCamera:GetVRFocus(subjectPosition, timeDelta)
		local lastFocus = self.LastCameraFocus or subjectPosition
		if not self.cameraFrozen then
			self.cameraTranslationConstraints = Vector3.new(self.cameraTranslationConstraints.x, math.min(1, self.cameraTranslationConstraints.y + 0.42 * timeDelta), self.cameraTranslationConstraints.z)
		end
	
		local newFocus
		if self.cameraFrozen and self.humanoidJumpOrigin and self.humanoidJumpOrigin.y > lastFocus.y then
			newFocus = CFrame.new(Vector3.new(subjectPosition.x, math.min(self.humanoidJumpOrigin.y, lastFocus.y + 5 * timeDelta), subjectPosition.z))
		else
			newFocus = CFrame.new(Vector3.new(subjectPosition.x, lastFocus.y, subjectPosition.z):lerp(subjectPosition, self.cameraTranslationConstraints.y))
		end
	
		if self.cameraFrozen then
			-- No longer in 3rd person
			if self.inFirstPerson then -- not VRService.VREnabled
				self:CancelCameraFreeze()
			end
			-- This case you jumped off a cliff and want to keep your character in view
			-- 0.5 is to fix floating point error when not jumping off cliffs
			if self.humanoidJumpOrigin and subjectPosition.y < (self.humanoidJumpOrigin.y - 0.5) then
				self:CancelCameraFreeze()
			end
		end
	
		return newFocus
	end
	
	function BaseCamera:GetRotateAmountValue(vrRotationIntensity)
		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
		if vrRotationIntensity then
			if vrRotationIntensity == "Low" then
				return VR_LOW_INTENSITY_ROTATION
			elseif vrRotationIntensity == "High" then
				return VR_HIGH_INTENSITY_ROTATION
			end
		end
		return ZERO_VECTOR2
	end
	
	function BaseCamera:GetRepeatDelayValue(vrRotationIntensity)
		vrRotationIntensity = vrRotationIntensity or StarterGui:GetCore("VRRotationIntensity")
		if vrRotationIntensity then
			if vrRotationIntensity == "Low" then
				return VR_LOW_INTENSITY_REPEAT
			elseif vrRotationIntensity == "High" then
				return VR_HIGH_INTENSITY_REPEAT
			end
		end
		return 0
	end
	
	function BaseCamera:Update(dt)
		error("BaseCamera:Update() This is a virtual function that should never be getting called.", 2)
	end
	
	BaseCamera.UpCFrame = CFrame.new()
	
	function BaseCamera:UpdateUpCFrame(cf)
		self.UpCFrame = cf
	end
	local ZERO = Vector3.new(0, 0, 0)
	function BaseCamera:CalculateNewLookCFrame(suppliedLookVector)
		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
		currLookVector = self.UpCFrame:VectorToObjectSpace(currLookVector)
		
		local currPitchAngle = math.asin(currLookVector.y)
		local yTheta = math.clamp(self.rotateInput.y, -MAX_Y + currPitchAngle, -MIN_Y + currPitchAngle)
		local constrainedRotateInput = Vector2.new(self.rotateInput.x, yTheta)
		local startCFrame = CFrame.new(ZERO, currLookVector)
		local newLookCFrame = CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)
		
		return newLookCFrame
	end
	
	return BaseCamera
end

function _BaseOcclusion()
	--[[ The Module ]]--
	local BaseOcclusion = {}
	BaseOcclusion.__index = BaseOcclusion
	setmetatable(BaseOcclusion, {
		__call = function(_, ...)
			return BaseOcclusion.new(...)
		end
	})
	
	function BaseOcclusion.new()
		local self = setmetatable({}, BaseOcclusion)
		return self
	end
	
	-- Called when character is added
	function BaseOcclusion:CharacterAdded(char, player)
	end
	
	-- Called when character is about to be removed
	function BaseOcclusion:CharacterRemoving(char, player)
	end
	
	function BaseOcclusion:OnCameraSubjectChanged(newSubject)
	end
	
	--[[ Derived classes are required to override and implement all of the following functions ]]--
	function BaseOcclusion:GetOcclusionMode()
		-- Must be overridden in derived classes to return an Enum.DevCameraOcclusionMode value
		warn("BaseOcclusion GetOcclusionMode must be overridden by derived classes")
		return nil
	end
	
	function BaseOcclusion:Enable(enabled)
		warn("BaseOcclusion Enable must be overridden by derived classes")
	end
	
	function BaseOcclusion:Update(dt, desiredCameraCFrame, desiredCameraFocus)
		warn("BaseOcclusion Update must be overridden by derived classes")
		return desiredCameraCFrame, desiredCameraFocus
	end
	
	return BaseOcclusion
end

function _Popper()
	
	local Players = game:GetService("Players")
	
	local camera = game.Workspace.CurrentCamera
	
	local min = math.min
	local tan = math.tan
	local rad = math.rad
	local inf = math.huge
	local ray = Ray.new
	
	local function getTotalTransparency(part)
		return 1 - (1 - part.Transparency)*(1 - part.LocalTransparencyModifier)
	end
	
	local function eraseFromEnd(t, toSize)
		for i = #t, toSize + 1, -1 do
			t[i] = nil
		end
	end
	
	local nearPlaneZ, projX, projY do
		local function updateProjection()
			local fov = rad(camera.FieldOfView)
			local view = camera.ViewportSize
			local ar = view.X/view.Y
	
			projY = 2*tan(fov/2)
			projX = ar*projY
		end
	
		camera:GetPropertyChangedSignal("FieldOfView"):Connect(updateProjection)
		camera:GetPropertyChangedSignal("ViewportSize"):Connect(updateProjection)
	
		updateProjection()
	
		nearPlaneZ = camera.NearPlaneZ
		camera:GetPropertyChangedSignal("NearPlaneZ"):Connect(function()
			nearPlaneZ = camera.NearPlaneZ
		end)
	end
	
	local blacklist = {} do
		local charMap = {}
	
		local function refreshIgnoreList()
			local n = 1
			blacklist = {}
			for _, character in pairs(charMap) do
				blacklist[n] = character
				n = n + 1
			end
		end
	
		local function playerAdded(player)
			local function characterAdded(character)
				charMap[player] = character
				refreshIgnoreList()
			end
			local function characterRemoving()
				charMap[player] = nil
				refreshIgnoreList()
			end
	
			player.CharacterAdded:Connect(characterAdded)
			player.CharacterRemoving:Connect(characterRemoving)
			if player.Character then
				characterAdded(player.Character)
			end
		end
	
		local function playerRemoving(player)
			charMap[player] = nil
			refreshIgnoreList()
		end
	
		Players.PlayerAdded:Connect(playerAdded)
		Players.PlayerRemoving:Connect(playerRemoving)
	
		for _, player in ipairs(Players:GetPlayers()) do
			playerAdded(player)
		end
		refreshIgnoreList()
	end
	
	--------------------------------------------------------------------------------------------
	-- Popper uses the level geometry find an upper bound on subject-to-camera distance.
	--
	-- Hard limits are applied immediately and unconditionally. They are generally caused
	-- when level geometry intersects with the near plane (with exceptions, see below).
	--
	-- Soft limits are only applied under certain conditions.
	-- They are caused when level geometry occludes the subject without actually intersecting
	-- with the near plane at the target distance.
	--
	-- Soft limits can be promoted to hard limits and hard limits can be demoted to soft limits.
	-- We usually don"t want the latter to happen.
	--
	-- A soft limit will be promoted to a hard limit if an obstruction
	-- lies between the current and target camera positions.
	--------------------------------------------------------------------------------------------
	
	local subjectRoot
	local subjectPart
	
	camera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
		local subject = camera.CameraSubject
		if subject:IsA("Humanoid") then
			subjectPart = subject.RootPart
		elseif subject:IsA("BasePart") then
			subjectPart = subject
		else
			subjectPart = nil
		end
	end)
	
	local function canOcclude(part)
		-- Occluders must be:
		-- 1. Opaque
		-- 2. Interactable
		-- 3. Not in the same assembly as the subject
	
		return
			getTotalTransparency(part) < 0.25 and
			part.CanCollide and
			subjectRoot ~= (part:GetRootPart() or part) and
			not part:IsA("TrussPart")
	end
	
	-- Offsets for the volume visibility test
	local SCAN_SAMPLE_OFFSETS = {
		Vector2.new( 0.4, 0.0),
		Vector2.new(-0.4, 0.0),
		Vector2.new( 0.0,-0.4),
		Vector2.new( 0.0, 0.4),
		Vector2.new( 0.0, 0.2),
	}
	
	--------------------------------------------------------------------------------
	-- Piercing raycasts
	
	local function getCollisionPoint(origin, dir)
		local originalSize = #blacklist
		repeat
			local hitPart, hitPoint = workspace:FindPartOnRayWithIgnoreList(
				ray(origin, dir), blacklist, false, true
			)
	
			if hitPart then
				if hitPart.CanCollide then
					eraseFromEnd(blacklist, originalSize)
					return hitPoint, true
				end
				blacklist[#blacklist + 1] = hitPart
			end
		until not hitPart
	
		eraseFromEnd(blacklist, originalSize)
		return origin + dir, false
	end
	
	--------------------------------------------------------------------------------
	
	local function queryPoint(origin, unitDir, dist, lastPos)
		debug.profilebegin("queryPoint")
	
		local originalSize = #blacklist
	
		dist = dist + nearPlaneZ
		local target = origin + unitDir*dist
	
		local softLimit = inf
		local hardLimit = inf
		local movingOrigin = origin
	
		repeat
			local entryPart, entryPos = workspace:FindPartOnRayWithIgnoreList(ray(movingOrigin, target - movingOrigin), blacklist, false, true)
	
			if entryPart then
				if canOcclude(entryPart) then
					local wl = {entryPart}
					local exitPart = workspace:FindPartOnRayWithWhitelist(ray(target, entryPos - target), wl, true)
	
					local lim = (entryPos - origin).Magnitude
	
					if exitPart then
						local promote = false
						if lastPos then
							promote =
								workspace:FindPartOnRayWithWhitelist(ray(lastPos, target - lastPos), wl, true) or
								workspace:FindPartOnRayWithWhitelist(ray(target, lastPos - target), wl, true)
						end
	
						if promote then
							-- Ostensibly a soft limit, but the camera has passed through it in the last frame, so promote to a hard limit.
							hardLimit = lim
						elseif dist < softLimit then
							-- Trivial soft limit
							softLimit = lim
						end
					else
						-- Trivial hard limit
						hardLimit = lim
					end
				end
	
				blacklist[#blacklist + 1] = entryPart
				movingOrigin = entryPos - unitDir*1e-3
			end
		until hardLimit < inf or not entryPart
	
		eraseFromEnd(blacklist, originalSize)
	
		debug.profileend()
		return softLimit - nearPlaneZ, hardLimit - nearPlaneZ
	end
	
	local function queryViewport(focus, dist)
		debug.profilebegin("queryViewport")
	
		local fP =  focus.p
		local fX =  focus.rightVector
		local fY =  focus.upVector
		local fZ = -focus.lookVector
	
		local viewport = camera.ViewportSize
	
		local hardBoxLimit = inf
		local softBoxLimit = inf
	
		-- Center the viewport on the PoI, sweep points on the edge towards the target, and take the minimum limits
		for viewX = 0, 1 do
			local worldX = fX*((viewX - 0.5)*projX)
	
			for viewY = 0, 1 do
				local worldY = fY*((viewY - 0.5)*projY)
	
				local origin = fP + nearPlaneZ*(worldX + worldY)
				local lastPos = camera:ViewportPointToRay(
					viewport.x*viewX,
					viewport.y*viewY
				).Origin
	
				local softPointLimit, hardPointLimit = queryPoint(origin, fZ, dist, lastPos)
	
				if hardPointLimit < hardBoxLimit then
					hardBoxLimit = hardPointLimit
				end
				if softPointLimit < softBoxLimit then
					softBoxLimit = softPointLimit
				end
			end
		end
		debug.profileend()
	
		return softBoxLimit, hardBoxLimit
	end
	
	local function testPromotion(focus, dist, focusExtrapolation)
		debug.profilebegin("testPromotion")
	
		local fP = focus.p
		local fX = focus.rightVector
		local fY = focus.upVector
		local fZ = -focus.lookVector
	
		do
			-- Dead reckoning the camera rotation and focus
			debug.profilebegin("extrapolate")
	
			local SAMPLE_DT = 0.0625
			local SAMPLE_MAX_T = 1.25
	
			local maxDist = (getCollisionPoint(fP, focusExtrapolation.posVelocity*SAMPLE_MAX_T) - fP).Magnitude
			-- Metric that decides how many samples to take
			local combinedSpeed = focusExtrapolation.posVelocity.magnitude
	
			for dt = 0, min(SAMPLE_MAX_T, focusExtrapolation.rotVelocity.magnitude + maxDist/combinedSpeed), SAMPLE_DT do
				local cfDt = focusExtrapolation.extrapolate(dt) -- Extrapolated CFrame at time dt
	
				if queryPoint(cfDt.p, -cfDt.lookVector, dist) >= dist then
					return false
				end
			end
	
			debug.profileend()
		end
	
		do
			-- Test screen-space offsets from the focus for the presence of soft limits
			debug.profilebegin("testOffsets")
	
			for _, offset in ipairs(SCAN_SAMPLE_OFFSETS) do
				local scaledOffset = offset
				local pos = getCollisionPoint(fP, fX*scaledOffset.x + fY*scaledOffset.y)
				if queryPoint(pos, (fP + fZ*dist - pos).Unit, dist) == inf then
					return false
				end
			end
	
			debug.profileend()
		end
	
		debug.profileend()
		return true
	end
	
	local function Popper(focus, targetDist, focusExtrapolation)
		debug.profilebegin("popper")
	
		subjectRoot = subjectPart and subjectPart:GetRootPart() or subjectPart
	
		local dist = targetDist
		local soft, hard = queryViewport(focus, targetDist)
		if hard < dist then
			dist = hard
		end
		if soft < dist and testPromotion(focus, targetDist, focusExtrapolation) then
			dist = soft
		end
	
		subjectRoot = nil
	
		debug.profileend()
		return dist
	end
	
	return Popper
end

function _ZoomController()
	local ZOOM_STIFFNESS = 4.5
	local ZOOM_DEFAULT = 12.5
	local ZOOM_ACCELERATION = 0.0375
	
	local MIN_FOCUS_DIST = 0.5
	local DIST_OPAQUE = 1
	
	local Popper = _Popper()
	
	local clamp = math.clamp
	local exp = math.exp
	local min = math.min
	local max = math.max
	local pi = math.pi
	
	local cameraMinZoomDistance, cameraMaxZoomDistance do
		local Player = game:GetService("Players").LocalPlayer
	
		local function updateBounds()
			cameraMinZoomDistance = Player.CameraMinZoomDistance
			cameraMaxZoomDistance = Player.CameraMaxZoomDistance
		end
	
		updateBounds()
	
		Player:GetPropertyChangedSignal("CameraMinZoomDistance"):Connect(updateBounds)
		Player:GetPropertyChangedSignal("CameraMaxZoomDistance"):Connect(updateBounds)
	end
	
	local ConstrainedSpring = {} do
		ConstrainedSpring.__index = ConstrainedSpring
	
		function ConstrainedSpring.new(freq, x, minValue, maxValue)
			x = clamp(x, minValue, maxValue)
			return setmetatable({
				freq = freq, -- Undamped frequency (Hz)
				x = x, -- Current position
				v = 0, -- Current velocity
				minValue = minValue, -- Minimum bound
				maxValue = maxValue, -- Maximum bound
				goal = x, -- Goal position
			}, ConstrainedSpring)
		end
	
		function ConstrainedSpring:Step(dt)
			local freq = self.freq*2*pi -- Convert from Hz to rad/s
			local x = self.x
			local v = self.v
			local minValue = self.minValue
			local maxValue = self.maxValue
			local goal = self.goal
	
			-- Solve the spring ODE for position and velocity after time t, assuming critical damping:
			--   2*f*x'[t] + x''[t] = f^2*(g - x[t])
			-- Knowns are x[0] and x'[0].
			-- Solve for x[t] and x'[t].
	
			local offset = goal - x
			local step = freq*dt
			local decay = exp(-step)
	
			local x1 = goal + (v*dt - offset*(step + 1))*decay
			local v1 = ((offset*freq - v)*step + v)*decay
	
			-- Constrain
			if x1 < minValue then
				x1 = minValue
				v1 = 0
			elseif x1 > maxValue then
				x1 = maxValue
				v1 = 0
			end
	
			self.x = x1
			self.v = v1
	
			return x1
		end
	end
	
	local zoomSpring = ConstrainedSpring.new(ZOOM_STIFFNESS, ZOOM_DEFAULT, MIN_FOCUS_DIST, cameraMaxZoomDistance)
	
	local function stepTargetZoom(z, dz, zoomMin, zoomMax)
		z = clamp(z + dz*(1 + z*ZOOM_ACCELERATION), zoomMin, zoomMax)
		if z < DIST_OPAQUE then
			z = dz  DIST_OPAQUE then
				-- Make a pessimistic estimate of zoom distance for this step without accounting for poppercam
				local maxPossibleZoom = max(
					zoomSpring.x,
					stepTargetZoom(zoomSpring.goal, zoomDelta, cameraMinZoomDistance, cameraMaxZoomDistance)
				)
	
				-- Run the Popper algorithm on the feasible zoom range, [MIN_FOCUS_DIST, maxPossibleZoom]
				poppedZoom = Popper(
					focus*CFrame.new(0, 0, MIN_FOCUS_DIST),
					maxPossibleZoom - MIN_FOCUS_DIST,
					extrapolation
				) + MIN_FOCUS_DIST
			end
	
			zoomSpring.minValue = MIN_FOCUS_DIST
			zoomSpring.maxValue = min(cameraMaxZoomDistance, poppedZoom)
	
			return zoomSpring:Step(renderDt)
		end
	
		function Zoom.SetZoomParameters(targetZoom, newZoomDelta)
			zoomSpring.goal = targetZoom
			zoomDelta = newZoomDelta
		end
	end
	
	return Zoom
end

function _MouseLockController()
	--[[ Constants ]]--
	local DEFAULT_MOUSE_LOCK_CURSOR = "rbxasset://textures/MouseLockedCursor.png"
	
	local CONTEXT_ACTION_NAME = "MouseLockSwitchAction"
	local MOUSELOCK_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
	
	--[[ Services ]]--
	local PlayersService = game:GetService("Players")
	local ContextActionService = game:GetService("ContextActionService")
	local Settings = UserSettings()	-- ignore warning
	local GameSettings = Settings.GameSettings
	local Mouse = PlayersService.LocalPlayer:GetMouse()
	
	--[[ 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")
			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 = script:FindFirstChild("CameraOffset")
		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")
			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)
		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
					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 = script:FindFirstChild("CursorImage")
			if cursorImageValueObj and cursorImageValueObj:IsA("StringValue") and cursorImageValueObj.Value then
				self.savedMouseCursor = Mouse.Icon
				Mouse.Icon = cursorImageValueObj.Value
			else
				if cursorImageValueObj then
					cursorImageValueObj:Destroy()
				end
				cursorImageValueObj = Instance.new("StringValue")
				cursorImageValueObj.Name = "CursorImage"
				cursorImageValueObj.Value = DEFAULT_MOUSE_LOCK_CURSOR
				cursorImageValueObj.Parent = script
				self.savedMouseCursor = Mouse.Icon
				Mouse.Icon = DEFAULT_MOUSE_LOCK_CURSOR
			end
		else
			if self.savedMouseCursor then
				Mouse.Icon = self.savedMouseCursor
				self.savedMouseCursor = nil
			end
		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()
		return self.enabled and self.isMouseLocked
	end
	
	function MouseLockController:EnableMouseLock(enable)
		if enable ~= self.enabled then
	
			self.enabled = enable
	
			if self.enabled then
				-- Enabling the mode
				self:BindContextActions()
			else
				-- Disabling
				-- Restore mouse cursor
				if Mouse.Icon~="" then
					Mouse.Icon = ""
				end
	
				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
end

function _TransparencyController()
	
	local MAX_TWEEN_RATE = 2.8 -- per second
	
	local Util = _CameraUtils()
	
	--[[ The Module ]]--
	local TransparencyController = {}
	TransparencyController.__index = TransparencyController
	
	function TransparencyController.new()
		local self = setmetatable({}, TransparencyController)
	
		self.lastUpdate = tick()
		self.transparencyDirty = false
		self.enabled = false
		self.lastTransparency = nil
	
		self.descendantAddedConn, self.descendantRemovingConn = nil, nil
		self.toolDescendantAddedConns = {}
		self.toolDescendantRemovingConns = {}
		self.cachedParts = {}
	
		return self
	end
	
	
	function TransparencyController:HasToolAncestor(object)
		if object.Parent == nil then return false end
		return object.Parent:IsA('Tool') or self:HasToolAncestor(object.Parent)
	end
	
	function TransparencyController:IsValidPartToModify(part)
		if part:IsA('BasePart') or part:IsA('Decal') then
			return not self:HasToolAncestor(part)
		end
		return false
	end
	
	function TransparencyController:CachePartsRecursive(object)
		if object then
			if self:IsValidPartToModify(object) then
				self.cachedParts[object] = true
				self.transparencyDirty = true
			end
			for _, child in pairs(object:GetChildren()) do
				self:CachePartsRecursive(child)
			end
		end
	end
	
	function TransparencyController:TeardownTransparency()
		for child, _ in pairs(self.cachedParts) do
			child.LocalTransparencyModifier = 0
		end
		self.cachedParts = {}
		self.transparencyDirty = true
		self.lastTransparency = nil
	
		if self.descendantAddedConn then
			self.descendantAddedConn:disconnect()
			self.descendantAddedConn = nil
		end
		if self.descendantRemovingConn then
			self.descendantRemovingConn:disconnect()
			self.descendantRemovingConn = nil
		end
		for object, conn in pairs(self.toolDescendantAddedConns) do
			conn:Disconnect()
			self.toolDescendantAddedConns[object] = nil
		end
		for object, conn in pairs(self.toolDescendantRemovingConns) do
			conn:Disconnect()
			self.toolDescendantRemovingConns[object] = nil
		end
	end
	
	function TransparencyController:SetupTransparency(character)
		self:TeardownTransparency()
	
		if self.descendantAddedConn then self.descendantAddedConn:disconnect() end
		self.descendantAddedConn = character.DescendantAdded:Connect(function(object)
			-- This is a part we want to invisify
			if self:IsValidPartToModify(object) then
				self.cachedParts[object] = true
				self.transparencyDirty = true
			-- There is now a tool under the character
			elseif object:IsA('Tool') then
				if self.toolDescendantAddedConns[object] then self.toolDescendantAddedConns[object]:Disconnect() end
				self.toolDescendantAddedConns[object] = object.DescendantAdded:Connect(function(toolChild)
					self.cachedParts[toolChild] = nil
					if toolChild:IsA('BasePart') or toolChild:IsA('Decal') then
						-- Reset the transparency
						toolChild.LocalTransparencyModifier = 0
					end
				end)
				if self.toolDescendantRemovingConns[object] then self.toolDescendantRemovingConns[object]:disconnect() end
				self.toolDescendantRemovingConns[object] = object.DescendantRemoving:Connect(function(formerToolChild)
					wait() -- wait for new parent
					if character and formerToolChild and formerToolChild:IsDescendantOf(character) then
						if self:IsValidPartToModify(formerToolChild) then
							self.cachedParts[formerToolChild] = true
							self.transparencyDirty = true
						end
					end
				end)
			end
		end)
		if self.descendantRemovingConn then self.descendantRemovingConn:disconnect() end
		self.descendantRemovingConn = character.DescendantRemoving:connect(function(object)
			if self.cachedParts[object] then
				self.cachedParts[object] = nil
				-- Reset the transparency
				object.LocalTransparencyModifier = 0
			end
		end)
		self:CachePartsRecursive(character)
	end
	
	
	function TransparencyController:Enable(enable)
		if self.enabled ~= enable then
			self.enabled = enable
			self:Update()
		end
	end
	
	function TransparencyController:SetSubject(subject)
		local character = nil
		if subject and subject:IsA("Humanoid") then
			character = subject.Parent
		end
		if subject and subject:IsA("VehicleSeat") and subject.Occupant then
			character = subject.Occupant.Parent
		end
		if character then
			self:SetupTransparency(character)
		else
			self:TeardownTransparency()
		end
	end
	
	function TransparencyController:Update()
		local instant = false
		local now = tick()
		local currentCamera = workspace.CurrentCamera
	
		if currentCamera then
			local transparency = 0
			if not self.enabled then
				instant = true
			else
				local distance = (currentCamera.Focus.p - currentCamera.CoordinateFrame.p).magnitude
				transparency = (distance 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, 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)
		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, desiredCameraCFrame, desiredCameraFocus)
		-- 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
end

function _LegacyCamera()
	
	local ZERO_VECTOR2 = Vector2.new(0,0)
	
	local Util = _CameraUtils()
	
	--[[ Services ]]--
	local PlayersService = game:GetService('Players')
	
	--[[ The Module ]]--
	local BaseCamera = _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)
	
		-- Cannot update until cameraType has been set
		if not self.cameraType then return 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 = self:GetSubjectPosition()
	
		if self.cameraType == Enum.CameraType.Fixed then
			if self.lastUpdate then
				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
				local delta = math.min(0.1, now - self.lastUpdate)
				local gamepadRotation = self:UpdateGamepad()
				self.rotateInput = self.rotateInput + (gamepadRotation * delta)
			end
	
			if subjectPosition and player and camera then
				local distanceToSubject = self:GetCameraToSubjectDistance()
				local newLookVector = self:CalculateNewLookVector()
				self.rotateInput = ZERO_VECTOR2
	
				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
			if subjectPosition and camera then
				local distanceToSubject = self:GetCameraToSubjectDistance()
				local humanoid = self:GetHumanoid()
				if self.lastUpdate and humanoid and humanoid.RootPart then
	
					-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
					local delta = math.min(0.1, now - self.lastUpdate)
					local gamepadRotation = self:UpdateGamepad()
					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
	
					local forwardVector = humanoid.RootPart.CFrame.lookVector
	
					local y = Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
					if Util.IsFinite(y) then
						-- Preserve vertical rotation from user input
						self.rotateInput = Vector2.new(y, self.rotateInput.Y)
					end
				end
	
				local newLookVector = self:CalculateNewLookVector()
				self.rotateInput = ZERO_VECTOR2
	
				newCameraFocus = CFrame.new(subjectPosition)
				newCameraCFrame = CFrame.new(subjectPosition - (distanceToSubject * newLookVector), subjectPosition)
			end
		elseif self.cameraType == Enum.CameraType.Watch then
			if subjectPosition and player and camera then
				local cameraLook = nil
	
				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 = self:GetCameraToSubjectDistance()
				local newLookVector = self:CalculateNewLookVector(cameraLook)
				self.rotateInput = ZERO_VECTOR2
	
				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
end

function _OrbitalCamera()
	
	-- 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 ZERO_VECTOR2 = Vector2.new(0,0)
	local TAU = 2 * math.pi
	
	--[[ Gamepad Support ]]--
	local THUMBSTICK_DEADZONE = 0.2
	
	-- 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 = _CameraUtils()
	
	--[[ Services ]]--
	local PlayersService = game:GetService('Players')
	local VRService = game:GetService("VRService")
	
	--[[ The Module ]]--
	local BaseCamera = _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
	
		-- Gamepad
		self.r3ButtonDown = false
		self.l3ButtonDown = false
		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, 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
			print("externalProperties table has no entry for ",name)
			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)
		if not humanoid or not humanoid.RootPart then
			warn("OrbitalCamera could not set initial orientation due to missing humanoid")
			return
		end
		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
		self.rotateInput = Vector2.new(horizontalShift, vertShift)
	end
	
	--[[ Functions of BaseCamera that are overridden by OrbitalCamera ]]--
	function OrbitalCamera:GetCameraToSubjectDistance()
		return self.curDistance
	end
	
	function OrbitalCamera:SetCameraToSubjectDistance(desiredSubjectDistance)
		print("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, xyRotateVector)
		local currLookVector = suppliedLookVector or self:GetCameraLookVector()
		local currPitchAngle = math.asin(currLookVector.y)
		local yTheta = math.clamp(xyRotateVector.y, currPitchAngle - math.rad(MAX_ALLOWED_ELEVATION_DEG), currPitchAngle - math.rad(MIN_ALLOWED_ELEVATION_DEG))
		local constrainedRotateInput = Vector2.new(xyRotateVector.x, yTheta)
		local startCFrame = CFrame.new(ZERO_VECTOR3, currLookVector)
		local newLookVector = (CFrame.Angles(0, -constrainedRotateInput.x, 0) * startCFrame * CFrame.Angles(-constrainedRotateInput.y,0,0)).lookVector
		return newLookVector
	end
	
	function OrbitalCamera:GetGamepadPan(name, state, input)
		if input.UserInputType == self.activeGamepad and input.KeyCode == Enum.KeyCode.Thumbstick2 then
			if self.r3ButtonDown or self.l3ButtonDown then
			-- R3 or L3 Thumbstick is depressed, right stick controls dolly in/out
				if (input.Position.Y > THUMBSTICK_DEADZONE) then
					self.gamepadDollySpeedMultiplier = 0.96
				elseif (input.Position.Y < -THUMBSTICK_DEADZONE) then
					self.gamepadDollySpeedMultiplier = 1.04
				else
					self.gamepadDollySpeedMultiplier = 1.00
				end
			else
				if state == Enum.UserInputState.Cancel then
					self.gamepadPanningCamera = ZERO_VECTOR2
					return
				end
	
				local inputVector = Vector2.new(input.Position.X, -input.Position.Y)
				if inputVector.magnitude > THUMBSTICK_DEADZONE then
					self.gamepadPanningCamera = Vector2.new(input.Position.X, -input.Position.Y)
				else
					self.gamepadPanningCamera = ZERO_VECTOR2
				end
			end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	end
	
	function OrbitalCamera:DoGamepadZoom(name, state, input)
		if input.UserInputType == self.activeGamepad and (input.KeyCode == Enum.KeyCode.ButtonR3 or input.KeyCode == Enum.KeyCode.ButtonL3) then
			if (state == Enum.UserInputState.Begin) then
				self.r3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonR3
				self.l3ButtonDown = input.KeyCode == Enum.KeyCode.ButtonL3
			elseif (state == Enum.UserInputState.End) then
				if (input.KeyCode == Enum.KeyCode.ButtonR3) then
					self.r3ButtonDown = false
				elseif (input.KeyCode == Enum.KeyCode.ButtonL3) then
					self.l3ButtonDown = false
				end
				if (not self.r3ButtonDown) and (not self.l3ButtonDown) then
					self.gamepadDollySpeedMultiplier = 1.00
				end
			end
			return Enum.ContextActionResult.Sink
		end
		return Enum.ContextActionResult.Pass
	end
	
	function OrbitalCamera:BindGamepadInputActions()
		self:BindAction("OrbitalCamGamepadPan", function(name, state, input) return self:GetGamepadPan(name, state, input) end,
			false, Enum.KeyCode.Thumbstick2)
		self:BindAction("OrbitalCamGamepadZoom", function(name, state, input) return self:DoGamepadZoom(name, state, input) end,
			false, Enum.KeyCode.ButtonR3, Enum.KeyCode.ButtonL3)
	end
	
	
	-- [[ Update ]]--
	function OrbitalCamera:Update(dt)
		local now = tick()
		local timeDelta = (now - self.lastUpdate)
		local userPanningTheCamera = (self.UserPanningTheCamera == true)
		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
	
		if self.lastUpdate then
			local gamepadRotation = self:UpdateGamepad()
	
			if self:ShouldUseVRRotation() then
				self.RotateInput = self.RotateInput + self:GetVRRotationInput()
			else
				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
				local delta = math.min(0.1, timeDelta)
	
				if gamepadRotation ~= ZERO_VECTOR2 then
					userPanningTheCamera = true
					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
				end
	
				local angle = 0
				if not (isInVehicle or isOnASkateboard) then
					angle = angle + (self.TurningLeft and -120 or 0)
					angle = angle + (self.TurningRight and 120 or 0)
				end
	
				if angle ~= 0 then
					self.rotateInput = self.rotateInput +  Vector2.new(math.rad(angle * delta), 0)
					userPanningTheCamera = true
				end
			end
		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 cameraFocusP = newCameraFocus.p
			if VREnabled and not self:IsInFirstPerson() then
				local cameraHeight = self:GetCameraHeight()
				local vecToSubject = (subjectPosition - camera.CFrame.p)
				local distToSubject = vecToSubject.magnitude
	
				-- Only move the camera if it exceeded a maximum distance to the subject in VR
				if distToSubject > self.currentSubjectDistance or self.rotateInput.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(self.rotateInput.x, 0)) * desiredDist
	
					local newPos = cameraFocusP - vecToSubject
					local desiredLookDir = camera.CFrame.lookVector
					if self.rotateInput.x ~= 0 then
						desiredLookDir = vecToSubject
					end
					local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
					self.RotateInput = ZERO_VECTOR2
	
					newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
				end
			else
				-- self.RotateInput is a Vector2 of mouse movement deltas since last update
				self.curAzimuthRad = self.curAzimuthRad - self.rotateInput.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 + self.rotateInput.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)
	
				self.rotateInput = ZERO_VECTOR2
			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
end

function _ClassicCamera()
	
	-- Local private variables and constants
	local ZERO_VECTOR2 = Vector2.new(0,0)
	
	local tweenAcceleration = math.rad(220)		--Radians/Second^2
	local tweenSpeed = math.rad(0)				--Radians/Second
	local tweenMaxSpeed = math.rad(250)			--Radians/Second
	local TIME_BEFORE_AUTO_ROTATE = 2.0 		--Seconds, used when auto-aligning camera with vehicles
	
	local INITIAL_CAMERA_ANGLE = CFrame.fromOrientation(math.rad(-15), 0, 0)
	
	local FFlagUserCameraToggle do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
		end)
		FFlagUserCameraToggle = success and result
	end
	
	--[[ Services ]]--
	local PlayersService = game:GetService('Players')
	local VRService = game:GetService("VRService")
	
	local CameraInput = _CameraInput()
	local Util = _CameraUtils()
	
	--[[ The Module ]]--
	local BaseCamera = _BaseCamera()
	local ClassicCamera = setmetatable({}, BaseCamera)
	ClassicCamera.__index = ClassicCamera
	
	function ClassicCamera.new()
		local self = setmetatable(BaseCamera.new(), ClassicCamera)
	
		self.isFollowCamera = false
		self.isCameraToggle = false
		self.lastUpdate = tick()
		self.cameraToggleSpring = Util.Spring.new(5, 0)
	
		return self
	end
	
	function ClassicCamera:GetCameraToggleOffset(dt)
		assert(FFlagUserCameraToggle)
	
		if self.isCameraToggle then
			local zoom = self.currentSubjectDistance
	
			if CameraInput.getTogglePan() then
				self.cameraToggleSpring.goal = math.clamp(Util.map(zoom, 0.5, self.FIRST_PERSON_DISTANCE_THRESHOLD, 0, 1), 0, 1)
			else
				self.cameraToggleSpring.goal = 0
			end
	
			local distanceOffset = math.clamp(Util.map(zoom, 0.5, 64, 0, 1), 0, 1) + 1
			return Vector3.new(0, self.cameraToggleSpring:step(dt)*distanceOffset, 0)
		end
	
		return Vector3.new()
	end
	
	-- Movement mode standardized to Enum.ComputerCameraMovementMode values
	function ClassicCamera:SetCameraMovementMode(cameraMovementMode)
		BaseCamera.SetCameraMovementMode(self, cameraMovementMode)
	
		self.isFollowCamera = cameraMovementMode == Enum.ComputerCameraMovementMode.Follow
		self.isCameraToggle = cameraMovementMode == Enum.ComputerCameraMovementMode.CameraToggle
	end
	
	function ClassicCamera:Update()
		local now = tick()
		local timeDelta = now - self.lastUpdate
	
		local camera = workspace.CurrentCamera
		local newCameraCFrame = camera.CFrame
		local newCameraFocus = camera.Focus
	
		local overrideCameraLookVector = nil
		if self.resetCameraAngle then
			local rootPart = self:GetHumanoidRootPart()
			if rootPart then
				overrideCameraLookVector = (rootPart.CFrame * INITIAL_CAMERA_ANGLE).lookVector
			else
				overrideCameraLookVector = INITIAL_CAMERA_ANGLE.lookVector
			end
			self.resetCameraAngle = false
		end
	
		local player = PlayersService.LocalPlayer
		local humanoid = self:GetHumanoid()
		local cameraSubject = camera.CameraSubject
		local isInVehicle = cameraSubject and cameraSubject:IsA('VehicleSeat')
		local isOnASkateboard = cameraSubject and cameraSubject:IsA('SkateboardPlatform')
		local isClimbing = humanoid and humanoid:GetState() == Enum.HumanoidStateType.Climbing
	
		if self.lastUpdate == nil or timeDelta > 1 then
			self.lastCameraTransform = nil
		end
	
		if self.lastUpdate then
			local gamepadRotation = self:UpdateGamepad()
	
			if self:ShouldUseVRRotation() then
				self.rotateInput = self.rotateInput + self:GetVRRotationInput()
			else
				-- Cap out the delta to 0.1 so we don't get some crazy things when we re-resume from
				local delta = math.min(0.1, timeDelta)
	
				if gamepadRotation ~= ZERO_VECTOR2 then
					self.rotateInput = self.rotateInput + (gamepadRotation * delta)
				end
	
				local angle = 0
				if not (isInVehicle or isOnASkateboard) then
					angle = angle + (self.turningLeft and -120 or 0)
					angle = angle + (self.turningRight and 120 or 0)
				end
	
				if angle ~= 0 then
					self.rotateInput = self.rotateInput +  Vector2.new(math.rad(angle * delta), 0)
				end
			end
		end
	
		local cameraHeight = self:GetCameraHeight()
	
		-- Reset tween speed if user is panning
		if self.userPanningTheCamera then
			tweenSpeed = 0
			self.lastUserPanCamera = tick()
		end
	
		local userRecentlyPannedCamera = now - self.lastUserPanCamera < TIME_BEFORE_AUTO_ROTATE
		local subjectPosition = self:GetSubjectPosition()
	
		if subjectPosition and player and camera then
			local zoom = self:GetCameraToSubjectDistance()
			if zoom < 0.5 then
				zoom = 0.5
			end
	
			if self:GetIsMouseLocked() and not self:IsInFirstPerson() then
				-- We need to use the right vector of the camera after rotation, not before
				local newLookCFrame = self:CalculateNewLookCFrame(overrideCameraLookVector)
	
				local offset = self:GetMouseLockOffset()
				local cameraRelativeOffset = offset.X * newLookCFrame.rightVector + offset.Y * newLookCFrame.upVector + offset.Z * newLookCFrame.lookVector
	
				--offset can be NAN, NAN, NAN if newLookVector has only y component
				if Util.IsFiniteVector3(cameraRelativeOffset) then
					subjectPosition = subjectPosition + cameraRelativeOffset
				end
			else
				if not self.userPanningTheCamera and self.lastCameraTransform then
	
					local isInFirstPerson = self:IsInFirstPerson()
	
					if (isInVehicle or isOnASkateboard or (self.isFollowCamera and isClimbing)) and self.lastUpdate and humanoid and humanoid.Torso then
						if isInFirstPerson then
							if self.lastSubjectCFrame and (isInVehicle or isOnASkateboard) and cameraSubject:IsA('BasePart') then
								local y = -Util.GetAngleBetweenXZVectors(self.lastSubjectCFrame.lookVector, cameraSubject.CFrame.lookVector)
								if Util.IsFinite(y) then
									self.rotateInput = self.rotateInput + Vector2.new(y, 0)
								end
								tweenSpeed = 0
							end
						elseif not userRecentlyPannedCamera then
							local forwardVector = humanoid.Torso.CFrame.lookVector
							if isOnASkateboard then
								forwardVector = cameraSubject.CFrame.lookVector
							end
	
							tweenSpeed = math.clamp(tweenSpeed + tweenAcceleration * timeDelta, 0, tweenMaxSpeed)
	
							local percent = math.clamp(tweenSpeed * timeDelta, 0, 1)
							if self:IsInFirstPerson() and not (self.isFollowCamera and self.isClimbing) then
								percent = 1
							end
	
							local y = Util.GetAngleBetweenXZVectors(forwardVector, self:GetCameraLookVector())
							if Util.IsFinite(y) and math.abs(y) > 0.0001 then
								self.rotateInput = self.rotateInput + Vector2.new(y * percent, 0)
							end
						end
	
					elseif self.isFollowCamera and (not (isInFirstPerson or userRecentlyPannedCamera) and not VRService.VREnabled) then
						-- Logic that was unique to the old FollowCamera module
						local lastVec = -(self.lastCameraTransform.p - subjectPosition)
	
						local y = Util.GetAngleBetweenXZVectors(lastVec, self:GetCameraLookVector())
	
						-- This cutoff is to decide if the humanoid's angle of movement,
						-- relative to the camera's look vector, is enough that
						-- we want the camera to be following them. The point is to provide
						-- a sizable dead zone to allow more precise forward movements.
						local thetaCutoff = 0.4
	
						-- Check for NaNs
						if Util.IsFinite(y) and math.abs(y) > 0.0001 and math.abs(y) > thetaCutoff * timeDelta then
							self.rotateInput = self.rotateInput + Vector2.new(y, 0)
						end
					end
				end
			end
	
			if not self.isFollowCamera then
				local VREnabled = VRService.VREnabled
	
				if VREnabled then
					newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)
				else
					newCameraFocus = CFrame.new(subjectPosition)
				end
	
				local cameraFocusP = newCameraFocus.p
				if VREnabled and not self:IsInFirstPerson() then
					local vecToSubject = (subjectPosition - camera.CFrame.p)
					local distToSubject = vecToSubject.magnitude
	
					-- Only move the camera if it exceeded a maximum distance to the subject in VR
					if distToSubject > zoom or self.rotateInput.x ~= 0 then
						local desiredDist = math.min(distToSubject, zoom)
						vecToSubject = self:CalculateNewLookVectorVR() * desiredDist
						local newPos = cameraFocusP - vecToSubject
						local desiredLookDir = camera.CFrame.lookVector
						if self.rotateInput.x ~= 0 then
							desiredLookDir = vecToSubject
						end
						local lookAt = Vector3.new(newPos.x + desiredLookDir.x, newPos.y, newPos.z + desiredLookDir.z)
						self.rotateInput = ZERO_VECTOR2
	
						newCameraCFrame = CFrame.new(newPos, lookAt) + Vector3.new(0, cameraHeight, 0)
					end
				else
					local newLookVector = self:CalculateNewLookVector(overrideCameraLookVector)
					self.rotateInput = ZERO_VECTOR2
					newCameraCFrame = CFrame.new(cameraFocusP - (zoom * newLookVector), cameraFocusP)
				end
			else -- is FollowCamera
				local newLookVector = self:CalculateNewLookVector(overrideCameraLookVector)
				self.rotateInput = ZERO_VECTOR2
	
				if VRService.VREnabled then
					newCameraFocus = self:GetVRFocus(subjectPosition, timeDelta)
				else
					newCameraFocus = CFrame.new(subjectPosition)
				end
				newCameraCFrame = CFrame.new(newCameraFocus.p - (zoom * newLookVector), newCameraFocus.p) + Vector3.new(0, cameraHeight, 0)
			end
	
			if FFlagUserCameraToggle then
				local toggleOffset = self:GetCameraToggleOffset(timeDelta)
				newCameraFocus = newCameraFocus + toggleOffset
				newCameraCFrame = newCameraCFrame + toggleOffset
			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
	
	function ClassicCamera:EnterFirstPerson()
		self.inFirstPerson = true
		self:UpdateMouseBehavior()
	end
	
	function ClassicCamera:LeaveFirstPerson()
		self.inFirstPerson = false
		self:UpdateMouseBehavior()
	end
	
	return ClassicCamera
end

function _CameraUtils()

	local CameraUtils = {}
	
	local FFlagUserCameraToggle do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
		end)
		FFlagUserCameraToggle = success and result
	end
	
	local function round(num)
		return math.floor(num + 0.5)
	end
	
	-- Critically damped spring class for fluid motion effects
	local Spring = {} do
		Spring.__index = Spring
	
		-- Initialize to a given undamped frequency and default position
		function Spring.new(freq, pos)
			return setmetatable({
				freq = freq,
				goal = pos,
				pos = pos,
				vel = 0,
			}, Spring)
		end
	
		-- Advance the spring simulation by `dt` seconds
		function Spring:step(dt)
			local f = self.freq*2*math.pi
			local g = self.goal
			local p0 = self.pos
			local v0 = self.vel
	
			local offset = p0 - g
			local decay = math.exp(-f*dt)
	
			local p1 = (offset*(1 + f*dt) + v0*dt)*decay + g
			local v1 = (v0*(1 - f*dt) - offset*(f*f*dt))*decay
	
			self.pos = p1
			self.vel = v1
	
			return p1
		end
	end
	
	CameraUtils.Spring = Spring
	
	-- map a value from one range to another
	function CameraUtils.map(x, inMin, inMax, outMin, outMax)
		return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
	end
	
	-- From TransparencyController
	function CameraUtils.Round(num, places)
		local decimalPivot = 10^places
		return math.floor(num * decimalPivot + 0.5) / decimalPivot
	end
	
	function CameraUtils.IsFinite(val)
		return val == val and val ~= math.huge and val ~= -math.huge
	end
	
	function CameraUtils.IsFiniteVector3(vec3)
		return CameraUtils.IsFinite(vec3.X) and CameraUtils.IsFinite(vec3.Y) and CameraUtils.IsFinite(vec3.Z)
	end
	
	-- Legacy implementation renamed
	function CameraUtils.GetAngleBetweenXZVectors(v1, v2)
		return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
	end
	
	function  CameraUtils.RotateVectorByAngleAndRound(camLook, rotateAngle, roundAmount)
		if camLook.Magnitude > 0 then
			camLook = camLook.unit
			local currAngle = math.atan2(camLook.z, camLook.x)
			local newAngle = round((math.atan2(camLook.z, camLook.x) + rotateAngle) / roundAmount) * roundAmount
			return newAngle - currAngle
		end
		return 0
	end
	
	-- K is a tunable parameter that changes the shape of the S-curve
	-- the larger K is the more straight/linear the curve gets
	local k = 0.35
	local lowerK = 0.8
	local function SCurveTranform(t)
		t = math.clamp(t, -1, 1)
		if t >= 0 then
			return (k*t) / (k - t + 1)
		end
		return -((lowerK*-t) / (lowerK + t + 1))
	end
	
	local DEADZONE = 0.1
	local function toSCurveSpace(t)
		return (1 + DEADZONE) * (2*math.abs(t) - 1) - DEADZONE
	end
	
	local function fromSCurveSpace(t)
		return t/2 + 0.5
	end
	
	function CameraUtils.GamepadLinearToCurve(thumbstickPosition)
		local function onAxis(axisValue)
			local sign = 1
			if axisValue < 0 then
				sign = -1
			end
			local point = fromSCurveSpace(SCurveTranform(toSCurveSpace(math.abs(axisValue))))
			point = point * sign
			return math.clamp(point, -1, 1)
		end
		return Vector2.new(onAxis(thumbstickPosition.x), onAxis(thumbstickPosition.y))
	end
	
	-- This function converts 4 different, redundant enumeration types to one standard so the values can be compared
	function CameraUtils.ConvertCameraModeEnumToStandard(enumValue)
		if enumValue == Enum.TouchCameraMovementMode.Default then
			return Enum.ComputerCameraMovementMode.Follow
		end
	
		if enumValue == Enum.ComputerCameraMovementMode.Default then
			return Enum.ComputerCameraMovementMode.Classic
		end
	
		if enumValue == Enum.TouchCameraMovementMode.Classic or
			enumValue == Enum.DevTouchCameraMovementMode.Classic or
			enumValue == Enum.DevComputerCameraMovementMode.Classic or
			enumValue == Enum.ComputerCameraMovementMode.Classic then
			return Enum.ComputerCameraMovementMode.Classic
		end
	
		if enumValue == Enum.TouchCameraMovementMode.Follow or
			enumValue == Enum.DevTouchCameraMovementMode.Follow or
			enumValue == Enum.DevComputerCameraMovementMode.Follow or
			enumValue == Enum.ComputerCameraMovementMode.Follow then
			return Enum.ComputerCameraMovementMode.Follow
		end
	
		if enumValue == Enum.TouchCameraMovementMode.Orbital or
			enumValue == Enum.DevTouchCameraMovementMode.Orbital or
			enumValue == Enum.DevComputerCameraMovementMode.Orbital or
			enumValue == Enum.ComputerCameraMovementMode.Orbital then
			return Enum.ComputerCameraMovementMode.Orbital
		end
	
		if FFlagUserCameraToggle then
			if enumValue == Enum.ComputerCameraMovementMode.CameraToggle or
				enumValue == Enum.DevComputerCameraMovementMode.CameraToggle then
				return Enum.ComputerCameraMovementMode.CameraToggle
			end
		end
	
		-- Note: Only the Dev versions of the Enums have UserChoice as an option
		if enumValue == Enum.DevTouchCameraMovementMode.UserChoice or
			enumValue == Enum.DevComputerCameraMovementMode.UserChoice then
			return Enum.DevComputerCameraMovementMode.UserChoice
		end
	
		-- For any unmapped options return Classic camera
		return Enum.ComputerCameraMovementMode.Classic
	end
	
	return CameraUtils
end

function _CameraModule()
	local CameraModule = {}
	CameraModule.__index = CameraModule
	
	local FFlagUserCameraToggle do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserCameraToggle")
		end)
		FFlagUserCameraToggle = success and result
	end
	
	local FFlagUserRemoveTheCameraApi do
		local success, result = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserRemoveTheCameraApi")
		end)
		FFlagUserRemoveTheCameraApi = success and result
	end
	
	-- NOTICE: Player property names do not all match their StarterPlayer equivalents,
	-- with the differences noted in the comments on the right
	local PLAYER_CAMERA_PROPERTIES =
	{
		"CameraMinZoomDistance",
		"CameraMaxZoomDistance",
		"CameraMode",
		"DevCameraOcclusionMode",
		"DevComputerCameraMode",			-- Corresponds to StarterPlayer.DevComputerCameraMovementMode
		"DevTouchCameraMode",				-- Corresponds to StarterPlayer.DevTouchCameraMovementMode
	
		-- Character movement mode
		"DevComputerMovementMode",
		"DevTouchMovementMode",
		"DevEnableMouseLock",				-- Corresponds to StarterPlayer.EnableMouseLockOption
	}
	
	local USER_GAME_SETTINGS_PROPERTIES =
	{
		"ComputerCameraMovementMode",
		"ComputerMovementMode",
		"ControlMode",
		"GamepadCameraSensitivity",
		"MouseSensitivity",
		"RotationType",
		"TouchCameraMovementMode",
		"TouchMovementMode",
	}
	
	--[[ Roblox Services ]]--
	local Players = game:GetService("Players")
	local RunService = game:GetService("RunService")
	local UserInputService = game:GetService("UserInputService")
	local UserGameSettings = UserSettings():GetService("UserGameSettings")
	
	-- Camera math utility library
	local CameraUtils = _CameraUtils()
	
	-- Load Roblox Camera Controller Modules
	local ClassicCamera = _ClassicCamera()
	local OrbitalCamera = _OrbitalCamera()
	local LegacyCamera = _LegacyCamera()
	
	-- Load Roblox Occlusion Modules
	local Invisicam = _Invisicam()
	local Poppercam = _Poppercam()
	
	-- Load the near-field character transparency controller and the mouse lock "shift lock" controller
	local TransparencyController = _TransparencyController()
	local MouseLockController = _MouseLockController()
	
	-- Table of camera controllers that have been instantiated. They are instantiated as they are used.
	local instantiatedCameraControllers = {}
	local instantiatedOcclusionModules = {}
	
	-- Management of which options appear on the Roblox User Settings screen
	do
		local PlayerScripts = Players.LocalPlayer:WaitForChild("PlayerScripts")
	
		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Default)
		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Follow)
		PlayerScripts:RegisterTouchCameraMovementMode(Enum.TouchCameraMovementMode.Classic)
	
		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Default)
		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Follow)
		PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.Classic)
		if FFlagUserCameraToggle then
			PlayerScripts:RegisterComputerCameraMovementMode(Enum.ComputerCameraMovementMode.CameraToggle)
		end
	end
	
	CameraModule.FFlagUserCameraToggle = FFlagUserCameraToggle
	
	
	function CameraModule.new()
		local self = setmetatable({},CameraModule)
	
		-- Current active controller instances
		self.activeCameraController = nil
		self.activeOcclusionModule = nil
		self.activeTransparencyController = nil
		self.activeMouseLockController = nil
	
		self.currentComputerCameraMovementMode = nil
	
		-- Connections to events
		self.cameraSubjectChangedConn = nil
		self.cameraTypeChangedConn = nil
	
		-- Adds CharacterAdded and CharacterRemoving event handlers for all current players
		for _,player in pairs(Players:GetPlayers()) do
			self:OnPlayerAdded(player)
		end
	
		-- Adds CharacterAdded and CharacterRemoving event handlers for all players who join in the future
		Players.PlayerAdded:Connect(function(player)
			self:OnPlayerAdded(player)
		end)
	
		self.activeTransparencyController = TransparencyController.new()
		self.activeTransparencyController:Enable(true)
	
		if not UserInputService.TouchEnabled then
			self.activeMouseLockController = MouseLockController.new()
			local toggleEvent = self.activeMouseLockController:GetBindableToggleEvent()
			if toggleEvent then
				toggleEvent:Connect(function()
					self:OnMouseLockToggled()
				end)
			end
		end
	
		self:ActivateCameraController(self:GetCameraControlChoice())
		self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
		self:OnCurrentCameraChanged() -- Does initializations and makes first camera controller
		RunService:BindToRenderStep("cameraRenderUpdate", Enum.RenderPriority.Camera.Value, function(dt) self:Update(dt) end)
	
		-- Connect listeners to camera-related properties
		for _, propertyName in pairs(PLAYER_CAMERA_PROPERTIES) do
			Players.LocalPlayer:GetPropertyChangedSignal(propertyName):Connect(function()
				self:OnLocalPlayerCameraPropertyChanged(propertyName)
			end)
		end
	
		for _, propertyName in pairs(USER_GAME_SETTINGS_PROPERTIES) do
			UserGameSettings:GetPropertyChangedSignal(propertyName):Connect(function()
				self:OnUserGameSettingsPropertyChanged(propertyName)
			end)
		end
		game.Workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(function()
			self:OnCurrentCameraChanged()
		end)
	
		self.lastInputType = UserInputService:GetLastInputType()
		UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
			self.lastInputType = newLastInputType
		end)
	
		return self
	end
	
	function CameraModule:GetCameraMovementModeFromSettings()
		local cameraMode = Players.LocalPlayer.CameraMode
	
		-- Lock First Person trumps all other settings and forces ClassicCamera
		if cameraMode == Enum.CameraMode.LockFirstPerson then
			return CameraUtils.ConvertCameraModeEnumToStandard(Enum.ComputerCameraMovementMode.Classic)
		end
	
		local devMode, userMode
		if UserInputService.TouchEnabled then
			devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevTouchCameraMode)
			userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.TouchCameraMovementMode)
		else
			devMode = CameraUtils.ConvertCameraModeEnumToStandard(Players.LocalPlayer.DevComputerCameraMode)
			userMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
		end
	
		if devMode == Enum.DevComputerCameraMovementMode.UserChoice then
			-- Developer is allowing user choice, so user setting is respected
			return userMode
		end
	
		return devMode
	end
	
	function CameraModule:ActivateOcclusionModule( occlusionMode )
		local newModuleCreator
		if occlusionMode == Enum.DevCameraOcclusionMode.Zoom then
			newModuleCreator = Poppercam
		elseif occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
			newModuleCreator = Invisicam
		else
			warn("CameraScript ActivateOcclusionModule called with unsupported mode")
			return
		end
	
		-- First check to see if there is actually a change. If the module being requested is already
		-- the currently-active solution then just make sure it's enabled and exit early
		if self.activeOcclusionModule and self.activeOcclusionModule:GetOcclusionMode() == occlusionMode then
			if not self.activeOcclusionModule:GetEnabled() then
				self.activeOcclusionModule:Enable(true)
			end
			return
		end
	
		-- Save a reference to the current active module (may be nil) so that we can disable it if
		-- we are successful in activating its replacement
		local prevOcclusionModule = self.activeOcclusionModule
	
		-- If there is no active module, see if the one we need has already been instantiated
		self.activeOcclusionModule = instantiatedOcclusionModules[newModuleCreator]
	
		-- If the module was not already instantiated and selected above, instantiate it
		if not self.activeOcclusionModule then
			self.activeOcclusionModule = newModuleCreator.new()
			if self.activeOcclusionModule then
				instantiatedOcclusionModules[newModuleCreator] = self.activeOcclusionModule
			end
		end
	
		-- If we were successful in either selecting or instantiating the module,
		-- enable it if it's not already the currently-active enabled module
		if self.activeOcclusionModule then
			local newModuleOcclusionMode = self.activeOcclusionModule:GetOcclusionMode()
			-- Sanity check that the module we selected or instantiated actually supports the desired occlusionMode
			if newModuleOcclusionMode ~= occlusionMode then
				warn("CameraScript ActivateOcclusionModule mismatch: ",self.activeOcclusionModule:GetOcclusionMode(),"~=",occlusionMode)
			end
	
			-- Deactivate current module if there is one
			if prevOcclusionModule then
				-- Sanity check that current module is not being replaced by itself (that should have been handled above)
				if prevOcclusionModule ~= self.activeOcclusionModule then
					prevOcclusionModule:Enable(false)
				else
					warn("CameraScript ActivateOcclusionModule failure to detect already running correct module")
				end
			end
	
			-- Occlusion modules need to be initialized with information about characters and cameraSubject
			-- Invisicam needs the LocalPlayer's character
			-- Poppercam needs all player characters and the camera subject
			if occlusionMode == Enum.DevCameraOcclusionMode.Invisicam then
				-- Optimization to only send Invisicam what we know it needs
				if Players.LocalPlayer.Character then
					self.activeOcclusionModule:CharacterAdded(Players.LocalPlayer.Character, Players.LocalPlayer )
				end
			else
				-- When Poppercam is enabled, we send it all existing player characters for its raycast ignore list
				for _, player in pairs(Players:GetPlayers()) do
					if player and player.Character then
						self.activeOcclusionModule:CharacterAdded(player.Character, player)
					end
				end
				self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
			end
	
			-- Activate new choice
			self.activeOcclusionModule:Enable(true)
		end
	end
	
	-- When supplied, legacyCameraType is used and cameraMovementMode is ignored (should be nil anyways)
	-- Next, if userCameraCreator is passed in, that is used as the cameraCreator
	function CameraModule:ActivateCameraController(cameraMovementMode, legacyCameraType)
		local newCameraCreator = nil
	
		if legacyCameraType~=nil then
			--[[
				This function has been passed a CameraType enum value. Some of these map to the use of
				the LegacyCamera module, the value "Custom" will be translated to a movementMode enum
				value based on Dev and User settings, and "Scriptable" will disable the camera controller.
			--]]
	
			if legacyCameraType == Enum.CameraType.Scriptable then
				if self.activeCameraController then
					self.activeCameraController:Enable(false)
					self.activeCameraController = nil
					return
				end
			elseif legacyCameraType == Enum.CameraType.Custom then
				cameraMovementMode = self:GetCameraMovementModeFromSettings()
	
			elseif legacyCameraType == Enum.CameraType.Track then
				-- Note: The TrackCamera module was basically an older, less fully-featured
				-- version of ClassicCamera, no longer actively maintained, but it is re-implemented in
				-- case a game was dependent on its lack of ClassicCamera's extra functionality.
				cameraMovementMode = Enum.ComputerCameraMovementMode.Classic
	
			elseif legacyCameraType == Enum.CameraType.Follow then
				cameraMovementMode = Enum.ComputerCameraMovementMode.Follow
	
			elseif legacyCameraType == Enum.CameraType.Orbital then
				cameraMovementMode = Enum.ComputerCameraMovementMode.Orbital
	
			elseif legacyCameraType == Enum.CameraType.Attach or
				   legacyCameraType == Enum.CameraType.Watch or
				   legacyCameraType == Enum.CameraType.Fixed then
				newCameraCreator = LegacyCamera
			else
				warn("CameraScript encountered an unhandled Camera.CameraType value: ",legacyCameraType)
			end
		end
	
		if not newCameraCreator then
			if cameraMovementMode == Enum.ComputerCameraMovementMode.Classic or
				cameraMovementMode == Enum.ComputerCameraMovementMode.Follow or
				cameraMovementMode == Enum.ComputerCameraMovementMode.Default or
				(FFlagUserCameraToggle and cameraMovementMode == Enum.ComputerCameraMovementMode.CameraToggle) then
				newCameraCreator = ClassicCamera
			elseif cameraMovementMode == Enum.ComputerCameraMovementMode.Orbital then
				newCameraCreator = OrbitalCamera
			else
				warn("ActivateCameraController did not select a module.")
				return
			end
		end
	
		-- Create the camera control module we need if it does not already exist in instantiatedCameraControllers
		local newCameraController
		if not instantiatedCameraControllers[newCameraCreator] then
			newCameraController = newCameraCreator.new()
			instantiatedCameraControllers[newCameraCreator] = newCameraController
		else
			newCameraController = instantiatedCameraControllers[newCameraCreator]
		end
	
		-- If there is a controller active and it's not the one we need, disable it,
		-- if it is the one we need, make sure it's enabled
		if self.activeCameraController then
			if self.activeCameraController ~= newCameraController then
				self.activeCameraController:Enable(false)
				self.activeCameraController = newCameraController
				self.activeCameraController:Enable(true)
			elseif not self.activeCameraController:GetEnabled() then
				self.activeCameraController:Enable(true)
			end
		elseif newCameraController ~= nil then
			self.activeCameraController = newCameraController
			self.activeCameraController:Enable(true)
		end
	
		if self.activeCameraController then
			if cameraMovementMode~=nil then
				self.activeCameraController:SetCameraMovementMode(cameraMovementMode)
			elseif legacyCameraType~=nil then
				-- Note that this is only called when legacyCameraType is not a type that
				-- was convertible to a ComputerCameraMovementMode value, i.e. really only applies to LegacyCamera
				self.activeCameraController:SetCameraType(legacyCameraType)
			end
		end
	end
	
	-- Note: The active transparency controller could be made to listen for this event itself.
	function CameraModule:OnCameraSubjectChanged()
		if self.activeTransparencyController then
			self.activeTransparencyController:SetSubject(game.Workspace.CurrentCamera.CameraSubject)
		end
	
		if self.activeOcclusionModule then
			self.activeOcclusionModule:OnCameraSubjectChanged(game.Workspace.CurrentCamera.CameraSubject)
		end
	end
	
	function CameraModule:OnCameraTypeChanged(newCameraType)
		if newCameraType == Enum.CameraType.Scriptable then
			if UserInputService.MouseBehavior == Enum.MouseBehavior.LockCenter then
				UserInputService.MouseBehavior = Enum.MouseBehavior.Default
			end
		end
	
		-- Forward the change to ActivateCameraController to handle
		self:ActivateCameraController(nil, newCameraType)
	end
	
	-- Note: Called whenever workspace.CurrentCamera changes, but also on initialization of this script
	function CameraModule:OnCurrentCameraChanged()
		local currentCamera = game.Workspace.CurrentCamera
		if not currentCamera then return end
	
		if self.cameraSubjectChangedConn then
			self.cameraSubjectChangedConn:Disconnect()
		end
	
		if self.cameraTypeChangedConn then
			self.cameraTypeChangedConn:Disconnect()
		end
	
		self.cameraSubjectChangedConn = currentCamera:GetPropertyChangedSignal("CameraSubject"):Connect(function()
			self:OnCameraSubjectChanged(currentCamera.CameraSubject)
		end)
	
		self.cameraTypeChangedConn = currentCamera:GetPropertyChangedSignal("CameraType"):Connect(function()
			self:OnCameraTypeChanged(currentCamera.CameraType)
		end)
	
		self:OnCameraSubjectChanged(currentCamera.CameraSubject)
		self:OnCameraTypeChanged(currentCamera.CameraType)
	end
	
	function CameraModule:OnLocalPlayerCameraPropertyChanged(propertyName)
		if propertyName == "CameraMode" then
			-- CameraMode is only used to turn on/off forcing the player into first person view. The
			-- Note: The case "Classic" is used for all other views and does not correspond only to the ClassicCamera module
			if Players.LocalPlayer.CameraMode == Enum.CameraMode.LockFirstPerson then
				-- Locked in first person, use ClassicCamera which supports this
				if not self.activeCameraController or self.activeCameraController:GetModuleName() ~= "ClassicCamera" then
					self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(Enum.DevComputerCameraMovementMode.Classic))
				end
	
				if self.activeCameraController then
					self.activeCameraController:UpdateForDistancePropertyChange()
				end
			elseif Players.LocalPlayer.CameraMode == Enum.CameraMode.Classic then
				-- Not locked in first person view
				local cameraMovementMode =self: GetCameraMovementModeFromSettings()
				self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
			else
				warn("Unhandled value for property player.CameraMode: ",Players.LocalPlayer.CameraMode)
			end
	
		elseif propertyName == "DevComputerCameraMode" or 
			   propertyName == "DevTouchCameraMode" then
			local cameraMovementMode = self:GetCameraMovementModeFromSettings()
			self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
	
		elseif propertyName == "DevCameraOcclusionMode" then
			self:ActivateOcclusionModule(Players.LocalPlayer.DevCameraOcclusionMode)
	
		elseif propertyName == "CameraMinZoomDistance" or propertyName == "CameraMaxZoomDistance" then
			if self.activeCameraController then
				self.activeCameraController:UpdateForDistancePropertyChange()
			end
		elseif propertyName == "DevTouchMovementMode" then
		elseif propertyName == "DevComputerMovementMode" then
		elseif propertyName == "DevEnableMouseLock" then
			-- This is the enabling/disabling of "Shift Lock" mode, not LockFirstPerson (which is a CameraMode)
			-- Note: Enabling and disabling of MouseLock mode is normally only a publish-time choice made via
			-- the corresponding EnableMouseLockOption checkbox of StarterPlayer, and this script does not have
			-- support for changing the availability of MouseLock at runtime (this would require listening to
			-- Player.DevEnableMouseLock changes)
		end
	end
	
	function CameraModule:OnUserGameSettingsPropertyChanged(propertyName)
		if propertyName == 	"ComputerCameraMovementMode" then
			local cameraMovementMode = self:GetCameraMovementModeFromSettings()
			self:ActivateCameraController(CameraUtils.ConvertCameraModeEnumToStandard(cameraMovementMode))
		end
	end
	
	--[[
		Main RenderStep Update. The camera controller and occlusion module both have opportunities
		to set and modify (respectively) the CFrame and Focus before it is set once on CurrentCamera.
		The camera and occlusion modules should only return CFrames, not set the CFrame property of
		CurrentCamera directly.
	--]]
	function CameraModule:Update(dt)
		if self.activeCameraController then
			if FFlagUserCameraToggle then
				self.activeCameraController:UpdateMouseBehavior()
			end
	
			local newCameraCFrame, newCameraFocus = self.activeCameraController:Update(dt)
			self.activeCameraController:ApplyVRTransform()
			if self.activeOcclusionModule then
				newCameraCFrame, newCameraFocus = self.activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
			end
	
			-- Here is where the new CFrame and Focus are set for this render frame
			game.Workspace.CurrentCamera.CFrame = newCameraCFrame
			game.Workspace.CurrentCamera.Focus = newCameraFocus
	
			-- Update to character local transparency as needed based on camera-to-subject distance
			if self.activeTransparencyController then
				self.activeTransparencyController:Update()
			end
		end
	end
	
	-- Formerly getCurrentCameraMode, this function resolves developer and user camera control settings to
	-- decide which camera control module should be instantiated. The old method of converting redundant enum types
	function CameraModule:GetCameraControlChoice()
		local player = Players.LocalPlayer
	
		if player then
			if self.lastInputType == Enum.UserInputType.Touch or UserInputService.TouchEnabled then
				-- Touch
				if player.DevTouchCameraMode == Enum.DevTouchCameraMovementMode.UserChoice then
					return CameraUtils.ConvertCameraModeEnumToStandard( UserGameSettings.TouchCameraMovementMode )
				else
					return CameraUtils.ConvertCameraModeEnumToStandard( player.DevTouchCameraMode )
				end
			else
				-- Computer
				if player.DevComputerCameraMode == Enum.DevComputerCameraMovementMode.UserChoice then
					local computerMovementMode = CameraUtils.ConvertCameraModeEnumToStandard(UserGameSettings.ComputerCameraMovementMode)
					return CameraUtils.ConvertCameraModeEnumToStandard(computerMovementMode)
				else
					return CameraUtils.ConvertCameraModeEnumToStandard(player.DevComputerCameraMode)
				end
			end
		end
	end
	
	function CameraModule:OnCharacterAdded(char, player)
		if self.activeOcclusionModule then
			self.activeOcclusionModule:CharacterAdded(char, player)
		end
	end
	
	function CameraModule:OnCharacterRemoving(char, player)
		if self.activeOcclusionModule then
			self.activeOcclusionModule:CharacterRemoving(char, player)
		end
	end
	
	function CameraModule:OnPlayerAdded(player)
		player.CharacterAdded:Connect(function(char)
			self:OnCharacterAdded(char, player)
		end)
		player.CharacterRemoving:Connect(function(char)
			self:OnCharacterRemoving(char, player)
		end)
	end
	
	function CameraModule:OnMouseLockToggled()
		if self.activeMouseLockController then
			local mouseLocked = self.activeMouseLockController:GetIsMouseLocked()
			local mouseLockOffset = self.activeMouseLockController:GetMouseLockOffset()
			if self.activeCameraController then
				self.activeCameraController:SetIsMouseLocked(mouseLocked)
				self.activeCameraController:SetMouseLockOffset(mouseLockOffset)
			end
		end
	end
	--begin edit
	local Camera = CameraModule
	local IDENTITYCF = CFrame.new()
	local lastUpCFrame = IDENTITYCF
	
	Camera.UpVector = Vector3.new(0, 1, 0)
	Camera.TransitionRate = 0.15
	Camera.UpCFrame = IDENTITYCF
	
	function Camera:GetUpVector(oldUpVector)
		return oldUpVector
	end
	local function getRotationBetween(u, v, axis)
		local dot, uxv = u:Dot(v), u:Cross(v)
		if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
		return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
	end
	function Camera:CalculateUpCFrame()
		local oldUpVector = self.UpVector
		local newUpVector = self:GetUpVector(oldUpVector)
		
		local backup = game.Workspace.CurrentCamera.CFrame.RightVector
		local transitionCF = getRotationBetween(oldUpVector, newUpVector, backup)
		local vecSlerpCF = IDENTITYCF:Lerp(transitionCF, self.TransitionRate)
		
		self.UpVector = vecSlerpCF * oldUpVector
		self.UpCFrame = vecSlerpCF * self.UpCFrame
		
		lastUpCFrame = self.UpCFrame
	end
	
	function Camera:Update(dt)
		if self.activeCameraController then
			if Camera.FFlagUserCameraToggle then
				self.activeCameraController:UpdateMouseBehavior()
			end
			
			local newCameraCFrame, newCameraFocus = self.activeCameraController:Update(dt)
			self.activeCameraController:ApplyVRTransform()
			
			self:CalculateUpCFrame()
			self.activeCameraController:UpdateUpCFrame(self.UpCFrame)
			
			-- undo shift-lock offset
	
			local lockOffset = Vector3.new(0, 0, 0)
			if (self.activeMouseLockController and self.activeMouseLockController:GetIsMouseLocked()) then
				lockOffset = self.activeMouseLockController:GetMouseLockOffset()
			end
			
			local offset = newCameraFocus:ToObjectSpace(newCameraCFrame)
			local camRotation = self.UpCFrame * offset
			newCameraFocus = newCameraFocus - newCameraCFrame:VectorToWorldSpace(lockOffset) + camRotation:VectorToWorldSpace(lockOffset)
			newCameraCFrame = newCameraFocus * camRotation
			
			--local offset = newCameraFocus:Inverse() * newCameraCFrame
			--newCameraCFrame = newCameraFocus * self.UpCFrame * offset
			
			if (self.activeCameraController.lastCameraTransform) then
				self.activeCameraController.lastCameraTransform = newCameraCFrame
				self.activeCameraController.lastCameraFocus = newCameraFocus
			end
			
			if self.activeOcclusionModule then
				newCameraCFrame, newCameraFocus = self.activeOcclusionModule:Update(dt, newCameraCFrame, newCameraFocus)
			end
	
			game.Workspace.CurrentCamera.CFrame = newCameraCFrame
			game.Workspace.CurrentCamera.Focus = newCameraFocus
	
			if self.activeTransparencyController then
				self.activeTransparencyController:Update()
			end
		end
	end
	
	function Camera:IsFirstPerson()
		if self.activeCameraController then
			return self.activeCameraController:InFirstPerson()
		end
		return false
	end
	
	function Camera:IsMouseLocked()
		if self.activeCameraController then
			return self.activeCameraController:GetIsMouseLocked()
		end
		return false
	end
	function Camera:IsToggleMode()
		if self.activeCameraController then
			return self.activeCameraController.isCameraToggle
		end
		return false
	end
	function Camera:IsCamRelative()
		return self:IsMouseLocked() or self:IsFirstPerson()
		--return self:IsToggleMode(), self:IsMouseLocked(), self:IsFirstPerson()
	end
	--
	local Utils = _CameraUtils()
	function Utils.GetAngleBetweenXZVectors(v1, v2)
		local upCFrame = lastUpCFrame
		v1 = upCFrame:VectorToObjectSpace(v1)
		v2 = upCFrame:VectorToObjectSpace(v2)
		return math.atan2(v2.X*v1.Z-v2.Z*v1.X, v2.X*v1.X+v2.Z*v1.Z)
	end
	--end edit
	local cameraModuleObject = CameraModule.new()
	local cameraApi = {}
	return cameraModuleObject
end

function _ClickToMoveDisplay()
	local ClickToMoveDisplay = {}
	
	local FAILURE_ANIMATION_ID = "rbxassetid://2874840706"
	
	local TrailDotIcon = "rbxasset://textures/ui/traildot.png"
	local EndWaypointIcon = "rbxasset://textures/ui/waypoint.png"
	
	local WaypointsAlwaysOnTop = false
	
	local WAYPOINT_INCLUDE_FACTOR = 2
	local LAST_DOT_DISTANCE = 3
	
	local WAYPOINT_BILLBOARD_SIZE = UDim2.new(0, 1.68 * 25, 0, 2 * 25)
	
	local ENDWAYPOINT_SIZE_OFFSET_MIN = Vector2.new(0, 0.5)
	local ENDWAYPOINT_SIZE_OFFSET_MAX = Vector2.new(0, 1)
	
	local FAIL_WAYPOINT_SIZE_OFFSET_CENTER = Vector2.new(0, 0.5)
	local FAIL_WAYPOINT_SIZE_OFFSET_LEFT = Vector2.new(0.1, 0.5)
	local FAIL_WAYPOINT_SIZE_OFFSET_RIGHT = Vector2.new(-0.1, 0.5)
	
	local FAILURE_TWEEN_LENGTH = 0.125
	local FAILURE_TWEEN_COUNT = 4
	
	local TWEEN_WAYPOINT_THRESHOLD = 5
	
	local TRAIL_DOT_PARENT_NAME = "ClickToMoveDisplay"
	
	local TrailDotSize = Vector2.new(1.5, 1.5)
	
	local TRAIL_DOT_MIN_SCALE = 1
	local TRAIL_DOT_MIN_DISTANCE = 10
	local TRAIL_DOT_MAX_SCALE = 2.5
	local TRAIL_DOT_MAX_DISTANCE = 100
	
	local PlayersService = game:GetService("Players")
	local TweenService = game:GetService("TweenService")
	local RunService = game:GetService("RunService")
	local Workspace = game:GetService("Workspace")
	
	local LocalPlayer = PlayersService.LocalPlayer
	
	local function CreateWaypointTemplates()
		local TrailDotTemplate = Instance.new("Part")
		TrailDotTemplate.Size = Vector3.new(1, 1, 1)
		TrailDotTemplate.Anchored = true
		TrailDotTemplate.CanCollide = false
		TrailDotTemplate.Name = "TrailDot"
		TrailDotTemplate.Transparency = 1
		local TrailDotImage = Instance.new("ImageHandleAdornment")
		TrailDotImage.Name = "TrailDotImage"
		TrailDotImage.Size = TrailDotSize
		TrailDotImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
		TrailDotImage.AlwaysOnTop = WaypointsAlwaysOnTop
		TrailDotImage.Image = TrailDotIcon
		TrailDotImage.Adornee = TrailDotTemplate
		TrailDotImage.Parent = TrailDotTemplate
	
		local EndWaypointTemplate = Instance.new("Part")
		EndWaypointTemplate.Size = Vector3.new(2, 2, 2)
		EndWaypointTemplate.Anchored = true
		EndWaypointTemplate.CanCollide = false
		EndWaypointTemplate.Name = "EndWaypoint"
		EndWaypointTemplate.Transparency = 1
		local EndWaypointImage = Instance.new("ImageHandleAdornment")
		EndWaypointImage.Name = "TrailDotImage"
		EndWaypointImage.Size = TrailDotSize
		EndWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
		EndWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
		EndWaypointImage.Image = TrailDotIcon
		EndWaypointImage.Adornee = EndWaypointTemplate
		EndWaypointImage.Parent = EndWaypointTemplate
		local EndWaypointBillboard = Instance.new("BillboardGui")
		EndWaypointBillboard.Name = "EndWaypointBillboard"
		EndWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
		EndWaypointBillboard.LightInfluence = 0
		EndWaypointBillboard.SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MIN
		EndWaypointBillboard.AlwaysOnTop = true
		EndWaypointBillboard.Adornee = EndWaypointTemplate
		EndWaypointBillboard.Parent = EndWaypointTemplate
		local EndWaypointImageLabel = Instance.new("ImageLabel")
		EndWaypointImageLabel.Image = EndWaypointIcon
		EndWaypointImageLabel.BackgroundTransparency = 1
		EndWaypointImageLabel.Size = UDim2.new(1, 0, 1, 0)
		EndWaypointImageLabel.Parent = EndWaypointBillboard
	
	
		local FailureWaypointTemplate = Instance.new("Part")
		FailureWaypointTemplate.Size = Vector3.new(2, 2, 2)
		FailureWaypointTemplate.Anchored = true
		FailureWaypointTemplate.CanCollide = false
		FailureWaypointTemplate.Name = "FailureWaypoint"
		FailureWaypointTemplate.Transparency = 1
		local FailureWaypointImage = Instance.new("ImageHandleAdornment")
		FailureWaypointImage.Name = "TrailDotImage"
		FailureWaypointImage.Size = TrailDotSize
		FailureWaypointImage.SizeRelativeOffset = Vector3.new(0, 0, -0.1)
		FailureWaypointImage.AlwaysOnTop = WaypointsAlwaysOnTop
		FailureWaypointImage.Image = TrailDotIcon
		FailureWaypointImage.Adornee = FailureWaypointTemplate
		FailureWaypointImage.Parent = FailureWaypointTemplate
		local FailureWaypointBillboard = Instance.new("BillboardGui")
		FailureWaypointBillboard.Name = "FailureWaypointBillboard"
		FailureWaypointBillboard.Size = WAYPOINT_BILLBOARD_SIZE
		FailureWaypointBillboard.LightInfluence = 0
		FailureWaypointBillboard.SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER
		FailureWaypointBillboard.AlwaysOnTop = true
		FailureWaypointBillboard.Adornee = FailureWaypointTemplate
		FailureWaypointBillboard.Parent = FailureWaypointTemplate
		local FailureWaypointFrame = Instance.new("Frame")
		FailureWaypointFrame.BackgroundTransparency = 1
		FailureWaypointFrame.Size = UDim2.new(0, 0, 0, 0)
		FailureWaypointFrame.Position = UDim2.new(0.5, 0, 1, 0)
		FailureWaypointFrame.Parent = FailureWaypointBillboard
		local FailureWaypointImageLabel = Instance.new("ImageLabel")
		FailureWaypointImageLabel.Image = EndWaypointIcon
		FailureWaypointImageLabel.BackgroundTransparency = 1
		FailureWaypointImageLabel.Position = UDim2.new(
			0, -WAYPOINT_BILLBOARD_SIZE.X.Offset/2, 0, -WAYPOINT_BILLBOARD_SIZE.Y.Offset
		)
		FailureWaypointImageLabel.Size = WAYPOINT_BILLBOARD_SIZE
		FailureWaypointImageLabel.Parent = FailureWaypointFrame
	
		return TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate
	end
	
	local TrailDotTemplate, EndWaypointTemplate, FailureWaypointTemplate = CreateWaypointTemplates()
	
	local function getTrailDotParent()
		local camera = Workspace.CurrentCamera
		local trailParent = camera:FindFirstChild(TRAIL_DOT_PARENT_NAME)
		if not trailParent then
			trailParent = Instance.new("Model")
			trailParent.Name = TRAIL_DOT_PARENT_NAME
			trailParent.Parent = camera
		end
		return trailParent
	end
	
	local function placePathWaypoint(waypointModel, position)
		local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
		local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
			ray,
			{ Workspace.CurrentCamera, LocalPlayer.Character }
		)
		if hitPart then
			waypointModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
			waypointModel.Parent = getTrailDotParent()
		end
	end
	
	local TrailDot = {}
	TrailDot.__index = TrailDot
	
	function TrailDot:Destroy()
		self.DisplayModel:Destroy()
	end
	
	function TrailDot:NewDisplayModel(position)
		local newDisplayModel = TrailDotTemplate:Clone()
		placePathWaypoint(newDisplayModel, position)
		return newDisplayModel
	end
	
	function TrailDot.new(position, closestWaypoint)
		local self = setmetatable({}, TrailDot)
	
		self.DisplayModel = self:NewDisplayModel(position)
		self.ClosestWayPoint = closestWaypoint
	
		return self
	end
	
	local EndWaypoint = {}
	EndWaypoint.__index = EndWaypoint
	
	function EndWaypoint:Destroy()
		self.Destroyed = true
		self.Tween:Cancel()
		self.DisplayModel:Destroy()
	end
	
	function EndWaypoint:NewDisplayModel(position)
		local newDisplayModel = EndWaypointTemplate:Clone()
		placePathWaypoint(newDisplayModel, position)
		return newDisplayModel
	end
	
	function EndWaypoint:CreateTween()
		local tweenInfo = TweenInfo.new(0.5, Enum.EasingStyle.Sine, Enum.EasingDirection.Out, -1, true)
		local tween = TweenService:Create(
			self.DisplayModel.EndWaypointBillboard,
			tweenInfo,
			{ SizeOffset = ENDWAYPOINT_SIZE_OFFSET_MAX }
		)
		tween:Play()
		return tween
	end
	
	function EndWaypoint:TweenInFrom(originalPosition)
		local currentPositon = self.DisplayModel.Position
		local studsOffset = originalPosition - currentPositon
		self.DisplayModel.EndWaypointBillboard.StudsOffset = Vector3.new(0, studsOffset.Y, 0)
		local tweenInfo = TweenInfo.new(1, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
		local tween = TweenService:Create(
			self.DisplayModel.EndWaypointBillboard,
			tweenInfo,
			{ StudsOffset = Vector3.new(0, 0, 0) }
		)
		tween:Play()
		return tween
	end
	
	function EndWaypoint.new(position, closestWaypoint, originalPosition)
		local self = setmetatable({}, EndWaypoint)
	
		self.DisplayModel = self:NewDisplayModel(position)
		self.Destroyed = false
		if originalPosition and (originalPosition - position).magnitude > TWEEN_WAYPOINT_THRESHOLD then
			self.Tween = self:TweenInFrom(originalPosition)
			coroutine.wrap(function()
				self.Tween.Completed:Wait()
				if not self.Destroyed then
					self.Tween = self:CreateTween()
				end
			end)()
		else
			self.Tween = self:CreateTween()
		end
		self.ClosestWayPoint = closestWaypoint
	
		return self
	end
	
	local FailureWaypoint = {}
	FailureWaypoint.__index = FailureWaypoint
	
	function FailureWaypoint:Hide()
		self.DisplayModel.Parent = nil
	end
	
	function FailureWaypoint:Destroy()
		self.DisplayModel:Destroy()
	end
	
	function FailureWaypoint:NewDisplayModel(position)
		local newDisplayModel = FailureWaypointTemplate:Clone()
		placePathWaypoint(newDisplayModel, position)
		local ray = Ray.new(position + Vector3.new(0, 2.5, 0), Vector3.new(0, -10, 0))
		local hitPart, hitPoint, hitNormal = Workspace:FindPartOnRayWithIgnoreList(
			ray, { Workspace.CurrentCamera, LocalPlayer.Character }
		)
		if hitPart then
			newDisplayModel.CFrame = CFrame.new(hitPoint, hitPoint + hitNormal)
			newDisplayModel.Parent = getTrailDotParent()
		end
		return newDisplayModel
	end
	
	function FailureWaypoint:RunFailureTween()
		wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore starting tweening
		-- Tween out from center
		local tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
		local tweenLeft = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_LEFT })
		tweenLeft:Play()
	
		local tweenLeftRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
			{ Rotation = 10 })
		tweenLeftRoation:Play()
	
		tweenLeft.Completed:wait()
	
		-- Tween back and forth
		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
			FAILURE_TWEEN_COUNT - 1, true)
		local tweenSideToSide = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_RIGHT})
		tweenSideToSide:Play()
	
		-- Tween flash dark and roate left and right
		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH, Enum.EasingStyle.Sine, Enum.EasingDirection.Out,
			FAILURE_TWEEN_COUNT - 1, true)
		local tweenFlash = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame.ImageLabel, tweenInfo,
			{ ImageColor3 = Color3.new(0.75, 0.75, 0.75)})
		tweenFlash:Play()
	
		local tweenRotate = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
			{ Rotation = -10 })
		tweenRotate:Play()
	
		tweenSideToSide.Completed:wait()
	
		-- Tween back to center
		tweenInfo = TweenInfo.new(FAILURE_TWEEN_LENGTH/2, Enum.EasingStyle.Sine, Enum.EasingDirection.Out)
		local tweenCenter = TweenService:Create(self.DisplayModel.FailureWaypointBillboard, tweenInfo,
			{ SizeOffset = FAIL_WAYPOINT_SIZE_OFFSET_CENTER })
		tweenCenter:Play()
	
		local tweenRoation = TweenService:Create(self.DisplayModel.FailureWaypointBillboard.Frame, tweenInfo,
			{ Rotation = 0 })
		tweenRoation:Play()
	
		tweenCenter.Completed:wait()
	
		wait(FAILURE_TWEEN_LENGTH) -- Delay one tween length betfore removing
	end
	
	function FailureWaypoint.new(position)
		local self = setmetatable({}, FailureWaypoint)
	
		self.DisplayModel = self:NewDisplayModel(position)
	
		return self
	end
	
	local failureAnimation = Instance.new("Animation")
	failureAnimation.AnimationId = FAILURE_ANIMATION_ID
	
	local lastHumanoid = nil
	local lastFailureAnimationTrack = nil
	
	local function getFailureAnimationTrack(myHumanoid)
		if myHumanoid == lastHumanoid then
			return lastFailureAnimationTrack
		end
		lastFailureAnimationTrack = myHumanoid:LoadAnimation(failureAnimation)
		lastFailureAnimationTrack.Priority = Enum.AnimationPriority.Action
		lastFailureAnimationTrack.Looped = false
		return lastFailureAnimationTrack
	end
	
	local function findPlayerHumanoid()
		local character = LocalPlayer.Character
		if character then
			return character:FindFirstChildOfClass("Humanoid")
		end
	end
	
	local function createTrailDots(wayPoints, originalEndWaypoint)
		local newTrailDots = {}
		local count = 1
		for i = 1, #wayPoints - 1 do
			local closeToEnd = (wayPoints[i].Position - wayPoints[#wayPoints].Position).magnitude < LAST_DOT_DISTANCE
			local includeWaypoint = i % WAYPOINT_INCLUDE_FACTOR == 0 and not closeToEnd
			if includeWaypoint then
				local trailDot = TrailDot.new(wayPoints[i].Position, i)
				newTrailDots[count] = trailDot
				count = count + 1
			end
		end
	
		local newEndWaypoint = EndWaypoint.new(wayPoints[#wayPoints].Position, #wayPoints, originalEndWaypoint)
		table.insert(newTrailDots, newEndWaypoint)
	
		local reversedTrailDots = {}
		count = 1
		for i = #newTrailDots, 1, -1 do
			reversedTrailDots[count] = newTrailDots[i]
			count = count + 1
		end
		return reversedTrailDots
	end
	
	local function getTrailDotScale(distanceToCamera, defaultSize)
		local rangeLength = TRAIL_DOT_MAX_DISTANCE - TRAIL_DOT_MIN_DISTANCE
		local inRangePoint = math.clamp(distanceToCamera - TRAIL_DOT_MIN_DISTANCE, 0, rangeLength)/rangeLength
		local scale = TRAIL_DOT_MIN_SCALE + (TRAIL_DOT_MAX_SCALE - TRAIL_DOT_MIN_SCALE)*inRangePoint
		return defaultSize * scale
	end
	
	local createPathCount = 0
	-- originalEndWaypoint is optional, causes the waypoint to tween from that position.
	function ClickToMoveDisplay.CreatePathDisplay(wayPoints, originalEndWaypoint)
		createPathCount = createPathCount + 1
		local trailDots = createTrailDots(wayPoints, originalEndWaypoint)
	
		local function removePathBeforePoint(wayPointNumber)
			-- kill all trailDots before and at wayPointNumber
			for i = #trailDots, 1, -1 do
				local trailDot = trailDots[i]
				if trailDot.ClosestWayPoint  0 then
						self.jumpButton.Visible = true
					end
				end
			end
		else
			self.jumpButton.Visible = false
			self.isJumping = false
			self.jumpButton.ImageRectOffset = Vector2.new(1, 146)
		end
	end
	
	function TouchJump:UpdateEnabled()
		if self.jumpPower > 0 and self.jumpStateEnabled then
			self:EnableButton(true)
		else
			self:EnableButton(false)
		end
	end
	
	function TouchJump:HumanoidChanged(prop)
		local humanoid = Players.LocalPlayer.Character and Players.LocalPlayer.Character:FindFirstChildOfClass("Humanoid")
		if humanoid then
			if prop == "JumpPower" then
				self.jumpPower =  humanoid.JumpPower
				self:UpdateEnabled()
			elseif prop == "Parent" then
				if not humanoid.Parent then
					self.humanoidChangeConn:Disconnect()
				end
			end
		end
	end
	
	function TouchJump:HumanoidStateEnabledChanged(state, isEnabled)
		if state == Enum.HumanoidStateType.Jumping then
			self.jumpStateEnabled = isEnabled
			self:UpdateEnabled()
		end
	end
	
	function TouchJump:CharacterAdded(char)
		if self.humanoidChangeConn then
			self.humanoidChangeConn:Disconnect()
			self.humanoidChangeConn = nil
		end
	
		self.humanoid = char:FindFirstChildOfClass("Humanoid")
		while not self.humanoid do
			char.ChildAdded:wait()
			self.humanoid = char:FindFirstChildOfClass("Humanoid")
		end
	
		self.humanoidJumpPowerConn = self.humanoid:GetPropertyChangedSignal("JumpPower"):Connect(function()
			self.jumpPower =  self.humanoid.JumpPower
			self:UpdateEnabled()
		end)
	
		self.humanoidParentConn = self.humanoid:GetPropertyChangedSignal("Parent"):Connect(function()
			if not self.humanoid.Parent then
				self.humanoidJumpPowerConn:Disconnect()
				self.humanoidJumpPowerConn = nil
				self.humanoidParentConn:Disconnect()
				self.humanoidParentConn = nil
			end
		end)
	
		self.humanoidStateEnabledChangedConn = self.humanoid.StateEnabledChanged:Connect(function(state, enabled)
			self:HumanoidStateEnabledChanged(state, enabled)
		end)
	
		self.jumpPower = self.humanoid.JumpPower
		self.jumpStateEnabled = self.humanoid:GetStateEnabled(Enum.HumanoidStateType.Jumping)
		self:UpdateEnabled()
	end
	
	function TouchJump:SetupCharacterAddedFunction()
		self.characterAddedConn = Players.LocalPlayer.CharacterAdded:Connect(function(char)
			self:CharacterAdded(char)
		end)
		if Players.LocalPlayer.Character then
			self:CharacterAdded(Players.LocalPlayer.Character)
		end
	end
	
	function TouchJump:Enable(enable, parentFrame)
		if parentFrame then
			self.parentUIFrame = parentFrame
		end
		self.externallyEnabled = enable
		self:EnableButton(enable)
	end
	
	function TouchJump:Create()
		if not self.parentUIFrame then
			return
		end
	
		if self.jumpButton then
			self.jumpButton:Destroy()
			self.jumpButton = nil
		end
	
		local minAxis = math.min(self.parentUIFrame.AbsoluteSize.x, self.parentUIFrame.AbsoluteSize.y)
		local isSmallScreen = minAxis  ALMOST_ZERO then
				this.CurrentWaypointPlaneNormal	= this.CurrentWaypointPlaneNormal.Unit
				this.CurrentWaypointPlaneDistance = this.CurrentWaypointPlaneNormal:Dot(nextWaypoint.Position)
			else
				-- Next waypoint is the same as current waypoint so no plane
				this.CurrentWaypointPlaneNormal	= ZERO_VECTOR3
				this.CurrentWaypointPlaneDistance = 0
			end
	
			-- Should we jump
			this.CurrentWaypointNeedsJump = nextWaypoint.Action == Enum.PathWaypointAction.Jump;
	
			-- Remember next waypoint position
			this.CurrentWaypointPosition = nextWaypoint.Position
	
			-- Move to next point
			this.CurrentPoint = nextWaypointIdx
	
			-- Finally reset Timeout
			this.Timeout = 0
		end
	
		function this:Start(overrideShowPath)
			if not this.AgentCanFollowPath then
				this.PathFailed:Fire()
				return
			end
	
			if this.Started then return end
			this.Started = true
	
			ClickToMoveDisplay.CancelFailureAnimation()
	
			if ShowPath then
				if overrideShowPath == nil or overrideShowPath then
					this.stopTraverseFunc, this.setPointFunc = ClickToMoveDisplay.CreatePathDisplay(this.pointList, this.OriginalTargetPoint)
				end
			end
	
			if #this.pointList > 0 then
				-- Determine the humanoid offset from the path's first point
				-- Offset of the first waypoint from the path's origin point
				this.HumanoidOffsetFromPath = Vector3.new(0, this.pointList[1].Position.Y - this.OriginPoint.Y, 0)
	
				-- As well as its current position and velocity
				this.CurrentHumanoidPosition = this.Humanoid.RootPart.Position + this.HumanoidOffsetFromPath
				this.CurrentHumanoidVelocity = this.Humanoid.RootPart.Velocity
	
				-- Connect to events
				this.SeatedConn = this.Humanoid.Seated:Connect(function(isSeated, seat) this:OnPathInterrupted() end)
				this.DiedConn = this.Humanoid.Died:Connect(function() this:OnPathInterrupted() end)
				this.TeleportedConn = this.Humanoid.RootPart:GetPropertyChangedSignal("CFrame"):Connect(function() this:OnPathInterrupted() end)
	
				-- Actually start
				this.CurrentPoint = 1 -- The first waypoint is always the start location. Skip it.
				this:OnPointReached(true) -- Move to first point
			else
				this.PathFailed:Fire()
				if this.stopTraverseFunc then
					this.stopTraverseFunc()
				end
			end
		end
	
		--We always raycast to the ground in the case that the user clicked a wall.
		local offsetPoint = this.TargetPoint + this.TargetSurfaceNormal*1.5
		local ray = Ray.new(offsetPoint, Vector3.new(0,-1,0)*50)
		local newHitPart, newHitPos = Workspace:FindPartOnRayWithIgnoreList(ray, getIgnoreList())
		if newHitPart then
			this.TargetPoint = newHitPos
		end
		this:ComputePath()
	
		return this
	end
	
	-------------------------------------------------------------------------
	
	local function CheckAlive()
		local humanoid = findPlayerHumanoid(Player)
		return humanoid ~= nil and humanoid.Health > 0
	end
	
	local function GetEquippedTool(character)
		if character ~= nil then
			for _, child in pairs(character:GetChildren()) do
				if child:IsA('Tool') then
					return child
				end
			end
		end
	end
	
	local ExistingPather = nil
	local ExistingIndicator = nil
	local PathCompleteListener = nil
	local PathFailedListener = nil
	
	local function CleanupPath()
		if ExistingPather then
			ExistingPather:Cancel()
			ExistingPather = nil
		end
		if PathCompleteListener then
			PathCompleteListener:Disconnect()
			PathCompleteListener = nil
		end
		if PathFailedListener then
			PathFailedListener:Disconnect()
			PathFailedListener = nil
		end
		if ExistingIndicator then
			ExistingIndicator:Destroy()
		end
	end
	
	local function HandleMoveTo(thisPather, hitPt, hitChar, character, overrideShowPath)
		if ExistingPather then
			CleanupPath()
		end
		ExistingPather = thisPather
		thisPather:Start(overrideShowPath)
	
		PathCompleteListener = thisPather.Finished.Event:Connect(function()
			CleanupPath()
			if hitChar then
				local currentWeapon = GetEquippedTool(character)
				if currentWeapon then
					currentWeapon:Activate()
				end
			end
		end)
		PathFailedListener = thisPather.PathFailed.Event:Connect(function()
			CleanupPath()
			if overrideShowPath == nil or overrideShowPath then
				local shouldPlayFailureAnim = PlayFailureAnimation and not (ExistingPather and ExistingPather:IsActive())
				if shouldPlayFailureAnim then
					ClickToMoveDisplay.PlayFailureAnimation()
				end
				ClickToMoveDisplay.DisplayFailureWaypoint(hitPt)
			end
		end)
	end
	
	local function ShowPathFailedFeedback(hitPt)
		if ExistingPather and ExistingPather:IsActive() then
			ExistingPather:Cancel()
		end
		if PlayFailureAnimation then
			ClickToMoveDisplay.PlayFailureAnimation()
		end
		ClickToMoveDisplay.DisplayFailureWaypoint(hitPt)
	end
	
	function OnTap(tapPositions, goToPoint, wasTouchTap)
		-- Good to remember if this is the latest tap event
		local camera = Workspace.CurrentCamera
		local character = Player.Character
	
		if not CheckAlive() then return end
	
		-- This is a path tap position
		if #tapPositions == 1 or goToPoint then
			if camera then
				local unitRay = camera:ScreenPointToRay(tapPositions[1].x, tapPositions[1].y)
				local ray = Ray.new(unitRay.Origin, unitRay.Direction*1000)
	
				local myHumanoid = findPlayerHumanoid(Player)
				local hitPart, hitPt, hitNormal = Utility.Raycast(ray, true, getIgnoreList())
	
				local hitChar, hitHumanoid = Utility.FindCharacterAncestor(hitPart)
				if wasTouchTap and hitHumanoid and StarterGui:GetCore("AvatarContextMenuEnabled") then
					local clickedPlayer = Players:GetPlayerFromCharacter(hitHumanoid.Parent)
					if clickedPlayer then
						CleanupPath()
						return
					end
				end
				if goToPoint then
					hitPt = goToPoint
					hitChar = nil
				end
				if hitPt and character then
					-- Clean up current path
					CleanupPath()
					local thisPather = Pather(hitPt, hitNormal)
					if thisPather:IsValidPath() then
						HandleMoveTo(thisPather, hitPt, hitChar, character)
					else
						-- Clean up
						thisPather:Cleanup()
						-- Feedback here for when we don't have a good path
						ShowPathFailedFeedback(hitPt)
					end
				end
			end
		elseif #tapPositions >= 2 then
			if camera then
				-- Do shoot
				local currentWeapon = GetEquippedTool(character)
				if currentWeapon then
					currentWeapon:Activate()
				end
			end
		end
	end
	
	local function DisconnectEvent(event)
		if event then
			event:Disconnect()
		end
	end
	
	--[[ The ClickToMove Controller Class ]]--
	local KeyboardController = _Keyboard()
	local ClickToMove = setmetatable({}, KeyboardController)
	ClickToMove.__index = ClickToMove
	
	function ClickToMove.new(CONTROL_ACTION_PRIORITY)
		local self = setmetatable(KeyboardController.new(CONTROL_ACTION_PRIORITY), ClickToMove)
	
		self.fingerTouches = {}
		self.numUnsunkTouches = 0
		-- PC simulation
		self.mouse1Down = tick()
		self.mouse1DownPos = Vector2.new()
		self.mouse2DownTime = tick()
		self.mouse2DownPos = Vector2.new()
		self.mouse2UpTime = tick()
	
		self.keyboardMoveVector = ZERO_VECTOR3
	
		self.tapConn = nil
		self.inputBeganConn = nil
		self.inputChangedConn = nil
		self.inputEndedConn = nil
		self.humanoidDiedConn = nil
		self.characterChildAddedConn = nil
		self.onCharacterAddedConn = nil
		self.characterChildRemovedConn = nil
		self.renderSteppedConn = nil
		self.menuOpenedConnection = nil
	
		self.running = false
	
		self.wasdEnabled = false
	
		return self
	end
	
	function ClickToMove:DisconnectEvents()
		DisconnectEvent(self.tapConn)
		DisconnectEvent(self.inputBeganConn)
		DisconnectEvent(self.inputChangedConn)
		DisconnectEvent(self.inputEndedConn)
		DisconnectEvent(self.humanoidDiedConn)
		DisconnectEvent(self.characterChildAddedConn)
		DisconnectEvent(self.onCharacterAddedConn)
		DisconnectEvent(self.renderSteppedConn)
		DisconnectEvent(self.characterChildRemovedConn)
		DisconnectEvent(self.menuOpenedConnection)
	end
	
	function ClickToMove:OnTouchBegan(input, processed)
		if self.fingerTouches[input] == nil and not processed then
			self.numUnsunkTouches = self.numUnsunkTouches + 1
		end
		self.fingerTouches[input] = processed
	end
	
	function ClickToMove:OnTouchChanged(input, processed)
		if self.fingerTouches[input] == nil then
			self.fingerTouches[input] = processed
			if not processed then
				self.numUnsunkTouches = self.numUnsunkTouches + 1
			end
		end
	end
	
	function ClickToMove:OnTouchEnded(input, processed)
		if self.fingerTouches[input] ~= nil and self.fingerTouches[input] == false then
			self.numUnsunkTouches = self.numUnsunkTouches - 1
		end
		self.fingerTouches[input] = nil
	end
	
	
	function ClickToMove:OnCharacterAdded(character)
		self:DisconnectEvents()
	
		self.inputBeganConn = UserInputService.InputBegan:Connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				self:OnTouchBegan(input, processed)
			end
	
			-- Cancel path when you use the keyboard controls if wasd is enabled.
			if self.wasdEnabled and processed == false and input.UserInputType == Enum.UserInputType.Keyboard
				and movementKeys[input.KeyCode] then
				CleanupPath()
				ClickToMoveDisplay.CancelFailureAnimation()
			end
			if input.UserInputType == Enum.UserInputType.MouseButton1 then
				self.mouse1DownTime = tick()
				self.mouse1DownPos = input.Position
			end
			if input.UserInputType == Enum.UserInputType.MouseButton2 then
				self.mouse2DownTime = tick()
				self.mouse2DownPos = input.Position
			end
		end)
	
		self.inputChangedConn = UserInputService.InputChanged:Connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				self:OnTouchChanged(input, processed)
			end
		end)
	
		self.inputEndedConn = UserInputService.InputEnded:Connect(function(input, processed)
			if input.UserInputType == Enum.UserInputType.Touch then
				self:OnTouchEnded(input, processed)
			end
	
			if input.UserInputType == Enum.UserInputType.MouseButton2 then
				self.mouse2UpTime = tick()
				local currPos = input.Position
				-- We allow click to move during path following or if there is no keyboard movement
				local allowed = ExistingPather or self.keyboardMoveVector.Magnitude = frameCornerTopLeft.X and inputPosition.Y >= frameCornerTopLeft.Y then
			if inputPosition.X  0 then
					self:DoMove(direction)
					self:MoveStick(inputObject.Position)
				end
				return Enum.ContextActionResult.Sink
			end
			return Enum.ContextActionResult.Pass
		end
	
		local function inputEnded(inputObject)
			if inputObject == self.moveTouchObject then
				self:OnInputEnded()
				if self.moveTouchLockedIn then
					return Enum.ContextActionResult.Sink
				end
			end
			return Enum.ContextActionResult.Pass
		end
	
		local function handleInput(actionName, inputState, inputObject)
			if inputState == Enum.UserInputState.Begin then
				return inputBegan(inputObject)
			elseif inputState == Enum.UserInputState.Change then
				return inputChanged(inputObject)
			elseif inputState == Enum.UserInputState.End then
				return inputEnded(inputObject)
			elseif inputState == Enum.UserInputState.Cancel then
				self:OnInputEnded()
			end
		end
	
		ContextActionService:BindActionAtPriority(
			DYNAMIC_THUMBSTICK_ACTION_NAME,
			handleInput,
			false,
			DYNAMIC_THUMBSTICK_ACTION_PRIORITY,
			Enum.UserInputType.Touch)
	end
	
	function DynamicThumbstick:Create(parentFrame)
		if self.thumbstickFrame then
			self.thumbstickFrame:Destroy()
			self.thumbstickFrame = nil
			if self.onRenderSteppedConn then
				self.onRenderSteppedConn:Disconnect()
				self.onRenderSteppedConn = nil
			end
		end
	
		self.thumbstickSize = 45
		self.thumbstickRingSize = 20
		self.middleSize = 10
		self.middleSpacing = self.middleSize + 4
		self.radiusOfDeadZone = 2
		self.radiusOfMaxSpeed = 20
	
		local screenSize = parentFrame.AbsoluteSize
		local isBigScreen = math.min(screenSize.x, screenSize.y) > 500
		if isBigScreen then
			self.thumbstickSize = self.thumbstickSize * 2
			self.thumbstickRingSize = self.thumbstickRingSize * 2
			self.middleSize = self.middleSize * 2
			self.middleSpacing = self.middleSpacing * 2
			self.radiusOfDeadZone = self.radiusOfDeadZone * 2
			self.radiusOfMaxSpeed = self.radiusOfMaxSpeed * 2
		end
	
		local function layoutThumbstickFrame(portraitMode)
			if portraitMode then
				self.thumbstickFrame.Size = UDim2.new(1, 0, 0.4, 0)
				self.thumbstickFrame.Position = UDim2.new(0, 0, 0.6, 0)
			else
				self.thumbstickFrame.Size = UDim2.new(0.4, 0, 2/3, 0)
				self.thumbstickFrame.Position = UDim2.new(0, 0, 1/3, 0)
			end
		end
	
		self.thumbstickFrame = Instance.new("Frame")
		self.thumbstickFrame.BorderSizePixel = 0
		self.thumbstickFrame.Name = "DynamicThumbstickFrame"
		self.thumbstickFrame.Visible = false
		self.thumbstickFrame.BackgroundTransparency = 1.0
		self.thumbstickFrame.BackgroundColor3 = Color3.fromRGB(0, 0, 0)
		self.thumbstickFrame.Active = false
		layoutThumbstickFrame(false)
	
		self.startImage = Instance.new("ImageLabel")
		self.startImage.Name = "ThumbstickStart"
		self.startImage.Visible = true
		self.startImage.BackgroundTransparency = 1
		self.startImage.Image = TOUCH_CONTROLS_SHEET
		self.startImage.ImageRectOffset = Vector2.new(1,1)
		self.startImage.ImageRectSize = Vector2.new(144, 144)
		self.startImage.ImageColor3 = Color3.new(0, 0, 0)
		self.startImage.AnchorPoint = Vector2.new(0.5, 0.5)
		self.startImage.Position = UDim2.new(0, self.thumbstickRingSize * 3.3, 1, -self.thumbstickRingSize  * 2.8)
		self.startImage.Size = UDim2.new(0, self.thumbstickRingSize  * 3.7, 0, self.thumbstickRingSize  * 3.7)
		self.startImage.ZIndex = 10
		self.startImage.Parent = self.thumbstickFrame
	
		self.endImage = Instance.new("ImageLabel")
		self.endImage.Name = "ThumbstickEnd"
		self.endImage.Visible = true
		self.endImage.BackgroundTransparency = 1
		self.endImage.Image = TOUCH_CONTROLS_SHEET
		self.endImage.ImageRectOffset = Vector2.new(1,1)
		self.endImage.ImageRectSize =  Vector2.new(144, 144)
		self.endImage.AnchorPoint = Vector2.new(0.5, 0.5)
		self.endImage.Position = self.startImage.Position
		self.endImage.Size = UDim2.new(0, self.thumbstickSize * 0.8, 0, self.thumbstickSize * 0.8)
		self.endImage.ZIndex = 10
		self.endImage.Parent = self.thumbstickFrame
	
		for i = 1, NUM_MIDDLE_IMAGES do
			self.middleImages[i] = Instance.new("ImageLabel")
			self.middleImages[i].Name = "ThumbstickMiddle"
			self.middleImages[i].Visible = false
			self.middleImages[i].BackgroundTransparency = 1
			self.middleImages[i].Image = TOUCH_CONTROLS_SHEET
			self.middleImages[i].ImageRectOffset = Vector2.new(1,1)
			self.middleImages[i].ImageRectSize = Vector2.new(144, 144)
			self.middleImages[i].ImageTransparency = MIDDLE_TRANSPARENCIES[i]
			self.middleImages[i].AnchorPoint = Vector2.new(0.5, 0.5)
			self.middleImages[i].ZIndex = 9
			self.middleImages[i].Parent = self.thumbstickFrame
		end
	
		local CameraChangedConn = nil
		local function onCurrentCameraChanged()
			if CameraChangedConn then
				CameraChangedConn:Disconnect()
				CameraChangedConn = nil
			end
			local newCamera = workspace.CurrentCamera
			if newCamera then
				local function onViewportSizeChanged()
					local size = newCamera.ViewportSize
					local portraitMode = size.X < size.Y
					layoutThumbstickFrame(portraitMode)
				end
				CameraChangedConn = newCamera:GetPropertyChangedSignal("ViewportSize"):Connect(onViewportSizeChanged)
				onViewportSizeChanged()
			end
		end
		workspace:GetPropertyChangedSignal("CurrentCamera"):Connect(onCurrentCameraChanged)
		if workspace.CurrentCamera then
			onCurrentCameraChanged()
		end
	
		self.moveTouchStartPosition = nil
	
		self.startImageFadeTween = nil
		self.endImageFadeTween = nil
		self.middleImageFadeTweens = {}
	
		self.onRenderSteppedConn = RunService.RenderStepped:Connect(function()
			if self.tweenInAlphaStart ~= nil then
				local delta = tick() - self.tweenInAlphaStart
				local fadeInTime = (self.fadeInAndOutHalfDuration * 2 * self.fadeInAndOutBalance)
				self.thumbstickFrame.BackgroundTransparency = 1 - FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeInTime, 1)
				if delta > fadeInTime then
					self.tweenOutAlphaStart = tick()
					self.tweenInAlphaStart = nil
				end
			elseif self.tweenOutAlphaStart ~= nil then
				local delta = tick() - self.tweenOutAlphaStart
				local fadeOutTime = (self.fadeInAndOutHalfDuration * 2) - (self.fadeInAndOutHalfDuration * 2 * self.fadeInAndOutBalance)
				self.thumbstickFrame.BackgroundTransparency = 1 - FADE_IN_OUT_MAX_ALPHA + FADE_IN_OUT_MAX_ALPHA*math.min(delta/fadeOutTime, 1)
				if delta > fadeOutTime  then
					self.tweenOutAlphaStart = nil
				end
			end
		end)
	
		self.onTouchEndedConn = UserInputService.TouchEnded:connect(function(inputObject)
			if inputObject == self.moveTouchObject then
				self:OnInputEnded()
			end
		end)
	
		GuiService.MenuOpened:connect(function()
			if self.moveTouchObject then
				self:OnInputEnded()
			end
		end)
	
		local playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
		while not playerGui do
			LocalPlayer.ChildAdded:wait()
			playerGui = LocalPlayer:FindFirstChildOfClass("PlayerGui")
		end
	
		local playerGuiChangedConn = nil
		local originalScreenOrientationWasLandscape =	playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeLeft or
														playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.LandscapeRight
	
		local function longShowBackground()
			self.fadeInAndOutHalfDuration = 2.5
			self.fadeInAndOutBalance = 0.05
			self.tweenInAlphaStart = tick()
		end
	
		playerGuiChangedConn = playerGui:GetPropertyChangedSignal("CurrentScreenOrientation"):Connect(function()
			if (originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation == Enum.ScreenOrientation.Portrait) or
				(not originalScreenOrientationWasLandscape and playerGui.CurrentScreenOrientation ~= Enum.ScreenOrientation.Portrait) then
	
				playerGuiChangedConn:disconnect()
				longShowBackground()
	
				if originalScreenOrientationWasLandscape then
					self.hasFadedBackgroundInPortrait = true
				else
					self.hasFadedBackgroundInLandscape = true
				end
			end
		end)
	
		self.thumbstickFrame.Parent = parentFrame
	
		if game:IsLoaded() then
			longShowBackground()
		else
			coroutine.wrap(function()
				game.Loaded:Wait()
				longShowBackground()
			end)()
		end
	end
	
	return DynamicThumbstick
end

function _Gamepad()
	local UserInputService = game:GetService("UserInputService")
	local ContextActionService = game:GetService("ContextActionService")
	
	--[[ Constants ]]--
	local ZERO_VECTOR3 = Vector3.new(0,0,0)
	local NONE = Enum.UserInputType.None
	local thumbstickDeadzone = 0.2
	
	--[[ The Module ]]--
	local BaseCharacterController = _BaseCharacterController()
	local Gamepad = setmetatable({}, BaseCharacterController)
	Gamepad.__index = Gamepad
	
	function Gamepad.new(CONTROL_ACTION_PRIORITY)
		local self = setmetatable(BaseCharacterController.new(), Gamepad)
	
		self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
	
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
	
		self.activeGamepad = NONE	-- Enum.UserInputType.Gamepad1, 2, 3...
		self.gamepadConnectedConn = nil
		self.gamepadDisconnectedConn = nil
		return self
	end
	
	function Gamepad:Enable(enable)
		if not UserInputService.GamepadEnabled then
			return false
		end
	
		if enable == self.enabled then
			-- Module is already in the state being requested. True is returned here since the module will be in the state
			-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
			-- no action was necessary. False indicates failure to be in requested/expected state.
			return true
		end
	
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
		self.moveVector = ZERO_VECTOR3
		self.isJumping = false
	
		if enable then
			self.activeGamepad = self:GetHighestPriorityGamepad()
			if self.activeGamepad ~= NONE then
				self:BindContextActions()
				self:ConnectGamepadConnectionListeners()
			else
				-- No connected gamepads, failure to enable
				return false
			end
		else
			self:UnbindContextActions()
			self:DisconnectGamepadConnectionListeners()
			self.activeGamepad = NONE
		end
	
		self.enabled = enable
		return true
	end
	
	-- This function selects the lowest number gamepad from the currently-connected gamepad
	-- and sets it as the active gamepad
	function Gamepad:GetHighestPriorityGamepad()
		local connectedGamepads = UserInputService:GetConnectedGamepads()
		local bestGamepad = NONE -- Note that this value is higher than all valid gamepad values
		for _, gamepad in pairs(connectedGamepads) do
			if gamepad.Value < bestGamepad.Value then
				bestGamepad = gamepad
			end
		end
		return bestGamepad
	end
	
	function Gamepad:BindContextActions()
	
		if self.activeGamepad == NONE then
			-- There must be an active gamepad to set up bindings
			return false
		end
	
		local handleJumpAction = function(actionName, inputState, inputObject)
			self.isJumping = (inputState == Enum.UserInputState.Begin)
			return Enum.ContextActionResult.Sink
		end
	
		local handleThumbstickInput = function(actionName, inputState, inputObject)
	
			if inputState == Enum.UserInputState.Cancel then
				self.moveVector = ZERO_VECTOR3
				return Enum.ContextActionResult.Sink
			end
	
			if self.activeGamepad ~= inputObject.UserInputType then
				return Enum.ContextActionResult.Pass
			end
			if inputObject.KeyCode ~= Enum.KeyCode.Thumbstick1 then return end
	
			if inputObject.Position.magnitude > thumbstickDeadzone then
				self.moveVector  =  Vector3.new(inputObject.Position.X, 0, -inputObject.Position.Y)
			else
				self.moveVector = ZERO_VECTOR3
			end
			return Enum.ContextActionResult.Sink
		end
	
		ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
		ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
			self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.ButtonA)
		ContextActionService:BindActionAtPriority("moveThumbstick", handleThumbstickInput, false,
			self.CONTROL_ACTION_PRIORITY, Enum.KeyCode.Thumbstick1)
	
		return true
	end
	
	function Gamepad:UnbindContextActions()
		if self.activeGamepad ~= NONE then
			ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
		end
		ContextActionService:UnbindAction("moveThumbstick")
		ContextActionService:UnbindAction("jumpAction")
	end
	
	function Gamepad:OnNewGamepadConnected()
		-- A new gamepad has been connected.
		local bestGamepad = self:GetHighestPriorityGamepad()
	
		if bestGamepad == self.activeGamepad then
			-- A new gamepad was connected, but our active gamepad is not changing
			return
		end
	
		if bestGamepad == NONE then
			-- There should be an active gamepad when GamepadConnected fires, so this should not
			-- normally be hit. If there is no active gamepad, unbind actions but leave
			-- the module enabled and continue to listen for a new gamepad connection.
			warn("Gamepad:OnNewGamepadConnected found no connected gamepads")
			self:UnbindContextActions()
			return
		end
	
		if self.activeGamepad ~= NONE then
			-- Switching from one active gamepad to another
			self:UnbindContextActions()
		end
	
		self.activeGamepad = bestGamepad
		self:BindContextActions()
	end
	
	function Gamepad:OnCurrentGamepadDisconnected()
		if self.activeGamepad ~= NONE then
			ContextActionService:UnbindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
		end
	
		local bestGamepad = self:GetHighestPriorityGamepad()
	
		if self.activeGamepad ~= NONE and bestGamepad == self.activeGamepad then
			warn("Gamepad:OnCurrentGamepadDisconnected found the supposedly disconnected gamepad in connectedGamepads.")
			self:UnbindContextActions()
			self.activeGamepad = NONE
			return
		end
	
		if bestGamepad == NONE then
			-- No active gamepad, unbinding actions but leaving gamepad connection listener active
			self:UnbindContextActions()
			self.activeGamepad = NONE
		else
			-- Set new gamepad as active and bind to tool activation
			self.activeGamepad = bestGamepad
			ContextActionService:BindActivate(self.activeGamepad, Enum.KeyCode.ButtonR2)
		end
	end
	
	function Gamepad:ConnectGamepadConnectionListeners()
		self.gamepadConnectedConn = UserInputService.GamepadConnected:Connect(function(gamepadEnum)
			self:OnNewGamepadConnected()
		end)
	
		self.gamepadDisconnectedConn = UserInputService.GamepadDisconnected:Connect(function(gamepadEnum)
			if self.activeGamepad == gamepadEnum then
				self:OnCurrentGamepadDisconnected()
			end
		end)
	
	end
	
	function Gamepad:DisconnectGamepadConnectionListeners()
		if self.gamepadConnectedConn then
			self.gamepadConnectedConn:Disconnect()
			self.gamepadConnectedConn = nil
		end
	
		if self.gamepadDisconnectedConn then
			self.gamepadDisconnectedConn:Disconnect()
			self.gamepadDisconnectedConn = nil
		end
	end
	
	return Gamepad
end

function _Keyboard()
	
	--[[ Roblox Services ]]--
	local UserInputService = game:GetService("UserInputService")
	local ContextActionService = game:GetService("ContextActionService")
	
	--[[ Constants ]]--
	local ZERO_VECTOR3 = Vector3.new(0,0,0)
	
	--[[ The Module ]]--
	local BaseCharacterController = _BaseCharacterController()
	local Keyboard = setmetatable({}, BaseCharacterController)
	Keyboard.__index = Keyboard
	
	function Keyboard.new(CONTROL_ACTION_PRIORITY)
		local self = setmetatable(BaseCharacterController.new(), Keyboard)
	
		self.CONTROL_ACTION_PRIORITY = CONTROL_ACTION_PRIORITY
	
		self.textFocusReleasedConn = nil
		self.textFocusGainedConn = nil
		self.windowFocusReleasedConn = nil
	
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
	
		self.jumpEnabled = true
	
		return self
	end
	
	function Keyboard:Enable(enable)
		if not UserInputService.KeyboardEnabled then
			return false
		end
	
		if enable == self.enabled then
			-- Module is already in the state being requested. True is returned here since the module will be in the state
			-- expected by the code that follows the Enable() call. This makes more sense than returning false to indicate
			-- no action was necessary. False indicates failure to be in requested/expected state.
			return true
		end
	
		self.forwardValue  = 0
		self.backwardValue = 0
		self.leftValue = 0
		self.rightValue = 0
		self.moveVector = ZERO_VECTOR3
		self.jumpRequested = false
		self:UpdateJump()
	
		if enable then
			self:BindContextActions()
			self:ConnectFocusEventListeners()
		else
			self:UnbindContextActions()
			self:DisconnectFocusEventListeners()
		end
	
		self.enabled = enable
		return true
	end
	
	function Keyboard:UpdateMovement(inputState)
		if inputState == Enum.UserInputState.Cancel then
			self.moveVector = ZERO_VECTOR3
		else
			self.moveVector = Vector3.new(self.leftValue + self.rightValue, 0, self.forwardValue + self.backwardValue)
		end
	end
	
	function Keyboard:UpdateJump()
		self.isJumping = self.jumpRequested
	end
	
	function Keyboard:BindContextActions()
	
		-- Note: In the previous version of this code, the movement values were not zeroed-out on UserInputState. Cancel, now they are,
		-- which fixes them from getting stuck on.
		-- We return ContextActionResult.Pass here for legacy reasons.
		-- Many games rely on gameProcessedEvent being false on UserInputService.InputBegan for these control actions.
		local handleMoveForward = function(actionName, inputState, inputObject)
			self.forwardValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
			self:UpdateMovement(inputState)
			return Enum.ContextActionResult.Pass
		end
	
		local handleMoveBackward = function(actionName, inputState, inputObject)
			self.backwardValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
			self:UpdateMovement(inputState)
			return Enum.ContextActionResult.Pass
		end
	
		local handleMoveLeft = function(actionName, inputState, inputObject)
			self.leftValue = (inputState == Enum.UserInputState.Begin) and -1 or 0
			self:UpdateMovement(inputState)
			return Enum.ContextActionResult.Pass
		end
	
		local handleMoveRight = function(actionName, inputState, inputObject)
			self.rightValue = (inputState == Enum.UserInputState.Begin) and 1 or 0
			self:UpdateMovement(inputState)
			return Enum.ContextActionResult.Pass
		end
	
		local handleJumpAction = function(actionName, inputState, inputObject)
			self.jumpRequested = self.jumpEnabled and (inputState == Enum.UserInputState.Begin)
			self:UpdateJump()
			return Enum.ContextActionResult.Pass
		end
	
		-- TODO: Revert to KeyCode bindings so that in the future the abstraction layer from actual keys to
		-- movement direction is done in Lua
		ContextActionService:BindActionAtPriority("moveForwardAction", handleMoveForward, false,
			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterForward)
		ContextActionService:BindActionAtPriority("moveBackwardAction", handleMoveBackward, false,
			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterBackward)
		ContextActionService:BindActionAtPriority("moveLeftAction", handleMoveLeft, false,
			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterLeft)
		ContextActionService:BindActionAtPriority("moveRightAction", handleMoveRight, false,
			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterRight)
		ContextActionService:BindActionAtPriority("jumpAction", handleJumpAction, false,
			self.CONTROL_ACTION_PRIORITY, Enum.PlayerActions.CharacterJump)
	end
	
	function Keyboard:UnbindContextActions()
		ContextActionService:UnbindAction("moveForwardAction")
		ContextActionService:UnbindAction("moveBackwardAction")
		ContextActionService:UnbindAction("moveLeftAction")
		ContextActionService:UnbindAction("moveRightAction")
		ContextActionService:UnbindAction("jumpAction")
	end
	
	function Keyboard:ConnectFocusEventListeners()
		local function onFocusReleased()
			self.moveVector = ZERO_VECTOR3
			self.forwardValue  = 0
			self.backwardValue = 0
			self.leftValue = 0
			self.rightValue = 0
			self.jumpRequested = false
			self:UpdateJump()
		end
	
		local function onTextFocusGained(textboxFocused)
			self.jumpRequested = false
			self:UpdateJump()
		end
	
		self.textFocusReleasedConn = UserInputService.TextBoxFocusReleased:Connect(onFocusReleased)
		self.textFocusGainedConn = UserInputService.TextBoxFocused:Connect(onTextFocusGained)
		self.windowFocusReleasedConn = UserInputService.WindowFocused:Connect(onFocusReleased)
	end
	
	function Keyboard:DisconnectFocusEventListeners()
		if self.textFocusReleasedCon then
			self.textFocusReleasedCon:Disconnect()
			self.textFocusReleasedCon = nil
		end
		if self.textFocusGainedConn then
			self.textFocusGainedConn:Disconnect()
			self.textFocusGainedConn = nil
		end
		if self.windowFocusReleasedConn then
			self.windowFocusReleasedConn:Disconnect()
			self.windowFocusReleasedConn = nil
		end
	end
	
	return Keyboard
end

function _ControlModule()
	local ControlModule = {}
	ControlModule.__index = ControlModule
	
	--[[ Roblox Services ]]--
	local Players = game:GetService("Players")
	local RunService = game:GetService("RunService")
	local UserInputService = game:GetService("UserInputService")
	local Workspace = game:GetService("Workspace")
	local UserGameSettings = UserSettings():GetService("UserGameSettings")
	
	-- Roblox User Input Control Modules - each returns a new() constructor function used to create controllers as needed
	local Keyboard = _Keyboard()
	local Gamepad = _Gamepad()
	local DynamicThumbstick = _DynamicThumbstick()
	
	local FFlagUserMakeThumbstickDynamic do
		local success, value = pcall(function()
			return UserSettings():IsUserFeatureEnabled("UserMakeThumbstickDynamic")
		end)
		FFlagUserMakeThumbstickDynamic = success and value
	end
	
	local TouchThumbstick = FFlagUserMakeThumbstickDynamic and DynamicThumbstick or _TouchThumbstick()
	
	-- These controllers handle only walk/run movement, jumping is handled by the
	-- TouchJump controller if any of these are active
	local ClickToMove = _ClickToMoveController()
	local TouchJump = _TouchJump()
	
	local VehicleController = _VehicleController()
	
	local CONTROL_ACTION_PRIORITY = Enum.ContextActionPriority.Default.Value
	
	-- Mapping from movement mode and lastInputType enum values to control modules to avoid huge if elseif switching
	local movementEnumToModuleMap = {
		[Enum.TouchMovementMode.DPad] = DynamicThumbstick,
		[Enum.DevTouchMovementMode.DPad] = DynamicThumbstick,
		[Enum.TouchMovementMode.Thumbpad] = DynamicThumbstick,
		[Enum.DevTouchMovementMode.Thumbpad] = DynamicThumbstick,
		[Enum.TouchMovementMode.Thumbstick] = TouchThumbstick,
		[Enum.DevTouchMovementMode.Thumbstick] = TouchThumbstick,
		[Enum.TouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
		[Enum.DevTouchMovementMode.DynamicThumbstick] = DynamicThumbstick,
		[Enum.TouchMovementMode.ClickToMove] = ClickToMove,
		[Enum.DevTouchMovementMode.ClickToMove] = ClickToMove,
	
		-- Current default
		[Enum.TouchMovementMode.Default] = DynamicThumbstick,
	
		[Enum.ComputerMovementMode.Default] = Keyboard,
		[Enum.ComputerMovementMode.KeyboardMouse] = Keyboard,
		[Enum.DevComputerMovementMode.KeyboardMouse] = Keyboard,
		[Enum.DevComputerMovementMode.Scriptable] = nil,
		[Enum.ComputerMovementMode.ClickToMove] = ClickToMove,
		[Enum.DevComputerMovementMode.ClickToMove] = ClickToMove,
	}
	
	-- Keyboard controller is really keyboard and mouse controller
	local computerInputTypeToModuleMap = {
		[Enum.UserInputType.Keyboard] = Keyboard,
		[Enum.UserInputType.MouseButton1] = Keyboard,
		[Enum.UserInputType.MouseButton2] = Keyboard,
		[Enum.UserInputType.MouseButton3] = Keyboard,
		[Enum.UserInputType.MouseWheel] = Keyboard,
		[Enum.UserInputType.MouseMovement] = Keyboard,
		[Enum.UserInputType.Gamepad1] = Gamepad,
		[Enum.UserInputType.Gamepad2] = Gamepad,
		[Enum.UserInputType.Gamepad3] = Gamepad,
		[Enum.UserInputType.Gamepad4] = Gamepad,
	}
	
	local lastInputType
	
	function ControlModule.new()
		local self = setmetatable({},ControlModule)
	
		-- The Modules above are used to construct controller instances as-needed, and this
		-- table is a map from Module to the instance created from it
		self.controllers = {}
	
		self.activeControlModule = nil	-- Used to prevent unnecessarily expensive checks on each input event
		self.activeController = nil
		self.touchJumpController = nil
		self.moveFunction = Players.LocalPlayer.Move
		self.humanoid = nil
		self.lastInputType = Enum.UserInputType.None
	
		-- For Roblox self.vehicleController
		self.humanoidSeatedConn = nil
		self.vehicleController = nil
	
		self.touchControlFrame = nil
	
		self.vehicleController = VehicleController.new(CONTROL_ACTION_PRIORITY)
	
		Players.LocalPlayer.CharacterAdded:Connect(function(char) self:OnCharacterAdded(char) end)
		Players.LocalPlayer.CharacterRemoving:Connect(function(char) self:OnCharacterRemoving(char) end)
		if Players.LocalPlayer.Character then
			self:OnCharacterAdded(Players.LocalPlayer.Character)
		end
	
		RunService:BindToRenderStep("ControlScriptRenderstep", Enum.RenderPriority.Input.Value, function(dt)
			self:OnRenderStepped(dt)
		end)
	
		UserInputService.LastInputTypeChanged:Connect(function(newLastInputType)
			self:OnLastInputTypeChanged(newLastInputType)
		end)
	
	
		UserGameSettings:GetPropertyChangedSignal("TouchMovementMode"):Connect(function()
			self:OnTouchMovementModeChange()
		end)
		Players.LocalPlayer:GetPropertyChangedSignal("DevTouchMovementMode"):Connect(function()
			self:OnTouchMovementModeChange()
		end)
	
		UserGameSettings:GetPropertyChangedSignal("ComputerMovementMode"):Connect(function()
			self:OnComputerMovementModeChange()
		end)
		Players.LocalPlayer:GetPropertyChangedSignal("DevComputerMovementMode"):Connect(function()
			self:OnComputerMovementModeChange()
		end)
	
		--[[ Touch Device UI ]]--
		self.playerGui = nil
		self.touchGui = nil
		self.playerGuiAddedConn = nil
	
		if UserInputService.TouchEnabled then
			self.playerGui = Players.LocalPlayer:FindFirstChildOfClass("PlayerGui")
			if self.playerGui then
				self:CreateTouchGuiContainer()
				self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
			else
				self.playerGuiAddedConn = Players.LocalPlayer.ChildAdded:Connect(function(child)
					if child:IsA("PlayerGui") then
						self.playerGui = child
						self:CreateTouchGuiContainer()
						self.playerGuiAddedConn:Disconnect()
						self.playerGuiAddedConn = nil
						self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
					end
				end)
			end
		else
			self:OnLastInputTypeChanged(UserInputService:GetLastInputType())
		end
	
		return self
	end
	
	-- Convenience function so that calling code does not have to first get the activeController
	-- and then call GetMoveVector on it. When there is no active controller, this function returns
	-- nil so that this case can be distinguished from no current movement (which returns zero vector).
	function ControlModule:GetMoveVector()
		if self.activeController then
			return self.activeController:GetMoveVector()
		end
		return Vector3.new(0,0,0)
	end
	
	function ControlModule:GetActiveController()
		return self.activeController
	end
	
	function ControlModule:EnableActiveControlModule()
		if self.activeControlModule == ClickToMove then
			-- For ClickToMove, when it is the player's choice, we also enable the full keyboard controls.
			-- When the developer is forcing click to move, the most keyboard controls (WASD) are not available, only jump.
			self.activeController:Enable(
				true,
				Players.LocalPlayer.DevComputerMovementMode == Enum.DevComputerMovementMode.UserChoice,
				self.touchJumpController
			)
		elseif self.touchControlFrame then
			self.activeController:Enable(true, self.touchControlFrame)
		else
			self.activeController:Enable(true)
		end
	end
	
	function ControlModule:Enable(enable)
		if not self.activeController then
			return
		end
	
		if enable == nil then
			enable = true
		end
		if enable then
			self:EnableActiveControlModule()
		else
			self:Disable()
		end
	end
	
	-- For those who prefer distinct functions
	function ControlModule:Disable()
		if self.activeController then
			self.activeController:Enable(false)
	
			if self.moveFunction then
				self.moveFunction(Players.LocalPlayer, Vector3.new(0,0,0), true)
			end
		end
	end
	
	
	-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
	function ControlModule:SelectComputerMovementModule()
		if not (UserInputService.KeyboardEnabled or UserInputService.GamepadEnabled) then
			return nil, false
		end
	
		local computerModule
		local DevMovementMode = Players.LocalPlayer.DevComputerMovementMode
	
		if DevMovementMode == Enum.DevComputerMovementMode.UserChoice then
			computerModule = computerInputTypeToModuleMap[lastInputType]
			if UserGameSettings.ComputerMovementMode == Enum.ComputerMovementMode.ClickToMove and computerModule == Keyboard then
				-- User has ClickToMove set in Settings, prefer ClickToMove controller for keyboard and mouse lastInputTypes
				computerModule = ClickToMove
			end
		else
			-- Developer has selected a mode that must be used.
			computerModule = movementEnumToModuleMap[DevMovementMode]
	
			-- computerModule is expected to be nil here only when developer has selected Scriptable
			if (not computerModule) and DevMovementMode ~= Enum.DevComputerMovementMode.Scriptable then
				warn("No character control module is associated with DevComputerMovementMode ", DevMovementMode)
			end
		end
	
		if computerModule then
			return computerModule, true
		elseif DevMovementMode == Enum.DevComputerMovementMode.Scriptable then
			-- Special case where nil is returned and we actually want to set self.activeController to nil for Scriptable
			return nil, true
		else
			-- This case is for when computerModule is nil because of an error and no suitable control module could
			-- be found.
			return nil, false
		end
	end
	
	-- Choose current Touch control module based on settings (user, dev)
	-- Returns module (possibly nil) and success code to differentiate returning nil due to error vs Scriptable
	function ControlModule:SelectTouchModule()
		if not UserInputService.TouchEnabled then
			return nil, false
		end
		local touchModule
		local DevMovementMode = Players.LocalPlayer.DevTouchMovementMode
		if DevMovementMode == Enum.DevTouchMovementMode.UserChoice then
			touchModule = movementEnumToModuleMap[UserGameSettings.TouchMovementMode]
		elseif DevMovementMode == Enum.DevTouchMovementMode.Scriptable then
			return nil, true
		else
			touchModule = movementEnumToModuleMap[DevMovementMode]
		end
		return touchModule, true
	end
	
	local function calculateRawMoveVector(humanoid, cameraRelativeMoveVector)
		local camera = Workspace.CurrentCamera
		if not camera then
			return cameraRelativeMoveVector
		end
	
		if humanoid:GetState() == Enum.HumanoidStateType.Swimming then
			return camera.CFrame:VectorToWorldSpace(cameraRelativeMoveVector)
		end
	
		local c, s
		local _, _, _, R00, R01, R02, _, _, R12, _, _, R22 = camera.CFrame:GetComponents()
		if R12 < 1 and R12 > -1 then
			-- X and Z components from back vector.
			c = R22
			s = R02
		else
			-- In this case the camera is looking straight up or straight down.
			-- Use X components from right and up vectors.
			c = R00
			s = -R01*math.sign(R12)
		end
		local norm = math.sqrt(c*c + s*s)
		return Vector3.new(
			(c*cameraRelativeMoveVector.x + s*cameraRelativeMoveVector.z)/norm,
			0,
			(c*cameraRelativeMoveVector.z - s*cameraRelativeMoveVector.x)/norm
		)
	end
	
	function ControlModule:OnRenderStepped(dt)
		if self.activeController and self.activeController.enabled and self.humanoid then
			-- Give the controller a chance to adjust its state
			self.activeController:OnRenderStepped(dt)
	
			-- Now retrieve info from the controller
			local moveVector = self.activeController:GetMoveVector()
			local cameraRelative = self.activeController:IsMoveVectorCameraRelative()
	
			local clickToMoveController = self:GetClickToMoveController()
			if self.activeController ~= clickToMoveController then
				if moveVector.magnitude > 0 then
					-- Clean up any developer started MoveTo path
					clickToMoveController:CleanupPath()
				else
					-- Get move vector for developer started MoveTo
					clickToMoveController:OnRenderStepped(dt)
					moveVector = clickToMoveController:GetMoveVector()
					cameraRelative = clickToMoveController:IsMoveVectorCameraRelative()
				end
			end
	
			-- Are we driving a vehicle ?
			local vehicleConsumedInput = false
			if self.vehicleController then
				moveVector, vehicleConsumedInput = self.vehicleController:Update(moveVector, cameraRelative, self.activeControlModule==Gamepad)
			end
	
			-- If not, move the player
			-- Verification of vehicleConsumedInput is commented out to preserve legacy behavior,
			-- in case some game relies on Humanoid.MoveDirection still being set while in a VehicleSeat
			--if not vehicleConsumedInput then
				if cameraRelative then
					moveVector = calculateRawMoveVector(self.humanoid, moveVector)
				end
				self.moveFunction(Players.LocalPlayer, moveVector, false)
			--end
	
			-- And make them jump if needed
			self.humanoid.Jump = self.activeController:GetIsJumping() or (self.touchJumpController and self.touchJumpController:GetIsJumping())
		end
	end
	
	function ControlModule:OnHumanoidSeated(active, currentSeatPart)
		if active then
			if currentSeatPart and currentSeatPart:IsA("VehicleSeat") then
				if not self.vehicleController then
					self.vehicleController = self.vehicleController.new(CONTROL_ACTION_PRIORITY)
				end
				self.vehicleController:Enable(true, currentSeatPart)
			end
		else
			if self.vehicleController then
				self.vehicleController:Enable(false, currentSeatPart)
			end
		end
	end
	
	function ControlModule:OnCharacterAdded(char)
		self.humanoid = char:FindFirstChildOfClass("Humanoid")
		while not self.humanoid do
			char.ChildAdded:wait()
			self.humanoid = char:FindFirstChildOfClass("Humanoid")
		end
	
		if self.touchGui then
			self.touchGui.Enabled = true
		end
	
		if self.humanoidSeatedConn then
			self.humanoidSeatedConn:Disconnect()
			self.humanoidSeatedConn = nil
		end
		self.humanoidSeatedConn = self.humanoid.Seated:Connect(function(active, currentSeatPart)
			self:OnHumanoidSeated(active, currentSeatPart)
		end)
	end
	
	function ControlModule:OnCharacterRemoving(char)
		self.humanoid = nil
	
		if self.touchGui then
			self.touchGui.Enabled = false
		end
	end
	
	-- Helper function to lazily instantiate a controller if it does not yet exist,
	-- disable the active controller if it is different from the on being switched to,
	-- and then enable the requested controller. The argument to this function must be
	-- a reference to one of the control modules, i.e. Keyboard, Gamepad, etc.
	function ControlModule:SwitchToController(controlModule)
		if not controlModule then
			if self.activeController then
				self.activeController:Enable(false)
			end
			self.activeController = nil
			self.activeControlModule = nil
		else
			if not self.controllers[controlModule] then
				self.controllers[controlModule] = controlModule.new(CONTROL_ACTION_PRIORITY)
			end
	
			if self.activeController ~= self.controllers[controlModule] then
				if self.activeController then
					self.activeController:Enable(false)
				end
				self.activeController = self.controllers[controlModule]
				self.activeControlModule = controlModule -- Only used to check if controller switch is necessary
	
				if self.touchControlFrame and (self.activeControlModule == ClickToMove
							or self.activeControlModule == TouchThumbstick
							or self.activeControlModule == DynamicThumbstick) then
					if not self.controllers[TouchJump] then
						self.controllers[TouchJump] = TouchJump.new()
					end
					self.touchJumpController = self.controllers[TouchJump]
					self.touchJumpController:Enable(true, self.touchControlFrame)
				else
					if self.touchJumpController then
						self.touchJumpController:Enable(false)
					end
				end
	
				self:EnableActiveControlModule()
			end
		end
	end
	
	function ControlModule:OnLastInputTypeChanged(newLastInputType)
		if lastInputType == newLastInputType then
			warn("LastInputType Change listener called with current type.")
		end
		lastInputType = newLastInputType
	
		if lastInputType == Enum.UserInputType.Touch then
			-- TODO: Check if touch module already active
			local touchModule, success = self:SelectTouchModule()
			if success then
				while not self.touchControlFrame do
					wait()
				end
				self:SwitchToController(touchModule)
			end
		elseif computerInputTypeToModuleMap[lastInputType] ~= nil then
			local computerModule = self:SelectComputerMovementModule()
			if computerModule then
				self:SwitchToController(computerModule)
			end
		end
	end
	
	-- Called when any relevant values of GameSettings or LocalPlayer change, forcing re-evalulation of
	-- current control scheme
	function ControlModule:OnComputerMovementModeChange()
		local controlModule, success =  self:SelectComputerMovementModule()
		if success then
			self:SwitchToController(controlModule)
		end
	end
	
	function ControlModule:OnTouchMovementModeChange()
		local touchModule, success = self:SelectTouchModule()
		if success then
			while not self.touchControlFrame do
				wait()
			end
			self:SwitchToController(touchModule)
		end
	end
	
	function ControlModule:CreateTouchGuiContainer()
		if self.touchGui then self.touchGui:Destroy() end
	
		-- Container for all touch device guis
		self.touchGui = Instance.new("ScreenGui")
		self.touchGui.Name = "TouchGui"
		self.touchGui.ResetOnSpawn = false
		self.touchGui.ZIndexBehavior = Enum.ZIndexBehavior.Sibling
		self.touchGui.Enabled = self.humanoid ~= nil
	
		self.touchControlFrame = Instance.new("Frame")
		self.touchControlFrame.Name = "TouchControlFrame"
		self.touchControlFrame.Size = UDim2.new(1, 0, 1, 0)
		self.touchControlFrame.BackgroundTransparency = 1
		self.touchControlFrame.Parent = self.touchGui
	
		self.touchGui.Parent = self.playerGui
	end
	
	function ControlModule:GetClickToMoveController()
		if not self.controllers[ClickToMove] then
			self.controllers[ClickToMove] = ClickToMove.new(CONTROL_ACTION_PRIORITY)
		end
		return self.controllers[ClickToMove]
	end
	
	function ControlModule:IsJumping()
		if self.activeController then
			return self.activeController:GetIsJumping() or (self.touchJumpController and self.touchJumpController:GetIsJumping())
		end
		return false
	end
	
	return ControlModule.new()
end

function _PlayerModule()
	local PlayerModule = {}
	PlayerModule.__index = PlayerModule
	function PlayerModule.new()
		local self = setmetatable({},PlayerModule)
		self.cameras = _CameraModule()
		self.controls = _ControlModule()
		return self
	end
	function PlayerModule:GetCameras()
		return self.cameras
	end
	function PlayerModule:GetControls()
		return self.controls
	end
	function PlayerModule:GetClickToMoveController()
		return self.controls:GetClickToMoveController()
	end
	return PlayerModule.new()
end

function _sounds()
	
	local SetState = Instance.new("BindableEvent",script)
	
	local Players = game:GetService("Players")
	local RunService = game:GetService("RunService")
	
	local SOUND_DATA = {
		Climbing = {
			SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
			Looped = true,
		},
		Died = {
			SoundId = "rbxasset://sounds/uuhhh.mp3",
		},
		FreeFalling = {
			SoundId = "rbxasset://sounds/action_falling.mp3",
			Looped = true,
		},
		GettingUp = {
			SoundId = "rbxasset://sounds/action_get_up.mp3",
		},
		Jumping = {
			SoundId = "rbxasset://sounds/action_jump.mp3",
		},
		Landing = {
			SoundId = "rbxasset://sounds/action_jump_land.mp3",
		},
		Running = {
			SoundId = "rbxasset://sounds/action_footsteps_plastic.mp3",
			Looped = true,
			Pitch = 1.85,
		},
		Splash = {
			SoundId = "rbxasset://sounds/impact_water.mp3",
		},
		Swimming = {
			SoundId = "rbxasset://sounds/action_swim.mp3",
			Looped = true,
			Pitch = 1.6,
		},
	}
	
	 -- wait for the first of the passed signals to fire
	local function waitForFirst(...)
		local shunt = Instance.new("BindableEvent")
		local slots = {...}
	
		local function fire(...)
			for i = 1, #slots do
				slots[i]:Disconnect()
			end
	
			return shunt:Fire(...)
		end
	
		for i = 1, #slots do
			slots[i] = slots[i]:Connect(fire)
		end
	
		return shunt.Event:Wait()
	end
	
	-- map a value from one range to another
	local function map(x, inMin, inMax, outMin, outMax)
		return (x - inMin)*(outMax - outMin)/(inMax - inMin) + outMin
	end
	
	local function playSound(sound)
		sound.TimePosition = 0
		sound.Playing = true
	end
	
	local function stopSound(sound)
		sound.Playing = false
		sound.TimePosition = 0
	end
	
	local function shallowCopy(t)
		local out = {}
		for k, v in pairs(t) do
			out[k] = v
		end
		return out
	end
	
	local function initializeSoundSystem(player, humanoid, rootPart)
		local sounds = {}
	
		-- initialize sounds
		for name, props in pairs(SOUND_DATA) do
			local sound = Instance.new("Sound")
			sound.Name = name
	
			-- set default values
			sound.Archivable = false
			sound.EmitterSize = 5
			sound.MaxDistance = 150
			sound.Volume = 0.65
	
			for propName, propValue in pairs(props) do
				sound[propName] = propValue
			end
	
			sound.Parent = rootPart
			sounds[name] = sound
		end
	
		local playingLoopedSounds = {}
	
		local function stopPlayingLoopedSounds(except)
			for sound in pairs(shallowCopy(playingLoopedSounds)) do
				if sound ~= except then
					sound.Playing = false
					playingLoopedSounds[sound] = nil
				end
			end
		end
	
		-- state transition callbacks
		local stateTransitions = {
			[Enum.HumanoidStateType.FallingDown] = function()
				stopPlayingLoopedSounds()
			end,
	
			[Enum.HumanoidStateType.GettingUp] = function()
				stopPlayingLoopedSounds()
				playSound(sounds.GettingUp)
			end,
	
			[Enum.HumanoidStateType.Jumping] = function()
				stopPlayingLoopedSounds()
				playSound(sounds.Jumping)
			end,
	
			[Enum.HumanoidStateType.Swimming] = function()
				local verticalSpeed = math.abs(rootPart.Velocity.Y)
				if verticalSpeed > 0.1 then
					sounds.Splash.Volume = math.clamp(map(verticalSpeed, 100, 350, 0.28, 1), 0, 1)
					playSound(sounds.Splash)
				end
				stopPlayingLoopedSounds(sounds.Swimming)
				sounds.Swimming.Playing = true
				playingLoopedSounds[sounds.Swimming] = true
			end,
	
			[Enum.HumanoidStateType.Freefall] = function()
				sounds.FreeFalling.Volume = 0
				stopPlayingLoopedSounds(sounds.FreeFalling)
				playingLoopedSounds[sounds.FreeFalling] = true
			end,
	
			[Enum.HumanoidStateType.Landed] = function()
				stopPlayingLoopedSounds()
				local verticalSpeed = math.abs(rootPart.Velocity.Y)
				if verticalSpeed > 75 then
					sounds.Landing.Volume = math.clamp(map(verticalSpeed, 50, 100, 0, 1), 0, 1)
					playSound(sounds.Landing)
				end
			end,
	
			[Enum.HumanoidStateType.Running] = function()
				stopPlayingLoopedSounds(sounds.Running)
				sounds.Running.Playing = true
				playingLoopedSounds[sounds.Running] = true
			end,
	
			[Enum.HumanoidStateType.Climbing] = function()
				local sound = sounds.Climbing
				if math.abs(rootPart.Velocity.Y) > 0.1 then
					sound.Playing = true
					stopPlayingLoopedSounds(sound)
				else
					stopPlayingLoopedSounds()
				end
				playingLoopedSounds[sound] = true
			end,
	
			[Enum.HumanoidStateType.Seated] = function()
				stopPlayingLoopedSounds()
			end,
	
			[Enum.HumanoidStateType.Dead] = function()
				stopPlayingLoopedSounds()
				playSound(sounds.Died)
			end,
		}
	
		-- updaters for looped sounds
		local loopedSoundUpdaters = {
			[sounds.Climbing] = function(dt, sound, vel)
				sound.Playing = vel.Magnitude > 0.1
			end,
	
			[sounds.FreeFalling] = function(dt, sound, vel)
				if vel.Magnitude > 75 then
					sound.Volume = math.clamp(sound.Volume + 0.9*dt, 0, 1)
				else
					sound.Volume = 0
				end
			end,
	
			[sounds.Running] = function(dt, sound, vel)
				sound.Playing = vel.Magnitude > 0.5 and humanoid.MoveDirection.Magnitude > 0.5
			end,
		}
	
		-- state substitutions to avoid duplicating entries in the state table
		local stateRemap = {
			[Enum.HumanoidStateType.RunningNoPhysics] = Enum.HumanoidStateType.Running,
		}
	
		local activeState = stateRemap[humanoid:GetState()] or humanoid:GetState()
		local activeConnections = {}
	
		local stateChangedConn = humanoid.StateChanged:Connect(function(_, state)
			state = stateRemap[state] or state
	
			if state ~= activeState then
				local transitionFunc = stateTransitions[state]
	
				if transitionFunc then
					transitionFunc()
				end
	
				activeState = state
			end
		end)
		
		local customStateChangedConn = SetState.Event:Connect(function(state)
			state = stateRemap[state] or state
	
			if state ~= activeState then
				local transitionFunc = stateTransitions[state]
	
				if transitionFunc then
					transitionFunc()
				end
	
				activeState = state
			end
		end)
	
		local steppedConn = RunService.Stepped:Connect(function(_, worldDt)
			-- update looped sounds on stepped
			for sound in pairs(playingLoopedSounds) do
				local updater = loopedSoundUpdaters[sound]
	
				if updater then
					updater(worldDt, sound, rootPart.Velocity)
				end
			end
		end)
	
		local humanoidAncestryChangedConn
		local rootPartAncestryChangedConn
		local characterAddedConn
	
		local function terminate()
			stateChangedConn:Disconnect()
			customStateChangedConn:Disconnect()
			steppedConn:Disconnect()
			humanoidAncestryChangedConn:Disconnect()
			rootPartAncestryChangedConn:Disconnect()
			characterAddedConn:Disconnect()
		end
	
		humanoidAncestryChangedConn = humanoid.AncestryChanged:Connect(function(_, parent)
			if not parent then
				terminate()
			end
		end)
	
		rootPartAncestryChangedConn = rootPart.AncestryChanged:Connect(function(_, parent)
			if not parent then
				terminate()
			end
		end)
	
		characterAddedConn = player.CharacterAdded:Connect(terminate)
	end
	
	local function playerAdded(player)
		local function characterAdded(character)
			-- Avoiding memory leaks in the face of Character/Humanoid/RootPart lifetime has a few complications:
			-- * character deparenting is a Remove instead of a Destroy, so signals are not cleaned up automatically.
			-- ** must use a waitForFirst on everything and listen for hierarchy changes.
			-- * the character might not be in the dm by the time CharacterAdded fires
			-- ** constantly check consistency with player.Character and abort if CharacterAdded is fired again
			-- * Humanoid may not exist immediately, and by the time it's inserted the character might be deparented.
			-- * RootPart probably won't exist immediately.
			-- ** by the time RootPart is inserted and Humanoid.RootPart is set, the character or the humanoid might be deparented.
	
			if not character.Parent then
				waitForFirst(character.AncestryChanged, player.CharacterAdded)
			end
	
			if player.Character ~= character or not character.Parent then
				return
			end
	
			local humanoid = character:FindFirstChildOfClass("Humanoid")
			while character:IsDescendantOf(game) and not humanoid do
				waitForFirst(character.ChildAdded, character.AncestryChanged, player.CharacterAdded)
				humanoid = character:FindFirstChildOfClass("Humanoid")
			end
	
			if player.Character ~= character or not character:IsDescendantOf(game) then
				return
			end
	
			-- must rely on HumanoidRootPart naming because Humanoid.RootPart does not fire changed signals
			local rootPart = character:FindFirstChild("HumanoidRootPart")
			while character:IsDescendantOf(game) and not rootPart do
				waitForFirst(character.ChildAdded, character.AncestryChanged, humanoid.AncestryChanged, player.CharacterAdded)
				rootPart = character:FindFirstChild("HumanoidRootPart")
			end
	
			if rootPart and humanoid:IsDescendantOf(game) and character:IsDescendantOf(game) and player.Character == character then
				initializeSoundSystem(player, humanoid, rootPart)
			end
		end
	
		if player.Character then
			characterAdded(player.Character)
		end
		player.CharacterAdded:Connect(characterAdded)
	end
	
	Players.PlayerAdded:Connect(playerAdded)
	for _, player in ipairs(Players:GetPlayers()) do
		playerAdded(player)
	end
	return SetState
end

function _StateTracker()
	local EPSILON = 0.1
	
	local SPEED = {
		["onRunning"] = true,
		["onClimbing"] = true 
	}
	
	local INAIR = {
		["onFreeFall"] = true,
		["onJumping"] = true
	}
	
	local STATEMAP = {
		["onRunning"] = Enum.HumanoidStateType.Running,
		["onJumping"] = Enum.HumanoidStateType.Jumping,
		["onFreeFall"] = Enum.HumanoidStateType.Freefall
	}
	
	local StateTracker = {}
	StateTracker.__index = StateTracker
	
	function StateTracker.new(humanoid, soundState)
		local self = setmetatable({}, StateTracker)
		
		self.Humanoid = humanoid
		self.HRP = humanoid.RootPart
		
		self.Speed = 0
		self.State = "onRunning"
		self.Jumped = false
		self.JumpTick = tick()
		
		self.SoundState = soundState
		
		self._ChangedEvent = Instance.new("BindableEvent")
		self.Changed = self._ChangedEvent.Event
		
		return self
	end
	
	function StateTracker:Destroy()
		self._ChangedEvent:Destroy()
	end
	
	function StateTracker:RequestedJump()
		self.Jumped = true
		self.JumpTick = tick()
	end
	
	function StateTracker:OnStep(gravityUp, grounded, isMoving)
		local cVelocity = self.HRP.Velocity
		local gVelocity = cVelocity:Dot(gravityUp)
		
		local oldState, oldSpeed = self.State, self.Speed
		
		local newState
		local newSpeed = cVelocity.Magnitude
	
		if (not grounded) then
			if (gVelocity > 0) then
				if (self.Jumped) then
					newState = "onJumping"
				else
					newState = "onFreeFall"
				end
			else
				if (self.Jumped) then
					self.Jumped = false
				end
				newState = "onFreeFall"
			end
		else
			if (self.Jumped and tick() - self.JumpTick > 0.1) then
				self.Jumped = false
			end
			newSpeed = (cVelocity - gVelocity*gravityUp).Magnitude
			newState = "onRunning"
		end
		
		newSpeed = isMoving and newSpeed or 0
		
		if (oldState ~= newState or (SPEED[newState] and math.abs(oldSpeed - newSpeed) > EPSILON)) then
			self.State = newState
			self.Speed = newSpeed
			self.SoundState:Fire(STATEMAP[newState])
			self._ChangedEvent:Fire(self.State, self.Speed)
		end
	end
	
	return StateTracker
end
function _InitObjects()
	local model = workspace:FindFirstChild("objects") or game:GetObjects("rbxassetid://5045408489")[1]
	local SPHERE = model:WaitForChild("Sphere")
	local FLOOR = model:WaitForChild("Floor")
	local VFORCE = model:WaitForChild("VectorForce")
	local BGYRO = model:WaitForChild("BodyGyro")
	local function initObjects(self)
		local hrp = self.HRP
		local humanoid = self.Humanoid
		local sphere = SPHERE:Clone()
		sphere.Parent = self.Character
		local floor = FLOOR:Clone()
		floor.Parent = self.Character
		local isR15 = (humanoid.RigType == Enum.HumanoidRigType.R15)
		local height = isR15 and (humanoid.HipHeight + 0.05) or 2
		local weld = Instance.new("Weld")
		weld.C0 = CFrame.new(0, -height, 0.1)
		weld.Part0 = hrp
		weld.Part1 = sphere
		weld.Parent = sphere
		local weld2 = Instance.new("Weld")
		weld2.C0 = CFrame.new(0, -(height + 1.5), 0)
		weld2.Part0 = hrp
		weld2.Part1 = floor
		weld2.Parent = floor
		local gyro = BGYRO:Clone()
		gyro.CFrame = hrp.CFrame
		gyro.Parent = hrp
		local vForce = VFORCE:Clone()
		vForce.Attachment0 = isR15 and hrp:WaitForChild("RootRigAttachment") or hrp:WaitForChild("RootAttachment")
		vForce.Parent = hrp
		return sphere, gyro, vForce, floor
	end
	return initObjects
end
local plr = game.Players.LocalPlayer
local ms = plr:GetMouse()
local char
plr.CharacterAdded:Connect(function(c)
	char = c
end)
function _R6()
	function r6()
	local Figure = char
	local Torso = Figure:WaitForChild("Torso")
	local RightShoulder = Torso:WaitForChild("Right Shoulder")
	local LeftShoulder = Torso:WaitForChild("Left Shoulder")
	local RightHip = Torso:WaitForChild("Right Hip")
	local LeftHip = Torso:WaitForChild("Left Hip")
	local Neck = Torso:WaitForChild("Neck")
	local Humanoid = Figure:WaitForChild("Humanoid")
	local pose = "Standing"
	local currentAnim = ""
	local currentAnimInstance = nil
	local currentAnimTrack = nil
	local currentAnimKeyframeHandler = nil
	local currentAnimSpeed = 1.0
	local animTable = {}
	local animNames = { 
		idle = 	{	
					{ id = "http://www.roblox.com/asset/?id=180435571", weight = 9 },
					{ id = "http://www.roblox.com/asset/?id=180435792", weight = 1 }
				},
		walk = 	{ 	
					{ id = "http://www.roblox.com/asset/?id=180426354", weight = 10 } 
				}, 
		run = 	{
					{ id = "run.xml", weight = 10 } 
				}, 
		jump = 	{
					{ id = "http://www.roblox.com/asset/?id=125750702", weight = 10 } 
				}, 
		fall = 	{
					{ id = "http://www.roblox.com/asset/?id=180436148", weight = 10 } 
				}, 
		climb = {
					{ id = "http://www.roblox.com/asset/?id=180436334", weight = 10 } 
				}, 
		sit = 	{
					{ id = "http://www.roblox.com/asset/?id=178130996", weight = 10 } 
				},	
		toolnone = {
					{ id = "http://www.roblox.com/asset/?id=182393478", weight = 10 } 
				},
		toolslash = {
					{ id = "http://www.roblox.com/asset/?id=129967390", weight = 10 } 
	--				{ id = "slash.xml", weight = 10 } 
				},
		toollunge = {
					{ id = "http://www.roblox.com/asset/?id=129967478", weight = 10 } 
				},
		wave = {
					{ id = "http://www.roblox.com/asset/?id=128777973", weight = 10 } 
				},
		point = {
					{ id = "http://www.roblox.com/asset/?id=128853357", weight = 10 } 
				},
		dance1 = {
					{ id = "http://www.roblox.com/asset/?id=182435998", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491037", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491065", weight = 10 } 
				},
		dance2 = {
					{ id = "http://www.roblox.com/asset/?id=182436842", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491248", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491277", weight = 10 } 
				},
		dance3 = {
					{ id = "http://www.roblox.com/asset/?id=182436935", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491368", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=182491423", weight = 10 } 
				},
		laugh = {
					{ id = "http://www.roblox.com/asset/?id=129423131", weight = 10 } 
				},
		cheer = {
					{ id = "http://www.roblox.com/asset/?id=129423030", weight = 10 } 
				},
	}
	local dances = {"dance1", "dance2", "dance3"}
	-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
	local emoteNames = { wave = false, point = false, dance1 = true, dance2 = true, dance3 = true, laugh = false, cheer = false}
	function configureAnimationSet(name, fileList)
		if (animTable[name] ~= nil) then
			for _, connection in pairs(animTable[name].connections) do
				connection:disconnect()
			end
		end
		animTable[name] = {}
		animTable[name].count = 0
		animTable[name].totalWeight = 0	
		animTable[name].connections = {}
		-- check for config values
		local config = script:FindFirstChild(name)
		if (config ~= nil) then
	--		print("Loading anims " .. name)
			table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
			table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
			local idx = 1
			for _, childPart in pairs(config:GetChildren()) do
				if (childPart:IsA("Animation")) then
					table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
					animTable[name][idx] = {}
					animTable[name][idx].anim = childPart
					local weightObject = childPart:FindFirstChild("Weight")
					if (weightObject == nil) then
						animTable[name][idx].weight = 1
					else
						animTable[name][idx].weight = weightObject.Value
					end
					animTable[name].count = animTable[name].count + 1
					animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
		--			print(name .. " [" .. idx .. "] " .. animTable[name][idx].anim.AnimationId .. " (" .. animTable[name][idx].weight .. ")")
					idx = idx + 1
				end
			end
		end
		-- fallback to defaults
		if (animTable[name].count  animTable[animName][idx].weight) do
			roll = roll - animTable[animName][idx].weight
			idx = idx + 1
		end
	--		print(animName .. " " .. idx .. " [" .. origRoll .. "]")
		local anim = animTable[animName][idx].anim
	
		-- switch animation		
		if (anim ~= currentAnimInstance) then
			
			if (currentAnimTrack ~= nil) then
				currentAnimTrack:Stop(transitionTime)
				currentAnimTrack:Destroy()
			end
	
			currentAnimSpeed = 1.0
		
			-- load it to the humanoid; get AnimationTrack
			currentAnimTrack = humanoid:LoadAnimation(anim)
			currentAnimTrack.Priority = Enum.AnimationPriority.Core
			 
			-- play the animation
			currentAnimTrack:Play(transitionTime)
			currentAnim = animName
			currentAnimInstance = anim
	
			-- set up keyframe name triggers
			if (currentAnimKeyframeHandler ~= nil) then
				currentAnimKeyframeHandler:disconnect()
			end
			currentAnimKeyframeHandler = currentAnimTrack.KeyframeReached:connect(keyFrameReachedFunc)
			
		end
	
	end
	
	-------------------------------------------------------------------------------------------
	-------------------------------------------------------------------------------------------
	
	local toolAnimName = ""
	local toolAnimTrack = nil
	local toolAnimInstance = nil
	local currentToolAnimKeyframeHandler = nil
	
	function toolKeyFrameReachedFunc(frameName)
		if (frameName == "End") then
	--		print("Keyframe : ".. frameName)	
			playToolAnimation(toolAnimName, 0.0, Humanoid)
		end
	end
	
	
	function playToolAnimation(animName, transitionTime, humanoid, priority)	 
			
			local roll = math.random(1, animTable[animName].totalWeight) 
			local origRoll = roll
			local idx = 1
			while (roll > animTable[animName][idx].weight) do
				roll = roll - animTable[animName][idx].weight
				idx = idx + 1
			end
	--		print(animName .. " * " .. idx .. " [" .. origRoll .. "]")
			local anim = animTable[animName][idx].anim
	
			if (toolAnimInstance ~= anim) then
				
				if (toolAnimTrack ~= nil) then
					toolAnimTrack:Stop()
					toolAnimTrack:Destroy()
					transitionTime = 0
				end
						
				-- load it to the humanoid; get AnimationTrack
				toolAnimTrack = humanoid:LoadAnimation(anim)
				if priority then
					toolAnimTrack.Priority = priority
				end
				 
				-- play the animation
				toolAnimTrack:Play(transitionTime)
				toolAnimName = animName
				toolAnimInstance = anim
	
				currentToolAnimKeyframeHandler = toolAnimTrack.KeyframeReached:connect(toolKeyFrameReachedFunc)
			end
	end
	
	function stopToolAnimations()
		local oldAnim = toolAnimName
	
		if (currentToolAnimKeyframeHandler ~= nil) then
			currentToolAnimKeyframeHandler:disconnect()
		end
	
		toolAnimName = ""
		toolAnimInstance = nil
		if (toolAnimTrack ~= nil) then
			toolAnimTrack:Stop()
			toolAnimTrack:Destroy()
			toolAnimTrack = nil
		end
	
	
		return oldAnim
	end
	
	-------------------------------------------------------------------------------------------
	-------------------------------------------------------------------------------------------
	
	
	function onRunning(speed)
		if speed > 0.01 then
			playAnimation("walk", 0.1, Humanoid)
			if currentAnimInstance and currentAnimInstance.AnimationId == "http://www.roblox.com/asset/?id=180426354" then
				setAnimationSpeed(speed / 14.5)
			end
			pose = "Running"
		else
			if emoteNames[currentAnim] == nil then
				playAnimation("idle", 0.1, Humanoid)
				pose = "Standing"
			end
		end
	end
	
	function onDied()
		pose = "Dead"
	end
	
	function onJumping()
		playAnimation("jump", 0.1, Humanoid)
		jumpAnimTime = jumpAnimDuration
		pose = "Jumping"
	end
	
	function onClimbing(speed)
		playAnimation("climb", 0.1, Humanoid)
		setAnimationSpeed(speed / 12.0)
		pose = "Climbing"
	end
	
	function onGettingUp()
		pose = "GettingUp"
	end
	
	function onFreeFall()
		if (jumpAnimTime  0 then
			pose = "Running"
		else
			pose = "Standing"
		end
	end
	
	function getTool()	
		for _, kid in ipairs(Figure:GetChildren()) do
			if kid.className == "Tool" then return kid end
		end
		return nil
	end
	
	function getToolAnim(tool)
		for _, c in ipairs(tool:GetChildren()) do
			if c.Name == "toolanim" and c.className == "StringValue" then
				return c
			end
		end
		return nil
	end
	
	function animateTool()
		
		if (toolAnim == "None") then
			playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
			return
		end
	
		if (toolAnim == "Slash") then
			playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
			return
		end
	
		if (toolAnim == "Lunge") then
			playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
			return
		end
	end
	
	function moveSit()
		RightShoulder.MaxVelocity = 0.15
		LeftShoulder.MaxVelocity = 0.15
		RightShoulder:SetDesiredAngle(3.14 /2)
		LeftShoulder:SetDesiredAngle(-3.14 /2)
		RightHip:SetDesiredAngle(3.14 /2)
		LeftHip:SetDesiredAngle(-3.14 /2)
	end
	
	local lastTick = 0
	
	function move(time)
		local amplitude = 1
		local frequency = 1
	  	local deltaTime = time - lastTick
	  	lastTick = time
	
		local climbFudge = 0
		local setAngles = false
	
	  	if (jumpAnimTime > 0) then
	  		jumpAnimTime = jumpAnimTime - deltaTime
	  	end
	
		if (pose == "FreeFall" and jumpAnimTime  toolAnimTime then
				toolAnimTime = 0
				toolAnim = "None"
			end
	
			animateTool()		
		else
			stopToolAnimations()
			toolAnim = "None"
			toolAnimInstance = nil
			toolAnimTime = 0
		end
	end
	
	
	local events = {}
	local eventHum = Humanoid
	
	local function onUnhook()
		for i = 1, #events do
			events[i]:Disconnect()
		end
		events = {}
	end
	
	local function onHook()
		onUnhook()
		
		pose = eventHum.Sit and "Seated" or "Standing"
		
		events = {
			eventHum.Died:connect(onDied),
			eventHum.Running:connect(onRunning),
			eventHum.Jumping:connect(onJumping),
			eventHum.Climbing:connect(onClimbing),
			eventHum.GettingUp:connect(onGettingUp),
			eventHum.FreeFalling:connect(onFreeFall),
			eventHum.FallingDown:connect(onFallingDown),
			eventHum.Seated:connect(onSeated),
			eventHum.PlatformStanding:connect(onPlatformStanding),
			eventHum.Swimming:connect(onSwimming)
		}
	end
	
	
	onHook()
	
	-- setup emote chat hook
	game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
		local emote = ""
		if msg == "/e dance" then
			emote = dances[math.random(1, #dances)]
		elseif (string.sub(msg, 1, 3) == "/e ") then
			emote = string.sub(msg, 4)
		elseif (string.sub(msg, 1, 7) == "/emote ") then
			emote = string.sub(msg, 8)
		end
		
		if (pose == "Standing" and emoteNames[emote] ~= nil) then
			playAnimation(emote, 0.1, Humanoid)
		end
	
	end)
	
	
	-- main program
	
	-- initialize to idle
	playAnimation("idle", 0.1, Humanoid)
	pose = "Standing"
	
	spawn(function()
		while Figure.Parent ~= nil do
			local _, time = wait(0.1)
			move(time)
		end
	end)
	
	return {
		onRunning = onRunning, 
		onDied = onDied, 
		onJumping = onJumping, 
		onClimbing = onClimbing, 
		onGettingUp = onGettingUp, 
		onFreeFall = onFreeFall, 
		onFallingDown = onFallingDown, 
		onSeated = onSeated, 
		onPlatformStanding = onPlatformStanding,
		onHook = onHook,
		onUnhook = onUnhook
	}
	
	end
	return r6()
end

function _R15()
	local function r15()
		
	local Character = char
	local Humanoid = Character:WaitForChild("Humanoid")
	local pose = "Standing"
	
	local userNoUpdateOnLoopSuccess, userNoUpdateOnLoopValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserNoUpdateOnLoop") end)
	local userNoUpdateOnLoop = userNoUpdateOnLoopSuccess and userNoUpdateOnLoopValue
	local userAnimationSpeedDampeningSuccess, userAnimationSpeedDampeningValue = pcall(function() return UserSettings():IsUserFeatureEnabled("UserAnimationSpeedDampening") end)
	local userAnimationSpeedDampening = userAnimationSpeedDampeningSuccess and userAnimationSpeedDampeningValue
	
	local animateScriptEmoteHookFlagExists, animateScriptEmoteHookFlagEnabled = pcall(function()
		return UserSettings():IsUserFeatureEnabled("UserAnimateScriptEmoteHook")
	end)
	local FFlagAnimateScriptEmoteHook = animateScriptEmoteHookFlagExists and animateScriptEmoteHookFlagEnabled
	
	local AnimationSpeedDampeningObject = script:FindFirstChild("ScaleDampeningPercent")
	local HumanoidHipHeight = 2
	
	local EMOTE_TRANSITION_TIME = 0.1
	
	local currentAnim = ""
	local currentAnimInstance = nil
	local currentAnimTrack = nil
	local currentAnimKeyframeHandler = nil
	local currentAnimSpeed = 1.0
	
	local runAnimTrack = nil
	local runAnimKeyframeHandler = nil
	
	local animTable = {}
	local animNames = { 
		idle = 	{	
					{ id = "http://www.roblox.com/asset/?id=507766666", weight = 1 },
					{ id = "http://www.roblox.com/asset/?id=507766951", weight = 1 },
					{ id = "http://www.roblox.com/asset/?id=507766388", weight = 9 }
				},
		walk = 	{ 	
					{ id = "http://www.roblox.com/asset/?id=507777826", weight = 10 } 
				}, 
		run = 	{
					{ id = "http://www.roblox.com/asset/?id=507767714", weight = 10 } 
				}, 
		swim = 	{
					{ id = "http://www.roblox.com/asset/?id=507784897", weight = 10 } 
				}, 
		swimidle = 	{
					{ id = "http://www.roblox.com/asset/?id=507785072", weight = 10 } 
				}, 
		jump = 	{
					{ id = "http://www.roblox.com/asset/?id=507765000", weight = 10 } 
				}, 
		fall = 	{
					{ id = "http://www.roblox.com/asset/?id=507767968", weight = 10 } 
				}, 
		climb = {
					{ id = "http://www.roblox.com/asset/?id=507765644", weight = 10 } 
				}, 
		sit = 	{
					{ id = "http://www.roblox.com/asset/?id=2506281703", weight = 10 } 
				},	
		toolnone = {
					{ id = "http://www.roblox.com/asset/?id=507768375", weight = 10 } 
				},
		toolslash = {
					{ id = "http://www.roblox.com/asset/?id=522635514", weight = 10 } 
				},
		toollunge = {
					{ id = "http://www.roblox.com/asset/?id=522638767", weight = 10 } 
				},
		wave = {
					{ id = "http://www.roblox.com/asset/?id=507770239", weight = 10 } 
				},
		point = {
					{ id = "http://www.roblox.com/asset/?id=507770453", weight = 10 } 
				},
		dance = {
					{ id = "http://www.roblox.com/asset/?id=507771019", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507771955", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507772104", weight = 10 } 
				},
		dance2 = {
					{ id = "http://www.roblox.com/asset/?id=507776043", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507776720", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507776879", weight = 10 } 
				},
		dance3 = {
					{ id = "http://www.roblox.com/asset/?id=507777268", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507777451", weight = 10 }, 
					{ id = "http://www.roblox.com/asset/?id=507777623", weight = 10 } 
				},
		laugh = {
					{ id = "http://www.roblox.com/asset/?id=507770818", weight = 10 } 
				},
		cheer = {
					{ id = "http://www.roblox.com/asset/?id=507770677", weight = 10 } 
				},
	}
	
	-- Existance in this list signifies that it is an emote, the value indicates if it is a looping emote
	local emoteNames = { wave = false, point = false, dance = true, dance2 = true, dance3 = true, laugh = false, cheer = false}
	
	local PreloadAnimsUserFlag = false
	local PreloadedAnims = {}
	local successPreloadAnim, msgPreloadAnim = pcall(function()
		PreloadAnimsUserFlag = UserSettings():IsUserFeatureEnabled("UserPreloadAnimations")
	end)
	if not successPreloadAnim then
		PreloadAnimsUserFlag = false
	end
	
	math.randomseed(tick())
	
	function findExistingAnimationInSet(set, anim)
		if set == nil or anim == nil then
			return 0
		end
		
		for idx = 1, set.count, 1 do 
			if set[idx].anim.AnimationId == anim.AnimationId then
				return idx
			end
		end
		
		return 0
	end
	
	function configureAnimationSet(name, fileList)
		if (animTable[name] ~= nil) then
			for _, connection in pairs(animTable[name].connections) do
				connection:disconnect()
			end
		end
		animTable[name] = {}
		animTable[name].count = 0
		animTable[name].totalWeight = 0	
		animTable[name].connections = {}
	
		local allowCustomAnimations = true
	
		local success, msg = pcall(function() allowCustomAnimations = game:GetService("StarterPlayer").AllowCustomAnimations end)
		if not success then
			allowCustomAnimations = true
		end
	
		-- check for config values
		local config = script:FindFirstChild(name)
		if (allowCustomAnimations and config ~= nil) then
			table.insert(animTable[name].connections, config.ChildAdded:connect(function(child) configureAnimationSet(name, fileList) end))
			table.insert(animTable[name].connections, config.ChildRemoved:connect(function(child) configureAnimationSet(name, fileList) end))
			
			local idx = 0
			for _, childPart in pairs(config:GetChildren()) do
				if (childPart:IsA("Animation")) then
					local newWeight = 1
					local weightObject = childPart:FindFirstChild("Weight")
					if (weightObject ~= nil) then
						newWeight = weightObject.Value
					end
					animTable[name].count = animTable[name].count + 1
					idx = animTable[name].count
					animTable[name][idx] = {}
					animTable[name][idx].anim = childPart
					animTable[name][idx].weight = newWeight
					animTable[name].totalWeight = animTable[name].totalWeight + animTable[name][idx].weight
					table.insert(animTable[name].connections, childPart.Changed:connect(function(property) configureAnimationSet(name, fileList) end))
					table.insert(animTable[name].connections, childPart.ChildAdded:connect(function(property) configureAnimationSet(name, fileList) end))
					table.insert(animTable[name].connections, childPart.ChildRemoved:connect(function(property) configureAnimationSet(name, fileList) end))
				end
			end
		end
		
		-- fallback to defaults
		if (animTable[name].count  0.75 then
			local scale = 16.0
			playAnimation("walk", 0.2, Humanoid)
			setAnimationSpeed(speed / scale)
			pose = "Running"
		else
			if emoteNames[currentAnim] == nil and not currentlyPlayingEmote then
				playAnimation("idle", 0.2, Humanoid)
				pose = "Standing"
			end
		end
	end
	
	function onDied()
		pose = "Dead"
	end
	
	function onJumping()
		playAnimation("jump", 0.1, Humanoid)
		jumpAnimTime = jumpAnimDuration
		pose = "Jumping"
	end
	
	function onClimbing(speed)
		local scale = 5.0
		playAnimation("climb", 0.1, Humanoid)
		setAnimationSpeed(speed / scale)
		pose = "Climbing"
	end
	
	function onGettingUp()
		pose = "GettingUp"
	end
	
	function onFreeFall()
		if (jumpAnimTime  1.00 then
			local scale = 10.0
			playAnimation("swim", 0.4, Humanoid)
			setAnimationSpeed(speed / scale)
			pose = "Swimming"
		else
			playAnimation("swimidle", 0.4, Humanoid)
			pose = "Standing"
		end
	end
	
	function animateTool()
		if (toolAnim == "None") then
			playToolAnimation("toolnone", toolTransitionTime, Humanoid, Enum.AnimationPriority.Idle)
			return
		end
	
		if (toolAnim == "Slash") then
			playToolAnimation("toolslash", 0, Humanoid, Enum.AnimationPriority.Action)
			return
		end
	
		if (toolAnim == "Lunge") then
			playToolAnimation("toollunge", 0, Humanoid, Enum.AnimationPriority.Action)
			return
		end
	end
	
	function getToolAnim(tool)
		for _, c in ipairs(tool:GetChildren()) do
			if c.Name == "toolanim" and c.className == "StringValue" then
				return c
			end
		end
		return nil
	end
	
	local lastTick = 0
	
	function stepAnimate(currentTime)
		local amplitude = 1
		local frequency = 1
	  	local deltaTime = currentTime - lastTick
	  	lastTick = currentTime
	
		local climbFudge = 0
		local setAngles = false
	
	  	if (jumpAnimTime > 0) then
	  		jumpAnimTime = jumpAnimTime - deltaTime
	  	end
	
		if (pose == "FreeFall" and jumpAnimTime  toolAnimTime then
				toolAnimTime = 0
				toolAnim = "None"
			end
	
			animateTool()		
		else
			stopToolAnimations()
			toolAnim = "None"
			toolAnimInstance = nil
			toolAnimTime = 0
		end
	end
	
	-- connect events
	
	local events = {}
	local eventHum = Humanoid
	
	local function onUnhook()
		for i = 1, #events do
			events[i]:Disconnect()
		end
		events = {}
	end
	
	local function onHook()
		onUnhook()
		
		pose = eventHum.Sit and "Seated" or "Standing"
		
		events = {
			eventHum.Died:connect(onDied),
			eventHum.Running:connect(onRunning),
			eventHum.Jumping:connect(onJumping),
			eventHum.Climbing:connect(onClimbing),
			eventHum.GettingUp:connect(onGettingUp),
			eventHum.FreeFalling:connect(onFreeFall),
			eventHum.FallingDown:connect(onFallingDown),
			eventHum.Seated:connect(onSeated),
			eventHum.PlatformStanding:connect(onPlatformStanding),
			eventHum.Swimming:connect(onSwimming)
		}
	end
	
	
	onHook()
	
	-- setup emote chat hook
	game:GetService("Players").LocalPlayer.Chatted:connect(function(msg)
		local emote = ""
		if (string.sub(msg, 1, 3) == "/e ") then
			emote = string.sub(msg, 4)
		elseif (string.sub(msg, 1, 7) == "/emote ") then
			emote = string.sub(msg, 8)
		end
		
		if (pose == "Standing" and emoteNames[emote] ~= nil) then
			playAnimation(emote, EMOTE_TRANSITION_TIME, Humanoid)
		end
	end)
	
	--[[ emote bindable hook
	if FFlagAnimateScriptEmoteHook then
		script:WaitForChild("PlayEmote").OnInvoke = function(emote)
			-- Only play emotes when idling
			if pose ~= "Standing" then
				return
			end
			if emoteNames[emote] ~= nil then
				-- Default emotes
				playAnimation(emote, EMOTE_TRANSITION_TIME, Humanoid)
				return true
			elseif typeof(emote) == "Instance" and emote:IsA("Animation") then
				-- Non-default emotes
				playEmote(emote, EMOTE_TRANSITION_TIME, Humanoid)
				return true
			end
			-- Return false to indicate that the emote could not be played
			return false
		end
	end
	]]
	-- initialize to idle
	playAnimation("idle", 0.1, Humanoid)
	pose = "Standing"
	-- loop to handle timed state transitions and tool animations
	spawn(function()
		while Character.Parent ~= nil do
			local _, currentGameTime = wait(0.1)
			stepAnimate(currentGameTime)
		end
	end)
	return {
		onRunning = onRunning, 
		onDied = onDied, 
		onJumping = onJumping, 
		onClimbing = onClimbing, 
		onGettingUp = onGettingUp, 
		onFreeFall = onFreeFall, 
		onFallingDown = onFallingDown, 
		onSeated = onSeated, 
		onPlatformStanding = onPlatformStanding,
		onHook = onHook,
		onUnhook = onUnhook
	}
	end
	return r15()
end
while true do
	wait(.1)
	if plr.Character ~= nil then
		char = plr.Character
		break
	end
end
function _Controller()
	local humanoid = char:WaitForChild("Humanoid")
	local animFuncs = {}
	if (humanoid.RigType == Enum.HumanoidRigType.R6) then
		animFuncs = _R6()
	else
		animFuncs = _R15()
	end
	print("Animation succes")
	return animFuncs
end
function _AnimationHandler()
local AnimationHandler = {}
AnimationHandler.__index = AnimationHandler

function AnimationHandler.new(humanoid, animate)
	local self = setmetatable({}, AnimationHandler)
	
	self._AnimFuncs = _Controller()
	self.Humanoid = humanoid
	
	return self
end

function AnimationHandler:EnableDefault(bool)
	if (bool) then
		self._AnimFuncs.onHook()
	else
		self._AnimFuncs.onUnhook()
	end
end

function AnimationHandler:Run(name, ...)
	self._AnimFuncs[name](...)
end

return AnimationHandler
end

function _GravityController()

local ZERO = Vector3.new(0, 0, 0)
local UNIT_X = Vector3.new(1, 0, 0)
local UNIT_Y = Vector3.new(0, 1, 0)
local UNIT_Z = Vector3.new(0, 0, 1)
local VEC_XY = Vector3.new(1, 0, 1)

local IDENTITYCF = CFrame.new()

local JUMPMODIFIER = 1.2
local TRANSITION = 0.15
local WALKF = 200 / 3

local UIS = game:GetService("UserInputService")
local RUNSERVICE = game:GetService("RunService")

local InitObjects = _InitObjects()
local AnimationHandler = _AnimationHandler()
local StateTracker = _StateTracker()

-- Class

local GravityController = {}
GravityController.__index = GravityController

-- Private Functions

local function getRotationBetween(u, v, axis)
	local dot, uxv = u:Dot(v), u:Cross(v)
	if (dot < -0.99999) then return CFrame.fromAxisAngle(axis, math.pi) end
	return CFrame.new(0, 0, 0, uxv.x, uxv.y, uxv.z, 1 + dot)
end

local function lookAt(pos, forward, up)
	local r = forward:Cross(up)
	local u = r:Cross(forward)
	return CFrame.fromMatrix(pos, r.Unit, u.Unit)
end

local function getMass(array)
	local mass = 0
	for _, part in next, array do
		if (part:IsA("BasePart")) then
			mass = mass + part:GetMass()
		end
	end
	return mass
end

-- Public Constructor
local ExecutedPlayerModule = _PlayerModule()
local ExecutedSounds = _sounds()
function GravityController.new(player)
	local self = setmetatable({}, GravityController)

	--[[ Camera
	local loaded = player.PlayerScripts:WaitForChild("PlayerScriptsLoader"):WaitForChild("Loaded")
	if (not loaded.Value) then
		--loaded.Changed:Wait()
	end
	]]
	local playerModule = ExecutedPlayerModule
	self.Controls = playerModule:GetControls()
	self.Camera = playerModule:GetCameras()
	
	-- Player and character
	self.Player = player
	self.Character = player.Character
	self.Humanoid = player.Character:WaitForChild("Humanoid")
	self.HRP = player.Character:WaitForChild("HumanoidRootPart")
	
	-- Animation
	self.AnimationHandler = AnimationHandler.new(self.Humanoid, self.Character:WaitForChild("Animate"))
	self.AnimationHandler:EnableDefault(false)
	local ssss = game:GetService("Players").LocalPlayer.PlayerScripts:FindFirstChild("SetState") or Instance.new("BindableEvent",game:GetService("Players").LocalPlayer.PlayerScripts)
	local soundState = ExecutedSounds
	ssss.Name = "SetState"
	
	self.StateTracker = StateTracker.new(self.Humanoid, soundState)
	self.StateTracker.Changed:Connect(function(name, speed)
		self.AnimationHandler:Run(name, speed)
	end)
	
	-- Collider and forces
	local collider, gyro, vForce, floor = InitObjects(self)
	
	floor.Touched:Connect(function() end)
	collider.Touched:Connect(function() end)
	
	self.Collider = collider
	self.VForce = vForce
	self.Gyro = gyro
	self.Floor = floor
	
	-- Attachment to parts
	self.LastPart = workspace.Terrain
	self.LastPartCFrame = IDENTITYCF
	
	-- Gravity properties
	self.GravityUp = UNIT_Y
	self.Ignores = {self.Character}
	
	function self.Camera.GetUpVector(this, oldUpVector)
		return self.GravityUp
	end
	
	-- Events etc
	self.Humanoid.PlatformStand = true
	
	self.CharacterMass = getMass(self.Character:GetDescendants())
	self.Character.AncestryChanged:Connect(function() self.CharacterMass = getMass(self.Character:GetDescendants()) end)
	
	self.JumpCon = RUNSERVICE.RenderStepped:Connect(function(dt) 
		if (self.Controls:IsJumping()) then
			self:OnJumpRequest()
		end
	end)
	
	self.DeathCon = self.Humanoid.Died:Connect(function() self:Destroy() end)
	self.SeatCon = self.Humanoid.Seated:Connect(function(active) if (active) then self:Destroy() end end)
	self.HeartCon = RUNSERVICE.Heartbeat:Connect(function(dt) self:OnHeartbeatStep(dt) end)
	RUNSERVICE:BindToRenderStep("GravityStep", Enum.RenderPriority.Input.Value + 1, function(dt) self:OnGravityStep(dt) end)
	
	
	return self
end

-- Public Methods

function GravityController:Destroy()
	self.JumpCon:Disconnect()
	self.DeathCon:Disconnect()
	self.SeatCon:Disconnect()
	self.HeartCon:Disconnect()
	
	RUNSERVICE:UnbindFromRenderStep("GravityStep")
	
	self.Collider:Destroy()
	self.VForce:Destroy()
	self.Gyro:Destroy()
	self.StateTracker:Destroy()
	
	self.Humanoid.PlatformStand = false
	self.AnimationHandler:EnableDefault(true)
	
	self.GravityUp = UNIT_Y
end

function GravityController:GetGravityUp(oldGravity)
	return oldGravity
end

function GravityController:IsGrounded(isJumpCheck)
	if (not isJumpCheck) then
		local parts = self.Floor:GetTouchingParts()
		for _, part in next, parts do
			if (not part:IsDescendantOf(self.Character)) then
				return true
			end
		end
	else
		if (self.StateTracker.Jumped) then
			return false
		end
	
		-- 1. check we are touching something with the collider
		local valid = {}
		local parts = self.Collider:GetTouchingParts()
		for _, part in next, parts do
			if (not part:IsDescendantOf(self.Character)) then
				table.insert(valid, part)
			end
		end
		
		if (#valid > 0) then
			-- 2. do a decently long downwards raycast
			local max = math.cos(self.Humanoid.MaxSlopeAngle)
			local ray = Ray.new(self.Collider.Position, -10 * self.GravityUp)
			local hit, pos, normal = workspace:FindPartOnRayWithWhitelist(ray, valid, true)
			
			-- 3. use slope to decide on jump
			if (hit and max  0.5 and -math.sign(fDot)*camCF.UpVector or camCF.LookVector
	
	local left = cForward:Cross(-newGravity).Unit
	local forward = -left:Cross(newGravity).Unit
	
	local move = self:GetMoveVector()
	local worldMove = forward*move.z - left*move.x
	worldMove = worldMove:Dot(worldMove) > 1 and worldMove.Unit or worldMove
	
	local isInputMoving = worldMove:Dot(worldMove) > 0
	
	-- get the desired character cframe
	local hrpCFLook = self.HRP.CFrame.LookVector
	local charF = hrpCFLook:Dot(forward)*forward + hrpCFLook:Dot(left)*left
	local charR = charF:Cross(newGravity).Unit
	local newCharCF = CFrame.fromMatrix(ZERO, charR, newGravity, -charF)
	
	local newCharRotation = IDENTITYCF
	if (isInputMoving) then
		newCharRotation = IDENTITYCF:Lerp(getRotationBetween(charF, worldMove, newGravity), 0.7)	
	end
	
	-- calculate forces
	local g = workspace.Gravity
	local gForce = g * self.CharacterMass * (UNIT_Y - newGravity)
	
	local cVelocity = self.HRP.Velocity
	local tVelocity = self.Humanoid.WalkSpeed * worldMove
	local gVelocity = cVelocity:Dot(newGravity)*newGravity
	local hVelocity = cVelocity - gVelocity
	
	if (hVelocity:Dot(hVelocity) < 1) then
		hVelocity = ZERO
	end
	
	local dVelocity = tVelocity - hVelocity
	local walkForceM = math.min(10000, WALKF * self.CharacterMass * dVelocity.Magnitude / (dt*60))
	local walkForce = walkForceM > 0 and dVelocity.Unit*walkForceM or ZERO
	
	-- mouse lock
	local charRotation = newCharRotation * newCharCF
	
	if (self.Camera:IsCamRelative()) then
		local lv = workspace.CurrentCamera.CFrame.LookVector
		local hlv = lv - charRotation.UpVector:Dot(lv)*charRotation.UpVector
		charRotation = lookAt(ZERO, hlv, charRotation.UpVector)
	end
	
	-- get state
	self.StateTracker:OnStep(self.GravityUp, self:IsGrounded(), isInputMoving)

	-- update values
	self.VForce.Force = walkForce + gForce
	self.Gyro.CFrame = charRotation
end
return GravityController
end
function _Draw3D()
	local module = {}
	
	-- Style Guide
	
	module.StyleGuide = {
		Point = {
			Thickness = 0.5;
			Color = Color3.new(0, 1, 0);
		},
		
		Line = {
			Thickness = 0.1;
			Color = Color3.new(1, 1, 0);
		},
		
		Ray = {
			Thickness = 0.1;
			Color = Color3.new(1, 0, 1);
		},
		
		Triangle = {
			Thickness = 0.05;
		};
		
		CFrame = {
			Thickness = 0.1;
			RightColor3 = Color3.new(1, 0, 0);
			UpColor3 = Color3.new(0, 1, 0);
			BackColor3 = Color3.new(0, 0, 1);
			PartProperties = {
				Material = Enum.Material.SmoothPlastic;
			};
		}
	}
	
	-- CONSTANTS
	
	local WEDGE = Instance.new("WedgePart")
	WEDGE.Material = Enum.Material.SmoothPlastic
	WEDGE.Anchored = true
	WEDGE.CanCollide = false
	
	local PART = Instance.new("Part")
	PART.Size = Vector3.new(0.1, 0.1, 0.1)
	PART.Anchored = true
	PART.CanCollide = false
	PART.TopSurface = Enum.SurfaceType.Smooth
	PART.BottomSurface = Enum.SurfaceType.Smooth
	PART.Material = Enum.Material.SmoothPlastic
	
	-- Functions
	
	local function draw(properties, style)
		local part = PART:Clone()
		for k, v in next, properties do
			part[k] = v
		end
		if (style) then
			for k, v in next, style do
				if (k ~= "Thickness") then
					part[k] = v
				end
			end
		end
		return part
	end
	
	function module.Draw(parent, properties)
		properties.Parent = parent
		return draw(properties, nil)
	end
	
	function module.Point(parent, cf_v3)
		local thickness = module.StyleGuide.Point.Thickness
		return draw({
			Size = Vector3.new(thickness, thickness, thickness);
			CFrame = (typeof(cf_v3) == "CFrame" and cf_v3 or CFrame.new(cf_v3));
			Parent = parent;
		}, module.StyleGuide.Point)
	end
	
	function module.Line(parent, a, b)
		local thickness = module.StyleGuide.Line.Thickness
		return draw({
			CFrame = CFrame.new((a + b)/2, b);
			Size = Vector3.new(thickness, thickness, (b - a).Magnitude);
			Parent = parent;
		}, module.StyleGuide.Line)
	end
	
	function module.Ray(parent, origin, direction)
		local thickness = module.StyleGuide.Ray.Thickness
		return draw({
			CFrame = CFrame.new(origin + direction/2, origin + direction);
			Size = Vector3.new(thickness, thickness, direction.Magnitude);
			Parent = parent;
		}, module.StyleGuide.Ray)
	end
	
	function module.Triangle(parent, a, b, c)
		local ab, ac, bc = b - a, c - a, c - b
		local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
		
		if (abd > acd and abd > bcd) then
			c, a = a, c
		elseif (acd > bcd and acd > abd) then
			a, b = b, a
		end
		
		ab, ac, bc = b - a, c - a, c - b
		
		local right = ac:Cross(ab).Unit
		local up = bc:Cross(right).Unit
		local back = bc.Unit
		
		local height = math.abs(ab:Dot(up))
		local width1 = math.abs(ab:Dot(back))
		local width2 = math.abs(ac:Dot(back))
		
		local thickness = module.StyleGuide.Triangle.Thickness
		
		local w1 = WEDGE:Clone()
		w1.Size = Vector3.new(thickness, height, width1)
		w1.CFrame = CFrame.fromMatrix((a + b)/2, right, up, back)
		w1.Parent = parent
		
		local w2 = WEDGE:Clone()
		w2.Size = Vector3.new(thickness, height, width2)
		w2.CFrame = CFrame.fromMatrix((a + c)/2, -right, up, -back)
		w2.Parent = parent
		
		for k, v in next, module.StyleGuide.Triangle do
			if (k ~= "Thickness") then
				w1[k] = v
				w2[k] = v
			end
		end
		
		return w1, w2
	end
	
	function module.CFrame(parent, cf)
		local origin = cf.Position
		local r = cf.RightVector
		local u = cf.UpVector
		local b = -cf.LookVector
		
		local thickness = module.StyleGuide.CFrame.Thickness
		
		local right = draw({
			CFrame = CFrame.new(origin + r/2, origin + r);
			Size = Vector3.new(thickness, thickness, r.Magnitude);
			Color = module.StyleGuide.CFrame.RightColor3;
			Parent = parent;
		}, module.StyleGuide.CFrame.PartProperties)
		
		local up = draw({
			CFrame = CFrame.new(origin + u/2, origin + u);
			Size = Vector3.new(thickness, thickness, r.Magnitude);
			Color = module.StyleGuide.CFrame.UpColor3;
			Parent = parent;
		}, module.StyleGuide.CFrame.PartProperties)
		
		local back = draw({
			CFrame = CFrame.new(origin + b/2, origin + b);
			Size = Vector3.new(thickness, thickness, u.Magnitude);
			Color = module.StyleGuide.CFrame.BackColor3;
			Parent = parent;
		}, module.StyleGuide.CFrame.PartProperties)
		
		return right, up, back
	end
	
	-- Return
	
	return module
end
function _Draw2D()
	local module = {}
	
	-- Style Guide
	
	module.StyleGuide = {
		Point = {
			BorderSizePixel = 0;
			Size = UDim2.new(0, 4, 0, 4);
			BorderColor3 = Color3.new(0, 0, 0);
			BackgroundColor3 = Color3.new(0, 1, 0);
		},
		
		Line = {
			Thickness = 1;
			BorderSizePixel = 0;
			BorderColor3 = Color3.new(0, 0, 0);
			BackgroundColor3 = Color3.new(0, 1, 0);
		},
		
		Ray = {
			Thickness = 1;
			BorderSizePixel = 0;
			BorderColor3 = Color3.new(0, 0, 0);
			BackgroundColor3 = Color3.new(0, 1, 0);
		},
		
		Triangle = {
			ImageTransparency = 0;
			ImageColor3 = Color3.new(0, 1, 0);
		}
	}
	
	-- CONSTANTS
	
	local HALF = Vector2.new(0.5, 0.5)
	
	local RIGHT = "rbxassetid://2798177521"
	local LEFT = "rbxassetid://2798177955"
	
	local IMG = Instance.new("ImageLabel")
	IMG.BackgroundTransparency = 1
	IMG.AnchorPoint = HALF
	IMG.BorderSizePixel = 0
	
	local FRAME = Instance.new("Frame")
	FRAME.BorderSizePixel = 0
	FRAME.Size = UDim2.new(0, 0, 0, 0)
	FRAME.BackgroundColor3 = Color3.new(1, 1, 1)
	
	-- Functions
	
	function draw(properties, style)
		local frame = FRAME:Clone()
		for k, v in next, properties do
			frame[k] = v
		end
		if (style) then
			for k, v in next, style do
				if (k ~= "Thickness") then
					frame[k] = v
				end
			end
		end
		return frame
	end
	
	function module.Draw(parent, properties)
		properties.Parent = parent
		return draw(properties, nil)
	end
	
	function module.Point(parent, v2)
		return draw({
			AnchorPoint = HALF;
			Position = UDim2.new(0, v2.x, 0, v2.y);
			Parent = parent;
		}, module.StyleGuide.Point)
	end
	
	function module.Line(parent, a, b)
		local v = (b - a)
		local m = (a + b)/2
		
		return draw({
			AnchorPoint = HALF;
			Position = UDim2.new(0, m.x, 0, m.y);
			Size = UDim2.new(0, module.StyleGuide.Line.Thickness, 0, v.magnitude);
			Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
			BackgroundColor3 = Color3.new(1, 1, 0);
			Parent = parent;
		}, module.StyleGuide.Line)
	end
	
	function module.Ray(parent, origin, direction)
		local a, b = origin, origin + direction
		local v = (b - a)
		local m = (a + b)/2
		
		return draw({
			AnchorPoint = HALF;
			Position = UDim2.new(0, m.x, 0, m.y);
			Size = UDim2.new(0, module.StyleGuide.Ray.Thickness, 0, v.magnitude);
			Rotation = math.deg(math.atan2(v.y, v.x)) - 90;
			Parent = parent;
		}, module.StyleGuide.Ray)
	end
	
	function module.Triangle(parent, a, b, c)
		local ab, ac, bc = b - a, c - a, c - b
		local abd, acd, bcd = ab:Dot(ab), ac:Dot(ac), bc:Dot(bc)
		
		if (abd > acd and abd > bcd) then
			c, a = a, c
		elseif (acd > bcd and acd > abd) then
			a, b = b, a
		end
		
		ab, ac, bc = b - a, c - a, c - b
		
		local unit = bc.unit
		local height = unit:Cross(ab)
		local flip = (height >= 0)
		local theta = math.deg(math.atan2(unit.y, unit.x)) + (flip and 0 or 180)
		
		local m1 = (a + b)/2
		local m2 = (a + c)/2
		
		local w1 = IMG:Clone()
		w1.Image = flip and RIGHT or LEFT
		w1.AnchorPoint = HALF
		w1.Size = UDim2.new(0, math.abs(unit:Dot(ab)), 0, height)
		w1.Position = UDim2.new(0, m1.x, 0, m1.y)
		w1.Rotation = theta
		w1.Parent = parent
		
		local w2 = IMG:Clone()
		w2.Image = flip and LEFT or RIGHT
		w2.AnchorPoint = HALF
		w2.Size = UDim2.new(0, math.abs(unit:Dot(ac)), 0, height)
		w2.Position = UDim2.new(0, m2.x, 0, m2.y)
		w2.Rotation = theta
		w2.Parent = parent
		
		for k, v in next, module.StyleGuide.Triangle do
			w1[k] = v
			w2[k] = v
		end
		
		return w1, w2
	end
	
	-- Return
	
	return module
end
function _DrawClass()
	local Draw2DModule = _Draw2D()
	local Draw3DModule = _Draw3D()
	
	--
	
	local DrawClass = {}
	local DrawClassStorage = setmetatable({}, {__mode = "k"})
	DrawClass.__index = DrawClass
	
	function DrawClass.new(parent)
		local self = setmetatable({}, DrawClass)
		
		self.Parent = parent
		DrawClassStorage[self] = {}
		
		self.Draw3D = {}
		for key, func in next, Draw3DModule do
			self.Draw3D[key] = function(...)
				local returns = {func(self.Parent, ...)}
				for i = 1, #returns do
					table.insert(DrawClassStorage[self], returns[i])
				end
				return unpack(returns)
			end
		end
		
		self.Draw2D = {}
		for key, func in next, Draw2DModule do
			self.Draw2D[key] = function(...)
				local returns = {func(self.Parent, ...)}
				for i = 1, #returns do
					table.insert(DrawClassStorage[self], returns[i])
				end
				return unpack(returns)
			end
		end
		
		return self
	end
	
	--
	
	function DrawClass:Clear()
		local t = DrawClassStorage[self]
		while (#t > 0) do
			local part = table.remove(t)
			if (part) then
				part:Destroy()
			end
		end
		DrawClassStorage[self] = {}
	end
	
	--
	
	return DrawClass
end


--END TEST

local PLAYERS = game:GetService("Players")

local GravityController = _GravityController()
local Controller = GravityController.new(PLAYERS.LocalPlayer)

local DrawClass = _DrawClass()

local PI2 = math.pi*2
local ZERO = Vector3.new(0, 0, 0)

local LOWER_RADIUS_OFFSET = 3 
local NUM_DOWN_RAYS = 24
local ODD_DOWN_RAY_START_RADIUS = 3	
local EVEN_DOWN_RAY_START_RADIUS = 2
local ODD_DOWN_RAY_END_RADIUS = 1.66666
local EVEN_DOWN_RAY_END_RADIUS = 1

local NUM_FEELER_RAYS = 9
local FEELER_LENGTH = 2
local FEELER_START_OFFSET = 2
local FEELER_RADIUS = 3.5
local FEELER_APEX_OFFSET = 1
local FEELER_WEIGHTING = 8

function GetGravityUp(self, oldGravityUp)
	local ignoreList = {}
	for i, player in next, PLAYERS:GetPlayers() do
		ignoreList[i] = player.Character
	end
	
	-- get the normal
	
	local hrpCF = self.HRP.CFrame
	local isR15 = (self.Humanoid.RigType == Enum.HumanoidRigType.R15)
	
	local origin = isR15 and hrpCF.p or hrpCF.p + 0.35*oldGravityUp
	local radialVector = math.abs(hrpCF.LookVector:Dot(oldGravityUp)) < 0.999 and hrpCF.LookVector:Cross(oldGravityUp) or hrpCF.RightVector:Cross(oldGravityUp)
	
	local centerRayLength = 25
	local centerRay = Ray.new(origin, -centerRayLength * oldGravityUp)
	local centerHit, centerHitPoint, centerHitNormal = workspace:FindPartOnRayWithIgnoreList(centerRay, ignoreList)
	
	--[[disable
	DrawClass:Clear()
	DrawClass.Draw3D.Ray(centerRay.Origin, centerRay.Direction)
	]]
	local downHitCount = 0
	local totalHitCount = 0
	local centerRayHitCount = 0
	local evenRayHitCount = 0
	local oddRayHitCount = 0
	
	local mainDownNormal = ZERO
	if (centerHit) then
		mainDownNormal = centerHitNormal
		centerRayHitCount = 0
	end
	
	local downRaySum = ZERO
	for i = 1, NUM_DOWN_RAYS do
		local dtheta = PI2 * ((i-1)/NUM_DOWN_RAYS)
		
		local angleWeight = 0.25 + 0.75 * math.abs(math.cos(dtheta))
		local isEvenRay = (i%2 == 0)
		local startRadius = isEvenRay and EVEN_DOWN_RAY_START_RADIUS or ODD_DOWN_RAY_START_RADIUS	
		local endRadius = isEvenRay and EVEN_DOWN_RAY_END_RADIUS or ODD_DOWN_RAY_END_RADIUS
		local downRayLength = centerRayLength
		
		local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
		local dir = (LOWER_RADIUS_OFFSET * -oldGravityUp + (endRadius - startRadius) * offset)
		local ray = Ray.new(origin + startRadius * offset, downRayLength * dir.unit)
		local hit, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
		--[[disable
		DrawClass.Draw3D.Ray(ray.Origin, ray.Direction)
		]]
		if (hit) then
			downRaySum = downRaySum + angleWeight * hitNormal
			downHitCount = downHitCount + 1
			if isEvenRay then
				evenRayHitCount = evenRayHitCount + 1					
			else
				oddRayHitCount = oddRayHitCount + 1
			end
		end
	end
	
	local feelerHitCount = 0	
	local feelerNormalSum = ZERO
	
	for i = 1, NUM_FEELER_RAYS do
		local dtheta = 2 * math.pi * ((i-1)/NUM_FEELER_RAYS)
		local angleWeight =  0.25 + 0.75 * math.abs(math.cos(dtheta))	
		local offset = CFrame.fromAxisAngle(oldGravityUp, dtheta) * radialVector
		local dir = (FEELER_RADIUS * offset + LOWER_RADIUS_OFFSET * -oldGravityUp).unit
		local feelerOrigin = origin - FEELER_APEX_OFFSET * -oldGravityUp + FEELER_START_OFFSET * dir
		local ray = Ray.new(feelerOrigin, FEELER_LENGTH * dir)
		local hit, hitPoint, hitNormal = workspace:FindPartOnRayWithIgnoreList(ray, ignoreList)
		--[[disable
		DrawClass.Draw3D.Ray(ray.Origin, ray.Direction)
		]]
		if (hit) then
			feelerNormalSum = feelerNormalSum + FEELER_WEIGHTING * angleWeight * hitNormal --* hitDistSqInv
			feelerHitCount = feelerHitCount + 1
		end
	end
	
	if (centerRayHitCount + downHitCount + feelerHitCount > 0) then
		local normalSum = mainDownNormal + downRaySum + feelerNormalSum
		if (normalSum ~= ZERO) then
			return normalSum.unit
		end
	end
	
	return oldGravityUp
end

Controller.GetGravityUp = GetGravityUp

-- E is toggle
game:GetService("ContextActionService"):BindAction("Toggle", function(action, state, input)
	if not (state == Enum.UserInputState.Begin) then
		return
	end
	
	if (Controller) then
		Controller:Destroy()
		Controller = nil
	else
		Controller = GravityController.new(PLAYERS.LocalPlayer)
		Controller.GetGravityUp = GetGravityUp
	end
end, false, Enum.KeyCode.Z)
print("end")