local H=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 A=game:GetService("\080\108\097\121\101\114\115")local Q=game:GetService("\087\111\114\107\115\112\097\099\101")local u=game:GetService("\082\117\110\083\101\114\118\105\099\101")local T=math.sin local V=math.sqrt local K=math.atan2 local x=Vector2.new local h=Vector3.new local U=UDim2.new local z={}z._maxParticles=400 z._windSpeed=10 local function j(H)local A=Instance.new("\070\114\097\109\101")A.BorderSizePixel=0 A.Name=H A.Archivable=false return A end function z.Init(T,Q)T._remoteEvent=H.GetRemoteEvent("\080\097\114\116\105\099\108\101\069\118\101\110\116\068\105\115\116\114\105\098\117\116\111\114")T._screen=Q or error("\078\111 \115\099\114\101\101\110")T._player=A.LocalPlayer or error("\078\111 \076\111\099\097\108\080\108\097\121\101\114")T._remoteEvent.OnClientEvent:Connect(function(...)T:ParticleNew(...)end)T._lastUpdateTime=tick()T._particleCount=0 T._particles={}T._particleFrames={}for H=1,T._maxParticles,1 do T._particleFrames[H]=j("\095\112\097\114\116\105\099\108\101")end u.Heartbeat:Connect(function()debug.profilebegin("\080\097\114\116\105\099\108\101\085\112\100\097\116\101")T:_update()debug.profileend()end)return T end function z.Remove(A,H)if A._particles[H]then A._particles[H]=nil A._particleCount=A._particleCount-1 end end function z.Add(A,H)if A._particles[H]then return end H.Position=H.Position or Vector3.new()H.Velocity=H.Velocity or Vector3.new()H.Size=H.Size or x(.2,.2)H.Bloom=H.Bloom or x()H.Gravity=H.Gravity or Vector3.new()H.Color=H.Color or Color3.new(1,1,1)H.Transparency=H.Transparency or.5 if H.Global then local Q=H.Function local u=H.RemoveOnCollision H.Global=nil H.Function=nil H.RemoveOnCollision=H.RemoveOnCollision and true or nil A._remoteEvent:FireServer(H)H.Function=Q H.RemoveOnCollision=u end H.LifeTime=H.LifeTime and H.LifeTime+tick()if A._particleCount>A._maxParticles then A._particles[next(A._particles)]=nil else A._particleCount=A._particleCount+1 end A._particles[H]=H end local function C(H,A)local Q,u,V=A.x+A.y,A.y+A.z,A.z+A.x return h((T(u+H*2)+T(u+H))/2+T((u+H)/10)/2,(T(V+H*2)+T(V+H))/2+T((V+H)/10)/2,(T(Q+H*2)+T(Q+H))/2+T((Q+H)/10)/2)end function z._updatePosVel(u,H,A,Q)H.Position=H.Position+H.Velocity*A local T if H.WindResistance then T=(C(Q,H.Position)*u._windSpeed-H.Velocity)*H.WindResistance else T=h()end H.Velocity=H.Velocity+(H.Gravity+T)*A end function z._updateParticle(T,H,A,u)if H.LifeTime-A<=0 then return false end if H.Function then H.Function(H,u,A)end local V=H.Position T:_updatePosVel(H,u,A)if not H.RemoveOnCollision then return true end local K=H.Position-V local x=K.Magnitude if x>999 then K=K*(999/x)end local h=Ray.new(V,K)local U,z,j,C=Q:FindPartOnRay(h,T._player.Character,true)if not U then return true end if type(H.RemoveOnCollision)=="\102\117\110\099\116\105\111\110"then if not H.RemoveOnCollision(H,U,z,j,C)then return false end else return false end return true end function z._update(H)local A=tick()local Q=A-H._lastUpdateTime H._lastUpdateTime=A local u={}for T in pairs(H._particles)do if not H:_updateParticle(T,A,Q)then u[T]=true end end for A in pairs(u)do H:Remove(A)end H:_updateRender()end function z._updateRender(H)local A=Q.CurrentCamera H:_updateScreenInfo(A)local u=A.CFrame:inverse()local T=A.CFrame.p local V,K=next(H._particleFrames)for A in pairs(H._particles)do if H:_particleRender(T,u,K,A)then K.Parent=H._screen V,K=next(H._particleFrames,V)end end while V and K.Parent do K.Parent=nil V,K=next(H._particleFrames,V)end end function z._particleRender(h,H,A,u,T)local z=A*T.Position local j=T._lastScreenPosition if not(z.z<-1 and j)then if z.z>0 then T._lastScreenPosition=nil else local H=z/z.z T._lastScreenPosition=Vector2.new((.5-H.x/h._planeSizeX)*h._screenSizeX,(.5+H.y/h._planeSizeY)*h._screenSizeY)end return false end local C=z/z.z local o=T.Bloom local E=T.Transparency local w=(.5-C.x/h._planeSizeX)*h._screenSizeX local p=(.5+C.y/h._planeSizeY)*h._screenSizeY local d=((-T.Size.y/z.z)*h._screenSizeY)/h._planeSizeY local l=((-T.Size.x/z.z)*h._screenSizeY)/h._planeSizeY+o.x local q,a=w-j.x,p-j.y local G=(d+V(q*q+a*a))+o.y T._lastScreenPosition=x(w,p)if T.Occlusion then local A=T.Position-H local u=A.Magnitude if u>999 then A=A*(999/u)end if Q:FindPartOnRay(Ray.new(H,A),h._player.Character,true)then return false end end u.Position=U(0,((w+j.x)-l)/2,0,((p+j.y)-G)/2)u.Size=U(0,l,0,G)u.Rotation=90+K(a,q)*57.295779513082 u.BackgroundColor3=T.Color u.BackgroundTransparency=E+(1-E)*(1-d/G)return true end function z._updateScreenInfo(A,H)A._screenSizeX=A._screen.AbsoluteSize.x A._screenSizeY=A._screen.AbsoluteSize.y A._planeSizeY=2*math.tan(H.FieldOfView*.0087266462599716)A._planeSizeX=(A._planeSizeY*A._screenSizeX)/A._screenSizeY end return z
--- Legacy code written by AxisAngles to simulate particles with Guis
-- @module ParticleEngine

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

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

