From 598bbb0e068153b78c2950ba5070e2118804ea4d Mon Sep 17 00:00:00 2001 From: 0auBSQ <58159635+0auBSQ@users.noreply.github.com> Date: Mon, 17 Jun 2024 02:00:14 +0900 Subject: [PATCH] Additional unlockable methods --- OpenTaiko/Databases/NameplateUnlockables.db3 | Bin 122880 -> 122880 bytes .../Databases/UnlockablesDocumentation.txt | 6 +- OpenTaiko/src/Common/BestPlayRecords.cs | 172 ++++++++++++++++++ OpenTaiko/src/Common/LogNotification.cs | 67 +++++++ OpenTaiko/src/Common/SaveFile.cs | 81 ++++++++- .../src/Databases/DBNameplateUnlockables.cs | 2 +- OpenTaiko/src/Databases/DBSaves.cs | 121 ++++++++---- OpenTaiko/src/Databases/DBUnlockables.cs | 112 +++++------- .../src/Stages/07.Game/Taiko/ModIcons.cs | 14 ++ 9 files changed, 468 insertions(+), 107 deletions(-) create mode 100644 OpenTaiko/src/Common/BestPlayRecords.cs create mode 100644 OpenTaiko/src/Common/LogNotification.cs diff --git a/OpenTaiko/Databases/NameplateUnlockables.db3 b/OpenTaiko/Databases/NameplateUnlockables.db3 index 7ffa8cd76550cec7e8edb7694704bd5d44e3e9e0..94224014bd5f545cfdbd2dfafc01878f2d99b687 100644 GIT binary patch delta 277 zcmZoTz}|3xeS$Qj#6%fqMv09H%k3GNCv!NsGm38xaM;HoY@}lt8y(BQz+j=m$)L(8 zDJUpDSD4U0Le#wAT#;=HG!ms eKS*VPKS<3Ie_bGX$scG-^Kbv{zx^3mCjbDNflwm= delta 277 zcmZoTz}|3xeS$Qj_(U0JM)8da%k3E%CUZErGm2~uaM;HoV4!0d8y#z{!pWe@C@Cl? zJ6X|Fnnl-8$6&Ipr#++5yi5`SGFdC4DWOY?94?Z5pQStkGh_)<`3 diff --git a/OpenTaiko/Databases/UnlockablesDocumentation.txt b/OpenTaiko/Databases/UnlockablesDocumentation.txt index 09c290ac..8d1d1d57 100644 --- a/OpenTaiko/Databases/UnlockablesDocumentation.txt +++ b/OpenTaiko/Databases/UnlockablesDocumentation.txt @@ -3,10 +3,10 @@ * ch : "Coins here", coin requirement, payable within the heya menu, 1 value : [Coin price] * cs : "Coins shop", coin requirement, payable only within the Medal shop selection screen * cm : "Coins menu", coin requirement, payable only within the song select screen (used only for songs) -* dp : "Difficulty pass", count of difficulties pass, unlock check during the results screen, condition 3 values : [Difficulty int (0~4), Clear status (0~2), Number of performances], input 1 value [Plays fitting the condition] -* lp : "Level pass", count of level pass, unlock check during the results screen, condition 3 values : [Star rating, Clear status (0~2), Number of performances], input 1 value [Plays fitting the condition] +* dp : "Difficulty pass", count of difficulties pass, unlock check during the results screen, condition 3 values : [Difficulty int (0~4), Clear status (0~4), Number of performances], input 1 value [Plays fitting the condition] +* lp : "Level pass", count of level pass, unlock check during the results screen, condition 3 values : [Star rating, Clear status (0~4), Number of performances], input 1 value [Plays fitting the condition] * sp : "Song performance", count of a specific song pass, unlock check during the results screen, condition 2 x n values for n songs : [Difficulty int (0~4, if -1 : Any), Clear status (0~2), ...], input 1 value [Count of fullfiled songs], n references for n songs (Song ids) -* sg : "Song genre (performance)", count of any song pass within a specific genre folder, unlock check during the results screen, condition 3 x n values for n songs : [Song count, Difficulty int (0~4, if -1 : Any), Clear status (0~2), ...], input 1 value [Count of fullfiled genres], n references for n genres (Genre names) +* sg : "Song genre (performance)", count of any song pass within a specific genre folder, unlock check during the results screen, condition 2 x n values for n songs : [Song count, Clear status (0~4), ...], input 1 value [Count of fullfiled genres], n references for n genres (Genre names) == UnlockType == diff --git a/OpenTaiko/src/Common/BestPlayRecords.cs b/OpenTaiko/src/Common/BestPlayRecords.cs new file mode 100644 index 00000000..2fbe2823 --- /dev/null +++ b/OpenTaiko/src/Common/BestPlayRecords.cs @@ -0,0 +1,172 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading.Tasks; + +namespace TJAPlayer3 +{ + internal class BestPlayRecords + { + + public enum EClearStatus + { + NONE = 0, + ASSISTED_CLEAR = 1, + CLEAR = 2, + FC = 3, + PERFECT = 4, + TOTAL = 5 + } + + public class CBestPlayRecord + { + public string ChartUniqueId = "none"; + public string ChartGenre = "none"; + public string Charter = "none"; + public string Artist = "none"; + public Int64 PlayMods = 0; + public Int64 ChartDifficulty = 3; + public Int64 ChartLevel = 8; + public Int64 ClearStatus = -1; + public Int64 ScoreRank = -1; + public Int64 HighScore = 0; + public Int64 TowerBestFloor = 0; + public List DanExam1 = new List { -1 }; + public List DanExam2 = new List { -1 }; + public List DanExam3 = new List { -1 }; + public List DanExam4 = new List { -1 }; + public List DanExam5 = new List { -1 }; + public List DanExam6 = new List { -1 }; + public List DanExam7 = new List { -1 }; + public Int64 PlayCount = 1; + public Int64 HighScoreGoodCount = 0; + public Int64 HighScoreOkCount = 0; + public Int64 HighScoreBadCount = 0; + public Int64 HighScoreMaxCombo = 0; + public Int64 HighScoreRollCount = 0; + public Int64 HighScoreADLibCount = 0; + public Int64 HighScoreBoomCount = 0; + } + + public class CBestPlayStats + { + public int DistinctPlays = 0; + public int DistinctClears = 0; + public int DistinctFCs = 0; + public int DistinctPerfects = 0; + public int SongDistinctPlays = 0; + public int SongDistinctClears = 0; + public int SongDistinctFCs = 0; + public int SongDistinctPerfects = 0; + public int[][] ClearStatuses = new int[(int)Difficulty.Total][]; + public int[][] ScoreRanks = new int[(int)Difficulty.Total][]; + public Dictionary LevelPlays = new Dictionary(); + public Dictionary LevelClears = new Dictionary(); + public Dictionary LevelFCs = new Dictionary(); + public Dictionary LevelPerfects = new Dictionary(); + public Dictionary GenrePlays = new Dictionary(); + public Dictionary GenreClears = new Dictionary(); + public Dictionary GenreFCs = new Dictionary(); + public Dictionary GenrePerfects = new Dictionary(); + public Dictionary SongGenrePlays = new Dictionary(); + public Dictionary SongGenreClears = new Dictionary(); + public Dictionary SongGenreFCs = new Dictionary(); + public Dictionary SongGenrePerfects = new Dictionary(); + public Dictionary CharterPlays = new Dictionary(); + public Dictionary CharterClears = new Dictionary(); + public Dictionary CharterFCs = new Dictionary(); + public Dictionary CharterPerfects = new Dictionary(); + + public CBestPlayStats() + { + // 0 : Not clear, 1 : Assisted clear, 2 : Clear, 3 : FC, 4 : Perfect + ClearStatuses[0] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 }; + ClearStatuses[1] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 }; + ClearStatuses[2] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 }; + ClearStatuses[3] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 }; + ClearStatuses[4] = new int[(int)EClearStatus.TOTAL] { 0, 0, 0, 0, 0 }; + + // 0 : None, 1 : E, 2 : D, 3 : C, 4 : B, 5 : A, 6 : S, 7 : Omega + ScoreRanks[0] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; + ScoreRanks[1] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; + ScoreRanks[2] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; + ScoreRanks[3] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; + ScoreRanks[4] = new int[8] { 0, 0, 0, 0, 0, 0, 0, 0 }; + } + } + + static private void InitOrAddDict(Dictionary dict, T entry) where T : notnull + { + if (!dict.ContainsKey(entry)) + dict[entry] = 0; + dict[entry]++; + } + + static public CBestPlayStats tGenerateBestPlayStats( + Dictionary.ValueCollection uniqueChartBestPlays, + Dictionary.ValueCollection uniqueSongBestPlays + ) + { + CBestPlayStats stats = new CBestPlayStats(); + + // Individual charts + foreach (CBestPlayRecord record in uniqueChartBestPlays) + { + Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty)); + if (roundedDifficulty <= (int)Difficulty.Edit) + { + string[] ChartersArr = record.Charter.Split(",", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + Int64 roundedScoreRank = Math.Max(0, Math.Min(7, record.ScoreRank + 1)); + Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1)); + + stats.ScoreRanks[roundedDifficulty][roundedScoreRank]++; + stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++; + foreach (string Charter in ChartersArr) + { + InitOrAddDict(stats.CharterPlays, Charter); + if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.CharterClears, Charter); + if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.CharterFCs, Charter); + if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.CharterPerfects, Charter); + } + InitOrAddDict(stats.GenrePlays, record.ChartGenre); + if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.GenreClears, record.ChartGenre); + if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.GenreFCs, record.ChartGenre); + if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.GenrePerfects, record.ChartGenre); + InitOrAddDict(stats.LevelPlays, (int)record.ChartLevel); + if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.LevelClears, (int)record.ChartLevel); + if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.LevelFCs, (int)record.ChartLevel); + if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.LevelPerfects, (int)record.ChartLevel); + stats.DistinctPlays++; + if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.DistinctClears++; + if (roundedClearStatus >= (int)EClearStatus.FC) stats.DistinctFCs++; + if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.DistinctPerfects++; + } + // TODO: Add Dan and Tower + } + + // Individual songs + foreach (CBestPlayRecord record in uniqueSongBestPlays) + { + Int64 roundedDifficulty = Math.Max((int)Difficulty.Easy, Math.Min((int)Difficulty.Total - 1, record.ChartDifficulty)); + + if (roundedDifficulty <= (int)Difficulty.Edit) + { + Int64 roundedClearStatus = Math.Max((int)EClearStatus.NONE, Math.Min((int)EClearStatus.PERFECT, record.ClearStatus + 1)); + + InitOrAddDict(stats.SongGenrePlays, record.ChartGenre); + if (roundedClearStatus >= (int)EClearStatus.CLEAR) InitOrAddDict(stats.SongGenreClears, record.ChartGenre); + if (roundedClearStatus >= (int)EClearStatus.FC) InitOrAddDict(stats.SongGenreFCs, record.ChartGenre); + if (roundedClearStatus == (int)EClearStatus.PERFECT) InitOrAddDict(stats.SongGenrePerfects, record.ChartGenre); + stats.SongDistinctPlays++; + if (roundedClearStatus >= (int)EClearStatus.CLEAR) stats.SongDistinctClears++; + if (roundedClearStatus >= (int)EClearStatus.FC) stats.SongDistinctFCs++; + if (roundedClearStatus == (int)EClearStatus.PERFECT) stats.SongDistinctPerfects++; + } + } + + return stats; + } + } +} diff --git a/OpenTaiko/src/Common/LogNotification.cs b/OpenTaiko/src/Common/LogNotification.cs new file mode 100644 index 00000000..29cb8dc7 --- /dev/null +++ b/OpenTaiko/src/Common/LogNotification.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using FDK; + +namespace TJAPlayer3 +{ + internal class LogNotification + { + private static Queue Notifications = new Queue(); + + public enum ENotificationType + { + EINFO, + ESUCCESS, + EWARNING, + EERROR, + } + + public class CLogNotification + { + public CLogNotification(ENotificationType nt, string msg) + { + NotificationType = nt; + Message = msg; + } + + public ENotificationType NotificationType = ENotificationType.EINFO; + public string Message = ""; + public CCounter LifeTime = new CCounter(0, 1000, 1, TJAPlayer3.Timer); + } + + + public static void PopError(string message) + { + Notifications.Enqueue(new CLogNotification(ENotificationType.EERROR, message)); + Trace.TraceError(": " + message); + } + + public static void PopWarning(string message) + { + Notifications.Enqueue(new CLogNotification(ENotificationType.EWARNING, message)); + Trace.TraceWarning(": " + message); + } + + public static void PopSuccess(string message) + { + Notifications.Enqueue(new CLogNotification(ENotificationType.ESUCCESS, message)); + Trace.TraceInformation(": " + message); + } + + public static void PopInfo(string message) + { + Notifications.Enqueue(new CLogNotification(ENotificationType.EINFO, message)); + Trace.TraceInformation(": " + message); + } + + public static void Display() + { + while (Notifications.Count > 0 && Notifications.Peek().LifeTime.IsEnded) Notifications.Dequeue(); + // Add an optimized method to display the notifications here + } + } +} diff --git a/OpenTaiko/src/Common/SaveFile.cs b/OpenTaiko/src/Common/SaveFile.cs index 18137604..5416de9d 100644 --- a/OpenTaiko/src/Common/SaveFile.cs +++ b/OpenTaiko/src/Common/SaveFile.cs @@ -24,8 +24,13 @@ namespace TJAPlayer3 } tLoadFile(); + + data.bestPlays = DBSaves.GetBestPlaysAsDict(data.SaveId); + data.tFactorizeBestPlays(); } + + #region [Medals] public void tEarnCoins(int amount) @@ -193,7 +198,7 @@ namespace TJAPlayer3 public class Data { [JsonProperty("saveId")] - public long SaveId = 0; + public Int64 SaveId = 0; [JsonProperty("name")] public string Name = "プレイヤー1"; @@ -249,6 +254,80 @@ namespace TJAPlayer3 [JsonProperty("standardPasses")] public Dictionary standardPasses = new Dictionary(); + [JsonProperty("___unused_00")] + public Dictionary bestPlays = new Dictionary (); + + [JsonProperty("___unused_01")] + public Dictionary bestPlaysDistinctCharts = new Dictionary(); + + [JsonProperty("___unused_02")] + public Dictionary bestPlaysDistinctSongs = new Dictionary(); + + [JsonProperty("___unused_03")] + public BestPlayRecords.CBestPlayStats bestPlaysStats = new BestPlayRecords.CBestPlayStats (); + + #region [Factorize best plays] + + public void tFactorizeBestPlays() + { + bestPlaysDistinctCharts = new Dictionary(); + + foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlays.Values) + { + string key = bestPlay.ChartUniqueId + bestPlay.ChartDifficulty.ToString(); + if (!bestPlaysDistinctCharts.ContainsKey(key)) + { + bestPlaysDistinctCharts[key] = bestPlay; + } + else + { + if (bestPlay.HighScore > bestPlaysDistinctCharts[key].HighScore) + { + bestPlaysDistinctCharts[key].HighScore = bestPlay.HighScore; + bestPlaysDistinctCharts[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount; + bestPlaysDistinctCharts[key].HighScoreOkCount = bestPlay.HighScoreOkCount; + bestPlaysDistinctCharts[key].HighScoreBadCount = bestPlay.HighScoreBadCount; + bestPlaysDistinctCharts[key].HighScoreRollCount = bestPlay.HighScoreRollCount; + bestPlaysDistinctCharts[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount; + bestPlaysDistinctCharts[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo; + bestPlaysDistinctCharts[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount; + } + bestPlaysDistinctCharts[key].ScoreRank = Math.Max(bestPlaysDistinctCharts[key].ScoreRank, bestPlay.ScoreRank); + bestPlaysDistinctCharts[key].ClearStatus = Math.Max(bestPlaysDistinctCharts[key].ClearStatus, bestPlay.ClearStatus); + } + } + + bestPlaysDistinctSongs = new Dictionary(); + + foreach (BestPlayRecords.CBestPlayRecord bestPlay in bestPlaysDistinctCharts.Values) + { + string key = bestPlay.ChartUniqueId; + if (!bestPlaysDistinctSongs.ContainsKey(key)) + { + bestPlaysDistinctSongs[key] = bestPlay; + } + else + { + if (bestPlay.HighScore > bestPlaysDistinctSongs[key].HighScore) + { + bestPlaysDistinctSongs[key].HighScore = bestPlay.HighScore; + bestPlaysDistinctSongs[key].HighScoreGoodCount = bestPlay.HighScoreGoodCount; + bestPlaysDistinctSongs[key].HighScoreOkCount = bestPlay.HighScoreOkCount; + bestPlaysDistinctSongs[key].HighScoreBadCount = bestPlay.HighScoreBadCount; + bestPlaysDistinctSongs[key].HighScoreRollCount = bestPlay.HighScoreRollCount; + bestPlaysDistinctSongs[key].HighScoreBoomCount = bestPlay.HighScoreBoomCount; + bestPlaysDistinctSongs[key].HighScoreMaxCombo = bestPlay.HighScoreMaxCombo; + bestPlaysDistinctSongs[key].HighScoreADLibCount = bestPlay.HighScoreADLibCount; + } + bestPlaysDistinctSongs[key].ScoreRank = Math.Max(bestPlaysDistinctSongs[key].ScoreRank, bestPlay.ScoreRank); + bestPlaysDistinctSongs[key].ClearStatus = Math.Max(bestPlaysDistinctSongs[key].ClearStatus, bestPlay.ClearStatus); + } + } + + bestPlaysStats = BestPlayRecords.tGenerateBestPlayStats(bestPlaysDistinctCharts.Values, bestPlaysDistinctSongs.Values); + } + + #endregion } public Data data = new Data(); diff --git a/OpenTaiko/src/Databases/DBNameplateUnlockables.cs b/OpenTaiko/src/Databases/DBNameplateUnlockables.cs index 59d57081..85b68336 100644 --- a/OpenTaiko/src/Databases/DBNameplateUnlockables.cs +++ b/OpenTaiko/src/Databases/DBNameplateUnlockables.cs @@ -53,7 +53,7 @@ namespace TJAPlayer3 data[((Int64)reader["NameplateId"]).ToString()] = nu; } - + reader.Close(); } } public class NameplateUnlockable diff --git a/OpenTaiko/src/Databases/DBSaves.cs b/OpenTaiko/src/Databases/DBSaves.cs index c0182cee..747aebda 100644 --- a/OpenTaiko/src/Databases/DBSaves.cs +++ b/OpenTaiko/src/Databases/DBSaves.cs @@ -11,56 +11,103 @@ namespace TJAPlayer3 { internal class DBSaves { - private static string _savesDBPath = @$"{TJAPlayer3.strEXEのあるフォルダ}Saves.db3"; + private static string _savesDBFilename = $@"Saves.db3"; + private static string _savesDBPath = @$"{TJAPlayer3.strEXEのあるフォルダ}{_savesDBFilename}"; private static SqliteConnection SavesDBConnection = new SqliteConnection(@$"Data Source={_savesDBPath}"); - public static SqliteConnection GetSavesDBConnection() + private static string _DBNotFoundError = @$"The database {_savesDBFilename} was not found or the connection failed"; + + public static SqliteConnection? GetSavesDBConnection() { - if (SavesDBConnection != null && SavesDBConnection.State == ConnectionState.Closed) SavesDBConnection.Open(); - return SavesDBConnection; + try + { + if (SavesDBConnection != null && SavesDBConnection.State == ConnectionState.Closed) SavesDBConnection.Open(); + return SavesDBConnection; + } + catch + { + LogNotification.PopError(_DBNotFoundError); + return null; + } } - public class CBestPlayRecord + public static Int64 GetPlayerSaveId(int player) { - public string ChartUniqueId = "none"; - public string ChartGenre = "none"; - public string Charter = "none"; - public string Artist = "none"; - public Int64 PlayMods = 0; - public Int64 ChartDifficulty = 3; - public Int64 ChartLevel = 8; - public Int64 ClearStatus = -1; - public Int64 ScoreRank = -1; - public Int64 HighScore = 0; - public Int64 TowerBestFloor = 0; - public List DanExam1 = new List { -1 }; - public List DanExam2 = new List { -1 }; - public List DanExam3 = new List { -1 }; - public List DanExam4 = new List { -1 }; - public List DanExam5 = new List { -1 }; - public List DanExam6 = new List { -1 }; - public List DanExam7 = new List { -1 }; - public Int64 PlayCount = 1; - public Int64 HighScoreGoodCount = 0; - public Int64 HighScoreOkCount = 0; - public Int64 HighScoreBadCount = 0; - public Int64 HighScoreMaxCombo = 0; - public Int64 HighScoreRollCount = 0; - public Int64 HighScoreADLibCount = 0; - public Int64 HighScoreBoomCount = 0; + return TJAPlayer3.SaveFileInstances[TJAPlayer3.GetActualPlayer(player)].data.SaveId; } + + #region [best_plays Table] + + public static Dictionary GetBestPlaysAsDict(Int64 saveId) + { + Dictionary _bestPlays = new Dictionary(); + SqliteConnection? connection = GetSavesDBConnection(); + if (connection == null) return _bestPlays; + + var command = connection.CreateCommand(); + command.CommandText = + @$" + SELECT * + FROM best_plays + WHERE SaveId={saveId}; + "; + SqliteDataReader reader = command.ExecuteReader(); + while (reader.Read()) + { + BestPlayRecords.CBestPlayRecord record = new BestPlayRecords.CBestPlayRecord(); + + record.ChartUniqueId = (string)reader["ChartUniqueId"]; + record.ChartGenre = (string)reader["ChartGenre"]; + record.Charter = (string)reader["Charter"]; + record.Artist = (string)reader["Artist"]; + record.PlayMods = (Int64)reader["PlayMods"]; + record.ChartDifficulty = (Int64)reader["ChartDifficulty"]; ; + record.ChartLevel = (Int64)reader["ChartLevel"]; + record.ClearStatus = (Int64)reader["ClearStatus"]; + record.ScoreRank = (Int64)reader["ScoreRank"]; + record.HighScore = (Int64)reader["HighScore"]; + record.TowerBestFloor = (Int64)reader["TowerBestFloor"]; + record.DanExam1 = JsonConvert.DeserializeObject>((string)reader["DanExam1"] ?? "[]") ?? new List(); + record.DanExam2 = JsonConvert.DeserializeObject>((string)reader["DanExam2"] ?? "[]") ?? new List(); + record.DanExam3 = JsonConvert.DeserializeObject>((string)reader["DanExam3"] ?? "[]") ?? new List(); + record.DanExam4 = JsonConvert.DeserializeObject>((string)reader["DanExam4"] ?? "[]") ?? new List(); + record.DanExam5 = JsonConvert.DeserializeObject>((string)reader["DanExam5"] ?? "[]") ?? new List(); + record.DanExam6 = JsonConvert.DeserializeObject>((string)reader["DanExam6"] ?? "[]") ?? new List(); + record.DanExam7 = JsonConvert.DeserializeObject>((string)reader["DanExam7"] ?? "[]") ?? new List(); + record.PlayCount = (Int64)reader["PlayCount"]; + record.HighScoreGoodCount = (Int64)reader["HighScoreGoodCount"]; + record.HighScoreOkCount = (Int64)reader["HighScoreOkCount"]; + record.HighScoreBadCount = (Int64)reader["HighScoreBadCount"]; + record.HighScoreMaxCombo = (Int64)reader["HighScoreMaxCombo"]; + record.HighScoreRollCount = (Int64)reader["HighScoreRollCount"]; + record.HighScoreADLibCount = (Int64)reader["HighScoreADLibCount"]; + record.HighScoreBoomCount = (Int64)reader["HighScoreBoomCount"]; + + string key = record.ChartUniqueId + record.ChartDifficulty.ToString() + record.PlayMods.ToString(); + _bestPlays[key] = record; + } + reader.Close(); + + return _bestPlays; + } + public static void RegisterPlay(int player, int clearStatus, int scoreRank) { - SqliteConnection connection = GetSavesDBConnection(); + SqliteConnection? connection = GetSavesDBConnection(); + if (connection == null) return; + SaveFile.Data saveData = TJAPlayer3.SaveFileInstances[TJAPlayer3.GetActualPlayer(player)].data; - CBestPlayRecord currentPlay = new CBestPlayRecord(); + BestPlayRecords.CBestPlayRecord currentPlay = new BestPlayRecords.CBestPlayRecord(); var choosenSong = TJAPlayer3.stageSongSelect.rChoosenSong; var choosenDifficulty = TJAPlayer3.stageSongSelect.nChoosenSongDifficulty[player]; var chartScore = TJAPlayer3.stage演奏ドラム画面.CChartScore[player]; List[] danResults = new List[7] { new List(), new List(), new List(), new List(), new List(), new List(), new List() }; + // Do not register the play if Dan/Tower and any mod is ON + if ((choosenDifficulty == (int)Difficulty.Tower || choosenDifficulty == (int)Difficulty.Dan) && !ModIcons.tPlayIsStock(player)) return; + // 1st step: Init best play record class { @@ -167,6 +214,7 @@ namespace TJAPlayer3 } } } + reader.Close(); } // Intermede: Dan results to Dan exams @@ -183,6 +231,13 @@ namespace TJAPlayer3 } } + // Intermede: Update locally the play on the save file to reload it without requerying the database + { + string key = currentPlay.ChartUniqueId + currentPlay.ChartDifficulty.ToString() + currentPlay.PlayMods.ToString(); + saveData.bestPlays[key] = currentPlay; + saveData.tFactorizeBestPlays(); + } + // 3rd step: Insert/Update to database { SqliteCommand cmd = connection.CreateCommand(); diff --git a/OpenTaiko/src/Databases/DBUnlockables.cs b/OpenTaiko/src/Databases/DBUnlockables.cs index a5ee7602..5f671a2b 100644 --- a/OpenTaiko/src/Databases/DBUnlockables.cs +++ b/OpenTaiko/src/Databases/DBUnlockables.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.IO; using System.Runtime.InteropServices; using Newtonsoft.Json; +using static TJAPlayer3.BestPlayRecords; namespace TJAPlayer3 { @@ -15,7 +16,7 @@ namespace TJAPlayer3 ["dp"] = 3, ["lp"] = 3, ["sp"] = 2, - ["sg"] = 3, + ["sg"] = 2, }; public class CUnlockConditions @@ -94,10 +95,10 @@ namespace TJAPlayer3 * ch : "Coins here", coin requirement, payable within the heya menu, 1 value : [Coin price] * cs : "Coins shop", coin requirement, payable only within the Medal shop selection screen * cm : "Coins menu", coin requirement, payable only within the song select screen (used only for songs) - * dp : "Difficulty pass", count of difficulties pass, unlock check during the results screen, condition 3 values : [Difficulty int (0~4), Clear status (0~2), Number of performances], input 1 value [Plays fitting the condition] - * lp : "Level pass", count of level pass, unlock check during the results screen, condition 3 values : [Star rating, Clear status (0~2), Number of performances], input 1 value [Plays fitting the condition] - * sp : "Song performance", count of a specific song pass, unlock check during the results screen, condition 2 x n values for n songs : [Difficulty int (0~4, if -1 : Any), Clear status (0~2), ...], input 1 value [Count of fullfiled songs], n references for n songs (Song ids) - * sg : "Song genre (performance)", count of any song pass within a specific genre folder, unlock check during the results screen, condition 3 x n values for n songs : [Song count, Difficulty int (0~4, if -1 : Any), Clear status (0~2), ...], input 1 value [Count of fullfiled genres], n references for n genres (Genre names) + * dp : "Difficulty pass", count of difficulties pass, unlock check during the results screen, condition 3 values : [Difficulty int (0~4), Clear status (0~4), Number of performances], input 1 value [Plays fitting the condition] + * lp : "Level pass", count of level pass, unlock check during the results screen, condition 3 values : [Star rating, Clear status (0~4), Number of performances], input 1 value [Plays fitting the condition] + * sp : "Song performance", count of a specific song pass, unlock check during the results screen, condition 2 x n values for n songs : [Difficulty int (0~4, if -1 : Any), Clear status (0~4), ...], input 1 value [Count of fullfiled songs], n references for n songs (Song ids) + * sg : "Song genre (performance)", count of any unique song pass within a specific genre folder, unlock check during the results screen, condition 2 x n values for n songs : [Song count, Clear status (0~4), ...], input 1 value [Count of fullfiled genres], n references for n genres (Genre names) * * */ @@ -237,47 +238,32 @@ namespace TJAPlayer3 { _aimedDifficulty = this.Values[0]; // Difficulty if dp, Level if lp _aimedStatus = this.Values[1]; + + // dp and lp only work for regular (Dan and Tower excluded) charts + if (_aimedStatus < (int)EClearStatus.NONE || _aimedStatus >= (int)EClearStatus.TOTAL) return 0; + if (this.Condition == "dp" && (_aimedDifficulty < (int)Difficulty.Easy || _aimedDifficulty > (int)Difficulty.Edit)) return 0; } - var _sf = TJAPlayer3.SaveFileInstances[player].data.standardPasses; - if (_sf == null - || _aimedDifficulty < 0 - || _aimedStatus < 0 - || (this.Condition == "dp" && _aimedDifficulty > 4) - || _aimedStatus > 2) - return -1; + var bpDistinctCharts = TJAPlayer3.SaveFileInstances[player].data.bestPlaysDistinctCharts; + var chartStats = TJAPlayer3.SaveFileInstances[player].data.bestPlaysStats; switch (this.Condition) { case "dp": + var _table = chartStats.ClearStatuses[_aimedDifficulty]; + var _ura = chartStats.ClearStatuses[(int)Difficulty.Edit]; int _count = 0; - foreach (KeyValuePair cps in _sf) + for (int i = _aimedStatus; i < (int)EClearStatus.TOTAL; i++) { - var _values = cps.Value.d; - if (_values[_aimedDifficulty] >= _aimedStatus) - _count++; - // Extreme includes Extra - if (_aimedDifficulty == 3 && _values[4] >= _aimedStatus) - _count++; + _count += _table[i]; + if (_aimedDifficulty == (int)Difficulty.Oni) _count += _ura[i]; } return _count; case "lp": - _count = 0; - foreach (KeyValuePair cps in _sf) - { - var _node = CSongDict.tGetNodeFromID(cps.Key); - if (_node != null) - { - var _values = cps.Value.d; - var _levels = _node.nLevel; - for (int i = 0; i <= (int)Difficulty.Edit; i++) - { - if (_levels[i] == _aimedDifficulty && _values[i] >= _aimedStatus) - _count++; - } - } - } - return _count; + if (_aimedStatus == (int)EClearStatus.NONE) return chartStats.LevelPlays.TryGetValue(_aimedDifficulty, out var value) ? value : 0; + else if (_aimedStatus <= (int)EClearStatus.CLEAR) return chartStats.LevelClears.TryGetValue(_aimedDifficulty, out var value) ? value : 0; + else if (_aimedStatus == (int)EClearStatus.FC) return chartStats.LevelFCs.TryGetValue(_aimedDifficulty, out var value) ? value : 0; + else return chartStats.LevelPerfects.TryGetValue(_aimedDifficulty, out var value) ? value : 0; case "sp": _count = 0; for (int i = 0; i < this.Values.Length / this.RequiredArgCount; i++) @@ -287,18 +273,24 @@ namespace TJAPlayer3 _aimedDifficulty = this.Values[_base]; _aimedStatus = this.Values[_base + 1]; - if (_sf.ContainsKey(_songId)) + if (_aimedDifficulty >= (int)Difficulty.Easy && _aimedDifficulty <= (int)Difficulty.Edit) { - var _values = _sf[_songId].d; - if (_aimedDifficulty >= 0 && _aimedDifficulty < 4) + string key = _songId + _aimedDifficulty.ToString(); + var _cht = bpDistinctCharts.TryGetValue(key, out var value) ? value : null; + if (_cht != null && _cht.ClearStatus >= _aimedStatus) _count++; + + } + else if (_aimedDifficulty < (int)Difficulty.Easy) + { + for (int diff = (int)Difficulty.Easy; diff <= (int)Difficulty.Edit; diff++) { - if (_values[_aimedDifficulty] >= _aimedStatus) - _count++; - } - else - { - if (Array.Exists(_values, _v => _v >= _aimedStatus)) + string key = _songId + diff.ToString(); + var _cht = bpDistinctCharts.TryGetValue(key, out var value) ? value : null; + if (_cht != null && _cht.ClearStatus >= _aimedStatus) + { _count++; + break; + } } } } @@ -310,33 +302,15 @@ namespace TJAPlayer3 int _base = i * this.RequiredArgCount; string _genreName = this.Reference[i]; int _songCount = this.Values[_base]; - _aimedDifficulty = this.Values[_base + 1]; - _aimedStatus = this.Values[_base + 2]; - int _innerCount = 0; + _aimedStatus = this.Values[_base + 1]; - var _songList = CSongDict.tGetNodesByGenreName(_genreName); - foreach (string songId in _songList) - { - _innerCount = 0; - if (_sf.ContainsKey(songId)) - { - var _values = _sf[songId].d; - if (_aimedDifficulty >= 0 && _aimedDifficulty < 4) - { - if (_values[_aimedDifficulty] >= _aimedStatus) - _innerCount++; - } - else - { - if (Array.Exists(_values, _v => _v >= _aimedStatus)) - _innerCount++; - } - } - } - - if (_innerCount >= _songCount) - _count++; + int _satifsiedCount = 0; + if (_aimedStatus == (int)EClearStatus.NONE) _satifsiedCount = chartStats.SongGenrePlays.TryGetValue(_genreName, out var value) ? value : 0; + else if (_aimedStatus <= (int)EClearStatus.CLEAR) _satifsiedCount = chartStats.SongGenreClears.TryGetValue(_genreName, out var value) ? value : 0; + else if (_aimedStatus == (int)EClearStatus.FC) _satifsiedCount = chartStats.SongGenreFCs.TryGetValue(_genreName, out var value) ? value : 0; + else return _satifsiedCount = chartStats.SongGenrePerfects.TryGetValue(_genreName, out var value) ? value : 0; + if (_satifsiedCount >= _songCount) _count++; } return _count; } diff --git a/OpenTaiko/src/Stages/07.Game/Taiko/ModIcons.cs b/OpenTaiko/src/Stages/07.Game/Taiko/ModIcons.cs index 66921cde..1152cd0d 100644 --- a/OpenTaiko/src/Stages/07.Game/Taiko/ModIcons.cs +++ b/OpenTaiko/src/Stages/07.Game/Taiko/ModIcons.cs @@ -162,6 +162,20 @@ namespace TJAPlayer3 #region [Mod flags] + static public bool tPlayIsStock(int player) + { + int actual = TJAPlayer3.GetActualPlayer(player); + + if (TJAPlayer3.ConfigIni.nFunMods[actual] != EFunMods.NONE) return false; + if (TJAPlayer3.ConfigIni.bJust[actual] != 0) return false; + if (TJAPlayer3.ConfigIni.nTimingZones[actual] != 2) return false; + if (TJAPlayer3.ConfigIni.n演奏速度 != 20) return false; + if (TJAPlayer3.ConfigIni.eRandom[actual] != Eランダムモード.OFF) return false; + if (TJAPlayer3.ConfigIni.eSTEALTH[actual] != EStealthMode.OFF) return false; + if (TJAPlayer3.ConfigIni.nScrollSpeed[actual] != 9) return false; + + return true; + } static public Int64 tModsToPlayModsFlags(int player) { byte[] _flags = new byte[8] { 0, 0, 0, 0, 0, 0, 0, 0 };