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


local ignoreEmitterList = { Emitter }

local raycastFunctions = {
	[CollisionMode.None] = function(ray, ignoreCharacter)
		return nil-- 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
	local updateTick = .05
	local lastUpdate = 0
	table.insert(connections, RunService.RenderStepped:connect(function()
		if tick()-lastUpdate >= updateTick then
			lastUpdate = tick()
		-- 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
	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