local sin = math.sin
local sqrt = math.sqrt
local atan2 = math.atan2
local v2 = Vector2.new
local v3 = Vector3.new
local ud2 = UDim2.new

local ParticleEngineClient = {}

-- 3000 if you have a good computer
ParticleEngineClient._maxParticles = 400

-- Speed of wind to simulate
ParticleEngineClient._windSpeed = 10

local function newFrame(name)
	local frame = Instance.new("Frame")
	frame.BorderSizePixel = 0
	frame.Name = name
	frame.Archivable = false
	return frame
end

function ParticleEngineClient:Init(screen)
	self._remoteEvent = require.GetRemoteEvent("ParticleEventDistributor")
	self._screen = screen or error("No screen")
	self._player = Players.LocalPlayer or error("No LocalPlayer")

	self._remoteEvent.OnClientEvent:Connect(function(...)
		self:ParticleNew(...)
	end)

	self._lastUpdateTime = tick()
	self._particleCount = 0
	self._particles = {}
	self._particleFrames = {}

	for i=1, self._maxParticles do
		self._particleFrames[i] = newFrame("_particle")
	end

	RunService.Heartbeat:Connect(function()
		debug.profilebegin("ParticleUpdate")
		self:_update()
		debug.profileend()
	end)

	return self
end

--- Removes a particle
function ParticleEngineClient:Remove(p)
	if self._particles[p] then
		self._particles[p] = nil
		self._particleCount = self._particleCount - 1
	end
end

-- Adds a new particle
-- @param p PropertiesTable
--[[
{
	Position = Vector3

	-- Optional
	Global = Bool
	Velocity = Vector3
	Gravity = Vector3
	WindResistance  = Number
	LifeTime = Number
	Size = Vector2
	Bloom = Vector2
	Transparency = Number
	Color = Color3
	Occlusion = Bool
	RemoveOnCollision = function(BasePart Hit, Vector3 Position))
	Function = function(Table ParticleProperties, Number dt, Number t)
}
--]]
function ParticleEngineClient:Add(p)
	if self._particles[p] then
		return
	end

	p.Position = p.Position or Vector3.new()
	p.Velocity = p.Velocity or Vector3.new()
	p.Size = p.Size or v2(0.2,0.2)
	p.Bloom = p.Bloom or v2()
	p.Gravity = p.Gravity or Vector3.new()
	p.Color = p.Color or Color3.new(1,1,1)
	p.Transparency = p.Transparency or 0.5

	if p.Global then
		local func = p.Function
		local removeOnCollision = p.RemoveOnCollision
		p.Global = nil
		p.Function = nil
		p.RemoveOnCollision = p.RemoveOnCollision and true or nil

		self._remoteEvent:FireServer(p)

		p.Function = func
		p.RemoveOnCollision = removeOnCollision
	end

	p.LifeTime = p.LifeTime and p.LifeTime+tick()

	if self._particleCount > self._maxParticles then
		self._particles[next(self._particles)] = nil
	else
		self._particleCount = self._particleCount + 1
	end

	self._particles[p] = p
end

-- @param p Position
local function particleWind(t, p)
	local xy,yz,zx=p.x+p.y,p.y+p.z,p.z+p.x
	return v3(
		(sin(yz+t*2)+sin(yz+t))/2+sin((yz+t)/10)/2,
		(sin(zx+t*2)+sin(zx+t))/2+sin((zx+t)/10)/2,
		(sin(xy+t*2)+sin(xy+t))/2+sin((xy+t)/10)/2
	)
end

