PlayerScripts/StarterPlayerScripts_NewStructure/RobloxPlayerScript/CameraScript/Poppercam.lua
--[[
	Poppercam - Occlusion module that brings the camera closer to the subject when objects are blocking the view.
--]]

--[[ Camera Maths Utilities Library ]]--
local Util = require(script.Parent:WaitForChild("CameraUtils"))

local RunService = game:GetService("RunService")
local PlayersService = game:GetService("Players")
local ContextActionService = game:GetService("ContextActionService")
local UserInputService = game:GetService("UserInputService")
local GameSettings = UserSettings().GameSettings

-- Note: Zoom and Subject modules return single functions, Zoom() and Subject() respectively,
-- whereas Rotate returns a table with two functions, Step() and GetDelta()
local Zoom =  require(script:WaitForChild("Zoom"))
local Subject =  require(script:WaitForChild("Subject"))

local Rotate =  require(script:WaitForChild("Rotate"))
local SetTransparency =  require(script:WaitForChild("SetTransparency"))


-- From Input
local INPUT_PRIORITY = Enum.ContextActionPriority.Low.Value
local ROTATION_SPEED_KEYS    = math.rad(120)
local ROTATION_SPEED_MOUSE   = Vector2.new(1, 0.77)*math.rad(15)
local ROTATION_SPEED_GAMEPAD = Vector2.new(1, 0.77)*math.rad(165)
local ZOOM_SPEED_MOUSE = 4
local ZOOM_SPEED_KEYS = 0.75

-- Gamepad Thumbstick Helper Function
local AnalogCurve do
	local DEADZONE = 0.1
	function AnalogCurve(x)
		local y = (math.abs(x) - DEADZONE)/(1 - DEADZONE)
		y = 0.255000975*(2^(2.299113817*y) - 1)
		return math.clamp(y, 0, 1)*math.sign(x)
	end
end


local portraitPopperFixFlagExists, portraitPopperFixFlagEnabled = pcall(function()
	return UserSettings():IsUserFeatureEnabled("UserPortraitPopperFix")
end)
local FFlagUserPortraitPopperFix = portraitPopperFixFlagExists and portraitPopperFixFlagEnabled


--[[ The Module ]]--
local BaseOcclusion = require(script.Parent:WaitForChild("BaseOcclusion"))
local Poppercam = setmetatable({}, BaseOcclusion)
Poppercam.__index = Poppercam

function Poppercam.new()
	local self = setmetatable(BaseOcclusion.new(), Poppercam)
	
	self.gamepad = {
		Thumbstick2 = Vector2.new(),
	}
	self.keyboard = {
		Left = 0,
		Right = 0,
		I = 0,
		O = 0
	}
	self.mouse = {
		Movement = Vector2.new(),
		Wheel = 0,
	}
	
	self.UISConnections = {}
	self.steppedConn =  nil
	self.worldDt = 1/60
	return self
end

function Poppercam:GetPanning()
	for _, input in ipairs(UserInputService:GetMouseButtonsPressed()) do
		if input.UserInputType == Enum.UserInputType.MouseButton2 then
			return true
		end
	end
	return false
end

function Poppercam:GetRotation()
	local kKeyboard = Vector2.new(self.keyboard.Left - self.keyboard.Right, 0)
	local kGamepad = self.gamepad.Thumbstick2
	local kMouse = self.mouse.Movement
	self.mouse.Movement = Vector2.new()
	local result = kKeyboard*ROTATION_SPEED_KEYS + kGamepad*ROTATION_SPEED_GAMEPAD + kMouse*ROTATION_SPEED_MOUSE
	return result.Y, result.X
end

function Poppercam:GetZoomDelta()
	local kKeyboard = self.keyboard.O - self.keyboard.I
	local kMouse = -self.mouse.Wheel
	self.mouse.Wheel = 0
	return kKeyboard*ZOOM_SPEED_KEYS + kMouse*ZOOM_SPEED_MOUSE
end

function Poppercam:GetOcclusionMode()
	return Enum.DevCameraOcclusionMode.Zoom
end

function Poppercam:Enable(enable)
	if enable then
		-- Enabling	
		
		self:BindInputs()		
		
			
	else
		-- Disabling
		self:UnbindInputs()
		self:ResetDeviceInputs()
	end
end

--function Poppercam:MouseLock(isFirstPerson)
--	if isFirstPerson then
--		GameSettings.RotationType = Enum.RotationType.CameraRelative
--		UserInputService.MouseBehavior = Enum.MouseBehavior.LockCenter
--	else
--		GameSettings.RotationType = Enum.RotationType.MovementRelative
--		if self:GetPanning() then
--			UserInputService.MouseBehavior = Enum.MouseBehavior.LockCurrentPosition
--		else
--			UserInputService.MouseBehavior = Enum.MouseBehavior.Default
--		end
--	end
--end

