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("\083\112\114\105\110\103")local d=N("\083\117\109\109\101\100\067\097\109\101\114\097")local K={}K.ClassName="\083\109\111\111\116\104\082\111\116\097\116\101\100\067\097\109\101\114\097"K._MaxY=math.rad(80)K._MinY=math.rad(-80)K._ZoomGiveY=math.rad(5)function K.new()local N=setmetatable({},K)N.SpringX=h.new(0)N.SpringY=h.new(0)N.Speed=15 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 C.TargetAngleX=C.AngleX C.TargetAngleY=C.AngleY 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=="\084\097\114\103\101\116\067\070\114\097\109\101"then local N=F(C)h.TargetAngleXZ=math.atan2(N.lookVector.x,N.lookVector.z)+math.pi local d=(N:toObjectSpace(C)).lookVector.y h.TargetAngleY=math.asin(d)elseif N=="\065\110\103\108\101\089"then h.SpringY.Value=C elseif N=="\065\110\103\108\101\088"or N=="\065\110\103\108\101\088\090"then h.SpringX.Value=C elseif N=="\084\097\114\103\101\116\065\110\103\108\101\089"then h.SpringY.Target=C elseif N=="\084\097\114\103\101\116\065\110\103\108\101\088"or N=="\084\097\114\103\101\116\065\110\103\108\101\088\090"then h.SpringX.Target=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 elseif N=="\077\105\110\089"then assert(C<=h.MaxY,"\077\105\110\089 \109\117\115\116 \098\101 \108\101\115\115 \116\104\097\110 \077\097\120\089")h._MinY=C elseif N=="\083\112\101\101\100\065\110\103\108\101\088"or N=="\083\112\101\101\100\065\110\103\108\101\088\090"then h.SpringX.Speed=C elseif N=="\083\112\101\101\100\065\110\103\108\101\089"then h.SpringY.Speed=C elseif N=="\083\112\101\101\100"then h.SpeedAngleX=C h.SpeedAngleY=C elseif N=="\090\111\111\109\071\105\118\101\089"then h._ZoomGiveY=C elseif K[N]~=nil or N=="\083\112\114\105\110\103\088"or N=="\083\112\114\105\110\103\089"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 \083\109\111\111\116\104\082\111\116\097\116\101\100\067\097\109\101\114\097")end end function K.SnapIntoBounds(N)N.TargetAngleY=math.clamp(N.TargetAngleY,N.MinY,N.MaxY)end function K.GetPastBounds(C,N)if N<C.MinY then return N-C.MinY elseif N>C.MaxY then return N-C.MaxY else return 0 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\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.RenderAngleXZ,0)*CFrame.Angles(F.RenderAngleY,0,0)elseif N=="\084\097\114\103\101\116\067\070\114\097\109\101"then return CFrame.Angles(0,F.TargetAngleXZ,0)*CFrame.Angles(F.TargetAngleY,0,0)elseif N=="\082\101\110\100\101\114\065\110\103\108\101\089"then local N=F.AngleY local C=F:GetPastBounds(N)local h=math.abs(C)/F.ZoomGiveY local d=1-.25^math.abs(h)if C<0 then return F.MinY-F.ZoomGiveY*d elseif C>0 then return F.MaxY+F.ZoomGiveY*d else return N end elseif N=="\082\101\110\100\101\114\065\110\103\108\101\088"or N=="\082\101\110\100\101\114\065\110\103\108\101\088\090"then return F.AngleX elseif N=="\065\110\103\108\101\089"then return F.SpringY.Value elseif N=="\065\110\103\108\101\088"or N=="\065\110\103\108\101\088\090"then return F.SpringX.Value elseif N=="\084\097\114\103\101\116\065\110\103\108\101\089"then return F.SpringY.Target elseif N=="\084\097\114\103\101\116\065\110\103\108\101\088"or N=="\084\097\114\103\101\116\065\110\103\108\101\088\090"then return F.SpringX.Target elseif N=="\083\112\101\101\100\065\110\103\108\101\089"then return F.SpringX.Speed elseif N=="\077\097\120\089"then return F._MaxY elseif N=="\077\105\110\089"then return F._MinY elseif N=="\090\111\111\109\071\105\118\101\089"then return F._ZoomGiveY else return K[N]end end return K
--- Allow freedom of movement around a current place, much like the classic script works now.
-- Not intended to be use with the current character script. This is the rotation component.
-- Intended to be used with a SummedCamera, relative.
-- @classmod SmoothRotatedCamera

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

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

local SmoothRotatedCamera = {}
SmoothRotatedCamera.ClassName = "SmoothRotatedCamera"

