[V2] Sunray Intensity Compensation System

Hey! This is my SIC System— an ongoing project I’ve been working on that adds a bit of a unique feel of immersion to your experience!

Allow me to introduce you to the second version my Sunray Intensity Compensation System!


This system tweaks a SunRaysEffect instance to a specific value every time the camera moves. If the camera is looking closer towards the sun than the previous check, then sunray intensity will be decreased.

The closer you look towards the sun, the less intense sunrays are. This eliminates the issues with high intensity values, while maintaining 99% of control over the sun, sky, clouds, and anything else.

Divider

As of April 13th, 2024, I have updated the LocalScript to version `2.8.5`
This system will inform you of new updates in your Studio Output window upon test playing!
This system will update automatically upon server start. All default settings are now handled in the root server script's attributes section of the properties panel. Further client settings are handled using the systems included API functions.

Check the changelog thread here for new changes; I will continue to keep this updated.

Divider

If there are any better ways to code sections of this script or the entirety of it, do feel free to post a comment. I will gladly adjust the script to add the necessary changes and give credit where credit is due.

This also applies to bugs and any instances of lag being caused by this script. Please let me know if you encounter either of these!


Instructions

•Drop in ServerScriptService

•Edit any necessary settings that you’d like in the SunrayIntensityController.Settings module and the SunrayIntensityController.Presets module
•Edit any necessary default settings in the “SunrayIntensitySystem” server script’s attributes section.

-For custom skyboxes, replace the skies in the CustomSkies folder in the Settings module.
-Daytime skies MUST be named “DaySky” and nighttime “NightSky” in order for the system to successfully locate and differentiate between each sky. (See attached photo)
image

The above is no longer necessary. As the system now checks for existing skyboxes and uses the Roblox default night skybox when transitioning to night-mode. (Editable within code)

Notes

•In the beginning of March (2023), I created version 1.0. This version will always be available. However, due to the massive changes this version made to the system, I decided to keep version 1.0 around but I won’t be updating it. With the exception of bug fixes.

-The DevForum post for version 1.0 can be found here:
[Lighting] Sunray Intensity Compensation

Test Experience, free to test!


Comparison
Late this summer I caught myself up in Dying Light 2. I saw what they did with their sun rays, and it serves as a great example to what my system achieves.

SICS_Comparison

↑ Dying Light 2

↓ Jeebus’ SICS

SICS_Showcase


Showcase Video
Excuse the UI designs I used, I was testing out UI stuff I don’t typically play with… I’m also not a UI designer.
As of writing this (10/10/2023) I’ve noticed this video is of a very early build. I will have new things to showcase relatively soon. However, the full experience can still be tested in the above test place.


Get SICS here: https://www.roblox.com/library/13400931193/V2-Sunray-Intensity-Compensation-System

Get the plugin: https://www.roblox.com/library/14103372000/Sunray-Intensity-Compensation-Plugin


API

Use :GetSetting() to easily grab an updated attribute from the settings module.

-- Get the value for any setting that's part of the root script's attribute list
local exampleSetting = SettingsModule:GetSetting("SettingName" : string) : Any

:GetSky() is just that, a function that returns the sky. As unnecessary as this function is, it’s mainly just used to ensure we’re looking at the correct sun in the sky (in the case there are multiple).

-- This will return the current Sky being used
local sky = SettingsModule:GetSky()

Use :UpdateNightStatus(boolean) to switch between night-mode and normal day rays. This function tweens the time to a point at which the sun is no longer visible and the sky is Roblox’s night sky. It is then switched with a nighttime skybox and the time is set back up to daytime, but with a moon as the sun’s image ID. This allows us to have the moon give off light rays “during the night”.

--[[
This function enables/disables Night Mode. When activated,
The time of day spans quickly through a single night into the next morning. However, halfway through the change,
the custom Sky that is in the Settings module is replaced with the current CustomDaySky in game.Lighting.
-The CustomDaySky's Moon is disabled to not show its moon during the transition.
-After the "fake night" sky has been applied, the sun texture is replaced with a moon, and it is still daytime. Just with a night-time skybox
and a moon texture for the sun. This gives the impression of 'moon rays'.
]]
require(SettingsModule):UpdateNightStatus(isNight : boolean)
Source Code

