local K={}K.__index=K local q=debug.profilebegin local H=debug.profileend local r="\067\097\109\101\114\097\083\104\097\107\101\114\085\112\100\097\116\101"local G=Vector3.new local U=CFrame.new local v=CFrame.Angles local M=math.rad local c=G()local P=require(script.CameraShakeInstance)local X=P.CameraShakeState local Y=G(.15,.15,.15)local a=G(1,1,1)K.CameraShakeInstance=P function K.new(q,H)assert(type(q)=="\110\117\109\098\101\114","\082\101\110\100\101\114\080\114\105\111\114\105\116\121 \109\117\115\116 \098\101 \097 \110\117\109\098\101\114 \040\101\046\103\046\058 \069\110\117\109\046\082\101\110\100\101\114\080\114\105\111\114\105\116\121\046\067\097\109\101\114\097\046\086\097\108\117\101\041")assert(type(H)=="\102\117\110\099\116\105\111\110","\067\097\108\108\098\097\099\107 \109\117\115\116 \098\101 \097 \102\117\110\099\116\105\111\110")local r=setmetatable({_running=false;_renderName="\067\097\109\101\114\097\083\104\097\107\101\114",_renderPriority=q,_posAddShake=c,_rotAddShake=c,_camShakeInstances={},_removeInstances={},_callback=H},K)return r end function K.Start(K)if K._running then return end K._running=true local G=K._callback;(game:GetService("\082\117\110\083\101\114\118\105\099\101")):BindToRenderStep(K._renderName,K._renderPriority,function(U)q(r)local v=K:Update(U)H()G(v)end)end function K.Stop(K)if not K._running then return end;(game:GetService("\082\117\110\083\101\114\118\105\099\101")):UnbindFromRenderStep(K._renderName)K._running=false end function K.StopSustained(q,K)for q,H in pairs(q._camShakeInstances)do if H.fadeOutDuration==0 then H:StartFadeOut(K or H.fadeInDuration)end end end function K.Update(q,K)K=math.clamp(K,0,.6)local H=c local r=c local G=q._camShakeInstances for U=1,#G,1 do local v=G[U]local M=v:GetState()if M==X.Inactive and v.DeleteOnInactive then q._removeInstances[#q._removeInstances+1]=U elseif M~=X.Inactive then local q=v:UpdateShake(K)H=H+q*v.PositionInfluence r=r+q*v.RotationInfluence end end for K=#q._removeInstances,1,-1 do local H=q._removeInstances[K]table.remove(G,H)q._removeInstances[K]=nil end return(U(H)*v(0,M(r.Y),0))*v(M(r.X),0,M(r.Z))end function K.Shake(q,K)assert(type(K)=="\116\097\098\108\101"and K._camShakeInstance,"\083\104\097\107\101\073\110\115\116\097\110\099\101 \109\117\115\116 \098\101 \111\102 \116\121\112\101 \067\097\109\101\114\097\083\104\097\107\101\073\110\115\116\097\110\099\101")q._camShakeInstances[#q._camShakeInstances+1]=K return K end function K.ShakeSustain(q,K)assert(type(K)=="\116\097\098\108\101"and K._camShakeInstance,"\083\104\097\107\101\073\110\115\116\097\110\099\101 \109\117\115\116 \098\101 \111\102 \116\121\112\101 \067\097\109\101\114\097\083\104\097\107\101\073\110\115\116\097\110\099\101")q._camShakeInstances[#q._camShakeInstances+1]=K K:StartFadeIn(K.fadeInDuration)return K end function K.ShakeOnce(v,K,q,H,r,G,U)local M=P.new(K,q,H,r)M.PositionInfluence=typeof(G)=="\086\101\099\116\111\114\051"and G or Y M.RotationInfluence=typeof(U)=="\086\101\099\116\111\114\051"and U or a v._camShakeInstances[#v._camShakeInstances+1]=M return M end function K.StartShake(U,K,q,H,r,G)local v=P.new(K,q,H)v.PositionInfluence=typeof(r)=="\086\101\099\116\111\114\051"and r or Y v.RotationInfluence=typeof(G)=="\086\101\099\116\111\114\051"and G or a v:StartFadeIn(H)U._camShakeInstances[#U._camShakeInstances+1]=v return v end return K
--[[
	
	CameraShaker.CameraShakeInstance
	
	cameraShaker = CameraShaker.new(renderPriority, callbackFunction)
	
	CameraShaker:Start()
	CameraShaker:Stop()
	CameraShaker:StopSustained([fadeOutTime])
	CameraShaker:Shake(shakeInstance)
	CameraShaker:ShakeSustain(shakeInstance)
	CameraShaker:ShakeOnce(magnitude, roughness [, fadeInTime, fadeOutTime, posInfluence, rotInfluence])
	CameraShaker:StartShake(magnitude, roughness [, fadeInTime, posInfluence, rotInfluence])
	
	
	
	EXAMPLE:
	
		local camShake = CameraShaker.new(Enum.RenderPriority.Camera.Value, function(shakeCFrame)
			camera.CFrame = playerCFrame * shakeCFrame
		end)
		
		camShake:Start()
		
		-- Explosion shake:
		camShake:Shake(CameraShaker.Presets.Explosion)
		
		wait(1)
		
		-- Custom shake:
		camShake:ShakeOnce(3, 1, 0.2, 1.5)

		-- Sustained shake:
		camShake:ShakeSustain(CameraShaker.Presets.Earthquake)

		-- Stop all sustained shakes:
		camShake:StopSustained(1) -- Argument is the fadeout time (defaults to the same as fadein time if not supplied)

		-- Stop only one sustained shake:
		shakeInstance = camShake:ShakeSustain(CameraShaker.Presets.Earthquake)
		wait(2)
		shakeInstance:StartFadeOut(1) -- Argument is the fadeout time
	
	
	NOTE:
	
		This was based entirely on the EZ Camera Shake asset for Unity3D. I was given written
		permission by the developer, Road Turtle Games, to port this to Roblox.
		
		Original asset link: https://assetstore.unity.com/packages/tools/camera/ez-camera-shake-33148

		GitHub repository: https://github.com/Sleitnick/RbxCameraShaker
	
	
--]]

