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