using System; using System.Collections.Generic; using UnityEngine; [Serializable] public class PlayerRksSaveData { public float bestOverallRks; } public static class PlayerRksService { private const string SaveCategory = "player_rks"; private const string SaveKey = "overall"; private const float MaxScorePerChart = 1000000f; private const int TopChartCount = 20; public static event Action OnRksChanged; private static bool loaded; private static float bestOverallRks; public static void EnsureLoaded(Player_SO player = null) { if (loaded) { SyncPlayerSo(player); return; } loaded = true; bestOverallRks = 0f; PlayerRksSaveData saveData; if (SecureSaveVault.TryLoadJson(SaveCategory, SaveKey, out saveData) && saveData != null) { bestOverallRks = Mathf.Max(0f, saveData.bestOverallRks); } else if (PlayerProgressBackupService.TryRestoreRks(out float backupRks)) { bestOverallRks = Mathf.Max(0f, backupRks); } else if (player != null) { bestOverallRks = Mathf.Max(0f, player.URankingScore); } SyncPlayerSo(player); } public static float GetBestOverallRks(Player_SO player = null) { EnsureLoaded(player); return bestOverallRks; } public static float GetNormalizedOverallRks100(Player_SO player = null) { EnsureLoaded(player); float maxConstant = Mathf.Max(1f, GetMaxChartConstant()); return Mathf.Clamp(bestOverallRks / maxConstant * 100f, 0f, 100f); } public static float RefreshAndPersist(Player_SO player = null) { EnsureLoaded(player); float calculated = CalculateOverallRksRaw(); if (calculated > bestOverallRks + 0.0001f) { bestOverallRks = calculated; SecureSaveVault.SaveJson(SaveCategory, SaveKey, new PlayerRksSaveData { bestOverallRks = bestOverallRks }); PlayerProgressBackupService.SaveRks(bestOverallRks); SyncPlayerSo(player); OnRksChanged?.Invoke(bestOverallRks); } else { SyncPlayerSo(player); } return bestOverallRks; } public static void ClearPersistentState(Player_SO player = null) { loaded = true; bestOverallRks = 0f; SecureSaveVault.Delete(SaveCategory, SaveKey); PlayerProgressBackupService.ClearRksBackup(); SyncPlayerSo(player); OnRksChanged?.Invoke(bestOverallRks); } private static void SyncPlayerSo(Player_SO player) { Player_SO target = player != null ? player : LoadDefaultPlayerSo(); if (target != null && !Mathf.Approximately(target.URankingScore, bestOverallRks)) { target.SetURankingScore(bestOverallRks); } } private static Player_SO LoadDefaultPlayerSo() { Player_SO[] players = RuntimeResourcesCache.LoadAll(string.Empty); if (players == null) { return null; } for (int i = 0; i < players.Length; i++) { if (players[i] != null) { return players[i]; } } return null; } private static float CalculateOverallRksRaw() { SongData[] songs = RuntimeResourcesCache.LoadAllSongs(); if (songs == null || songs.Length == 0) { return 0f; } List chartRksValues = new List(64); for (int i = 0; i < songs.Length; i++) { SongData song = songs[i]; if (song == null || song.chartFiles == null) { continue; } for (int j = 0; j < song.chartFiles.Count; j++) { ChartFileEntry entry = song.chartFiles[j]; if (entry == null) { continue; } int bestTotal = entry.totalPersonalRecordForThisDifficulty; if (bestTotal <= 0) { bestTotal = song.GetPersonalRecord(entry.difficulty) + song.GetIdolRecord(entry.difficulty); } float chartRks = CalculateChartRks(entry.difficultyLEVEL, bestTotal); if (chartRks > 0f) { chartRksValues.Add(chartRks); } } } if (chartRksValues.Count == 0) { return 0f; } chartRksValues.Sort((a, b) => b.CompareTo(a)); int count = Mathf.Min(TopChartCount, chartRksValues.Count); float sum = 0f; for (int i = 0; i < count; i++) { sum += chartRksValues[i]; } return sum / Mathf.Max(1, count); } private static float GetMaxChartConstant() { SongData[] songs = RuntimeResourcesCache.LoadAllSongs(); float maxConstant = 0f; if (songs == null) { return 1f; } for (int i = 0; i < songs.Length; i++) { SongData song = songs[i]; if (song == null || song.chartFiles == null) { continue; } for (int j = 0; j < song.chartFiles.Count; j++) { ChartFileEntry entry = song.chartFiles[j]; if (entry != null && entry.difficultyLEVEL > maxConstant) { maxConstant = entry.difficultyLEVEL; } } } return Mathf.Max(1f, maxConstant); } private static float CalculateChartRks(float chartConstant, int totalScore) { if (chartConstant <= 0f || totalScore <= 0) { return 0f; } float scoreRate = Mathf.Clamp01(totalScore / MaxScorePerChart); float quality = Mathf.Pow(scoreRate, 1.35f); return chartConstant * quality; } }