Game/Fit.cs
using System;

/// <summary>Where one S&amp;S2 piece sits: centre from its bone's joint (rig px), size, extra turn, mirrored.</summary>
public readonly record struct FitEntry( float X, float Y, float Scale, float Turn, bool Flip );

/// <summary>
/// The fitting table: where every body part, face, hair, beard, helmet, armour piece, weapon and shield
/// sits on the gladiator. Starts from <see cref="FitDefaults"/>; the Fitting Room changes it and saves
/// only what you changed to fitting.json in the game's data folder.
/// </summary>
public static class Fit
{
	public const string FileName = "fitting.json";

	static Dictionary<string, FitEntry> values;

	/// <summary>Goes up every time something changes, so puppets know to redraw.</summary>
	public static int Version { get; private set; }

	static Dictionary<string, FitEntry> Values
	{
		get
		{
			if ( values is null ) Load();
			return values;
		}
	}

	public static FitEntry Get( string key ) => Values.TryGetValue( key, out var e ) ? e : new FitEntry( 0, 0, 1, 0, false );

	public static bool Has( string key ) => Values.ContainsKey( key );

	public static void Set( string key, FitEntry e )
	{
		Values[key] = e;
		Version++;
	}

	public static void Reset( string key )
	{
		if ( FitDefaults.Values.TryGetValue( key, out var d ) ) Values[key] = d;
		else Values.Remove( key );
		Version++;
	}

	public static bool Changed( string key ) =>
		Values.TryGetValue( key, out var v ) && (!FitDefaults.Values.TryGetValue( key, out var d ) || v != d);

	public static void Load()
	{
		values = new Dictionary<string, FitEntry>( FitDefaults.Values );
		try
		{
			if ( FileSystem.Data.FileExists( FileName ) )
			{
				var saved = FileSystem.Data.ReadJson<Dictionary<string, float[]>>( FileName );
				if ( saved is not null )
				{
					foreach ( var (k, v) in saved )
					{
						if ( v is null || v.Length < 5 ) continue;
						values[k] = new FitEntry( v[0], v[1], v[2], v[3], v[4] > 0.5f );
					}
				}
			}
		}
		catch ( Exception e )
		{
			Log.Warning( $"Couldn't read {FileName}: {e.Message}" );
		}
		Version++;
	}

	/// <summary>Writes every entry that differs from the defaults.</summary>
	public static void Save()
	{
		var outp = new Dictionary<string, float[]>();
		foreach ( var (k, v) in Values )
		{
			if ( !Changed( k ) ) continue;
			outp[k] = new[] { MathF.Round( v.X, 1 ), MathF.Round( v.Y, 1 ), MathF.Round( v.Scale, 3 ), MathF.Round( v.Turn, 1 ), v.Flip ? 1f : 0f };
		}
		FileSystem.Data.WriteJson( FileName, outp );
	}
}