There are four total sections to this system. The root server script, this is where the default values will be set. You can set these values via the root script’s attributes section in the properties window.
(Note: These attributes are copied from this server script to every player when they join. Each player can then update their own individual client-side values via SettingsModule:SetSetting(attributeName : string, newValue : any).

Server Script Source
local Players = game:GetService("Players")
local InsertService = game:GetService("InsertService")

local MainController = script:FindFirstChild("BetaSunrayIntensityController") or script:WaitForChild("SunrayIntensityController",5)

local SICSCoreId = 15024209529
local NativeUIAssetID = 15038123726
local potentialNewCore
local potentialNewNativeUI

local SICS_Server_Version = "2.4"

local autoUpdateDevSettings = {
	skipUpdate = false;
	currentRetry = 0;
	currentNativeUIRetry = 0;
	maxRetries = 4;
}

function addCoreAttributes(target)
	for attribute, value in pairs(script:GetAttributes()) do
		target:SetAttribute(attribute, value)
	end
	MainController:SetAttribute("SICS_Server_Version", SICS_Server_Version)
end

function addCoreTags(target)
	for i, tagName in pairs(script:GetTags()) do
		target:AddTag(tagName)
	end
end

function addNewCoreClone(plr)
	local clonedSystem = MainController:Clone()
	clonedSystem.Parent = (plr.Character or plr.CharacterAdded:Wait())
	clonedSystem.Enabled = true
	clonedSystem:SetAttribute("SICS_Server_Version", SICS_Server_Version)
	--// Updates from client-side system
	clonedSystem.DataStore.UpdateData.OnServerEvent:Connect(function(plr, dataName, dataValue)
		if not plr or not plr.SICS or not plr.SICS:FindFirstChild(dataName) then return warn("Data location not found") end
		plr.SICS[dataName].Value = dataValue
	end)
end

function getUISavedData(plr)
	local DataStore2 = require(1936396537)
	local Settings = require(MainController.DataStore)
	
	for dataName,valueTable in pairs(Settings) do
		local datastore = DataStore2(dataName, plr)
		local where = valueTable.Where
		if valueTable.Where ~= "Player" then
			if plr:findFirstChild(valueTable.Where) then
				where = plr[valueTable.Where]
			else
				local folder = Instance.new("Folder", plr)
				folder.Name = valueTable.Where
				where = folder
			end
		end
		
		if valueTable.Where == "Player" then
			where = plr
		end

		--// Creates the Value
		local val = Instance.new(valueTable.What,where)
		val.Name = dataName
		val.Value = valueTable.Value

		--// Loading
		if datastore:Get() ~= nil then -- If datastore already exists
			val.Value = datastore:Get() -- loads in player's data
			require(MainController.Settings):SetSetting(dataName, datastore:Get()) -- Update attribute value
		end

		--// Saving
		val.Changed:connect(function() -- if Value changes 
			datastore:Set(val.Value) -- Sets Datastore value to changed Value
		end)
	end
end

function insertBackupUI(plr)
	potentialNewNativeUI = script:WaitForChild("SICSPanel",10)
	potentialNewNativeUI.Enabled  = false
	potentialNewNativeUI:FindFirstChildOfClass("Frame").Size = UDim2.new(0,0,1,0)
	potentialNewNativeUI.Parent = plr.PlayerGui
end

function addNativeUI(plr)
	if autoUpdateDevSettings.currentNativeUIRetry > autoUpdateDevSettings.maxRetries then insertBackupUI(plr) return warn("SICS max retries exceeded! Relying on last still existing system.") end
	local plrGui = plr:WaitForChild("PlayerGui",10)
	local Success, result = pcall(InsertService.LoadAsset, InsertService, NativeUIAssetID)
	if Success and result then
		potentialNewNativeUI = result:WaitForChild("SICSPanel",10)
		potentialNewNativeUI.Enabled  = false
		potentialNewNativeUI:FindFirstChildOfClass("Frame").Size = UDim2.new(0,0,1,0)
		potentialNewNativeUI.Parent = plrGui
		result:Destroy()
		warn("Initilized the latest native SICS UI")
		return getUISavedData(plr)
	else
		autoUpdateDevSettings.currentNativeUIRetry += 1
		warn("Failed to fetch the SICS UI! Retrying...")
		task.wait(1)
	end
	task.wait()
	return checkForUpdate()
end

function addToExistingPlayers(playerList)
	for i,plr in pairs(playerList) do
		if plr then
			addNewCoreClone(plr)
			addNativeUI(plr)
		end
	end
end

function addKeybinds(targetModule)
	-- Setup custom keybinds
	if script:GetAttribute("AllowNativeUI") then 
		local KeybindModule = require(MainController.Keybinds)
		for attribute,value in pairs(script:GetAttributes()) do
			if string.find(attribute, "Bind") then
				MainController.Keybinds:SetAttribute(attribute, value)
			end
		end
	end
end

function checkForTestExperience()
	-- Test Experience SICSNativeUI ID - 15038856671
	if game.GameId == 4664865401 or game.PlaceId == 13400902755 then
		NativeUIAssetID = 15038856671
	end
end

function checkForUpdate()
	if autoUpdateDevSettings.currentRetry > autoUpdateDevSettings.maxRetries then return warn("SICS max retries exceeded! Relying on last still existing system.") end
	local Success, result = pcall(InsertService.LoadAsset, InsertService, SICSCoreId)
	if Success and result then
		potentialNewCore = result:WaitForChild("SunrayIntensityController",10)
		MainController = potentialNewCore
		script.SunrayIntensityController:Destroy()
		potentialNewCore.Parent = script
		result:Destroy()
		
		-- Update main core values to new defaults
		addCoreAttributes(MainController.Settings)
		addCoreTags(MainController.Settings)
		addKeybinds(MainController.Keybinds)
		
		-- Fallback to add new core to existing players (as the following PlayerAdded listener sometimes isn't reached in time for the update process to finish)
		addToExistingPlayers(Players:GetPlayers())
		warn("Initilized the latest version of SICS")
		return
	else
		autoUpdateDevSettings.currentRetry += 1
		warn("Failed to fetch the newest version of SICS! Retrying...")
		task.wait(1)
	end
	task.wait()
	return checkForUpdate()
end

-- Set native UI ID regardless
checkForTestExperience()

if script:GetAttribute("_AutoUpdate_") and not autoUpdateDevSettings.skipUpdate then
	checkForUpdate()
else
	-- Update main core values to new defaults
	addCoreAttributes(MainController.Settings)
	addCoreTags(MainController.Settings)
	addKeybinds(MainController.Keybinds)
end

Players.PlayerAdded:Connect(function(plr)
	plr.CharacterAdded:Connect(function(char)
		addNewCoreClone(plr)
		if script:GetAttribute("AllowNativeUI") then
			addNativeUI(plr)
		end
	end)
end)

local SystemInformationModule = require(15031763998) -- 13383585291 - SICS Client Version
local updateStatus,checkedVersion,latestRelease = SystemInformationModule.GetSystemVersion(MainController.Name, SICS_Server_Version)
local UpdateMessage = SystemInformationModule.GetVersionMessage(updateStatus)

if updateStatus == "Outdated" then
	warn(UpdateMessage, "Your version: "..checkedVersion, "|| Latest release: "..latestRelease)
--[[
else
	print(UpdateMessage, "Your version: "..checkedVersion, "|| Latest release: "..latestRelease)
	]]
end
Client Core
local Lighting = game:GetService("Lighting")
local Players = game:GetService("Players")
local TweenService = game:GetService("TweenService")
local RunService = game:GetService("RunService")
local RepStorage = game:GetService("ReplicatedStorage")
local UIS = game:GetService("UserInputService")
local CollectionService = game:GetService("CollectionService")
local StarterGui = game:GetService("StarterGui")

local plr = Players.LocalPlayer

local CurrentCamera = workspace.CurrentCamera

local ConfigModule = script:WaitForChild("Settings",10)
local PresetsModule = script:WaitForChild("Presets",10)
local InputFunctionsModule = script:WaitForChild("InputFunctions",10)

local SunrayConfig = require(ConfigModule)
local SunrayPresets = require(PresetsModule)
local InputFunctions = require(InputFunctionsModule)
-------------------------------------------------------------------
--Fake sun stuff to track objects obscuring the player's view of the sun
local distFromCam = 500;
local SunPositionPart

--Essential Sky Assets and Values
local NearSunrays
local FarSunrays
local CurrentAngle
local Sky
local Forgiveness
local ForgivenessRadius

--Device Info
local isKeyboard
local isMobile
local isGamepad

--Debug Options
local enableDebugMessages = false

local function printDebug(msgString,optional1,optional2,optional3)
	if enableDebugMessages then
		if optional1 then
			print(msgString,optional1)
		elseif optional2 then
			print(msgString,optional1,optional2)
		elseif optional3 then
			print(msgString,optional1,optional2,optional3)
		else
			print(msgString)
		end
	end
end

if UIS.KeyboardEnabled and UIS.GamepadEnabled then
	isKeyboard = true
	isMobile = false
	isGamepad = true
elseif UIS.KeyboardEnabled and not UIS.GamepadEnabled then
	isKeyboard = true
	isMobile = false
	isGamepad = false
elseif UIS.TouchEnabled and not UIS.GamepadEnabled then
	isKeyboard = false
	isMobile = true
	isGamepad = false
elseif UIS.TouchEnabled and UIS.GamepadEnabled then
	isKeyboard = false
	isMobile = true
	isGamepad = true
end

--Create new fake sun part (located far away in the direction of the sun)
local function NewSunPart()
	if game.Workspace.CurrentCamera:FindFirstChild("SunPositionBrick") then
		game.Workspace.CurrentCamera.SunPositionBrick:Destroy() --Create a new fake sun in case the old one bugged out for any reason
	end

	local part = Instance.new("Part")
	part.Name = "SunPositionBrick"
	part.Anchored = true
	part.Material = Enum.Material.ForceField
	part.Reflectance = 1000
	part.Color = Color3.fromRGB(255,255,255)
	part.Size = Vector3.new() * SunrayConfig:GetSky().SunAngularSize
	part.Shape = Enum.PartType.Ball
	part.CastShadow = false
	part.Parent = workspace.CurrentCamera
	part.CanCollide = false
	SunPositionPart = part
end

NewSunPart()
------------------------------------------------------------------
repeat task.wait() Sky = SunrayConfig:GetSky() until SunrayConfig:GetSky() ~= nil
script.Parent = plr.PlayerScripts
local function getIgnoredParts()
	local ignoreList = {plr.Character,SunPositionPart}
	for i,tag in pairs(ConfigModule:GetTags()) do
		for i,taggedInstance in pairs(CollectionService:GetTagged(tag)) do
			table.insert(ignoreList,taggedInstance)
		end
	end
	return ignoreList
end

local function SunToCamera(Character) --Is sun on screen and unobstructed?
	local _, OnScreen = workspace.CurrentCamera:WorldToScreenPoint(SunPositionPart.Position)
	local ignoreList = getIgnoredParts()
	
	local PlayerRoot = Character:WaitForChild("HumanoidRootPart",3)
	local rayStartPart = CurrentCamera
	local rayfinishPart = SunPositionPart

	local rayStartPosition = rayStartPart.CFrame.Position
	local rayDestination = rayfinishPart.Position
	local rayDirection = rayDestination - rayStartPosition
	local raycastParams = RaycastParams.new()
	raycastParams.FilterType = Enum.RaycastFilterType.Exclude
	raycastParams.FilterDescendantsInstances = {ignoreList}
	raycastParams.IgnoreWater = true
	
	if SunrayConfig:GetSetting("IgnoreTerrain") then
		raycastParams:AddToFilter(workspace.Terrain)
	end
	
	local NumOfObstructions = #workspace.CurrentCamera:GetPartsObscuringTarget({workspace.CurrentCamera.CFrame.Position,SunPositionPart.Position},ignoreList)
	local result = workspace:Raycast(rayStartPosition, rayDirection * (rayStartPosition - rayDestination).magnitude,raycastParams)
	
	if not SunrayConfig:GetSetting("IgnoreTerrain") then --Terrain Obstruction
		if result and result.Material ~= nil then
			return "OSO"
		end
	end
	
	if OnScreen then
		if NumOfObstructions == 0 then
			return "OSU" -- On-screen & unobstructed
		else
			return "OSO" -- On-screen & obstructed
		end
	else
		return "OFS" --Sun is off-screen
	end
end

local function RoundNumber(NumberToRound,DecimalPlaces)
	if NumberToRound == nil then
		return
	end
	local Multiplier = 10^(DecimalPlaces or 2)
	local Number = math.floor(NumberToRound*Multiplier)/Multiplier
	return Number
end

local function getForgivenessRadius(currentForgiveness)
	local radius = RoundNumber(math.cos(math.rad(currentForgiveness)))
	return radius
end

local HorizonRadius = RoundNumber(math.cos(math.rad(90)))

local function SetSunPosition()
	SunPositionPart.Size = Vector3.new() * SunrayConfig:GetSky().SunAngularSize
	SunPositionPart.Position = workspace.CurrentCamera.CFrame.Position + Lighting:GetSunDirection() * distFromCam;
end

--Get Preset TweenInfo
local function GetPresetInfo()
	local ChosenPreset
	if SunrayConfig:GetSetting("ActivePreset") == nil or
		SunrayConfig:GetSetting("ActivePreset") == "" or
		string.lower(SunrayConfig:GetSetting("ActivePreset")) == "default" then
		ChosenPreset = SunrayPresets.default
		return ChosenPreset
	elseif string.lower(SunrayConfig:GetSetting("ActivePreset")) == "custom" then
		ChosenPreset = SunrayPresets.custom
		return ChosenPreset
	else
		ChosenPreset = SunrayPresets[string.lower(SunrayConfig:GetSetting("ActivePreset"))]
		return ChosenPreset
	end
end

function CreateSunrayEffect()
	for i,v in pairs(Lighting:GetChildren()) do
		if v:IsA("SunRaysEffect") then
			v:Destroy()
		end
	end

	--Add a new SunRaysEffect if one doesn't already exist
	local NewNearSunrays = Instance.new("SunRaysEffect",Lighting)
	NewNearSunrays.Name = "NearSunRays"
	NewNearSunrays.Intensity = 0.085
	NewNearSunrays.Spread = 0.1
	NearSunrays = NewNearSunrays

	local NewFarSunrays = Instance.new("SunRaysEffect",Lighting)
	NewFarSunrays.Name = "FarSunRays"
	NewFarSunrays.Intensity = SunrayConfig:GetSetting("NearSunRaysIntensity")
	NewFarSunrays.Spread = SunrayConfig:GetSetting("DefaultSpread")
	FarSunrays = NewFarSunrays
end

CreateSunrayEffect()
SunrayConfig:SetSetting("Forgiveness", Sky.SunAngularSize)

--Get the sun angle relative to the camera and return the result
local function RoundAngle(Value,decimalPlaces)
	local CurrentAngle = Value
	local Result = CurrentAngle
	local mult = 10^(decimalPlaces or 2)
	Result = math.floor(Result*mult) / mult
	return Result
end

--This function will do the actual updating of the sunray intensity. Prior calculations are done by a function located below
local function updateIntensity(newIntensityValue,isObstructed)
	if SunrayConfig.isNightActive then
		FarSunrays.Intensity = 0.02
		NearSunrays.Intensity = 0.025
		return
	else
		if NearSunrays.Intensity ~= SunrayConfig:GetSetting("NearSunRaysIntensity") then
			NearSunrays.Intensity = SunrayConfig:GetSetting("NearSunRaysIntensity")
		end
	end

	if string.lower(SunrayConfig:GetSetting("ActivePreset")) == "instant" then
		FarSunrays.Intensity = newIntensityValue - (newIntensityValue * SunrayConfig:GetSetting("Dampening"))
	else
		if isObstructed then
			local TweenIntensity = TweenService:Create(FarSunrays,SunrayConfig.ObstructedTweenInfo,{Intensity = newIntensityValue - (newIntensityValue * SunrayConfig:GetSetting("Dampening"))})
			TweenIntensity:Play()
		else
			local TweenIntensity = TweenService:Create(FarSunrays,GetPresetInfo(),{Intensity = newIntensityValue - (newIntensityValue * SunrayConfig:GetSetting("Dampening"))})
			TweenIntensity:Play()
		end
	end
end

--Double check wait for sunray creation
if FarSunrays == nil or NearSunrays == nil then
	repeat
		CreateSunrayEffect()
		task.wait(1)
	until Lighting[FarSunrays] ~= nil and Lighting[NearSunrays] ~= nil
end

--[[
	Every heartbeat this script checks the camera direction relative
		to the current position of the sun on the client
]]

local function tweakSunray()
	-- Make sure SunPositionPart (Fake sun, a physical object) exists
	if SunPositionPart == nil then
		repeat NewSunPart() task.wait(0.5) until SunPositionPart ~= nil
	end
	SetSunPosition() --This should always update first after the fake sun is confirmed to exist
	Forgiveness = SunrayConfig:GetSetting("Forgiveness")
	ForgivenessRadius = RoundNumber(math.cos(math.rad(Forgiveness)))
	local dirSun = Lighting:GetSunDirection()
	local dirCamera = CurrentCamera.CFrame.LookVector
	CurrentAngle = RoundAngle(dirSun:Dot(dirCamera))
	local CurrentAngleInversed = RoundNumber((CurrentAngle * -1))
	local RoundedDirectIntensity = RoundNumber(CurrentAngleInversed+(1-ForgivenessRadius)+ForgivenessRadius) * 2
	local chr = plr.Character or plr.CharacterAdded:Wait()
	local sunObstructed = SunToCamera(chr)
	FarSunrays.Spread = SunrayConfig:GetSetting("DefaultSpread")
	
	--Handle SunRaysEffect Intensity
	if Lighting.ClockTime >= 6 and Lighting.ClockTime < 18 then --Must be daytime for this to take effect
		if CurrentAngle >= HorizonRadius then -- (If within the horizon radius)
			if sunObstructed == "OSO" then -- OSU = On-screen & unobstructed || OSO = On-screen & obstructed || OFS = Sun is off-screen
				updateIntensity(SunrayConfig:GetSetting("IntensityObstructed"),true)
			elseif CurrentAngle >= ForgivenessRadius and sunObstructed == "OSU" then --Within forgiveness radius
				if Forgiveness >= 30 then
					updateIntensity((RoundNumber((-1 * (CurrentAngleInversed+(1-ForgivenessRadius)) - 1) * -1,2)),false)
				elseif Forgiveness <= 30 and Forgiveness >= 22 then
					updateIntensity((RoundNumber((-1 * (CurrentAngleInversed+(1-ForgivenessRadius)) - 1) * -1,2)),false)
				elseif Forgiveness < 22 then
					updateIntensity(1-ForgivenessRadius,false)
				end
			elseif CurrentAngle < ForgivenessRadius and CurrentAngle >= HorizonRadius then --Within horizon radius, but sun is off-screen
				if Forgiveness >= 30 then
					updateIntensity((RoundNumber((-1 * (CurrentAngleInversed+(1-ForgivenessRadius)) - 1) * -1,2)),false)
				elseif Forgiveness <= 30 and Forgiveness >= 22 then
					updateIntensity(RoundedDirectIntensity-(ForgivenessRadius/20),false)
				elseif Forgiveness < 22 then
					updateIntensity(RoundedDirectIntensity-(ForgivenessRadius/20),false)
				end
			end
		else --Outside the horizon radius (sun should be totally off-screen)
			if CurrentAngle < HorizonRadius then
				updateIntensity(RoundedDirectIntensity,false)
			end
		end
	else --Night time
		if FarSunrays.Intensity ~= SunrayConfig:GetSetting("IntensityOffScreen") then
			updateIntensity(SunrayConfig:GetSetting("IntensityOffScreen"),false)
		end
	end
end

RunService.Heartbeat:Connect(tweakSunray)

------------------------------------------------------------------

-- Update forgiveness if sun size changes
Lighting:FindFirstChildOfClass("Sky"):GetPropertyChangedSignal("SunAngularSize"):Connect(function()
	SunrayConfig:SetSetting("Forgiveness", Sky.SunAngularSize)
end)

------------------------------------------------------------------

------------------------------------------------------------------

------------------------------------------------------------------

------------------------------------------------------------------

--Native UI Handler

if ConfigModule:GetAttribute("AllowNativeUI") == true then
	repeat task.wait() until plr.PlayerGui:FindFirstChild("SICSPanel")
	local KeybindsModule = script:WaitForChild("Keybinds",10)
	local Keybinds = require(KeybindsModule)
	local OpenPhaseSize = UDim2.new(1,0,1,0)
	local ClosePhaseSize = UDim2.new(0,0,1,0)
	local plrGui = plr:WaitForChild("PlayerGui")
	local TransitionTime = 0.5
	local TopBar = require(script:WaitForChild("TopBar",10))
	local MarketplaceService = game:GetService("MarketplaceService")
	
	local SICSPanelToggler
	local SICSPanelUI = plrGui.SICSPanel
	local BackgroundFrame = SICSPanelUI:WaitForChild("Background",10)
	local LowerFrame = BackgroundFrame:WaitForChild("LowerFrame",10)
	local HousingFrame = LowerFrame:WaitForChild("Housing",10)
	local Credit = HousingFrame:WaitForChild("Credit",10)

	local ButtonFrame = HousingFrame:WaitForChild("ButtonFrame",10)
	local CloseMenuButton = ButtonFrame:WaitForChild("CloseMenu",10)
	local GetSICSButton = ButtonFrame:WaitForChild("GetSystem",10)

	local function setKeybindTip(keyList)
		local tip = "SICS".." ["
		for i=1,#keyList do
			tip = tip..keyList[i].Name
			if i == #keyList then tip = tip.."]" else tip = tip.." + " end
		end
		SliderToggler:setTip(tip)
	end
	
	if script:GetAttribute("SICS_Server_Version") and tonumber(script:GetAttribute("SICS_Server_Version")) >= 2.3 then
		local SICSGlobalData = plr:WaitForChild("SICS",10)
		
		for i,dataName in pairs(SICSGlobalData:GetChildren()) do
			SunrayConfig:SetSetting(dataName.Name, dataName.Value)
			dataName:GetPropertyChangedSignal("Value"):Once(function()
				SunrayConfig:SetSetting(dataName.Name, dataName.Value)
			end)
		end
		SICSGlobalData.ChildAdded:Connect(function(dataName)
			SunrayConfig:SetSetting(dataName.Name, dataName.Value)
			dataName:GetPropertyChangedSignal("Value"):Once(function()
				SunrayConfig:SetSetting(dataName.Name, dataName.Value)
			end)
		end)
	
	
		Keybinds:InitializeBinds() -- Setup Keybinds
		-------
	end
	if SliderToggler == nil then
		SliderToggler = TopBar.new()
			:autoDeselect(false)
			:setName("SliderToggler")
			:setImage(1068088395)
		if string.find(InputFunctions.getInputType(),"Keyboard") or InputFunctions.getInputType == "Gamepad" then
			local inputType = InputFunctions.getInputType()
			inputType = (string.gsub(inputType,"InputType_",""))
			setKeybindTip(Keybinds.NativeMenuKeybind[inputType])
		end
	end
	
	-- Phase Functions

	local function OpenPhase()
		BackgroundFrame:TweenSize(OpenPhaseSize,Enum.EasingDirection.InOut,Enum.EasingStyle.Quart,TransitionTime,true)
	end

	local function ClosePhase()
		BackgroundFrame:TweenSize(ClosePhaseSize,Enum.EasingDirection.InOut,Enum.EasingStyle.Quart,TransitionTime,true)
	end
	
	local enabledCoreGui = {}
	
	-- Event Connections
	local CoreGuiEnums = {
		Enum.CoreGuiType.Backpack,
		Enum.CoreGuiType.Health,
		Enum.CoreGuiType.PlayerList,
		Enum.CoreGuiType.Chat,
		Enum.CoreGuiType.EmotesMenu,
	}
	
	local function setCoreUIEnabled(isGuiEnabled)
		if isGuiEnabled and #enabledCoreGui > 0 then
			for i,v in pairs(enabledCoreGui) do
				StarterGui:SetCoreGuiEnabled(v,isGuiEnabled)
			end
		else
			for i,v in pairs(CoreGuiEnums) do
				StarterGui:SetCoreGuiEnabled(v,isGuiEnabled)
			end
		end
	end
	
	local function pressingRequiredKeys(requiredKeys)
		for i,key in pairs(requiredKeys) do
			if not table.find(InputFunctions.getKeysPressed(),key) then return false end
		end
		return true
	end
	
	ConfigModule.AttributeChanged:Connect(function(attribute)
		--print(attribute,"changed values to", ConfigModule:GetAttribute(attribute))
		script.DataStore.UpdateData:FireServer(attribute, ConfigModule:GetAttribute(attribute))
	end)
	
	UIS.InputBegan:Connect(function(input, isTyping)
		local inputType = InputFunctions.getInputType()
		inputType = (string.gsub(inputType,"InputType_",""))
		if pressingRequiredKeys(Keybinds.NativeMenuKeybind[inputType]) and (not UIS:GetFocusedTextBox() and not isTyping) then
			local selectedOrDeselectedString = SliderToggler:getToggleState()
			if selectedOrDeselectedString == "selected" and not SliderToggler.locked then
				SliderToggler:deselect()
				SliderToggler:debounce(TransitionTime)
			elseif selectedOrDeselectedString == "deselected" and not SliderToggler.locked then
				SliderToggler:select()
				SliderToggler:debounce(TransitionTime)
			end
		end
		--if (InputFunctions.getKeysPressed() == Keybinds.NativeMenuKeybind.Keyboard or InputFunctions.getKeysPressed() == Keybinds.NativeMenuKeybind.Gamepad) and (not UIS:GetFocusedTextBox() and not isTyping) then
		--end
	end)
	
	CloseMenuButton.Activated:Connect(function(inputObject)
		SliderToggler:deselect()
		SliderToggler:debounce(TransitionTime)
	end)
	
	GetSICSButton.Activated:Connect(function(inputObject)
		MarketplaceService:PromptPurchase(plr, 13400931193, false)
	end)
	
	SliderToggler.selected:Connect(function()
		SICSPanelUI.Enabled = true
		table.clear(enabledCoreGui)
		for i,coreUIEnum in pairs(CoreGuiEnums) do
			if StarterGui:GetCoreGuiEnabled(coreUIEnum) then table.insert(enabledCoreGui, coreUIEnum) end
		end
		setCoreUIEnabled(false)
		BackgroundFrame.Size = ClosePhaseSize
		OpenPhase()
	end)

	SliderToggler.deselected:Connect(function()
		ClosePhase()
		task.wait(TransitionTime)
		SICSPanelUI.Enabled = false
		setCoreUIEnabled(true)
	end)

	for i,foundTextLabel in pairs(SICSPanelUI:GetDescendants()) do
		if foundTextLabel and foundTextLabel:IsA("TextLabel") then
			local newSizeConstraint = Instance.new("UITextSizeConstraint",foundTextLabel)
			if isMobile then
				newSizeConstraint.MaxTextSize = 28
			else
				newSizeConstraint.MaxTextSize = 40
			end
			newSizeConstraint.MinTextSize = 14
		end
	end
end


--[[
	
		BELOW THIS TEXT IS TEST PLACE UI STUFF ---> (SAFE TO KEEP OR DELETE)
		
		This updates the UI in my testing place. This will only check if the game is my test place via GameId.
		Meaning, the below block of code is safe to stay or remove, it will not make any changes to your experiences or places.
	
	]]
------------------------------------------------------------------

-- Test Experience UI Handler
if (game.GameId == 4664865401 or game.GameId == 4512400337) then
	local TopBar = require(RepStorage:WaitForChild("TopBar",10))
	local CoreDropdown
	local RayInfoToggler
	local nightToggler
	local PresetDropdown
	local isButtonSelected
	local plrGui = plr:WaitForChild("PlayerGui")
	local TestUI = plrGui:WaitForChild("AngleFromSun",4)
	
	-------

	local isKeyboard
	local isMobile
	local isGamepad

	if UIS.KeyboardEnabled and UIS.GamepadEnabled then
		isKeyboard = true
		isMobile = false
		isGamepad = true
	elseif UIS.KeyboardEnabled and not UIS.GamepadEnabled then
		isKeyboard = true
		isMobile = false
		isGamepad = false
	elseif UIS.TouchEnabled and not UIS.GamepadEnabled then
		isKeyboard = false
		isMobile = true
		isGamepad = false
	elseif UIS.TouchEnabled and UIS.GamepadEnabled then
		isKeyboard = false
		isMobile = true
		isGamepad = true
	end

	task.spawn(function()
		if RayInfoToggler == nil then
			RayInfoToggler = TopBar.new()
			RayInfoToggler:autoDeselect(false)
			RayInfoToggler:setName("RayInfoToggler")
			RayInfoToggler:setLabel("Info")
			RayInfoToggler:setTip("Toggle (Q)")
			RayInfoToggler:select()
		end

		if nightToggler == nil then
			nightToggler = TopBar.new()
			nightToggler:autoDeselect(false)
			nightToggler:setName("nightToggler")
			nightToggler:setLabel("Night")
			nightToggler:setTip("Toggle (V)")
		end

		if PresetDropdown == nil then
			PresetDropdown = TopBar.new()
			PresetDropdown:autoDeselect(true)
			PresetDropdown:setName("PresetsDropdown")
			PresetDropdown:setLabel("Presets")

			PresetDropdown:set("dropdownSquareCorners", false)
			PresetDropdown:setDropdown({
				TopBar.new()
					:setLabel("Default")
					:setName("DefaultPreset")
					:bindEvent("selected", function(self)
						SunrayConfig:SetSetting("ActivePreset","Default")
					end),
				TopBar.new()
					:setLabel("Realism")
					:setName("RealismPreset")
					:bindEvent("selected", function(self)
						SunrayConfig:SetSetting("ActivePreset","Realism")
					end)
					:select(),
				TopBar.new()
					:setLabel("Cinematic")
					:setName("CinematicPreset")
					:bindEvent("selected", function(self)
						SunrayConfig:SetSetting("ActivePreset","Cinematic")
					end),
				TopBar.new()
					:setLabel("Custom")
					:setName("CustomPreset")
					:bindEvent("selected", function(self)
						SunrayConfig:SetSetting("ActivePreset","Custom")
					end),
				TopBar.new()
					:setLabel("Instant")
					:setName("InstantPreset")
					:bindEvent("selected", function(self)
						SunrayConfig:SetSetting("ActivePreset","Instant")
					end),
			})
		end
		
		task.wait(2)
		while true do
			local character = plr.Character or plr.CharacterAdded:Wait()
			isButtonSelected = RayInfoToggler.isSelected

			if TestUI ~= nil then
				local MainFrame = TestUI:WaitForChild("MainFrame")
				local AngleLabel = MainFrame:WaitForChild("Angle")
				local OuterForgivenessLabel = MainFrame:WaitForChild("ForgivenessRadius")
				local IntensityLabel = MainFrame:WaitForChild("Intensity")

				AngleLabel.Text = "Current Angle: "..tostring(RoundNumber(CurrentAngle,2))
				IntensityLabel.Text = "Intensity: "..tostring(RoundNumber(FarSunrays.Intensity,2))
				OuterForgivenessLabel.Text = "Forgiveness: "..tostring(RoundNumber(getForgivenessRadius(Forgiveness),2)).." ("..tostring(RoundNumber(Forgiveness,2))..")".."("..tostring(RoundNumber(ForgivenessRadius/6,2))..", "..tostring(RoundNumber(ForgivenessRadius/20,2))..")"
			end
			RunService.Heartbeat:Wait()
		end
	end)

	UIS.InputBegan:Connect(function(input, gameProcessedEvent)
		if input.KeyCode == Enum.KeyCode.Q and plr.Character:FindFirstChildOfClass("Humanoid").Health > 0 and not UIS:GetFocusedTextBox() then
			if RayInfoToggler.isSelected then
				RayInfoToggler:deselect()
			else
				RayInfoToggler:select()
			end

		elseif input.KeyCode == Enum.KeyCode.V and plr.Character:FindFirstChildOfClass("Humanoid").Health > 0 and not UIS:GetFocusedTextBox() then
			if RayInfoToggler.isSelected then
				RayInfoToggler:deselect()
			else
				RayInfoToggler:select()
			end
		end
	end)

	RayInfoToggler.selected:Connect(function()
		TestUI.MainFrame.Visible = true
	end)
	RayInfoToggler.deselected:Connect(function()
		TestUI.MainFrame.Visible = false
	end)

	nightToggler.selected:Connect(function()
		SunrayConfig:UpdateNightStatus(true)
	end)
	nightToggler.deselected:Connect(function()
		SunrayConfig:UpdateNightStatus(false)
	end)
end
SettingsModule
local Lighting = game:GetService("Lighting")
local TweenService = game:GetService("TweenService")
local RepStorage = game:GetService("ReplicatedStorage")

local Sky
local SunrayPresets = require(script.Parent:WaitForChild("Presets",10))

local DaySky
local NightSky
local DaySkyBackup

MainConfig = {}

-- Stored Settings, Data, & System Information

MainConfig.isNightActive = false
MainConfig.NightSkyData = {
	SunTextureId = "rbxassetid://9525441443";
	SkyboxFaceLinks = "http://www.roblox.com/asset/?version=1&id=1014344"; --Every face on a default Roblox night sky is the same link, no need to list every one
	SunAngularSize = 11;
}

-- Main Sky Setup
if Lighting:FindFirstChildOfClass("Sky") then
	DaySky = Lighting:FindFirstChildOfClass("Sky")
else
	DaySky = Instance.new("Sky",Lighting)
end
DaySkyBackup = DaySky:Clone()

-- Handle Bloom

local BloomEffect = Lighting:FindFirstChildOfClass("BloomEffect")
local originalBloomSize
local originalBloomThreshold

if not BloomEffect then
	local bloom = Instance.new("BloomEffect",Lighting)
	bloom.Size = 22
	bloom.Threshold = 2.2
	originalBloomSize = bloom.Size
	originalBloomThreshold = bloom.Threshold
else
	originalBloomSize = BloomEffect.Size
	originalBloomThreshold = BloomEffect.Threshold
end

MainConfig.PresetList = { --If you add your own preset(s), add their name(s) to this list
	--Add your customs starting here. Underneath the line below are necessary for main system functionality.
	--"";

	--[[
		Side note:
		
		Most functions that work with the presets rely on string.lower(). So please keep newly added
			presets in this list all lowercase letters.
	]]

	-------------
	"realism";
	"cinematic";
	"default";
	"custom";
	"instant"
};


----------------------------------------------------------------------------------------

MainConfig.ObstructedTweenInfo = TweenInfo.new(
	5.21,
	Enum.EasingStyle.Quart,
	Enum.EasingDirection.Out
);

local sunShiftPhase1Info = TweenInfo.new(2.5,Enum.EasingStyle.Cubic,Enum.EasingDirection.In)
local sunShiftPhase2Info = TweenInfo.new(2.5,Enum.EasingStyle.Cubic,Enum.EasingDirection.Out)

function MainConfig:GetSky()
	return DaySky
end

local function tweenBloom(bloomInstance : Instance, newBloomSize : number, newBloomThreshold : number)
	local oldSize = bloomInstance.Size
	local oldThreshold = bloomInstance.Threshold
	local bloomTinfo = TweenInfo.new(1,Enum.EasingStyle.Sine,Enum.EasingDirection.Out)
	local TweenBloom = TweenService:Create(bloomInstance,bloomTinfo,{Size = newBloomSize, Threshold = newBloomThreshold})
	TweenBloom:Play()
end

function MainConfig:UpdateNightStatus(isNight)
	if MainConfig.isNightActive and isNight == false then --Turn Day if it's night
		local shiftPhase1
		if Lighting.ClockTime >= 12 then
			shiftPhase1 = TweenService:Create(Lighting,sunShiftPhase1Info,{ClockTime = 24})
		else
			shiftPhase1 = TweenService:Create(Lighting,sunShiftPhase1Info,{ClockTime = 0})
		end
		local shiftPhase2 = TweenService:Create(Lighting,sunShiftPhase2Info,{ClockTime = 8})
		shiftPhase1:Play()
		shiftPhase1.Completed:Connect(function(playbackState)
			if playbackState == Enum.PlaybackState.Completed then
				if Lighting:FindFirstChildOfClass("BloomEffect") then
					local bloom = Lighting:FindFirstChildOfClass("BloomEffect")
					tweenBloom(bloom,originalBloomSize,originalBloomThreshold)
				end
				MainConfig.isNightActive = false
				DaySky:Destroy()
				DaySky = DaySkyBackup:Clone()
				DaySky.Parent = Lighting
				shiftPhase2:Play()
			end
		end)
	elseif not MainConfig.isNightActive and isNight == true then --Turn night if it's day
		local shiftPhase1
		if Lighting.ClockTime >= 12 then
			shiftPhase1 = TweenService:Create(Lighting,sunShiftPhase1Info,{ClockTime = 24})
		else
			shiftPhase1 = TweenService:Create(Lighting,sunShiftPhase1Info,{ClockTime = 0})
		end
		local shiftPhase2 = TweenService:Create(Lighting,sunShiftPhase2Info,{ClockTime = 12})
		shiftPhase1:Play()
		DaySky.MoonAngularSize = 0
		shiftPhase1.Completed:Connect(function(playbackState)
			if playbackState == Enum.PlaybackState.Completed then
				if Lighting:FindFirstChildOfClass("BloomEffect") then
					local bloom = Lighting:FindFirstChildOfClass("BloomEffect")
					tweenBloom(bloom,10,3.6)
				end
				MainConfig.isNightActive = true
				DaySky.SkyboxBk = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SkyboxDn = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SkyboxFt = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SkyboxLf = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SkyboxRt = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SkyboxUp = MainConfig.NightSkyData.SkyboxFaceLinks
				DaySky.SunTextureId = MainConfig.NightSkyData.SunTextureId
				DaySky.MoonAngularSize = DaySkyBackup.MoonAngularSize
				DaySky.SunAngularSize = MainConfig.NightSkyData.SunAngularSize
				shiftPhase2:Play()
			end
		end)
	end
end

--// Forgiveness Radius \\--
MainConfig.Forgiveness = MainConfig:GetSky().SunAngularSize;

function MainConfig:UpdateForgiveness(newForgiveness : number)
	if MainConfig.Forgiveness ~= newForgiveness then
		MainConfig.Forgiveness = newForgiveness
	end
end

function MainConfig:GetSetting(settingName)
	if settingName == nil then return warn("Valid setting required") end
	for attribute,value in pairs(script:GetAttributes()) do
		if string.lower(attribute) == string.lower(settingName) then
			return value
		end
	end
	return warn("Setting", settingName, "not found")
end

function MainConfig:SetSetting(settingName, newValue)
	for attributeName,value in pairs(script:GetAttributes()) do
		if string.lower(attributeName) == string.lower(settingName) then
			if string.lower(settingName) == "activepreset" and table.find(MainConfig.PresetList,string.lower(newValue)) then
				script:SetAttribute(settingName, newValue)
				return
			elseif string.lower(settingName) == "activepreset" and not table.find(MainConfig.PresetList,string.lower(newValue)) then
				return warn("Preset:", "'"..newValue.."'", "not found!")
			end
			
			script:SetAttribute(settingName, newValue)
			return
		end
	end
	return warn("Setting", "'"..settingName.."'", "not found!")
end

return MainConfig
PresetModule
local module = {
	--// Preset Structures \\--
	
	default = TweenInfo.new(
		0.121,
		Enum.EasingStyle.Sine,
		Enum.EasingDirection.Out);
	
	realism = TweenInfo.new(
		0.067,
		Enum.EasingStyle.Sine,
		Enum.EasingDirection.Out
	);
	
	cinematic = TweenInfo.new(
		0.652,
		Enum.EasingStyle.Sine,
		Enum.EasingDirection.Out);
	
	--───────────────────────────────────────────────────────────────────────────────────────--
	
	custom = TweenInfo.new(	--Change this section if you set `SettingsModule.ActivePreset` to "Custom"
		0.254,						--Transition Time
		Enum.EasingStyle.Sine,		--Transition Style
		Enum.EasingDirection.Out);	--Transition Direction
}

return module


--[[

	These are nothing but simple `TweenInfo.new()` configuration tables. Feel free to play with them
		if you want to adjust the different presets!

]]

Disclaimer: This system has the option (enabled by default) to run functions directly using InsertService. If you do not want this system to do so, disable it by unchecking the “AutoUpdate” attribute of the root server script.

40 Likes

Could you please open source the test place.
You could remove the afk gui, and donate gui though

Hello! Sorry for the slightly late reply. The AFK gui and donation gui don’t worry me. It’s the feedback gui I setup that has a Discord webhook URL. And should that webhook URL get into ill-minded hands, my Discord server can be blown up repeatedly with unwanted messages.

As I’m writing this, I’ve come up with a new idea. I’ve removed the FeedbackGui from the main testing place and added it to a copylocked sub-place. Everything is 100% identical, minus the feedback gui.

The main test place is now uncopylocked :smiley:

1 Like

Alright thank you
And make sure to use the 1k dono from yesterday to good use :wink:

1 Like

I didn’t realize you donated! Thank you so much!

You’ve my word it will go to great use :slight_smile:

After seeing this system used in a few other projects and the results it put out, I came to the conclusion that this system still made sunray intensities a bit too blinding (as some older versions tended to be).

To combat this, I’ve added a “dampening” reduction effect in the Settings module. It uses a 0 to 1 number scale to indicate 0 to 100% dampening and will lower the sunray intensity by said percentage. By default dampening is set to 0.4 (40% reduction).

Hopefully this will work better for realism than previously. If anyone wants to continue using the system as it was in previous versions (without any dampening effect), simply set that setting to 0.

(You can now update settings using SettingsModule.SetSetting(settingName, newValue). I strongly encourage using this method over looping through the module’s .Settings section for writing values. Looping should be used for reading more than one setting.

You can also get setting values using SettingsModule.GetSetting(settingName))

1 Like

V2.3.0


+Added plugin support! Test your adjustments in real-time! Get it here!

-I’ve moved most customizable settings to a folder inside the Settings module. This is for easier compatibility with the plugin.

-Settings can still be updated using the require(SettingsModule).SetSetting("SettingName", newValue) function.

If there are any issues with the plugin or changes with the new settings location, let me know!

Any feedback is greatly appreciated!


Edit: I’ve not tested the new plugin in a Team Create session with others actively present. However I do predict that the plugin’s “Preview” feature will be buggy with multiple users in a Team Create session all trying to preview simultaneously. This system was designed to run in a LocalScript and be totally client-sided.

1 Like

Hi @WoahItsJeebus I noticed you have good experience on Roblox dev. I’m here to ask if you can help me do something in my game that has nothing to do with this topic.

Hello! For private inquiries you can send a message to my inbox! Thanks!

2 Likes

There should be option to not use the custom sky provided in the script.

If i insert my own skybox and when player die, the original sky got destroy and replace with the one in the script.

Still if i puy my own skybox in the script, it will not load the textute.

Hello! Thanks for letting me know about this! I’ve just updated the system to now use custom SkyBox’s when Settings.ActiveValues.useCustomSky.Value == true. The instructions script + instructions section above both have information on setting up custom skyboxes.

I apologize this feature wasn’t implemented into the main release until now. This was already done in an unreleased package that I worked on last week. But after realizing I can’t make packages public yet, I put that part of the project away and didn’t think to implement it into the main release.

So thank you for letting me know! Credit where credit is due, I tossed you a mention in the changelog above! :smiley: Enjoy!

1 Like

Thanks! Also sorry for several typo.

1 Like

New update. A CustomSkies folder is now included in the system instead of developers having to create one themselves for the system to use. You can find it nested in the Settings module.

See Photo

image


It is safe to keep a sky in game.Lighting as a preview for yourself while in Studio, the system will replace it with the same custom sky in the folder (assuming ActiveValues.useCustomSky is set to true)

The other DaySky/NightSky instances are the default ones the system will use when ActiveValues.useCustomSky.Value = false.


Version 2.4.0 is out. You can now add any number of tags to any part, model, or the like and the system will ignore those parts when looking for any view obstructions. Simply go to the TagsToIgnore Configuration instance in the ActiveValues folder and add any tags you’d like the system to ignore there!

This should help reduce sudden flashes in narrow/tight spaces, in slightly leafy area’s that don’t have as many leaves, etc.


Let me know if you have any issues! Any feedback or suggestions is very welcome, I encourage it!

Enjoy!

IgnoreTaggedPartsFeature

Hello again. I didn’t cover and revive this post for the last “major” update I put out. However I will still cover the main change(s) of the 2.5.0 update here as well.

2.5.0 added a few new settings to the ActiveValues folder:

  • IgnoreTerrain
  • IntensityObstructed
  • IntensityOffScreen
  • NearSunRaysIntensity

Developers can now use the IgnoreTerrain setting to ignore terrain cells when raycasting. Developers can also alter the settings for when the view of the sun is obstructed from the camera, when the sun is entirely off-screen, and the “NearSunRays” SunRaysEffects static value.

Removed SettingsModule:SetActivePreset(PresetName) in favor of SettingsModule:SetSetting("ActivePreset",PresetName)

2.5.1 included some simple bug fixes

2.6.0 has now released as of this post. Developers are no longer required to have a Sky instance in the Settings module, the system now checks for an existing sky, or otherwise creates one.
This update now includes Roblox’s default night skybox data to switch to when enabling night-mode (with respect to custom nighttime skyboxes when useCustomSky = true).


At some point, I also updated the Settings module’s functions from “MainConfig.functionName” to “MainConfig:functionName” – replacing their periods with colons.

Enjoy!

Hope everyone is doing well. I’ve put out a massive update early October 10th-11th— 2.7.0. This included the removal of the ActiveValues folder in favor of moving everything in that folder to the attributes section of the root server script.

This is so I can push updates out without the need for developers to reinsert the system from the toolbox repeatedly. The server script now inserts a fresh version of the client’s core into the game when any server starts. It will attempt to retry if failed, and this amount can further be customized in that same script. Furthermore, all attributes are transferred to each client core’s settings module. And can be accessed using the API listed in this thread. No more having to input your default settings over and over. True auto updates.

I have revamped the original post of this thread to include more detailed information, as well as a small comparison gif atop one another in the Showcase drop-down. The changelog for this system has also been moved to its own separate bulletin board thread to conserve space here. (It tended to get laggy when editing the main post with the entire changelog there.) — It can be found linked at the top of this thread.

Future plans

I’m currently in the process of creating a lens flare effect. With plans to add it to SICS as an attribute to toggle and customize.

I also plan to have a professional UI design created that’ll replace the slider menu in the testing experience. To expand on this a bit, I eventually want to have an option for developers to allow players to have a SICS client-side menu (created and handled by SICS & accessed using TopBarPlus by default). This menu will house things like sliders for various settings to give players a bit more flexibility and control over Roblox’s default 0-10 graphics slider, and perhaps information such as the size of the sun, intensity, a heat scale relative to sun size, and so on. However, the extra info part of that seems useless to me on the players end. Not much interest lying there. Graphics control through… :eyes:

I’d love to hear anyone’s thoughts. How this can be improved upon, how it can be shaped to fit a wider variety of experiences, ideas to add to it. Let me know!



Edit: Sorry for bringing this up to the top of resources again… But I’ve gone ahead with one of the above future plans and implemented a menu that comes with the system. All players in your experience can have this menu- they will by default. You can choose to not allow users to have this menu by toggling off the AllowNativeMenu attribute.

This video shows what the native menu could look like going forward. With different options, of course. I’d rather not have Time of Day, cloud control, and whatnot built-in… But I’d eventually like to allow developers to add their own sliders and options to the native menu.

Currently the native menu only consists of a Dampening slider. For experiences outside of the linked showcase test experience. I wanted to give players a bit of control over the system specifically to allow the decreasing of sunray effects if needed, since higher intensities can impact performance. Let me know what other things can be useful in this menu. Doesn’t have to be interactable, it can simply be information useful to players. Thanks for your time!

Does this have support for transparent parts?

Yes! You can specify certain tags that will add to a list of parts to ignore in the root script if you’d like to ignore translucent parts such as windows.

By default, transparent parts are treated the same as non-transparent parts (unless CanQuery is disabled, then the part is forcefully ignored).

1 Like