Persistence.cs

Player profile persistence for the Horizon RP gamemode. Stores each player's SteamId, RP name, wallet, bank, appearance and inventory as one JSON file per player under FileSystem.Data, and loads or creates a default profile on join.

File Access
namespace HorizonRP;

/// <summary>
/// Ce qu'on garde d'un joueur d'une session à l'autre. Le métier n'en fait pas
/// partie, exprès : comme sur DarkRP, on revient toujours citoyen.
/// </summary>
public sealed class PlayerProfile
{
	public ulong SteamId { get; set; }
	public string RpName { get; set; }
	public int Wallet { get; set; }
	public int Bank { get; set; }

	/// <summary>Tenue et morphologie (JSON ClothingContainer) ; vide = personnage à créer.</summary>
	public string Appearance { get; set; }

	/// <summary>Les cases de l'inventaire (JSON).</summary>
	public string Inventory { get; set; }

	/// <summary>Date de naissance du personnage, « AAAA-MM-JJ ».</summary>
	public string BirthDate { get; set; }

	/// <summary>« M » ou « F ».</summary>
	public string Sex { get; set; }

	public DateTime FirstSeen { get; set; }
	public DateTime LastSeen { get; set; }
}

/// <summary>
/// Sauvegarde des profils, un fichier JSON par joueur dans les données du
/// serveur (<c>FileSystem.Data</c>). Ne s'appelle que sur l'hôte : un client
/// n'a rien à écrire.
/// </summary>
public static class Persistence
{
	const string Folder = "profils";

	/// <summary>Argent de départ d'un nouveau joueur.</summary>
	public const int StartingWallet = 500;
	public const int StartingBank = 1000;

	static string PathOf( ulong steamId ) => $"{Folder}/{steamId}.json";

	public static PlayerProfile Load( ulong steamId, string displayName )
	{
		var path = PathOf( steamId );

		if ( FileSystem.Data.FileExists( path ) )
		{
			// Un fichier abîmé ne doit pas remettre le joueur à zéro en silence
			// à la prochaine sauvegarde : on le met de côté avant de repartir.
			var profile = FileSystem.Data.ReadJsonOrDefault<PlayerProfile>( path );
			if ( profile is not null )
				return profile;

			Log.Warning( $"[Horizon] Profil illisible pour {steamId}, mis de côté en {path}.illisible" );
			FileSystem.Data.WriteAllText( $"{path}.illisible", FileSystem.Data.ReadAllText( path ) );
		}

		return new PlayerProfile
		{
			SteamId = steamId,
			// Vide : la création du personnage le nomme (jamais le pseudo Steam).
			RpName = "",
			Wallet = StartingWallet,
			Bank = StartingBank,
			FirstSeen = DateTime.UtcNow,
			LastSeen = DateTime.UtcNow,
		};
	}

	public static void Save( PlayerProfile profile )
	{
		if ( profile is null || profile.SteamId == 0 )
			return;

		FileSystem.Data.CreateDirectory( Folder );
		profile.LastSeen = DateTime.UtcNow;
		FileSystem.Data.WriteJson( PathOf( profile.SteamId ), profile );
	}
}