World/GameClock.cs

Static utility that derives the in-game town clock from UTC time, a configurable day length and offset. Also exposes formatted time text, a daylight value and an IsNight flag used by lighting and car headlights, with an editor debug override.

namespace HorizonRP;

/// <summary>
/// L'heure de la ville (Julien, 29/10/2026) : une journée en jeu dure <see cref="DayMinutes"/>
/// minutes réelles (48 par défaut, comme GTA : une minute réelle, une demi-heure en jeu). Elle se
/// déduit de l'heure UTC et des réglages du serveur (« jour_duree_minutes », « heure_decalage »,
/// déclarés aussi dans SETTINGS d'api.php), si bien que toutes les machines ont la même sans rien
/// échanger. Le téléphone l'affiche ; la date, elle, reste la vraie. Le jour et la nuit suivent
/// (<see cref="DayNight"/>), comme les phares des voitures.
/// </summary>
public static class GameClock
{
	public static float DayMinutes => Math.Max( 10, ServerSettings.Number( "jour_duree_minutes" ) );
	public static float OffsetMinutes => ServerSettings.Number( "heure_decalage" );

	/// <summary>Essais depuis l'éditeur : une heure imposée (en heures, 0 à 24), au lieu de celle de la ville.</summary>
	public static float? DebugHours { get; set; }

	/// <summary>Les minutes écoulées depuis minuit, en jeu (0 à 1440).</summary>
	public static float Minutes
	{
		get
		{
			if ( DebugHours is { } forced )
				return Math.Clamp( forced, 0, 23.99f ) * 60f;
			var seconds = DateTimeOffset.UtcNow.ToUnixTimeMilliseconds() / 1000.0;
			var minutes = seconds * 1440.0 / (DayMinutes * 60.0) + OffsetMinutes;
			return (float)(((minutes % 1440) + 1440) % 1440);
		}
	}

	public static float Hours => Minutes / 60f;

	/// <summary>« 14:05 ».</summary>
	public static string Text
	{
		get
		{
			var m = (int)Minutes;
			return $"{m / 60:00}:{m % 60:00}";
		}
	}

	/// <summary>
	/// La lumière du jour, de 0 (nuit) à 1 (plein jour) : l'aube de 6 h à 8 h, le crépuscule de
	/// 18 h à 20 h 30.
	/// </summary>
	public static float Daylight
	{
		get
		{
			var h = Hours;
			if ( h >= 8 && h < 18 ) return 1;
			if ( h >= 6 && h < 8 ) return Smooth( (h - 6) / 2 );
			if ( h >= 18 && h < 20.5f ) return 1 - Smooth( (h - 18) / 2.5f );
			return 0;
		}
	}

	/// <summary>La nuit : les lampadaires s'allument, les phares aussi en montant en voiture.</summary>
	public static bool IsNight => Daylight < 0.5f;

	static float Smooth( float t )
	{
		t = Math.Clamp( t, 0, 1 );
		return t * t * (3 - 2 * t);
	}
}