local N={}N.__index=N function N.new()local T=setmetatable({},N)T.FIRST_PERSON_DISTANCE_THRESHOLD=FIRST_PERSON_DISTANCE_THRESHOLD 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,g in ipairs(N:GetChildren())do if g: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(g,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 g=game.Workspace.CurrentCamera local r=g and g.CameraSubject if r then if r:IsA("\072\117\109\097\110\111\105\100")then local g=r local s=g:GetState()==Enum.HumanoidStateType.Dead if VRService.VREnabled and(s and g==N.lastSubject)then T=N.lastSubjectPosition else local N=g.RootPart if s then if g.Parent and g.Parent:IsA("\077\111\100\101\108")then N=g.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 r if g.RigType==Enum.HumanoidRigType.R15 then if g.AutomaticScalingEnabled then r=R15_HEAD_OFFSET if N==g.RootPart then local N=g.RootPart.Size.Y/2-HUMANOID_ROOT_PART_SIZE.Y/2 r=r+Vector3.new(0,N,0)end else r=R15_HEAD_OFFSET_NO_SCALING end else r=HEAD_OFFSET end if s then r=ZERO_VECTOR3 end T=N.CFrame.p+N.CFrame:vectorToWorldSpace(r+g.CameraOffset)end end elseif r: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=r.CFrame.p+r.CFrame:vectorToWorldSpace(N)elseif r:IsA("\083\107\097\116\101\098\111\097\114\100\080\108\097\116\102\111\114\109")then T=r.CFrame.p+SEAT_OFFSET elseif r:IsA("\066\097\115\101\080\097\114\116")then T=r.CFrame.p elseif r:IsA("\077\111\100\101\108")then if r.PrimaryPart then T=(r:GetPrimaryPartCFrame()).p else T=(r:GetModelCFrame()).p end end else return end N.lastSubject=r 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 g=T.ViewportSize N.portraitMode=g.X<g.Y N.isSmallTouchScreen=UserInputService.TouchEnabled and(g.Y<500 or g.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.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
