function BaseCamera.OnCurrentCameraChanged(D)if UserInputService.TouchEnabled then if D.viewportSizeChangedConn then D.viewportSizeChangedConn:Disconnect()D.viewportSizeChangedConn=nil end local n=game.Workspace.CurrentCamera if n then D:OnViewportSizeChanged()D.viewportSizeChangedConn=(n:GetPropertyChangedSignal("\086\105\101\119\112\111\114\116\083\105\122\101")):Connect(function()D:OnViewportSizeChanged()end)end end if D.cameraSubjectChangedConn then D.cameraSubjectChangedConn:Disconnect()D.cameraSubjectChangedConn=nil end local n=game.Workspace.CurrentCamera if n then D.cameraSubjectChangedConn=(n:GetPropertyChangedSignal("\067\097\109\101\114\097\083\117\098\106\101\099\116")):Connect(function()D:OnNewCameraSubject()end)D:OnNewCameraSubject()end end function BaseCamera.OnDynamicThumbstickEnabled(D)if UserInputService.TouchEnabled then D.isDynamicThumbstickEnabled=true end end function BaseCamera.OnDynamicThumbstickDisabled(D)D.isDynamicThumbstickEnabled=false end function BaseCamera.OnGameSettingsTouchMovementModeChanged(D)if Players.LocalPlayer.DevTouchMovementMode==Enum.DevTouchMovementMode.UserChoice then if UserGameSettings.TouchMovementMode==Enum.TouchMovementMode.DynamicThumbstick or UserGameSettings.TouchMovementMode==Enum.TouchMovementMode.Default then D:OnDynamicThumbstickEnabled()else D:OnDynamicThumbstickDisabled()end end end function BaseCamera.OnDevTouchMovementModeChanged(D)if Players.LocalPlayer.DevTouchMovementMode.Name=="\068\121\110\097\109\105\099\084\104\117\109\098\115\116\105\099\107"then D:OnDynamicThumbstickEnabled()else D:OnGameSettingsTouchMovementModeChanged()end end function BaseCamera.OnPlayerCameraPropertyChange(D)D:SetCameraToSubjectDistance(D.currentSubjectDistance)end function BaseCamera.GetCameraHeight(D)if VRService.VREnabled and not D.inFirstPerson then return math.sin(VR_ANGLE)*D.currentSubjectDistance end return 0 end function BaseCamera.InputTranslationToCameraAngleChange(b,D,n)local M=game.Workspace.CurrentCamera if M and(M.ViewportSize.X>0 and(M.ViewportSize.Y>0 and M.ViewportSize.Y>M.ViewportSize.X))then return D*Vector2.new(n.Y,n.X)end return D*n end function BaseCamera.Enable(n,D)if n.enabled~=D then n.enabled=D if n.enabled then n:ConnectInputEvents()n:BindContextActions()if Players.LocalPlayer.CameraMode==Enum.CameraMode.LockFirstPerson then n.currentSubjectDistance=.5 if not n.inFirstPerson then n:EnterFirstPerson()end end else n:DisconnectInputEvents()n:UnbindContextActions()n:Cleanup()end end end function BaseCamera.GetEnabled(D)return D.enabled end function BaseCamera.OnInputBegan(b,D,n)if D.UserInputType==Enum.UserInputType.Touch then b:OnTouchBegan(D,n)elseif D.UserInputType==Enum.UserInputType.MouseButton2 then b:OnMouse2Down(D,n)elseif D.UserInputType==Enum.UserInputType.MouseButton3 then b:OnMouse3Down(D,n)end end function BaseCamera.OnInputChanged(b,D,n)if D.UserInputType==Enum.UserInputType.Touch then b:OnTouchChanged(D,n)elseif D.UserInputType==Enum.UserInputType.MouseMovement then b:OnMouseMoved(D,n)elseif D.UserInputType==Enum.UserInputType.MouseWheel and not FFlagUserPointerActionsInPlayerScripts then b:OnMouseWheel(D,n)end end function BaseCamera.OnInputEnded(b,D,n)if D.UserInputType==Enum.UserInputType.Touch then b:OnTouchEnded(D,n)elseif D.UserInputType==Enum.UserInputType.MouseButton2 then b:OnMouse2Up(D,n)elseif D.UserInputType==Enum.UserInputType.MouseButton3 then b:OnMouse3Up(D,n)end end function BaseCamera.OnPointerAction(m,D,n,b,M)if M then return end if n.Magnitude>0 then local D=Vector2.new(1,UserGameSettings:GetCameraYInvertValue())local b=m:InputTranslationToCameraAngleChange(PAN_SENSITIVITY*n,MOUSE_SENSITIVITY)*D m.rotateInput=m.rotateInput+b end local i=m.currentSubjectDistance local s=-(D+b)if abs(s)>0 then local D if m.inFirstPerson and s>0 then D=FIRST_PERSON_DISTANCE_THRESHOLD else D=i+s*(1+i*ZOOM_SENSITIVITY_CURVATURE)end m:SetCameraToSubjectDistance(D)end end function BaseCamera.ConnectInputEvents(D)if FFlagUserPointerActionsInPlayerScripts then D.pointerActionConn=UserInputService.PointerAction:Connect(function(n,b,M,m)D:OnPointerAction(n,b,M,m)end)end D.inputBeganConn=UserInputService.InputBegan:Connect(function(n,b)D:OnInputBegan(n,b)end)D.inputChangedConn=UserInputService.InputChanged:Connect(function(n,b)D:OnInputChanged(n,b)end)D.inputEndedConn=UserInputService.InputEnded:Connect(function(n,b)D:OnInputEnded(n,b)end)D.menuOpenedConn=GuiService.MenuOpened:connect(function()D:ResetInputStates()end)D.gamepadConnectedConn=UserInputService.GamepadDisconnected:connect(function(n)if D.activeGamepad~=n then return end D.activeGamepad=nil D:AssignActivateGamepad()end)D.gamepadDisconnectedConn=UserInputService.GamepadConnected:connect(function(n)if D.activeGamepad==nil then D:AssignActivateGamepad()end end)D:AssignActivateGamepad()D:UpdateMouseBehavior()end function BaseCamera.BindContextActions(D)D:BindGamepadInputActions()D:BindKeyboardInputActions()end function BaseCamera.AssignActivateGamepad(D)local n=UserInputService:GetConnectedGamepads()if#n>0 then for b=1,#n,1 do if D.activeGamepad==nil then D.activeGamepad=n[b]elseif n[b].Value<D.activeGamepad.Value then D.activeGamepad=n[b]end end end if D.activeGamepad==nil then D.activeGamepad=Enum.UserInputType.Gamepad1 end end function BaseCamera.DisconnectInputEvents(D)if D.inputBeganConn then D.inputBeganConn:Disconnect()D.inputBeganConn=nil end if D.inputChangedConn then D.inputChangedConn:Disconnect()D.inputChangedConn=nil end if D.inputEndedConn then D.inputEndedConn:Disconnect()D.inputEndedConn=nil end end function BaseCamera.UnbindContextActions(D)for n=1,#D.boundContextActions,1 do ContextActionService:UnbindAction(D.boundContextActions[n])end D.boundContextActions={}end function BaseCamera.Cleanup(D)if FFlagUserPointerActionsInPlayerScripts and D.pointerActionConn then D.pointerActionConn:Disconnect()D.pointerActionConn=nil end if D.menuOpenedConn then D.menuOpenedConn:Disconnect()D.menuOpenedConn=nil end if D.mouseLockToggleConn then D.mouseLockToggleConn:Disconnect()D.mouseLockToggleConn=nil end if D.gamepadConnectedConn then D.gamepadConnectedConn:Disconnect()D.gamepadConnectedConn=nil end if D.gamepadDisconnectedConn then D.gamepadDisconnectedConn:Disconnect()D.gamepadDisconnectedConn=nil end if D.subjectStateChangedConn then D.subjectStateChangedConn:Disconnect()D.subjectStateChangedConn=nil end if D.viewportSizeChangedConn then D.viewportSizeChangedConn:Disconnect()D.viewportSizeChangedConn=nil end if D.touchActivateConn then D.touchActivateConn:Disconnect()D.touchActivateConn=nil end D.turningLeft=false D.turningRight=false D.lastCameraTransform=nil D.lastSubjectCFrame=nil D.userPanningTheCamera=false D.rotateInput=Vector2.new()D.gamepadPanningCamera=Vector2.new(0,0)D.startPos=nil D.lastPos=nil D.panBeginLook=nil D.isRightMouseDown=false D.isMiddleMouseDown=false D.fingerTouches={}D.dynamicTouchInput=nil D.numUnsunkTouches=0 D.startingDiff=nil D.pinchBeginZoom=nil if UserInputService.MouseBehavior~=Enum.MouseBehavior.LockCenter then UserInputService.MouseBehavior=Enum.MouseBehavior.Default end end

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 Players.LocalPlayer.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 Players.LocalPlayer.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)
	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
	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 Players.LocalPlayer.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)
	elseif input.UserInputType == Enum.UserInputType.MouseWheel and not FFlagUserPointerActionsInPlayerScripts then -- remove with FFlagUserPointerActionsInPlayerScripts
		self:OnMouseWheel(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
			newZoom = zoom + zoomDelta*(1 + zoom*ZOOM_SENSITIVITY_CURVATURE)
		end

		self:SetCameraToSubjectDistance(newZoom)
	end
end

function BaseCamera:ConnectInputEvents()
	if FFlagUserPointerActionsInPlayerScripts then
		self.pointerActionConn = UserInputService.PointerAction:Connect(function(wheel, pan, pinch, processed)
			self:OnPointerAction(wheel, pan, pinch, processed)
		end)
	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()
	self:UpdateMouseBehavior()
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 FFlagUserPointerActionsInPlayerScripts and 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
