local M={Emitter}local N={[CollisionMode.None]=function(N,Z)return workspace:FindPartOnRayWithIgnoreList(N,Z and{Emitter;Players.LocalPlayer and Players.LocalPlayer.Character}or M)end;[CollisionMode.Blacklist]=function(M)return workspace:FindPartOnRayWithIgnoreList(M,collisionList)end;[CollisionMode.Whitelist]=function(M)return workspace:FindPartOnRayWithWhitelist(M,collisionList)end,[CollisionMode.Function]=function(N)local Z=N.Origin+N.Direction while N.Direction.magnitude>.001 do local L,m,E,q=workspace:FindPartOnRayWithIgnoreList(N,M)if not L or collisionFunc(L)then return L,m,E,q end local R=m+N.Direction.Unit*.001 N=Ray.new(R,Z-R)end end}local Z=N[collisionMode]local function L()local M=Random.new()local N=true local L=RAIN_UPDATE_PERIOD table.insert(connections,RunService.RenderStepped:connect(function()local M,m=Z(Ray.new(workspace.CurrentCamera.CFrame.p,-rainDirection*RAIN_SCANHEIGHT),true)if(not currentCeiling or workspace.CurrentCamera.CFrame.p.y<=currentCeiling)and not M then if volumeTarget<1 and not disabled then volumeTarget=1;(TweenService:Create(Sound,TweenInfo.new(.5),{Volume=1})):Play()end L=RAIN_UPDATE_PERIOD local M=math.abs(workspace.CurrentCamera.CFrame.lookVector:Dot(rainDirection))local Z=workspace.CurrentCamera.CFrame.p local m=workspace.CurrentCamera.CFrame.lookVector:Cross(-rainDirection)m=m.magnitude>.001 and m.unit or-rainDirection local E=(rainDirection:Cross(m)).unit Emitter.Size=v3(RAIN_EMITTER_DIM_DEFAULT,RAIN_EMITTER_DIM_DEFAULT,RAIN_EMITTER_DIM_DEFAULT+(1-M)*(RAIN_EMITTER_DIM_MAXFORWARD-RAIN_EMITTER_DIM_DEFAULT))Emitter.CFrame=(CFrame.new(Z.x,Z.y,Z.z,m.x,-rainDirection.x,E.x,m.y,-rainDirection.y,E.y,m.z,-rainDirection.z,E.z)+(((1-M)*workspace.CurrentCamera.CFrame.lookVector)*Emitter.Size.Z)/3)-(M*rainDirection)*RAIN_EMITTER_UP_MODIFIER Emitter.RainStraight.Enabled=true Emitter.RainTopDown.Enabled=true N=false else Emitter.RainStraight.Enabled=false Emitter.RainTopDown.Enabled=false N=true end end))local m=RunService:IsRunning()and RunService.Stepped or RunService.RenderStepped table.insert(connections,m:connect(function()L=L+1 if L>=RAIN_UPDATE_PERIOD then local M=math.abs(workspace.CurrentCamera.CFrame.lookVector:Dot(rainDirection))local m=NumberSequence.new({NSK010;NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T1,(1-M)*straightLowAlpha+M,0),NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T2,(1-M)*straightLowAlpha+M,0),NSK110})local E=NumberSequence.new({NSK010,NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T1,M*topdownLowAlpha+(1-M),0);NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T2,M*topdownLowAlpha+(1-M),0),NSK110})local q=workspace.Camera.CFrame:inverse()*(workspace.Camera.CFrame.p-rainDirection)local R=NumberRange.new(math.deg(math.atan2(-q.x,q.y)))if N then for M,N in pairs(rainAttachments)do N.RainStraight.Transparency=m N.RainStraight.Rotation=R N.RainTopDown.Transparency=E end if not disabled then local M=0 if not currentCeiling or workspace.CurrentCamera.CFrame.p.y<=currentCeiling then local N=RAIN_VOLUME_SCAN_RADIUS local L=-rainDirection*RAIN_SCANHEIGHT for M=1,#volumeScanGrid,1 do if not Z(Ray.new(workspace.CurrentCamera.CFrame*volumeScanGrid[M],L),true)then N=volumeScanGrid[M].magnitude break end end M=1-N/RAIN_VOLUME_SCAN_RADIUS end if math.abs(M-volumeTarget)>.01 then volumeTarget=M;(TweenService:Create(Sound,TweenInfo.new(1),{Volume=volumeTarget})):Play()end end else Emitter.RainStraight.Transparency=m Emitter.RainStraight.Rotation=R Emitter.RainTopDown.Transparency=E end L=0 end local m=workspace.CurrentCamera.CFrame.p local E=workspace.CurrentCamera.CFrame.lookVector:Cross(-rainDirection)E=E.magnitude>.001 and E.unit or-rainDirection local q=(rainDirection:Cross(E)).unit local R=CFrame.new(m.x,m.y,m.z,E.x,-rainDirection.x,q.x,E.y,-rainDirection.y,q.y,E.z,-rainDirection.z,q.z)local n=rainDirection*RAIN_OCCLUDECHECK_SCAN_Y for L=1,numSplashes,1 do local m=splashAttachments[L]local E=rainAttachments[L]local q=M:NextNumber(RAIN_OCCLUDECHECK_OFFSET_XZ_MIN,RAIN_OCCLUDECHECK_OFFSET_XZ_MAX)local F=M:NextNumber(RAIN_OCCLUDECHECK_OFFSET_XZ_MIN,RAIN_OCCLUDECHECK_OFFSET_XZ_MAX)local j,Q,e=Z(Ray.new(R*v3(q,RAIN_OCCLUDECHECK_OFFSET_Y,F),n))if j then m.Position=Q+e*RAIN_SPLASH_CORRECTION_Y m.RainSplash:Emit(1)if N then local M=Q-rainDirection*RAIN_SPLASH_STRAIGHT_OFFSET_Y if currentCeiling and(M.Y>currentCeiling and rainDirection.Y<0)then M=M+(rainDirection*(currentCeiling-M.Y))/rainDirection.Y end E.CFrame=(R-R.p)+M E.RainStraight:Emit(intensityOccludedRain)E.RainTopDown:Emit(intensityOccludedRain)end elseif N then local N=R*v3(q,M:NextNumber(RAIN_NOSPLASH_STRAIGHT_OFFSET_Y_MIN,RAIN_NOSPLASH_STRAIGHT_OFFSET_Y_MAX),F)if currentCeiling and(N.Y>currentCeiling and rainDirection.Y<0)then N=N+(rainDirection*(currentCeiling-N.Y))/rainDirection.Y end E.CFrame=(R-R.p)+N E.RainStraight:Emit(intensityOccludedRain)E.RainTopDown:Emit(intensityOccludedRain)end end end))end local function m()if#connections>0 then for M,N in pairs(connections)do N:disconnect()end connections={}end end local function E(M)volumeTarget=0 local N=TweenService:Create(Sound,M,{Volume=0})N.Completed:connect(function(M)if M==Enum.PlaybackState.Completed then Sound:Stop()end N:Destroy()end)N:Play()end local function q()m()Emitter.RainStraight.Enabled=false Emitter.RainTopDown.Enabled=false Emitter.Size=MIN_SIZE if not disabled then E(TweenInfo.new(RAIN_SOUND_FADEOUT_TIME))end end