function Poppercam:Update(renderDt, desiredCameraCFrame, desiredCameraFocus)
	--print("desiredCameraCFrame: ",desiredCameraCFrame," desiredCameraFocus: ",desiredCameraFocus)
	self.camera = game.Workspace.CurrentCamera
	if self.camera.CameraType == Enum.CameraType.Custom then
		local subject, subjectTransform, subjectRoot = Subject(self.worldDt)
		if subject then
			local pitch, yaw = self:GetRotation()
			local zoomDelta = self:GetZoomDelta()
			local newFocus = Rotate:Step(self.worldDt, subjectTransform, pitch, yaw) -- vehicle yaw spring must use the world step
			local zoom, transparency, firstPerson = Zoom(renderDt, zoomDelta, newFocus, subjectRoot)
			
			--debug.profilebegin('write')
				
			-- CameraScript should already be doing all of this. Perhaps return this module's
			-- recommendation for transparency?
			--SetTransparency(subject, transparency)
			--self:MouseLock(firstPerson)
			--self.camera.Focus = focus
			--self.camera.CFrame = focus*CFrame.new(0, 0, zoom)
			
			local newCameraCFrame = newFocus*CFrame.new(0, 0, zoom)
			--debug.profileend()
			return newCameraCFrame, newFocus
		end
	end
	
	return desiredCameraCFrame, desiredCameraFocus
end

function Poppercam:ResetDeviceInputs()
	for _, device in pairs{self.gamepad, self.keyboard, self.mouse} do
		for k, v in pairs(device) do
			device[k] = v*0
		end
	end
end

function Poppercam:BindInputs()
	local function Thumbstick(action, state, input)
		local position = input.Position
		self.gamepad[input.KeyCode.Name] = Vector2.new(AnalogCurve(position.X), AnalogCurve(position.Y))
	end

	local function MouseMove(action, state, input)
		local delta = input.Delta
		self.mouse.Movement = Vector2.new(-delta.X, -delta.Y)
		return Enum.ContextActionResult.Pass
	end
	
	local function MouseWheel(action, state, input)
		self.mouse.Wheel = input.Position.Z
		return Enum.ContextActionResult.Pass
	end
	
	local function Keypress(action, state, input)
		self.keyboard[input.KeyCode.Name] = state == Enum.UserInputState.Begin and 1 or 0
	end
		
	ContextActionService:BindActionAtPriority("Camera/Thumbstick", Thumbstick, false, INPUT_PRIORITY,
		Enum.KeyCode.Thumbstick2
	)
	ContextActionService:BindActionAtPriority("Camera/MouseMove",  MouseMove, false, INPUT_PRIORITY,
		Enum.UserInputType.MouseMovement
	)
	ContextActionService:BindActionAtPriority("Camera/MouseWheel", MouseWheel, false, INPUT_PRIORITY,
		Enum.UserInputType.MouseWheel
	)
	ContextActionService:BindActionAtPriority("Camera/Keypress", Keypress, false, INPUT_PRIORITY,
		Enum.KeyCode.Left,
		Enum.KeyCode.Right,
		Enum.KeyCode.I,
		Enum.KeyCode.O
	)
	
	local resetFunction = function() self:ResetDeviceInputs() end
	self.UISConnections["TextBoxFocused"] = UserInputService.TextBoxFocused:Connect(resetFunction)		
	self.UISConnections["TextBoxFocusReleased"] = UserInputService.TextBoxFocusReleased:Connect(resetFunction)
	self.UISConnections["WindowFocused"] = UserInputService.WindowFocused:Connect(resetFunction)
	self.UISConnections["WindowFocusReleased"] = UserInputService.WindowFocusReleased:Connect(resetFunction)
	
	self.steppedConn = RunService.Stepped:Connect(function(t,dt)
		self.worldDt = dt
	end)
end

function Poppercam:UnbindInputs()
	ContextActionService:UnbindAction("Camera/Thumbstick")
	ContextActionService:UnbindAction("Camera/MouseMove")
	ContextActionService:UnbindAction("Camera/MouseWheel")
	ContextActionService:UnbindAction("Camera/Keypress")	
	
	for _, connection in pairs(self.UISConnections) do
		connection:Disconnect()
	end
	self.UISConnections = {}
		
	if self.steppedConn then
		self.steppedConn:Disconnect()
	end
end

-- Called when character is added
function Poppercam:CharacterAdded(player, character)
	
end

-- Called when character is about to be removed
function Poppercam:CharacterRemoving(player, character)
	
end

function Poppercam:OnCameraSubjectChanged(newSubject)

end

return Poppercam