local p=require((game:GetService("\082\101\112\108\105\099\097\116\101\100\083\116\111\114\097\103\101")):WaitForChild("\078\101\118\101\114\109\111\114\101"))local N=game:GetService("\082\117\110\083\101\114\118\105\099\101")local q=game:GetService("\087\111\114\107\115\112\097\099\101")local k=p("\067\117\115\116\111\109\067\097\109\101\114\097\069\102\102\101\099\116")local a=p("\068\101\102\097\117\108\116\067\097\109\101\114\097")local Y=p("\073\109\112\117\108\115\101\067\097\109\101\114\097")assert(N:IsClient(),"\091\067\097\109\101\114\097\083\116\097\099\107\093 - \079\110\108\121 \114\101\113\117\105\114\101 \067\097\109\101\114\097\083\116\097\099\107 \111\110 \099\108\105\101\110\116")local E={}E.__index=E E.ClassName="\067\097\109\101\114\097\083\116\097\099\107"function E.new()local p=setmetatable({},E)p._stack={}p._rawDefaultCamera=a.new()p._impulseCamera=Y.new()p._defaultCamera=(p._rawDefaultCamera+p._impulseCamera):SetMode("\082\101\108\097\116\105\118\101")p._rawDefaultCamera:BindToRenderStep()p:Add(p._defaultCamera)N:BindToRenderStep("\067\097\109\101\114\097\083\116\097\099\107\085\112\100\097\116\101\073\110\116\101\114\110\097\108",Enum.RenderPriority.Camera.Value+75,function()debug.profilebegin("\067\097\109\101\114\097\083\116\097\099\107\085\112\100\097\116\101")local N=p:GetTopState()if N and N~=p._defaultCamera then N:Set(q.CurrentCamera)end debug.profileend()end)return p end function E.PrintCameraStack(p)for p,N in pairs(p._stack)do print(tostring(type(N)=="\116\097\098\108\101"and N.ClassName or tostring(N)))end end function E.GetDefaultCamera(p)return p._defaultCamera end function E.GetImpulseCamera(p)return p._impulseCamera end function E.GetRawDefaultCamera(p)return p._rawDefaultCamera end function E.GetTopState(p)if#p._stack>10 then warn(("\091\067\097\109\101\114\097\083\116\097\099\107\093 - \083\116\097\099\107 \105\115 \098\105\103\103\101\114 \116\104\097\110 \049\048 \105\110 \099\097\109\101\114\097\115\116\097\099\107 \040\037\100\041"):format(#p._stack))end local N=p._stack[#p._stack]if type(N)=="\116\097\098\108\101"then local p=N.CameraState or N if p then return p else warn("\091\067\097\109\101\114\097\083\116\097\099\107\093 - \078\111 \116\111\112 \115\116\097\116\101\033")end else warn("\091\067\097\109\101\114\097\083\116\097\099\107\093 - \066\097\100 \116\121\112\101 \111\110 \116\111\112 \111\102 \115\116\097\099\107")end end function E.GetNewStateBelow(p)local N=nil return k.new(function()local q=p:GetIndex(N)if q then local N=p._stack[q-1]if N then return N.CameraState or N else warn("\091\067\097\109\101\114\097\083\116\097\099\107\093 - \067\111\117\108\100 \110\111\116 \103\101\116 \115\116\097\116\101 \098\101\108\111\119\044 \102\111\117\110\100 \099\117\114\114\101\110\116 \115\116\097\116\101\046 \082\101\116\117\114\110\105\110\103 \100\101\102\097\117\108\116\046")return p._stack[1].CameraState end else warn("\091\067\097\109\101\114\097\083\116\097\099\107\093 - \067\111\117\108\100 \110\111\116 \103\101\116 \115\116\097\116\101\044 \114\101\116\117\114\110\105\110\103 \100\101\102\097\117\108\116")return p._stack[1].CameraState end end),function(p)N=p end end function E.GetIndex(N,p)for N,q in pairs(N._stack)do if q==p then return N end end end function E.Remove(N,p)local q=N:GetIndex(p)if q then table.remove(N._stack,q)end end function E.Add(N,p)table.insert(N._stack,p)end return E.new()
--- Holds camera states and allows for the last camera state to be retrieved. Also
-- initializes an impulse and default camera as the bottom of the stack. Is a singleton.
-- @module CameraStack