local ignoreEmitterList = { Emitter }

local raycastFunctions = {
	[CollisionMode.None] = function(ray, ignoreCharacter)
		return workspace:FindPartOnRayWithIgnoreList(ray, ignoreCharacter and {Emitter, Players.LocalPlayer and Players.LocalPlayer.Character} or ignoreEmitterList)
	end;
	[CollisionMode.Blacklist] = function(ray)
		return workspace:FindPartOnRayWithIgnoreList(ray, collisionList)
	end;
	[CollisionMode.Whitelist] = function(ray)
		return workspace:FindPartOnRayWithWhitelist(ray, collisionList)
	end;
	[CollisionMode.Function] = function(ray)
		local destination = ray.Origin + ray.Direction
		-- draw multiple raycasts concatenated to each other until no hit / valid hit found
		while ray.Direction.magnitude > 0.001 do
			local part, pos, norm, mat = workspace:FindPartOnRayWithIgnoreList(ray, ignoreEmitterList)
			if not part or collisionFunc(part) then
				return part, pos, norm, mat
			end
			local start = pos + ray.Direction.Unit * 0.001
			ray = Ray.new(start, destination - start)
		end
	end;
}
local raycast = raycastFunctions[collisionMode]

local function connectLoop()
	
	local rand = Random.new()
	
	local inside = true					-- Whether camera is currently in a spot occluded from the sky
	local frame = RAIN_UPDATE_PERIOD	-- Frame counter, and force update cycle right now
	
	-- Update Emitter on RenderStepped since it needs to be synced to Camera
	table.insert(connections, RunService.RenderStepped:connect(function()
		
		-- Check if camera is outside or inside
		local part, position = raycast(Ray.new(workspace.CurrentCamera.CFrame.p, -rainDirection * RAIN_SCANHEIGHT), true)
		
		if (not currentCeiling or workspace.CurrentCamera.CFrame.p.y <= currentCeiling) and not part then
			
			-- Camera is outside and under ceiling
				
			if volumeTarget < 1 and not disabled then
				volumeTarget = 1
				TweenService:Create(Sound, TweenInfo.new(.5), {Volume = 1}):Play()
			end
			
			frame = RAIN_UPDATE_PERIOD
			
			local t = math.abs(workspace.CurrentCamera.CFrame.lookVector:Dot(rainDirection))
			
			local center = workspace.CurrentCamera.CFrame.p
			local right = workspace.CurrentCamera.CFrame.lookVector:Cross(-rainDirection)
			right = right.magnitude > 0.001 and right.unit or -rainDirection
			local forward = rainDirection:Cross(right).unit
			
			Emitter.Size = v3(
				RAIN_EMITTER_DIM_DEFAULT,
				RAIN_EMITTER_DIM_DEFAULT,
				RAIN_EMITTER_DIM_DEFAULT + (1 - t)*(RAIN_EMITTER_DIM_MAXFORWARD - RAIN_EMITTER_DIM_DEFAULT)
			)
			
			Emitter.CFrame =
				CFrame.new(
					center.x, center.y, center.z,
					right.x, -rainDirection.x, forward.x,
					right.y, -rainDirection.y, forward.y,
					right.z, -rainDirection.z, forward.z
				)
				+ (1 - t) * workspace.CurrentCamera.CFrame.lookVector * Emitter.Size.Z/3
				- t * rainDirection * RAIN_EMITTER_UP_MODIFIER
			
			Emitter.RainStraight.Enabled = true
			Emitter.RainTopDown.Enabled = true
			
			inside = false
			
		else
			
			-- Camera is inside / above ceiling
			
			Emitter.RainStraight.Enabled = false
			Emitter.RainTopDown.Enabled = false
		
			inside = true
			
		end
		
	end))

	-- Do the other effects on Stepped
	local signal = RunService:IsRunning() and RunService.Stepped or RunService.RenderStepped
	table.insert(connections, signal:connect(function()
		
		frame = frame + 1
		
		-- Only do some updates once every few frames
		if frame >= RAIN_UPDATE_PERIOD then
			
			-- Measure of how much camera is facing down (0-1)
			local t = math.abs(workspace.CurrentCamera.CFrame.lookVector:Dot(rainDirection))
			
			-- More looking down = see straight particles less and see top-down particles more
			local straightSequence = NumberSequence.new {
				NSK010;
				NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T1, (1 - t)*straightLowAlpha + t, 0);
				NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T2, (1 - t)*straightLowAlpha + t, 0);
				NSK110;
			}
			local topdownSequence = NumberSequence.new {
				NSK010;
				NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T1, t*topdownLowAlpha + (1 - t), 0);
				NumberSequenceKeypoint.new(RAIN_TRANSPARENCY_T2, t*topdownLowAlpha + (1 - t), 0);
				NSK110;
			}
			
			-- Find desired rotation for the straight rain particles
			local mapped = workspace.Camera.CFrame:inverse() * (workspace.Camera.CFrame.p - rainDirection)
			local straightRotation = NumberRange.new(math.deg(math.atan2(-mapped.x, mapped.y)))
			
			if inside then
				
				-- Update emitter properties
				for _,v in pairs(rainAttachments) do
					v.RainStraight.Transparency = straightSequence
					v.RainStraight.Rotation = straightRotation
					v.RainTopDown.Transparency = topdownSequence
				end
				
				if not disabled then
					
					-- Only do occluded volume check if not moving towards disabled state
					
					local volume = 0
					
					if (not currentCeiling or workspace.CurrentCamera.CFrame.p.y <= currentCeiling) then
						
						-- Check how far away camera is from a space open to the sky using volume scan grid
						
						local minDistance = RAIN_VOLUME_SCAN_RADIUS
						local rayDirection = -rainDirection * RAIN_SCANHEIGHT
						
						for i = 1, #volumeScanGrid do -- In order, so first hit is closest
							if not raycast(Ray.new(workspace.CurrentCamera.CFrame * volumeScanGrid[i], rayDirection), true) then
								minDistance = volumeScanGrid[i].magnitude
								break
							end
						end
						
						-- Volume is inversely proportionate to minimum distance
						volume = 1 - minDistance / RAIN_VOLUME_SCAN_RADIUS
						
					end
					
					if math.abs(volume - volumeTarget) > .01 then
						-- Value is sufficiently different from previous target, overwrite it
						volumeTarget = volume
						TweenService:Create(Sound, TweenInfo.new(1), {Volume = volumeTarget}):Play()
					end
					
				end
				
			else
				
				-- Update emitter properties
				Emitter.RainStraight.Transparency = straightSequence
				Emitter.RainStraight.Rotation = straightRotation
				Emitter.RainTopDown.Transparency = topdownSequence
				
			end
			
			-- Reset frame counter
			frame = 0
			
		end
		
		local center = workspace.CurrentCamera.CFrame.p
		local right = workspace.CurrentCamera.CFrame.lookVector:Cross(-rainDirection)
		right = right.magnitude > 0.001 and right.unit or -rainDirection
		local forward = rainDirection:Cross(right).unit
		local transform = CFrame.new(
			center.x, center.y, center.z,
			right.x, -rainDirection.x, forward.x,
			right.y, -rainDirection.y, forward.y,
			right.z, -rainDirection.z, forward.z
		)
		local rayDirection = rainDirection * RAIN_OCCLUDECHECK_SCAN_Y
		
		-- Splash and occlusion effects
		for i = 1, numSplashes do
			
			local splashAttachment = splashAttachments[i]
			local rainAttachment = rainAttachments[i]
			
			-- Sample random splash position
			local x = rand:NextNumber(RAIN_OCCLUDECHECK_OFFSET_XZ_MIN, RAIN_OCCLUDECHECK_OFFSET_XZ_MAX)
			local z = rand:NextNumber(RAIN_OCCLUDECHECK_OFFSET_XZ_MIN, RAIN_OCCLUDECHECK_OFFSET_XZ_MAX)
			local part, position, normal = raycast(Ray.new(transform * v3(x, RAIN_OCCLUDECHECK_OFFSET_Y, z), rayDirection))
			
			if part then
				
				-- Draw a splash at hit
				splashAttachment.Position = position + normal * RAIN_SPLASH_CORRECTION_Y
				splashAttachment.RainSplash:Emit(1)
				
				if inside then
					
					-- Draw occlusion rain particles a little bit above the splash position
					local corrected = position - rainDirection * RAIN_SPLASH_STRAIGHT_OFFSET_Y
					if currentCeiling and corrected.Y > currentCeiling and rainDirection.Y < 0 then
						corrected = corrected + rainDirection * (currentCeiling - corrected.Y) / rainDirection.Y
					end
					rainAttachment.CFrame = transform - transform.p + corrected
					rainAttachment.RainStraight:Emit(intensityOccludedRain)
					rainAttachment.RainTopDown:Emit(intensityOccludedRain)
					
				end
				
			elseif inside then
				
				-- Draw occlusion rain particles on the XZ-position at around the camera's height
				local corrected = transform * v3(x, rand:NextNumber(RAIN_NOSPLASH_STRAIGHT_OFFSET_Y_MIN, RAIN_NOSPLASH_STRAIGHT_OFFSET_Y_MAX), z)
				if currentCeiling and corrected.Y > currentCeiling and rainDirection.Y < 0 then
					corrected = corrected + rainDirection * (currentCeiling - corrected.Y) / rainDirection.Y
				end
				rainAttachment.CFrame = transform - transform.p + corrected
				rainAttachment.RainStraight:Emit(intensityOccludedRain)
				rainAttachment.RainTopDown:Emit(intensityOccludedRain)
				
			end
			
		end
		
	end))

end

local function disconnectLoop()
	-- If present, disconnect all RunService connections
	if #connections > 0 then
		for _,v in pairs(connections) do
			v:disconnect()
		end
		connections = {}
	end
end

local function disableSound(tweenInfo)
	
	-- Tween the rain sound to be mute over a given easing function
	volumeTarget = 0
	local tween = TweenService:Create(Sound, tweenInfo, {Volume = 0})
	tween.Completed:connect(function(state)
		if state == Enum.PlaybackState.Completed then
			Sound:Stop()
		end
		tween:Destroy()
	end)
	tween:Play()
	
end

local function disable()
	
	disconnectLoop()
	
	-- Hide Emitter
	Emitter.RainStraight.Enabled = false
	Emitter.RainTopDown.Enabled = false
	Emitter.Size = MIN_SIZE
	
	-- Disable sound now if not tweened into disabled state beforehand
	if not disabled then
		disableSound(TweenInfo.new(RAIN_SOUND_FADEOUT_TIME))
	end
	
end
