Code/DayNightDriver.cs

Component that drives day/night visuals from a DayNightClock. Each update it finds the scene sun, sky and env probe and applies a SkyGrade based on clock time or an anchor grade when ShowCycle returns false.

using System;
using System.Linq;
using Sandbox;

namespace FieldGuide.DayNight;

/// <summary>
/// OPTIONAL convenience driver. Put it on the same GameObject as your scene's DirectionalLight and it applies
/// the day/night grade every frame from the <see cref="DayNightClock"/> in the scene: sun rotation + colour,
/// SkyBox2D tint, and EnvmapProbe tint. It resolves the sun from its own GameObject and the sky/envmap from
/// the scene (first of each). Delete this file if you would rather call <see cref="SkyGrade.ApplyGradeTo"/>
/// yourself.
///
/// It never touches exposure/shadows/fog, so it will not fight a locked-exposure camera.
///
/// The <see cref="ShowCycle"/> seam generalizes the source game's "am I possessing a character?" gate. While
/// it returns false the driver holds the stable ANCHOR grade (a fully-lit, non-moving model-viewer look) and
/// leaves the sky at your authoring backdrop; the moment it returns true the live cycle resumes at the true
/// game time (the clock keeps ticking underneath regardless). Default: always show the cycle.
/// </summary>
[Title( "Day Night Driver" )]
[Category( "Field Guide" )]
[Icon( "wb_sunny" )]
public sealed class DayNightDriver : Component
{
	/// <summary>Tuning. Defaults to the reference grade; set it to match your clock's config.</summary>
	public DayNightConfig Config { get; set; } = DayNightConfig.Default;

	/// <summary>Return false to hold the stable anchor grade instead of the live cycle (e.g. while the local
	/// player is in a menu / god-camera authoring mode). Null-safe: null means always show the cycle.</summary>
	public Func<bool> ShowCycle { get; set; }

	/// <summary>Optional sky tint to force while <see cref="ShowCycle"/> is false (your authoring backdrop). If
	/// null the anchor sky tint is used.</summary>
	public Color? AuthoringSkyTint { get; set; }

	DirectionalLight _sun;
	SkyBox2D _sky;
	EnvmapProbe _env;
	DayNightClock _clock;
	DayNightClock Clock => _clock ??= DayNightClock.For( Scene );

	protected override void OnEnabled()
	{
		_sun = GetComponent<DirectionalLight>();
		_sky = Scene.GetAllComponents<SkyBox2D>().FirstOrDefault();
		_env = Scene.GetAllComponents<EnvmapProbe>().FirstOrDefault();
	}

	protected override void OnUpdate()
	{
		if ( Scene.IsEditor ) return;                 // editor renders whatever you authored; the cycle is play-mode
		var clock = Clock;
		if ( clock is null || !_sun.IsValid() ) return;

		if ( ShowCycle is not null && !ShowCycle() )
		{
			// Stable anchor look (fully lit, not moving). Pass null for the sky so the anchor warm sky tint is not
			// applied, then force the authoring backdrop tint.
			SkyGrade.ApplyGradeTo( _sun, null, _env, Config.AnchorHours, WeatherKind.Clear, Config );
			if ( _sky.IsValid() ) _sky.Tint = AuthoringSkyTint ?? Config.SkyTint;
			return;
		}

		float total = clock.GetTimeHours();
		var weather = clock.EffectiveWeather( (int)MathF.Floor( total / 24f ) );
		SkyGrade.ApplyGradeTo( _sun, _sky, _env, total, weather, Config );
	}
}