local require = require(game:GetService("ReplicatedStorage"):WaitForChild("Nevermore"))

local RunService = game:GetService("RunService")
local Workspace = game:GetService("Workspace")

local CustomCameraEffect = require("CustomCameraEffect")
local DefaultCamera = require("DefaultCamera")
local ImpulseCamera = require("ImpulseCamera")

assert(RunService:IsClient(), "[CameraStack] - Only require CameraStack on client")

local CameraStack = {}
CameraStack.__index = CameraStack
CameraStack.ClassName = "CameraStack"

function CameraStack.new()
	local self = setmetatable({}, CameraStack)

	self._stack = {}

	-- Initialize default cameras
	self._rawDefaultCamera = DefaultCamera.new()
	self._impulseCamera = ImpulseCamera.new()
	self._defaultCamera = (self._rawDefaultCamera + self._impulseCamera):SetMode("Relative")
	self._rawDefaultCamera:BindToRenderStep()

	-- Add camera to stack
	self:Add(self._defaultCamera)

	RunService:BindToRenderStep("CameraStackUpdateInternal", Enum.RenderPriority.Camera.Value + 75, function()
		debug.profilebegin("CameraStackUpdate")

		local state = self:GetTopState()
		if state and state ~= self._defaultCamera then
			state:Set(Workspace.CurrentCamera)
		end

		debug.profileend()
	end)

	return self
end

--- Outputs the camera stack
-- @treturn nil
function CameraStack:PrintCameraStack()
	for _, value in pairs(self._stack) do
		print(tostring(type(value) == "table" and value.ClassName or tostring(value)))
	end
end

--- Returns the default camera
-- @treturn SummedCamera DefaultCamera + ImpulseCamera
function CameraStack:GetDefaultCamera()
	return self._defaultCamera
end

--- Returns the impulse camera. Useful for adding camera shake
-- @treturn ImpulseCamera
function CameraStack:GetImpulseCamera()
	return self._impulseCamera
end

--- Returns the default camera without any impulse cameras
-- @treturn DefaultCamera
function CameraStack:GetRawDefaultCamera()
	return self._rawDefaultCamera
end

--- Retrieves the top state off the stack
-- @treturn[1] CameraState
-- @treturn[2] nil
function CameraStack:GetTopState()
	if #self._stack > 10 then
		warn(("[CameraStack] - Stack is bigger than 10 in camerastack (%d)"):format(#self._stack))
	end
	local topState = self._stack[#self._stack]

	if type(topState) == "table" then
		local state = topState.CameraState or topState
		if state then
			return state
		else
			warn("[CameraStack] - No top state!")
		end
	else
		warn("[CameraStack] - Bad type on top of stack")
	end
end

--- Returns a new camera state that retrieves the state below its set state
-- @treturn[1] CustomCameraEffect
-- @treturn[1] NewStateToUse
function CameraStack:GetNewStateBelow()
	local _stateToUse = nil

	return CustomCameraEffect.new(function()
		local index = self:GetIndex(_stateToUse)
		if index then
			local below = self._stack[index-1]
			if below then
				return below.CameraState or below
			else
				warn("[CameraStack] - Could not get state below, found current state. Returning default.")
				return self._stack[1].CameraState
			end
		else
			warn("[CameraStack] - Could not get state, returning default")
			return self._stack[1].CameraState
		end
	end), function(newStateToUse)
		_stateToUse = newStateToUse
	end
end

--- Retrieves the index of a state
-- @tparam CameraState state
-- @treturn number Index of state
-- @treturn nil If non on stack
function CameraStack:GetIndex(state)
	for index, value in pairs(self._stack) do
		if value == state then
			return index
		end
	end
end

--- Removes the state from the stack
-- @tparam CameraState state
-- @treturn nil
function CameraStack:Remove(state)
	local index = self:GetIndex(state)

	if index then
		table.remove(self._stack, index)
	end
end

--- Adds a state to the stack
-- @tparam CameraState state
-- @treturn nil
function CameraStack:Add(state)
	table.insert(self._stack, state)
end

return CameraStack.new()