-- @param p ParticleProperties
-- @param dt ChangeInTime
function ParticleEngineClient:_updatePosVel(p, dt, t)
	p.Position = p.Position + p.Velocity*dt

	local wind
	if p.WindResistance then
		wind = (particleWind(t, p.Position)*self._windSpeed - p.Velocity)*p.WindResistance
	else
		wind = v3()
	end

	p.Velocity = p.Velocity + (p.Gravity + wind)*dt
end

--- Handles both priority and regular particles
-- @return boolean alive, true if still fine
function ParticleEngineClient:_updateParticle(particle, t, dt)
	if particle.LifeTime - t <= 0 then
		return false
	end

	-- Call this first, so any changes are reflected immediately
	if particle.Function then
		particle.Function(particle, dt, t)
	end

	local lastPos = particle.Position
	self:_updatePosVel(particle, dt, t)

	if not particle.RemoveOnCollision then
		return true
	end

	local displacement = particle.Position - lastPos
	local distance = displacement.Magnitude
	if distance > 999 then
		displacement = displacement * (999/distance)
	end

	local ray = Ray.new(lastPos, displacement)
	local hit, position, normal, material = Workspace:FindPartOnRay(ray, self._player.Character, true)
	if not hit then
		return true
	end

	if type(particle.RemoveOnCollision) == "function" then
		if not particle.RemoveOnCollision(particle, hit, position, normal, material) then
			return false
		end
	else
		return false
	end

	return true
end

function ParticleEngineClient:_update()
	local t = tick()
	local dt = t - self._lastUpdateTime
	self._lastUpdateTime = t

	local toRemove = {}

	-- Update particles
	for particle in pairs(self._particles) do
		if not self:_updateParticle(particle, t, dt) then
			toRemove[particle] = true
		end
	end

	-- Remove tagged particles
	for particle in pairs(toRemove) do
		self:Remove(particle)
	end

	-- Update render
	self:_updateRender()
end

function ParticleEngineClient:_updateRender()
	local camera = Workspace.CurrentCamera
	self:_updateScreenInfo(camera)

	local cameraInverse = camera.CFrame:inverse()
	local cameraPosition = camera.CFrame.p

	local frameIndex, frame = next(self._particleFrames)
	for particle in pairs(self._particles) do
		if self:_particleRender(cameraPosition, cameraInverse, frame, particle) then
			frame.Parent = self._screen
			frameIndex, frame = next(self._particleFrames, frameIndex)
		end
	end

	-- Cleanup remaining frames that are parented
	while frameIndex and frame.Parent do
		frame.Parent = nil
		frameIndex, frame = next(self._particleFrames, frameIndex)
	end
end

-- @param f frame
-- @param cameraInverse The inverse camera cframe
function ParticleEngineClient:_particleRender(cameraPosition, cameraInverse, frame, particle)
	local rp = cameraInverse*particle.Position
	local lsp = particle._lastScreenPosition

	if not (rp.z < -1 and lsp) then
		if rp.z > 0 then
			particle._lastScreenPosition = nil
		else
			local sp = rp/rp.z
			particle._lastScreenPosition = Vector2.new(
				(0.5-sp.x/self._planeSizeX)*self._screenSizeX,
				(0.5+sp.y/self._planeSizeY)*self._screenSizeY)
		end

		return false
	end

	local sp = rp/rp.z
	local b = particle.Bloom
	local bgt = particle.Transparency
	local px = (0.5-sp.x/self._planeSizeX)*self._screenSizeX
	local py = (0.5+sp.y/self._planeSizeY)*self._screenSizeY
	local preSizeY = -particle.Size.y/rp.z*self._screenSizeY/self._planeSizeY
	local sx = -particle.Size.x/rp.z*self._screenSizeY/self._planeSizeY + b.x
	local rppx,rppy = px-lsp.x,py-lsp.y
	local sy = preSizeY+sqrt(rppx*rppx+rppy*rppy) + b.y
	particle._lastScreenPosition = v2(px, py)

	if particle.Occlusion then
		local vec = particle.Position-cameraPosition
		local mag = vec.Magnitude
		if mag > 999 then
			vec = vec * (999/mag)
		end

		if Workspace:FindPartOnRay(Ray.new(cameraPosition, vec), self._player.Character, true) then
			return false
		end
	end

	frame.Position = ud2(0, (px+lsp.x-sx)/2, 0, (py+lsp.y-sy)/2)
	frame.Size = ud2(0, sx, 0,sy)
	frame.Rotation = 90+atan2(rppy,rppx)*57.295779513082
	frame.BackgroundColor3 = particle.Color
	frame.BackgroundTransparency = bgt+(1-bgt)*(1 - preSizeY/sy)

	return true
end

function ParticleEngineClient:_updateScreenInfo(camera)
	self._screenSizeX = self._screen.AbsoluteSize.x
	self._screenSizeY = self._screen.AbsoluteSize.y
	self._planeSizeY = 2*math.tan(camera.FieldOfView*0.0087266462599716)
	self._planeSizeX = self._planeSizeY*self._screenSizeX/self._screenSizeY
end

return ParticleEngineClient