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