local N=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 C=N("\067\097\109\101\114\097\083\116\097\116\101")local F=N("\103\101\116\082\111\116\097\116\105\111\110\073\110\088\090\080\108\097\110\101")local h=N("\077\097\116\104")local d=N("\083\117\109\109\101\100\067\097\109\101\114\097")local K={}K.ClassName="\080\117\115\104\067\097\109\101\114\097"K._maxY=math.rad(80)K._minY=math.rad(-80)K._angleXZ0=0 K._angleY=0 K.FadeBackTime=.5 K.DefaultAngleXZ0=0 K._lastUpdateTime=-1 K.PushBackAfter=.5 function K.new()local N=setmetatable({},K)return N end function K.__add(C,N)return d.new(C,N)end function K.RotateXY(C,N)C.AngleX=C.AngleX+N.x C.AngleY=C.AngleY+N.y end function K.StopRotateBack(N)N.CFrame=N.CFrame end function K.Reset(N)N.LastUpdateTime=0 end function K.__newindex(h,N,C)if N=="\067\070\114\097\109\101"then local N=F(C)h.AngleXZ=math.atan2(N.lookVector.x,N.lookVector.z)+math.pi local d=(N:toObjectSpace(C)).lookVector.y h.AngleY=math.asin(d)elseif N=="\068\101\102\097\117\108\116\067\070\114\097\109\101"then local N=F(C)h.DefaultAngleXZ0=math.atan2(N.lookVector.x,N.lookVector.z)+math.pi local d=(N:toObjectSpace(C)).lookVector.y h.AngleY=math.asin(d)elseif N=="\065\110\103\108\101\089"then h._angleY=math.clamp(C,h.MinY,h.MaxY)elseif N=="\065\110\103\108\101\088"or N=="\065\110\103\108\101\088\090"then h.LastUpdateTime=tick()h._angleXZ0=C elseif N=="\077\097\120\089"then assert(C>h.MinY,"\077\097\120\089 \109\117\115\116 \098\101 \103\114\101\097\116\101\114 \116\104\097\110 \077\105\110\089")h._maxY=C h.AngleY=h.AngleY elseif N=="\077\105\110\089"then assert(C<h.MinY,"\077\105\110\089 \109\117\115\116 \098\101 \108\101\115\115 \116\104\097\110 \077\101\101\101\097\120\089")h._maxY=C h.AngleY=h.AngleY elseif N=="\076\097\115\116\085\112\100\097\116\101\084\105\109\101"then h._lastUpdateTime=C elseif K[N]~=nil then rawset(h,N,C)else error(N.." \105\115 \110\111\116 \097 \118\097\108\105\100 \109\101\109\098\101\114 \111\114 \080\117\115\104\067\097\109\101\114\097")end end function K.__index(F,N)if N=="\083\116\097\116\101"or N=="\067\097\109\101\114\097\083\116\097\116\101"or N=="\067\097\109\101\114\097"then local N=C.new()N.CFrame=F.CFrame return N elseif N=="\076\097\115\116\085\112\100\097\116\101\084\105\109\101"then return F._lastUpdateTime elseif N=="\076\111\111\107\086\101\099\116\111\114"then return F.Rotation.lookVector elseif N=="\067\070\114\097\109\101"then return CFrame.Angles(0,F.AngleXZ,0)*CFrame.Angles(F.AngleY,0,0)elseif N=="\065\110\103\108\101\089"then return F._angleY elseif N=="\080\117\115\104\066\097\099\107\068\101\108\116\097"then return(tick()-F.LastUpdateTime)-F.PushBackAfter elseif N=="\080\101\114\099\101\110\116\070\097\100\101\100"then return math.clamp(F.PushBackDelta/F.FadeBackTime,0,1)elseif N=="\080\101\114\099\101\110\116\070\097\100\101\100\067\117\114\118\101\100"then return F.PercentFaded^2 elseif N=="\065\110\103\108\101\088"or N=="\065\110\103\108\101\088\090"then return h.lerp(F._angleXZ0,F.DefaultAngleXZ0,F.PercentFadedCurved)elseif N=="\077\097\120\089"then return F._maxY elseif N=="\077\105\110\089"then return F._minY else return K[N]end end return K
--- Like a rotated camera, except we end up pushing back to a default rotation.
-- This same behavior is seen in Roblox vehicle seats
-- @classmod PushCamera

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

local CameraState = require("CameraState")
local getRotationInXZPlane = require("getRotationInXZPlane")
local Math = require("Math")
local SummedCamera = require("SummedCamera")

local PushCamera = {}
PushCamera.ClassName = "PushCamera"

-- Max/Min aim up and down
PushCamera._maxY = math.rad(80)
PushCamera._minY = math.rad(-80)
PushCamera._angleXZ0 = 0 -- Initial
PushCamera._angleY = 0
PushCamera.FadeBackTime = 0.5
PushCamera.DefaultAngleXZ0 = 0
PushCamera._lastUpdateTime = -1
PushCamera.PushBackAfter = 0.5

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

	return self
end

function PushCamera:__add(other)
	return SummedCamera.new(self, other)
end
---
-- @param xzrotVector Vector2, the delta rotation to apply
function PushCamera:RotateXY(xzrotVector)
	self.AngleX = self.AngleX + xzrotVector.x
	self.AngleY = self.AngleY + xzrotVector.y
end

function PushCamera:StopRotateBack()
	self.CFrame = self.CFrame
end

--- Resets to default position automatically
function PushCamera:Reset()
	self.LastUpdateTime = 0
end

function PushCamera:__newindex(index, value)
	if index == "CFrame" then
		local xzrot = getRotationInXZPlane(value)
		self.AngleXZ = math.atan2(xzrot.lookVector.x, xzrot.lookVector.z) + math.pi

		local yrot = xzrot:toObjectSpace(value).lookVector.y
		self.AngleY = math.asin(yrot)
	elseif index == "DefaultCFrame" then
		local xzrot = getRotationInXZPlane(value)
		self.DefaultAngleXZ0 = math.atan2(xzrot.lookVector.x, xzrot.lookVector.z) + math.pi

		local yrot = xzrot:toObjectSpace(value).lookVector.y
		self.AngleY = math.asin(yrot)
	elseif index == "AngleY" then
		self._angleY = math.clamp(value, self.MinY, self.MaxY)
	elseif index == "AngleX" or index == "AngleXZ" then
		self.LastUpdateTime = tick()
		self._angleXZ0 = value
	elseif index == "MaxY" then
		assert(value > self.MinY, "MaxY must be greater than MinY")
		self._maxY = value
		self.AngleY = self.AngleY -- Reclamp value
	elseif index == "MinY" then
		assert(value < self.MinY, "MinY must be less than MeeeaxY")
		self._maxY = value
		self.AngleY = self.AngleY -- Reclamp value
	elseif index == "LastUpdateTime" then
		self._lastUpdateTime = value
	elseif PushCamera[index] ~= nil then
		rawset(self, index, value)
	else
		error(index .. " is not a valid member or PushCamera")
	end
end

function PushCamera:__index(index)
	if index == "State" or index == "CameraState" or index == "Camera" then
		local State = CameraState.new()
		State.CFrame = self.CFrame
		return State
	elseif index == "LastUpdateTime" then
		return self._lastUpdateTime
	elseif index == "LookVector" then
		return self.Rotation.lookVector
	elseif index == "CFrame" then
		return CFrame.Angles(0, self.AngleXZ, 0) * CFrame.Angles(self.AngleY, 0, 0)
	elseif index == "AngleY" then
		return self._angleY
	elseif index == "PushBackDelta" then
		return tick() - self.LastUpdateTime - self.PushBackAfter
	elseif index == "PercentFaded" then
		-- How far in we are to the animation. Starts at 0 upon update and goes slowly to 1.
		return math.clamp(self.PushBackDelta / self.FadeBackTime, 0, 1)
	elseif index == "PercentFadedCurved" then
		-- A curved value of PercentFaded
		return self.PercentFaded ^ 2
	elseif index == "AngleX" or index == "AngleXZ" then
		return Math.lerp(self._angleXZ0, self.DefaultAngleXZ0, self.PercentFadedCurved)
	elseif index == "MaxY" then
		return self._maxY
	elseif index == "MinY" then
		return self._minY
	else
		return PushCamera[index]
	end
end

return PushCamera