local CameraShaker = {}
CameraShaker.__index = CameraShaker

local profileBegin = debug.profilebegin
local profileEnd = debug.profileend
local profileTag = "CameraShakerUpdate"

local V3 = Vector3.new
local CF = CFrame.new
local ANG = CFrame.Angles
local RAD = math.rad
local v3Zero = V3()

local CameraShakeInstance = require(script.CameraShakeInstance)
local CameraShakeState = CameraShakeInstance.CameraShakeState

local defaultPosInfluence = V3(0.15, 0.15, 0.15)
local defaultRotInfluence = V3(1, 1, 1)

CameraShaker.CameraShakeInstance = CameraShakeInstance

function CameraShaker.new(renderPriority, callback)
	assert(type(renderPriority) == "number", "RenderPriority must be a number (e.g.: Enum.RenderPriority.Camera.Value)")
	assert(type(callback) == "function", "Callback must be a function")

	local self = setmetatable({
		_running = false,
		_renderName = "CameraShaker",
		_renderPriority = renderPriority,
		_posAddShake = v3Zero,
		_rotAddShake = v3Zero,
		_camShakeInstances = {},
		_removeInstances = {},
		_callback = callback,
	}, CameraShaker)

	return self
end

function CameraShaker:Start()
	if self._running then
		return
	end
	self._running = true
	local callback = self._callback
	game:GetService("RunService"):BindToRenderStep(self._renderName, self._renderPriority, function(dt)
		profileBegin(profileTag)
		local cf = self:Update(dt)
		profileEnd()
		callback(cf)
	end)
end

function CameraShaker:Stop()
	if not self._running then
		return
	end
	game:GetService("RunService"):UnbindFromRenderStep(self._renderName)
	self._running = false
end

function CameraShaker:StopSustained(duration)
	for _, c in pairs(self._camShakeInstances) do
		if c.fadeOutDuration == 0 then
			c:StartFadeOut(duration or c.fadeInDuration)
		end
	end
end

function CameraShaker:Update(dt)
	dt = math.clamp(dt, 0, 0.6)

	local posAddShake = v3Zero
	local rotAddShake = v3Zero

	local instances = self._camShakeInstances

	-- Update all instances:
	for i = 1, #instances do
		local c = instances[i]
		local state = c:GetState()

		if state == CameraShakeState.Inactive and c.DeleteOnInactive then
			self._removeInstances[#self._removeInstances + 1] = i
		elseif state ~= CameraShakeState.Inactive then
			local shake = c:UpdateShake(dt)
			posAddShake = posAddShake + (shake * c.PositionInfluence)
			rotAddShake = rotAddShake + (shake * c.RotationInfluence)
		end
	end

	-- Remove dead instances:
	for i = #self._removeInstances, 1, -1 do
		local instIndex = self._removeInstances[i]
		table.remove(instances, instIndex)
		self._removeInstances[i] = nil
	end

	return CF(posAddShake) * ANG(0, RAD(rotAddShake.Y), 0) * ANG(RAD(rotAddShake.X), 0, RAD(rotAddShake.Z))
end

function CameraShaker:Shake(shakeInstance)
	assert(
		type(shakeInstance) == "table" and shakeInstance._camShakeInstance,
		"ShakeInstance must be of type CameraShakeInstance"
	)
	self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
	return shakeInstance
end

function CameraShaker:ShakeSustain(shakeInstance)
	assert(
		type(shakeInstance) == "table" and shakeInstance._camShakeInstance,
		"ShakeInstance must be of type CameraShakeInstance"
	)
	self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
	shakeInstance:StartFadeIn(shakeInstance.fadeInDuration)
	return shakeInstance
end

function CameraShaker:ShakeOnce(magnitude, roughness, fadeInTime, fadeOutTime, posInfluence, rotInfluence)
	local shakeInstance = CameraShakeInstance.new(magnitude, roughness, fadeInTime, fadeOutTime)
	shakeInstance.PositionInfluence = (typeof(posInfluence) == "Vector3" and posInfluence or defaultPosInfluence)
	shakeInstance.RotationInfluence = (typeof(rotInfluence) == "Vector3" and rotInfluence or defaultRotInfluence)
	self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
	return shakeInstance
end

function CameraShaker:StartShake(magnitude, roughness, fadeInTime, posInfluence, rotInfluence)
	local shakeInstance = CameraShakeInstance.new(magnitude, roughness, fadeInTime)
	shakeInstance.PositionInfluence = (typeof(posInfluence) == "Vector3" and posInfluence or defaultPosInfluence)
	shakeInstance.RotationInfluence = (typeof(rotInfluence) == "Vector3" and rotInfluence or defaultRotInfluence)
	shakeInstance:StartFadeIn(fadeInTime)
	self._camShakeInstances[#self._camShakeInstances + 1] = shakeInstance
	return shakeInstance
end

return CameraShaker
