Jump/JumpScores.cs
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;

/// <summary>
/// 成绩记录:本地排行榜(FileSystem.Data 持久化,始终可用)
/// + 官方云端排行榜(Sandbox.Services 的 Stats/Leaderboards,尽力而为——
///   包未发布/离线时后端没有身份,拿不到数据就静默降级为纯本地)。
/// </summary>
public static class JumpScores
{
	const string SaveFile = "jump_scores.json";
	const int MaxKeep = 30;

	/// <summary> 一条本地成绩 </summary>
	public class ScoreEntry
	{
		public int Score { get; set; }
		public int Difficulty { get; set; }   // 0 简单 1 容易 2 困难
		public string Date { get; set; } = "";
	}

	static List<ScoreEntry> _scores = new();
	static bool _loaded;

	/// <summary> 云端排行榜条目(RefreshCloud 成功后填充) </summary>
	public static List<(long Rank, string Name, int Score)> Cloud { get; } = new();

	/// <summary> 云端排行榜是否成功拉取过 </summary>
	public static bool CloudLoaded { get; private set; }

	static JumpScores()
	{
		Load();
	}

		static void Load()
		{
			if ( _loaded ) return;

			try
			{
				// 编辑器早期(编辑模式/场景加载中)FileSystem.Data 还没就绪会抛 NRE,
				// 此时不能把 _loaded 置位,留给游戏运行期间的下次调用重试
				if ( FileSystem.Data is null ) return;

				if ( FileSystem.Data.FileExists( SaveFile ) )
				{
					_scores = Json.Deserialize<List<ScoreEntry>>( FileSystem.Data.ReadAllText( SaveFile ) ) ?? new List<ScoreEntry>();
				}
				_loaded = true;
			}
			catch ( Exception e )
			{
				Log.Warning( $"JumpScores load: {e.Message}" );
			}
		}

	/// <summary> 历史最高分 </summary>
	public static int Best => _scores.Count > 0 ? _scores[0].Score : 0;

	/// <summary> 本地前 N 名(降序,全难度) </summary>
	public static List<ScoreEntry> Top( int count )
	{
		Load();
		return _scores.Take( count ).ToList();
	}

	/// <summary> 指定难度的本地前 N 名(降序) </summary>
	public static List<ScoreEntry> Top( int count, JumpDifficulty difficulty )
	{
		Load();
		return _scores.Where( x => x.Difficulty == (int)difficulty ).Take( count ).ToList();
	}

	/// <summary> 局终记录成绩(云端上报顺带触发) </summary>
	public static void Record( int score, JumpDifficulty difficulty )
	{
		if ( score <= 0 ) return;
		Load();

		_scores.Add( new ScoreEntry { Score = score, Difficulty = (int)difficulty, Date = DateTime.Now.ToString( "MM-dd" ) } );
		_scores = _scores.OrderByDescending( x => x.Score ).Take( MaxKeep ).ToList();
		Save();

		SubmitCloud( score, difficulty );
	}

	static void Save()
	{
		try
		{
			FileSystem.Data.WriteAllText( SaveFile, Json.Serialize( _scores ) );
		}
		catch ( Exception e )
		{
			Log.Warning( $"JumpScores save: {e.Message}" );
		}
	}

	// ---- 云端(官方 Services,尽力而为)----

	/// <summary> 上报成绩到官方云端(打包发布后生效;本地开发包可能没有后端身份) </summary>
	public static void SubmitCloud( int score, JumpDifficulty difficulty )
	{
		try
		{
			Sandbox.Services.Stats.SetValue( "best_score", score, new Dictionary<string, object>
			{
				{ "difficulty", difficulty.ToString() }
			} );
			_ = FlushAndReloadCloud();
		}
		catch ( Exception e )
		{
			Log.Warning( $"JumpScores cloud submit: {e.Message}" );
		}
	}

	/// <summary> 拉取云端排行榜(聚合 = 每人历史最高分,降序) </summary>
	public static async Task RefreshCloud()
	{
		try
		{
			var board = Sandbox.Services.Leaderboards.GetFromStat( "best_score" );
			board.SetAggregationMax();
			board.MaxEntries = 10;

			await board.Refresh();

			Cloud.Clear();
			foreach ( var e in board.Entries )
			{
				Cloud.Add( ( e.Rank, e.DisplayName ?? "???", (int)e.Value ) );
			}

			CloudLoaded = true;
		}
		catch ( Exception )
		{
			// 本地包 / 离线:云端不可用,静默降级为纯本地排行
		}
	}

	static async Task FlushAndReloadCloud()
	{
		await Sandbox.Services.Stats.FlushAsync();
		await RefreshCloud();
	}
}