-- Max/Min aim up and down
SmoothRotatedCamera._MaxY = math.rad(80)
SmoothRotatedCamera._MinY = math.rad(-80)
SmoothRotatedCamera._ZoomGiveY = math.rad(5) -- Only on th

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

	self.SpringX = Spring.new(0)
	self.SpringY = Spring.new(0)
	self.Speed = 15

	return self
end

function SmoothRotatedCamera:__add(other)
	return SummedCamera.new(self, other)
end

---
-- @param xyRotateVector Vector2, the delta rotation to apply
function SmoothRotatedCamera:RotateXY(xyRotateVector)
	self.AngleX = self.AngleX + xyRotateVector.x
	self.AngleY = self.AngleY + xyRotateVector.y
	self.TargetAngleX = self.AngleX
	self.TargetAngleY = self.AngleY
end

function SmoothRotatedCamera:__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 == "TargetCFrame" then
		local xzrot = getRotationInXZPlane(value)
		self.TargetAngleXZ = math.atan2(xzrot.lookVector.x, xzrot.lookVector.z) + math.pi

		local yrot = xzrot:toObjectSpace(value).lookVector.y
		self.TargetAngleY = math.asin(yrot)
	elseif index == "AngleY" then
		self.SpringY.Value = value
	elseif index == "AngleX" or index == "AngleXZ" then
		self.SpringX.Value = value
	elseif index == "TargetAngleY" then
		self.SpringY.Target = value
	elseif index == "TargetAngleX" or index == "TargetAngleXZ" then
		self.SpringX.Target = value
	elseif index == "MaxY" then
		assert(value >= self.MinY, "MaxY must be greater than MinY")
		self._MaxY = value
	elseif index == "MinY" then
		assert(value <= self.MaxY, "MinY must be less than MaxY")
		self._MinY = value
	elseif index == "SpeedAngleX" or index == "SpeedAngleXZ" then
		self.SpringX.Speed = value
	elseif index == "SpeedAngleY" then
		self.SpringY.Speed = value
	elseif index == "Speed" then
		self.SpeedAngleX = value
		self.SpeedAngleY = value
	elseif index == "ZoomGiveY" then
		self._ZoomGiveY = value
	elseif SmoothRotatedCamera[index] ~= nil or index == "SpringX" or index == "SpringY" then
		rawset(self, index, value)
	else
		error(index .. " is not a valid member or SmoothRotatedCamera")
	end
end

function SmoothRotatedCamera:SnapIntoBounds()
	self.TargetAngleY = math.clamp(self.TargetAngleY, self.MinY, self.MaxY)
end

function SmoothRotatedCamera:GetPastBounds(angle)
	if angle < self.MinY then
		return angle - self.MinY
	elseif angle > self.MaxY then
		return angle - self.MaxY
	else
		return 0
	end
end

function SmoothRotatedCamera:__index(index)
	if index == "State" or index == "CameraState" or index == "Camera" then
		local State = CameraState.new()
		State.CFrame = self.CFrame
		return State
	elseif index == "LookVector" then
		return self.Rotation.lookVector
	elseif index == "CFrame" then
		return CFrame.Angles(0, self.RenderAngleXZ, 0) * CFrame.Angles(self.RenderAngleY, 0, 0)
	elseif index == "TargetCFrame" then
		return CFrame.Angles(0, self.TargetAngleXZ, 0) * CFrame.Angles(self.TargetAngleY, 0, 0)
	elseif index == "RenderAngleY" then
		local angle = self.AngleY
		local Past = self:GetPastBounds(angle)

		local TimesOverBounds = math.abs(Past) / self.ZoomGiveY
		local Scale = (1 - 0.25 ^ math.abs(TimesOverBounds))

		if Past < 0 then
			return self.MinY - self.ZoomGiveY*Scale
		elseif Past > 0 then
			return self.MaxY + self.ZoomGiveY*Scale
		else
			return angle
		end
	elseif index == "RenderAngleX" or index == "RenderAngleXZ" then
		return self.AngleX
	elseif index == "AngleY" then
		return self.SpringY.Value
	elseif index == "AngleX" or index == "AngleXZ" then
		return self.SpringX.Value
	elseif index == "TargetAngleY" then
		return self.SpringY.Target
	elseif index == "TargetAngleX" or index == "TargetAngleXZ" then
		return self.SpringX.Target
	elseif index == "SpeedAngleY" then
		return self.SpringX.Speed
	elseif index == "MaxY" then
		return self._MaxY
	elseif index == "MinY" then
		return self._MinY
	elseif index == "ZoomGiveY" then
		return self._ZoomGiveY
	else
		return SmoothRotatedCamera[index]
	end
end

return SmoothRotatedCamera