UI/LoadingOverlay.razor

Loading overlay panel for the Horizon RP gamemode. Shows a spinner, the current load step and rotating tips until the local player's profile is loaded, and plays looping menu music until the player enters the world.

@using Sandbox;
@using Sandbox.UI;
@namespace HorizonRP
@inherits PanelComponent

@*
	L'écran d'arrivée d'Horizon RP. Le chargement de la carte, lui, est dessiné
	par s&box (on ne peut qu'y mettre une image, à la publication du paquet) ;
	celui-ci prend le relais dès que le jeu tourne et jusqu'à ce que le profil du
	joueur soit chargé : même image, l'étape en cours et des astuces qui défilent.

	La musique du menu (sounds/menu_music.mp3) joue pendant l'arrivée et la
	création du personnage, puis s'éteint en fondu quand on entre en ville.
*@

<root class="@(Visible ? "" : "gone")">
	@if ( Visible || sinceHidden < 0.6f )
	{
		<div class="overlay @(Visible ? "" : "fading")">
			<div class="bottom">
				<div class="spinner"></div>
				<div class="step">@Step</div>
				<div class="tip"><span class="tip-label">ASTUCE</span> @Tips[(int)(sinceShown / 5f) % Tips.Length]</div>
			</div>
		</div>
	}
</root>

@code
{
	static readonly string[] Tips =
	{
		"La flèche ↑ ouvre votre téléphone : appelez, envoyez des SMS, faites un virement.",
		"I ouvre l'inventaire ; les cases 1 à 5 s'utilisent directement aux touches 1 à 5.",
		"K ouvre votre portefeuille : montrez votre carte d'identité aux joueurs proches.",
		"P sur une porte : achetez-la, verrouillez-la, donnez la clé à vos amis.",
		"Au garage (touche E), la concession vend des véhicules ; L les verrouille.",
		"F4 : choisissez un métier. Le salaire tombe toutes les cinq minutes.",
		"Pensez à manger : la faim fait baisser la vie quand elle est à zéro.",
		"Un souci ? Le téléphone a une application « Signaler » qui prévient le staff.",
	};

	RealTimeSince sinceShown;
	RealTimeSince sinceHidden;
	bool wasVisible = true;

	RoleplayPlayer Me => RoleplayPlayer.Local;

	/// <summary>Tant que le joueur n'existe pas, ou que son profil n'est pas encore arrivé.</summary>
	bool Visible => Me is null || (!Me.NeedsCharacter && string.IsNullOrEmpty( Me.AppearanceJson ));

	string Step => Me is null ? "Arrivée à Union City…" : "Chargement de votre profil…";

	protected override void OnStart() => sinceShown = 0;

	protected override void OnUpdate()
	{
		if ( wasVisible && !Visible )
			sinceHidden = 0;
		wasVisible = Visible;

		UpdateMusic();
	}

	protected override void OnDestroy() => music?.Stop( 0.5f );

	// Musique du menu

	const string MusicFile = "sounds/menu_music.mp3";
	const float MusicVolume = 0.45f;

	SoundHandle music;
	bool musicDone;

	/// <summary>Pendant l'arrivée, puis la création du personnage (première connexion).</summary>
	bool MusicWanted => Visible || (Me?.NeedsCharacter ?? false);

	void UpdateMusic()
	{
		if ( musicDone || Application.IsDedicatedServer )
			return;

		if ( !MusicWanted )
		{
			// Une fois en ville, elle ne revient plus de la session.
			music?.Stop( 3f );
			music = null;
			musicDone = true;
			return;
		}

		// En boucle : relancée quand le morceau se termine.
		if ( music is null || music.IsStopped )
		{
			music = Sound.PlayFile( SoundFile.Load( MusicFile ), MusicVolume );
			if ( music is null )
			{
				// Fichier absent ou illisible : on n'insiste pas à chaque image.
				musicDone = true;
				return;
			}
			music.ListenLocal = true;
			music.Position = Vector3.Forward * 10f;
		}
	}

	protected override int BuildHash() => HashCode.Combine( Visible, Step, (int)(sinceShown / 5f), sinceHidden < 0.6f );
}