From 332d187af183d359584421f540de29209fbc1866 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?DragonRatTiger=20/=20=E3=83=AA=E3=83=A5=E3=82=A6=E3=82=B3?= Date: Tue, 9 Jul 2024 04:50:27 -0500 Subject: [PATCH] Add ability to import ScoreIni files (debug only, for now) (#647) --- OpenTaiko/src/Common/BestPlayRecords.cs | 4 +- OpenTaiko/src/Songs/CScoreIni_Importer.cs | 281 ++++++++++++++++++ .../src/Stages/02.Title/CStageタイトル.cs | 11 +- 3 files changed, 292 insertions(+), 4 deletions(-) create mode 100644 OpenTaiko/src/Songs/CScoreIni_Importer.cs diff --git a/OpenTaiko/src/Common/BestPlayRecords.cs b/OpenTaiko/src/Common/BestPlayRecords.cs index cbd61a8d..3496df11 100644 --- a/OpenTaiko/src/Common/BestPlayRecords.cs +++ b/OpenTaiko/src/Common/BestPlayRecords.cs @@ -187,13 +187,13 @@ namespace TJAPlayer3 } else if (roundedDifficulty == (int)Difficulty.Tower) { - Int64 roundedClearStatus = Math.Max((int)ETowerClearStatus.NONE, Math.Min((int)ETowerClearStatus.TOTAL, record.ClearStatus + 1)); + Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)ETowerClearStatus.NONE, (int)ETowerClearStatus.TOTAL); stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++; } else if (roundedDifficulty == (int)Difficulty.Dan) { - Int64 roundedClearStatus = Math.Max((int)EDanClearStatus.NONE, Math.Min((int)EDanClearStatus.TOTAL, record.ClearStatus + 1)); + Int64 roundedClearStatus = Math.Clamp(record.ClearStatus + 1, (int)EDanClearStatus.NONE, (int)EDanClearStatus.TOTAL); stats.ClearStatuses[roundedDifficulty][roundedClearStatus]++; } diff --git a/OpenTaiko/src/Songs/CScoreIni_Importer.cs b/OpenTaiko/src/Songs/CScoreIni_Importer.cs new file mode 100644 index 00000000..efbec6d2 --- /dev/null +++ b/OpenTaiko/src/Songs/CScoreIni_Importer.cs @@ -0,0 +1,281 @@ +using Microsoft.Data.Sqlite; +using System; +using System.Collections.Generic; +using System.ComponentModel.Design; +using System.Diagnostics; +using System.Linq; +using System.Reflection.Emit; +using System.Text; +using System.Threading.Tasks; + +namespace TJAPlayer3 +{ + static class CScoreIni_Importer + { + public static string Status { get; private set; } = ""; + private static readonly string langSUBTITLE = "SUBTITLE" + CLangManager.LangInstance.Id.ToUpper(); + public static void ImportScoreInisToSavesDb3() + { + Trace.TraceInformation("Importing score.ini files to Saves.db3 database!"); + + Status = "Establishing connection to database..."; + SqliteConnection? connection = DBSaves.GetSavesDBConnection(); + if (connection == null) + { + Trace.TraceError("Could not establish a connection to Saves.db3 database. Aborting score import."); + return; + } + + Status = "Searching for scores..."; + string[] _scoreFiles = Directory.GetFiles(TJAPlayer3.ConfigIni.strSongsPath, "*.score.ini", SearchOption.AllDirectories); + Trace.TraceInformation($"{_scoreFiles.Length} score.ini files have been found. Beginning import."); + + int importcount = 0; + Status = "Importing scores..."; + foreach (string _score in _scoreFiles) + { + try + { + string directory = Path.GetDirectoryName(_score); + DirectoryInfo dir_parent = Directory.GetParent(directory); + + string[] TJAData = Array.Empty(); + if (File.Exists(GetTJAFile(_score))) + TJAData = File.ReadAllLines(GetTJAFile(_score)); + string[] Difficulties = TJAData.Where(str => str.StartsWith("COURSE:")).ToArray(); + string[] Levels = TJAData.Where(str => str.StartsWith("LEVEL:")).ToArray(); + + string[] ScoreData = File.ReadAllLines(_score); + + CSongUniqueID UniqueId = new CSongUniqueID(Path.Combine(directory, "uniqueID.json")); + CBoxDef boxdef = new CBoxDef(); + if (File.Exists(Path.Combine(dir_parent.FullName, "box.def"))) + boxdef = new CBoxDef(Path.Combine(dir_parent.FullName, "box.def")); + //string Charter = ""; + string[] Charters = new string[8] { "", "", "", "", "", "", "", "" }; + string Artist = ""; + + int[] Level = { -1, -1, -1, -1, -1, -1, -1 }; + int[] Clear = { -1, -1, -1, -1, -1, -1, -1 }; + int[] Rank = { -1, -1, -1, -1, -1, -1, -1 }; + int[] HighScore = { 0, 0, 0, 0, 0, 0, 0 }; + + foreach (string data in TJAData) + { + string[] split = data.Split(":", 2); + switch (split[0]) + { + case "SUBTITLE": + Artist = split[1].StartsWith("--") || split[1].StartsWith("++") ? split[1].Substring(2) : split[1]; + break; + case "MAKER": + Charters[0] = split[1]; + break; + case "NOTESDESIGNER0": + Charters[1] = split[1]; + break; + case "NOTESDESIGNER1": + Charters[2] = split[1]; + break; + case "NOTESDESIGNER2": + Charters[3] = split[1]; + break; + case "NOTESDESIGNER3": + Charters[4] = split[1]; + break; + case "NOTESDESIGNER4": + Charters[5] = split[1]; + break; + case "NOTESDESIGNER5": + Charters[6] = split[1]; + break; + case "NOTESDESIGNER6": + Charters[7] = split[1]; + break; + default: + if (split[0] == langSUBTITLE) + Artist = split[1]; + break; + } + } + + // Tower/Dan score data is saved in index 0 (Easy). + for (int i = 0; i < Difficulties.Length; i++) + { + int diff_index = Difficulties[i].IndexOf(':') + 1; + int lvl_index = Levels[i].IndexOf(":") + 1; + + int level = int.TryParse(Levels[i].Substring(lvl_index), out int result) ? result : -1; + switch (Difficulties[i].Substring(diff_index).ToLower()) + { + case "0": + case "easy": + Level[0] = level; + break; + case "1": + case "normal": + Level[1] = level; + break; + case "2": + case "hard": + Level[2] = level; + break; + case "3": + case "oni": + Level[3] = level; + break; + case "4": + case "edit": + Level[4] = level; + break; + case "5": + case "tower": + Level[5] = level; + break; + case "6": + case "dan": + Level[6] = level; + break; + } + } + + foreach (string data in ScoreData) + { + string[] split = data.Split('=', 2); + int num = 0; + if (split.Length == 2) num = int.TryParse(split[1], out int result) ? result : 0; + switch (split[0]) + { + case "HiScore1": + HighScore[0] = num; + HighScore[5] = num; + HighScore[6] = num; + break; + case "HiScore2": + HighScore[1] = num; + break; + case "HiScore3": + HighScore[2] = num; + break; + case "HiScore4": + HighScore[3] = num; + break; + case "HiScore5": + HighScore[4] = num; + break; + + case "Clear0": + Clear[0] = num == 0 ? -1 : num; + Clear[5] = num == 0 ? -1 : num; + Clear[6] = num == 0 ? -1 : num; + break; + case "Clear1": + Clear[1] = num == 0 ? -1 : num; + break; + case "Clear2": + Clear[2] = num == 0 ? -1 : num; + break; + case "Clear3": + Clear[3] = num == 0 ? -1 : num; + break; + case "Clear4": + Clear[4] = num == 0 ? -1 : num; + break; + + case "ScoreRank0": + Rank[0] = num - 1; + Rank[5] = num - 1; + Rank[6] = num - 1; + break; + case "ScoreRank1": + Rank[1] = num - 1; + break; + case "ScoreRank2": + Rank[2] = num - 1; + break; + case "ScoreRank3": + Rank[3] = num - 1; + break; + case "ScoreRank4": + Rank[4] = num - 1; + break; + } + } + + for (int i = 0; i < Level.Length; i++) + { + int score_index = i < 5 ? i : 0; + if (Level[i] != -1 && HighScore[score_index] > 0) + { + SqliteCommand cmd = connection.CreateCommand(); + cmd.CommandText = $@" + INSERT INTO best_plays(ChartUniqueId,ChartGenre,Charter,Artist,PlayMods,ChartDifficulty,ChartLevel,ClearStatus,ScoreRank,HighScore,SaveId,TowerBestFloor,DanExam1,DanExam2,DanExam3,DanExam4,DanExam5,DanExam6,DanExam7,PlayCount,HighScoreGoodCount,HighScoreOkCount,HighScoreBadCount,HighScoreMaxCombo,HighScoreRollCount,HighScoreADLibCount,HighScoreBoomCount) + VALUES( + '{UniqueId.data.id.Replace(@"'", @"''")}', + '{boxdef.Title.Replace(@"'", @"''")}', + '{(!string.IsNullOrEmpty(Charters[i + 1]) ? Charters[i + 1] : Charters[0]).Replace(@"'", @"''")}', + '{Artist.Replace(@"'", @"''")}', + 8925478921, + {i}, + {Level[i]}, + {Clear[i] + (i == 6 && Clear[i] > -1 ? 1 : 0)}, + {(i != 5 ? Rank[i] : -1)}, + {HighScore[score_index]}, + {GetPlayerId(_score)}, + {(i == 5 ? Rank[i]+1 : 0)}, + '[-1]', + '[-1]', + '[-1]', + '[-1]', + '[-1]', + '[-1]', + '[-1]', + 1, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ) + ON CONFLICT(ChartUniqueId,ChartDifficulty,PlayMods) DO NOTHING + "; + if (cmd.ExecuteNonQuery() > 0) + importcount++; + } + } + } + catch (Exception ex) + { + Trace.TraceWarning($"Failed to import {_score} into new database. More details:\n{ex}"); + } + } + Trace.TraceInformation($"Imported {importcount} of {_scoreFiles.Length} scores from score.ini files."); + Status = ""; + } + + private static string GetTJAFile(string path) + { + FileInfo info = new FileInfo(path); + + if (info.FullName.EndsWith("1P.score.ini")) return info.FullName.Replace("1P.score.ini", ""); + if (info.FullName.EndsWith("2P.score.ini")) return info.FullName.Replace("2P.score.ini", ""); + if (info.FullName.EndsWith("3P.score.ini")) return info.FullName.Replace("3P.score.ini", ""); + if (info.FullName.EndsWith("4P.score.ini")) return info.FullName.Replace("4P.score.ini", ""); + if (info.FullName.EndsWith("5P.score.ini")) return info.FullName.Replace("5P.score.ini", ""); + return info.FullName.Replace(".score.ini", ""); + } + private static int GetPlayerId(string path) + { + FileInfo info = new FileInfo(path); + + if (info.Name.EndsWith("1P.score.ini")) return 0; + if (info.Name.EndsWith("2P.score.ini")) return 1; + if (info.Name.EndsWith("3P.score.ini")) return 2; + if (info.Name.EndsWith("4P.score.ini")) return 3; + if (info.Name.EndsWith("5P.score.ini")) return 4; + return 0; + } + } +} diff --git a/OpenTaiko/src/Stages/02.Title/CStageタイトル.cs b/OpenTaiko/src/Stages/02.Title/CStageタイトル.cs index 1f3e96f5..f45c3060 100644 --- a/OpenTaiko/src/Stages/02.Title/CStageタイトル.cs +++ b/OpenTaiko/src/Stages/02.Title/CStageタイトル.cs @@ -191,6 +191,12 @@ namespace TJAPlayer3 base.ePhaseID = CStage.EPhase.Common_FADEOUT; } } +#if DEBUG + if (TJAPlayer3.InputManager.Keyboard.KeyPressed((int)SlimDXKeys.Key.F8)) + { + CScoreIni_Importer.ImportScoreInisToSavesDb3(); + } +#endif // Disable F1 keybind since menu is accessible from main menu @@ -937,9 +943,10 @@ namespace TJAPlayer3 TJAPlayer3.actTextConsole.tPrint(4, (TJAPlayer3.Skin.Resolution[1] - 24), CTextConsole.EFontType.White, "TJAPlayer3 forked TJAPlayer2 forPC(kairera0467)"); #endif - #endregion + //TJAPlayer3.actTextConsole.tPrint(4, 64, CTextConsole.EFontType.White, CScoreIni_Importer.Status); + #endregion - CStage.EPhase eフェーズid = base.ePhaseID; + CStage.EPhase eフェーズid = base.ePhaseID; switch (eフェーズid) { case CStage.EPhase.Common_FADEIN: