Admins.cs

Static helper that decides who is server staff. Reads Steam IDs from staff_serveur.json in the server's data folder, caches the parsed set and reloads it every 30 seconds, treats the local player as staff in the editor and nobody as staff when the file is missing.

File Access
namespace HorizonRP;

/// <summary>
/// Qui est staff sur le serveur.
///
/// La liste est dans <c>staff_serveur.json</c>, posé dans les données du serveur dédié, sur la
/// machine qui l'héberge (comme <c>api_serveur.json</c>), jamais dans le projet :
/// <c>{ "Staff": [ { "SteamId": "7656…", "Nom": "Azunis" } ] }</c>. Elle était dans
/// <c>Admins.Server.cs</c>, que s&box retire du paquet publié : un serveur dédié qui lance le jeu
/// publié (30/10/2026, il ne sert plus le dossier local) n'avait plus de staff. Relue toutes les
/// 30 secondes : une modification du fichier vaut sans redémarrer. Sans fichier, personne n'est
/// staff : c'est le défaut sûr. Seule exception : dans l'éditeur, celui qui lance la partie est
/// staff, pour pouvoir essayer le menu F6, le physgun et le noclip (jamais sur un serveur).
/// </summary>
public static class Admins
{
	public const string FilePath = "staff_serveur.json";

	sealed class StaffFile
	{
		public List<StaffEntry> Staff { get; set; }
	}

	sealed class StaffEntry
	{
		public string SteamId { get; set; }
		public string Nom { get; set; }
	}

	static HashSet<ulong> staff;
	static RealTimeSince sinceRead;

	public static bool IsAdmin( ulong steamId )
	{
		if ( Application.IsEditor && steamId == Connection.Local?.SteamId )
			return true;
		return Application.IsDedicatedServer && ServerList().Contains( steamId );
	}

	static HashSet<ulong> ServerList()
	{
		if ( staff is not null && sinceRead < 30 )
			return staff;
		sinceRead = 0;
		var list = new HashSet<ulong>();
		try
		{
			var file = FileSystem.Data.ReadJsonOrDefault<StaffFile>( FilePath );
			foreach ( var entry in file?.Staff ?? new() )
				if ( ulong.TryParse( entry.SteamId, out var id ) )
					list.Add( id );
			if ( file is null )
				Log.Warning( $"[Horizon] {FilePath} absent : personne n'est staff. À poser ici : {FileSystem.Data.GetFullPath( "/" )}" );
		}
		catch ( Exception e )
		{
			Log.Warning( $"[Horizon] {FilePath} illisible : {e.Message}" );
		}
		staff = list;
		return staff;
	}
}