Game/PlayerData.cs

Local player persistence object for the game. Caches display name/tag, last-applied round/game ids for deduping achievements and a boolean for whether the How To Play panel is open, and loads/saves this data as player.json using Sandbox FileSystem.Data.

File Access
using System;
using System.Text.Json;
using Sandbox;

namespace Splitclicker.Game;

// Local identity cache + achievement dedupe state, persisted to FileSystem.Data.
// Identity itself is the Steam account (resolved server-side); this just caches
// the display name/tag and the last-applied round/game ids so a reconnect replay
// can't double-count non-idempotent stat increments (PLAN §7.1).
public sealed class PlayerData
{
	public string Username { get; set; } = "";
	public string PlayerTag { get; set; } = "";

	/// <summary>round_id whose points_delta we last applied to the `points` stat.</summary>
	public string LastPointsRoundId { get; set; } = "";
	/// <summary>game_id whose win we last counted toward the `wins` stat.</summary>
	public string LastWinGameId { get; set; } = "";

	/// <summary>Whether the "How to Play" info panel is showing. Persisted so the
	/// player's choice to keep it open (or dismissed) survives across sessions —
	/// this is the local-storage state behind the GAME INFO button.</summary>
	public bool HowToPlayOpen { get; set; } = true;

	// Music persistence (track index / mute / volume / seed) lives in the Skafinity library
	// now — the SkafinityPlayer persists its own song index (PersistProgress), and the
	// library's SkafinityMusicPanel drives it. The game keeps no music state.

	const string FileName = "player.json";
	static PlayerData _cache;

	public static PlayerData Load()
	{
		if ( _cache != null ) return _cache;
		try
		{
			if ( FileSystem.Data.FileExists( FileName ) )
			{
				_cache = JsonSerializer.Deserialize<PlayerData>( FileSystem.Data.ReadAllText( FileName ) );
			}
		}
		catch ( Exception e )
		{
			Log.Warning( $"[Splitclicker] PlayerData load failed: {e.Message}" );
		}
		return _cache ??= new PlayerData();
	}

	public void Save()
	{
		_cache = this;
		try { FileSystem.Data.WriteAllText( FileName, JsonSerializer.Serialize( this ) ); }
		catch ( Exception e ) { Log.Warning( $"[Splitclicker] PlayerData save failed: {e.Message}" ); }
	}
}