Jump/JumpAchievements.cs
using System;
using System.Collections.Generic;
using System.Linq;
/// <summary>
/// 成就与累计进度。
///
/// 成就本身要在 sbox 网站后台按 id 建好(解锁模式选 Manual),代码里只负责在条件满足时调用
/// Achievements.Unlock(id)。后台没建过的 id 调用会被安静忽略,不会报错。
///
/// 累计计数(总跳跃次数等)存在本地文件里;磁盘写入只在"一局结束"和"解锁成就"时发生——
/// 每次起跳都写盘太浪费,所以计数先留在内存,局终再落盘。
/// </summary>
public static class JumpAchievements
{
/// <summary> 一条成就定义(id / 标题 / 描述),后台照着这个表填 </summary>
public readonly record struct Info( string Id, string Title, string Description );
/// <summary> 全部成就。id 是后端标识,一旦上传就别再改 </summary>
public static readonly Info[] All =
{
new( "first_jump", "First Step", "Make your first jump." ),
new( "first_landing", "Safe Landing", "Land on a platform." ),
new( "score_10", "Getting Warm", "Score 10 in a single run." ),
new( "score_30", "Steady Hand", "Score 30 in a single run." ),
new( "score_50", "Half Century", "Score 50 in a single run." ),
new( "score_100", "Centurion", "Score 100 in a single run." ),
new( "perfect_3", "Triple Perfect", "Land 3 perfect jumps in a row." ),
new( "perfect_10", "Perfect Ten", "Land 10 perfect jumps in a row." ),
new( "hard_20", "Steady On Hard", "Score 20 or more on Hard." ),
new( "backjump", "Homeward", "Jump back onto the platform you came from." ),
new( "total_100_jumps", "Hundred Leaps", "Jump 100 times in total." ),
new( "total_50_games", "Persistence", "Play 50 games in total." ),
new( "total_500_perfect","Sharpshooter", "Land 500 perfect jumps in total." ),
};
// ---- 累计进度(持久化) ----
public class Progress
{
public int TotalJumps { get; set; }
public int TotalPerfect { get; set; }
public int TotalGames { get; set; }
public int TotalBackJumps { get; set; }
public int BestStreak { get; set; }
}
const string SaveFile = "jump_progress.json";
static Progress _progress = new();
static bool _loaded;
static bool _dirty;
public static Progress Data
{
get { Load(); return _progress; }
}
static void Load()
{
if ( _loaded ) return;
try
{
// 编辑器早期 FileSystem.Data 还没就绪会抛异常,此时不置位 _loaded,留给下次重试
if ( FileSystem.Data is null ) return;
if ( FileSystem.Data.FileExists( SaveFile ) )
{
_progress = Json.Deserialize<Progress>( FileSystem.Data.ReadAllText( SaveFile ) ) ?? new Progress();
}
_loaded = true;
}
catch ( Exception e )
{
Log.Warning( $"JumpAchievements load: {e.Message}" );
}
}
public static void Save()
{
if ( !_dirty ) return;
Load();
try
{
FileSystem.Data.WriteAllText( SaveFile, Json.Serialize( _progress ) );
_dirty = false;
}
catch ( Exception e )
{
Log.Warning( $"JumpAchievements save: {e.Message}" );
}
}
// ---- 事件入口(都由 JumpGame 调用;菜单演示与新手演示不计入) ----
/// <summary> 起跳 </summary>
public static void OnJump()
{
Load();
_progress.TotalJumps++;
_dirty = true;
Unlock( "first_jump" );
if ( _progress.TotalJumps >= 100 ) Unlock( "total_100_jumps" );
}
/// <summary> 落在普通平台上 </summary>
public static void OnLanding() => Unlock( "first_landing" );
/// <summary> 完美落地,streak 为当前连击数 </summary>
public static void OnPerfect( int streak )
{
Load();
_progress.TotalPerfect++;
_progress.BestStreak = Math.Max( _progress.BestStreak, streak );
_dirty = true;
if ( streak >= 3 ) Unlock( "perfect_3" );
if ( streak >= 10 ) Unlock( "perfect_10" );
if ( _progress.TotalPerfect >= 500 ) Unlock( "total_500_perfect" );
}
/// <summary> 倒跳落回身后的平台 </summary>
public static void OnBackJump()
{
Load();
_progress.TotalBackJumps++;
_dirty = true;
Unlock( "backjump" );
}
/// <summary> 一局结束。计数在这里落盘 </summary>
public static void OnGameEnd( int score, JumpDifficulty difficulty )
{
Load();
_progress.TotalGames++;
_dirty = true;
if ( score >= 10 ) Unlock( "score_10" );
if ( score >= 30 ) Unlock( "score_30" );
if ( score >= 50 ) Unlock( "score_50" );
if ( score >= 100 ) Unlock( "score_100" );
if ( difficulty == JumpDifficulty.Hard && score >= 20 ) Unlock( "hard_20" );
if ( _progress.TotalGames >= 50 ) Unlock( "total_50_games" );
Save();
}
/// <summary> 该成就是否已解锁(本地缓存,未拉到后台数据时一律按未解锁处理) </summary>
public static bool IsUnlocked( string id )
{
try
{
return Sandbox.Services.Achievements.All.FirstOrDefault( x => x.Name == id )?.IsUnlocked ?? false;
}
catch ( Exception )
{
return false;
}
}
/// <summary> 解锁(重复调用会被引擎忽略)。成功后立即落盘累计计数 </summary>
static void Unlock( string id )
{
try
{
Sandbox.Services.Achievements.Unlock( id );
Save();
}
catch ( Exception e )
{
Log.Warning( $"JumpAchievements unlock {id}: {e.Message}" );
}
}
}