local l={}l.__index=l function l.new()local G=setmetatable({},l)G.FIRST_PERSON_DISTANCE_THRESHOLD=FIRST_PERSON_DISTANCE_THRESHOLD G.cameraType=nil G.cameraMovementMode=nil local M=Players.LocalPlayer G.lastCameraTransform=nil G.rotateInput=ZERO_VECTOR2 G.userPanningCamera=false G.lastUserPanCamera=tick()G.humanoidRootPart=nil G.humanoidCache={}G.lastSubject=nil G.lastSubjectPosition=Vector3.new(0,5,0)G.defaultSubjectDistance=Util.Clamp(M.CameraMinZoomDistance,M.CameraMaxZoomDistance,DEFAULT_DISTANCE)G.currentSubjectDistance=Util.Clamp(M.CameraMinZoomDistance,M.CameraMaxZoomDistance,DEFAULT_DISTANCE)G.inFirstPerson=false G.inMouseLockedMode=false G.portraitMode=false G.isSmallTouchScreen=false G.resetCameraAngle=true G.enabled=false G.inputBeganConn=nil G.inputChangedConn=nil G.inputEndedConn=nil G.startPos=nil G.lastPos=nil G.panBeginLook=nil G.panEnabled=true G.keyPanEnabled=true G.distanceChangeEnabled=true G.PlayerGui=nil G.cameraChangedConn=nil G.viewportSizeChangedConn=nil G.boundContextActions={}G.shouldUseVRRotation=false G.VRRotationIntensityAvailable=false G.lastVRRotationIntensityCheckTime=0 G.lastVRRotationTime=0 G.vrRotateKeyCooldown={}G.cameraTranslationConstraints=Vector3.new(1,1,1)G.humanoidJumpOrigin=nil G.trackingHumanoid=nil G.cameraFrozen=false G.headHeightR15=R15_HEAD_OFFSET G.heightScaleChangedConn=nil G.subjectStateChangedConn=nil G.humanoidChildAddedConn=nil G.humanoidChildRemovedConn=nil G.activeGamepad=nil G.gamepadPanningCamera=false G.lastThumbstickRotate=nil G.numOfSeconds=.7 G.currentSpeed=0 G.maxSpeed=6 G.vrMaxSpeed=4 G.lastThumbstickPos=Vector2.new(0,0)G.ySensitivity=.65 G.lastVelocity=nil G.gamepadConnectedConn=nil G.gamepadDisconnectedConn=nil G.currentZoomSpeed=1.0 G.L3ButtonDown=false G.dpadLeftDown=false G.dpadRightDown=false G.isDynamicThumbstickEnabled=false G.fingerTouches={}G.numUnsunkTouches=0 G.inputStartPositions={}G.inputStartTimes={}G.startingDiff=nil G.pinchBeginZoom=nil G.userPanningTheCamera=false G.touchActivateConn=nil G.mouseLockOffset=ZERO_VECTOR3 if M.Character then G:OnCharacterAdded(M.Character)end M.CharacterAdded:Connect(function(l)G:OnCharacterAdded(l)end)if G.cameraChangedConn then G.cameraChangedConn:Disconnect()end G.cameraChangedConn=(workspace:GetPropertyChangedSignal("\067\117\114\114\101\110\116\067\097\109\101\114\097")):Connect(function()G:OnCurrentCameraChanged()end)if FFlagUserNewDefaultCameraAngle then G:OnCurrentCameraChanged()end if G.playerCameraModeChangeConn then G.playerCameraModeChangeConn:Disconnect()end G.playerCameraModeChangeConn=(M:GetPropertyChangedSignal("\067\097\109\101\114\097\077\111\100\101")):Connect(function()G:OnPlayerCameraPropertyChange()end)if G.minDistanceChangeConn then G.minDistanceChangeConn:Disconnect()end G.minDistanceChangeConn=(M:GetPropertyChangedSignal("\067\097\109\101\114\097\077\105\110\090\111\111\109\068\105\115\116\097\110\099\101")):Connect(function()G:OnPlayerCameraPropertyChange()end)if G.maxDistanceChangeConn then G.maxDistanceChangeConn:Disconnect()end G.maxDistanceChangeConn=(M:GetPropertyChangedSignal("\067\097\109\101\114\097\077\097\120\090\111\111\109\068\105\115\116\097\110\099\101")):Connect(function()G:OnPlayerCameraPropertyChange()end)if G.playerDevTouchMoveModeChangeConn then G.playerDevTouchMoveModeChangeConn:Disconnect()end G.playerDevTouchMoveModeChangeConn=(M:GetPropertyChangedSignal("\068\101\118\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101")):Connect(function()G:OnDevTouchMovementModeChanged()end)G:OnDevTouchMovementModeChanged()if G.gameSettingsTouchMoveMoveChangeConn then G.gameSettingsTouchMoveMoveChangeConn:Disconnect()end G.gameSettingsTouchMoveMoveChangeConn=(UserGameSettings:GetPropertyChangedSignal("\084\111\117\099\104\077\111\118\101\109\101\110\116\077\111\100\101")):Connect(function()G:OnGameSettingsTouchMovementModeChanged()end)G:OnGameSettingsTouchMovementModeChanged()UserGameSettings:SetCameraYInvertVisible()UserGameSettings:SetGamepadCameraSensitivityVisible()G.hasGameLoaded=game:IsLoaded()if not G.hasGameLoaded then G.gameLoadedConn=game.Loaded:Connect(function()G.hasGameLoaded=true G.gameLoadedConn:Disconnect()G.gameLoadedConn=nil end)end return G end function l.GetModuleName(l)return"\066\097\115\101\067\097\109\101\114\097"end function l.OnCharacterAdded(G,l)if FFlagUserNewDefaultCameraAngle then G.resetCameraAngle=G.resetCameraAngle or G:GetEnabled()G.humanoidRootPart=nil end if UserInputService.TouchEnabled then G.PlayerGui=Players.LocalPlayer:WaitForChild("\080\108\097\121\101\114\071\117\105")for l,M in ipairs(l:GetChildren())do if M:IsA("\084\111\111\108")then G.isAToolEquipped=true end end l.ChildAdded:Connect(function(l)if l:IsA("\084\111\111\108")then G.isAToolEquipped=true end end)l.ChildRemoved:Connect(function(l)if l:IsA("\084\111\111\108")then G.isAToolEquipped=false end end)end end function l.GetHumanoidRootPart(l)if not l.humanoidRootPart then local G=Players.LocalPlayer if G.Character then local M=G.Character:FindFirstChildOfClass("\072\117\109\097\110\111\105\100")if M then l.humanoidRootPart=M.RootPart end end end return l.humanoidRootPart end function l.GetBodyPartToFollow(M,l,G)if l:GetState()==Enum.HumanoidStateType.Dead then local G=l.Parent if G and G:IsA("\077\111\100\101\108")then return G:FindFirstChild("\072\101\097\100")or l.RootPart end end return l.RootPart end function l.GetSubjectPosition(l)local G=l.lastSubjectPosition local M=game.Workspace.CurrentCamera local C=M and M.CameraSubject if C then if C:IsA("\072\117\109\097\110\111\105\100")then local M=C local s=M:GetState()==Enum.HumanoidStateType.Dead if VRService.VREnabled and(s and M==l.lastSubject)then G=l.lastSubjectPosition else local l=M.RootPart if s then if M.Parent and M.Parent:IsA("\077\111\100\101\108")then l=M.Parent:FindFirstChild("\072\101\097\100")or l end end if l and l:IsA("\066\097\115\101\080\097\114\116")then local C=M.RigType==Enum.HumanoidRigType.R15 and R15_HEAD_OFFSET or HEAD_OFFSET if s then C=ZERO_VECTOR3 end G=l.CFrame.p+l.CFrame:vectorToWorldSpace(C+M.CameraOffset)end end elseif C:IsA("\086\101\104\105\099\108\101\083\101\097\116")then local l=SEAT_OFFSET if VRService.VREnabled then l=VR_SEAT_OFFSET end G=C.CFrame.p+C.CFrame:vectorToWorldSpace(l)elseif C:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then G=C.CFrame.p+SEAT_OFFSET elseif C:IsA("\066\097\115\101\080\097\114\116")then G=C.CFrame.p elseif C:IsA("\077\111\100\101\108")then if C.PrimaryPart then G=(C:GetPrimaryPartCFrame()).p else G=(C:GetModelCFrame()).p end end else return end l.lastSubject=C l.lastSubjectPosition=G return G end function l.UpdateDefaultSubjectDistance(l)local G=Players.LocalPlayer if l.portraitMode then l.defaultSubjectDistance=Util.Clamp(G.CameraMinZoomDistance,G.CameraMaxZoomDistance,PORTRAIT_DEFAULT_DISTANCE)else l.defaultSubjectDistance=Util.Clamp(G.CameraMinZoomDistance,G.CameraMaxZoomDistance,DEFAULT_DISTANCE)end end function l.OnViewportSizeChanged(l)local G=game.Workspace.CurrentCamera local M=G.ViewportSize l.portraitMode=M.X<M.Y l.isSmallTouchScreen=UserInputService.TouchEnabled and(M.Y<500 or M.X<700)l:UpdateDefaultSubjectDistance()end
--
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

	local player = Players.LocalPlayer
	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 = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)
	self.currentSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)

	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.headHeightR15 = R15_HEAD_OFFSET
	self.heightScaleChangedConn = nil
	self.subjectStateChangedConn = nil
	self.humanoidChildAddedConn = nil
	self.humanoidChildRemovedConn = 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.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)
	if FFlagUserNewDefaultCameraAngle then
		self:OnCurrentCameraChanged()
	end

	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

	return self
end

function BaseCamera:GetModuleName()
	return "BaseCamera"
end

function BaseCamera:OnCharacterAdded(char)
	if FFlagUserNewDefaultCameraAngle then
		self.resetCameraAngle = self.resetCameraAngle or self:GetEnabled()
		self.humanoidRootPart = nil
	end
	if UserInputService.TouchEnabled then
		self.PlayerGui = Players.LocalPlayer: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
		local player = Players.LocalPlayer
		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 = humanoid.RigType == Enum.HumanoidRigType.R15 and R15_HEAD_OFFSET or HEAD_OFFSET
					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()
	local player = Players.LocalPlayer
	if self.portraitMode then
		self.defaultSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, PORTRAIT_DEFAULT_DISTANCE)
	else
		self.defaultSubjectDistance = Util.Clamp(player.CameraMinZoomDistance, player.CameraMaxZoomDistance, DEFAULT_DISTANCE)
	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
