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