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