diff --git a/SharedProject/Models/SongIntroductionData.cs b/SharedProject/Models/SongIntroductionData.cs new file mode 100644 index 0000000..fe6f82a --- /dev/null +++ b/SharedProject/Models/SongIntroductionData.cs @@ -0,0 +1,18 @@ +using System.Text.Json.Serialization; + +namespace SharedProject.Models; + +public class SongIntroductionData +{ + [JsonPropertyName("setId")] + public uint SetId { get; set; } + + [JsonPropertyName("verupNo")] + public uint VerupNo { get; set; } + + [JsonPropertyName("mainSongNo")] + public uint MainSongNo { get; set; } + + [JsonPropertyName("subSongNo")] + public uint[]? SubSongNo { get; set; } +} \ No newline at end of file diff --git a/TaikoLocalServer/Common/Constants.cs b/TaikoLocalServer/Common/Constants.cs index 427594e..665423c 100644 --- a/TaikoLocalServer/Common/Constants.cs +++ b/TaikoLocalServer/Common/Constants.cs @@ -24,6 +24,8 @@ public static class Constants public const string DAN_DATA_FILE_NAME = "dan_data.json"; + public const string INTRO_DATA_FILE_NAME = "intro_data.json"; + public const int MIN_DAN_ID = 1; public const int MAX_DAN_ID = 19; public const int GOT_DAN_BITS = MAX_DAN_ID * 4; diff --git a/TaikoLocalServer/Common/Utils/DanOdaiDataManager.cs b/TaikoLocalServer/Common/Utils/DanOdaiDataManager.cs deleted file mode 100644 index 111b2d1..0000000 --- a/TaikoLocalServer/Common/Utils/DanOdaiDataManager.cs +++ /dev/null @@ -1,48 +0,0 @@ -using System.Collections.Immutable; -using System.Text.Json; -using SharedProject.Models; -using Swan.Mapping; - -namespace TaikoLocalServer.Common.Utils; - -public class DanOdaiDataManager -{ - public ImmutableDictionary OdaiDataList { get; } - - static DanOdaiDataManager() {} - - private DanOdaiDataManager() - { - var dataPath = PathHelper.GetDataPath(); - var filePath = Path.Combine(dataPath, Constants.DAN_DATA_FILE_NAME); - var jsonString = File.ReadAllText(filePath); - - var result = JsonSerializer.Deserialize>(jsonString); - - if (result is null) - { - throw new ApplicationException("Cannot parse dan data json!"); - } - - OdaiDataList = result.ToImmutableDictionary(data => data.DanId, ToResponseOdaiData); - } - private GetDanOdaiResponse.OdaiData ToResponseOdaiData(DanData data) - { - var responseOdaiData = new GetDanOdaiResponse.OdaiData - { - DanId = data.DanId, - Title = data.Title, - VerupNo = data.VerupNo - }; - - var odaiSongs = data.OdaiSongList.Select(song => song.CopyPropertiesToNew()); - responseOdaiData.AryOdaiSongs.AddRange(odaiSongs); - - var odaiBorders = data.OdaiBorderList.Select(border => border.CopyPropertiesToNew()); - responseOdaiData.AryOdaiBorders.AddRange(odaiBorders); - - return responseOdaiData; - } - - public static DanOdaiDataManager Instance { get; } = new(); -} \ No newline at end of file diff --git a/TaikoLocalServer/Common/Utils/MusicAttributeManager.cs b/TaikoLocalServer/Common/Utils/MusicAttributeManager.cs deleted file mode 100644 index a7dadda..0000000 --- a/TaikoLocalServer/Common/Utils/MusicAttributeManager.cs +++ /dev/null @@ -1,42 +0,0 @@ -using System.Text.Json; - -namespace TaikoLocalServer.Common.Utils; - -public class MusicAttributeManager -{ - public readonly Dictionary MusicAttributes; - - static MusicAttributeManager() - { - } - - private MusicAttributeManager() - { - var dataPath = PathHelper.GetDataPath(); - var filePath = Path.Combine(dataPath, Constants.MUSIC_ATTRIBUTE_FILE_NAME); - var jsonString = File.ReadAllText(filePath); - - var result = JsonSerializer.Deserialize(jsonString); - if (result is null) - { - throw new ApplicationException("Cannot parse music attribute json!"); - } - - MusicAttributes = result.MusicAttributeEntries.ToDictionary(attribute => attribute.MusicId); - - Musics = MusicAttributes.Select(pair => pair.Key) - .ToList(); - Musics.Sort(); - - MusicsWithUra = MusicAttributes.Where(attribute => attribute.Value.HasUra) - .Select(pair => pair.Key) - .ToList(); - MusicsWithUra.Sort(); - } - - public static MusicAttributeManager Instance { get; } = new(); - - public readonly List Musics; - - public readonly List MusicsWithUra; -} \ No newline at end of file diff --git a/TaikoLocalServer/Controllers/Game/GetDanOdaiController.cs b/TaikoLocalServer/Controllers/Game/GetDanOdaiController.cs index 6fd7915..beae85c 100644 --- a/TaikoLocalServer/Controllers/Game/GetDanOdaiController.cs +++ b/TaikoLocalServer/Controllers/Game/GetDanOdaiController.cs @@ -1,9 +1,18 @@ -namespace TaikoLocalServer.Controllers.Game; +using TaikoLocalServer.Services.Interfaces; + +namespace TaikoLocalServer.Controllers.Game; [Route("/v12r03/chassis/getdanodai.php")] [ApiController] public class GetDanOdaiController : BaseController { + private readonly IGameDataService gameDataService; + + public GetDanOdaiController(IGameDataService gameDataService) + { + this.gameDataService = gameDataService; + } + [HttpPost] [Produces("application/protobuf")] public IActionResult GetDanOdai([FromBody] GetDanOdaiRequest request) @@ -19,11 +28,10 @@ public class GetDanOdaiController : BaseController { return Ok(response); } - - var manager = DanOdaiDataManager.Instance; + foreach (var danId in request.DanIds) { - manager.OdaiDataList.TryGetValue(danId, out var odaiData); + gameDataService.GetDanDataDictionary().TryGetValue(danId, out var odaiData); if (odaiData is null) { Logger.LogWarning("Requested dan id {Id} does not exist!", danId); diff --git a/TaikoLocalServer/Controllers/Game/GetSongIntroductionController.cs b/TaikoLocalServer/Controllers/Game/GetSongIntroductionController.cs index 15d3215..b17c4ee 100644 --- a/TaikoLocalServer/Controllers/Game/GetSongIntroductionController.cs +++ b/TaikoLocalServer/Controllers/Game/GetSongIntroductionController.cs @@ -1,9 +1,18 @@ -namespace TaikoLocalServer.Controllers.Game; +using TaikoLocalServer.Services.Interfaces; + +namespace TaikoLocalServer.Controllers.Game; [Route("/v12r03/chassis/getsongintroduction.php")] [ApiController] public class GetSongIntroductionController : BaseController { + private readonly IGameDataService gameDataService; + + public GetSongIntroductionController(IGameDataService gameDataService) + { + this.gameDataService = gameDataService; + } + [HttpPost] [Produces("application/protobuf")] public IActionResult GetSongIntroduction([FromBody] GetSongIntroductionRequest request) @@ -14,16 +23,17 @@ public class GetSongIntroductionController : BaseController { + private readonly IGameDataService gameDataService; + + public InitialDataCheckController(IGameDataService gameDataService) + { + this.gameDataService = gameDataService; + } + [HttpPost] [Produces("application/protobuf")] public IActionResult InitialDataCheck([FromBody] InitialdatacheckRequest request) @@ -27,6 +35,16 @@ public class InitialDataCheckController : BaseController(); + for (var setId = 1; setId <= gameDataService.GetSongIntroDictionary().Count; setId++) + { + introData.Add(new InitialdatacheckResponse.InformationData + { + InfoId = (uint)setId, + VerupNo = 1 + }); + } + var response = new InitialdatacheckResponse { Result = 1, @@ -95,6 +113,7 @@ public class InitialDataCheckController : BaseController { private readonly ISongBestDatumService songBestDatumService; + + private readonly IGameDataService gameDataService; - public SelfBestController(ISongBestDatumService songBestDatumService) + public SelfBestController(ISongBestDatumService songBestDatumService, IGameDataService gameDataService) { this.songBestDatumService = songBestDatumService; + this.gameDataService = gameDataService; } [HttpPost] @@ -25,8 +28,6 @@ public class SelfBestController : BaseController Result = 1, Level = request.Level }; - - var manager = MusicAttributeManager.Instance; var requestDifficulty = (Difficulty)request.Level; requestDifficulty.Throw().IfOutOfRange(); @@ -38,7 +39,7 @@ public class SelfBestController : BaseController .ToList(); foreach (var songNo in request.ArySongNoes) { - if (!manager.MusicAttributes.ContainsKey(songNo)) + if (!gameDataService.GetMusicAttributes().ContainsKey(songNo)) { Logger.LogWarning("Music no {No} is missing!", songNo); continue; diff --git a/TaikoLocalServer/Controllers/Game/UserDataController.cs b/TaikoLocalServer/Controllers/Game/UserDataController.cs index 554a51c..0c007a6 100644 --- a/TaikoLocalServer/Controllers/Game/UserDataController.cs +++ b/TaikoLocalServer/Controllers/Game/UserDataController.cs @@ -12,11 +12,14 @@ public class UserDataController : BaseController private readonly IUserDatumService userDatumService; private readonly ISongPlayDatumService songPlayDatumService; + + private readonly IGameDataService gameDataService; - public UserDataController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService) + public UserDataController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService, IGameDataService gameDataService) { this.userDatumService = userDatumService; this.songPlayDatumService = songPlayDatumService; + this.gameDataService = gameDataService; } [HttpPost] @@ -25,13 +28,11 @@ public class UserDataController : BaseController { Logger.LogInformation("UserData request : {Request}", request.Stringify()); - var musicAttributeManager = MusicAttributeManager.Instance; - var releaseSongArray = - FlagCalculator.GetBitArrayFromIds(musicAttributeManager.Musics, Constants.MUSIC_ID_MAX, Logger); + FlagCalculator.GetBitArrayFromIds(gameDataService.GetMusicList(), Constants.MUSIC_ID_MAX, Logger); var uraSongArray = - FlagCalculator.GetBitArrayFromIds(musicAttributeManager.MusicsWithUra, Constants.MUSIC_ID_MAX, Logger); + FlagCalculator.GetBitArrayFromIds(gameDataService.GetMusicWithUraList(), Constants.MUSIC_ID_MAX, Logger); var userData = await userDatumService.GetFirstUserDatumOrDefault(request.Baid); diff --git a/TaikoLocalServer/Program.cs b/TaikoLocalServer/Program.cs index e12a919..ea5cc72 100644 --- a/TaikoLocalServer/Program.cs +++ b/TaikoLocalServer/Program.cs @@ -6,6 +6,7 @@ using TaikoLocalServer.Services; using TaikoLocalServer.Services.Extentions; using TaikoLocalServer.Services.Interfaces; using TaikoLocalServer.Settings; +using Throw; var builder = WebApplication.CreateBuilder(args); // Manually enable tls 1.0 @@ -19,6 +20,7 @@ builder.WebHost.UseKestrel(kestrelOptions => // Add services to the container. builder.Services.AddOptions(); +builder.Services.AddSingleton(); builder.Services.Configure(builder.Configuration.GetSection(nameof(UrlSettings))); builder.Services.AddControllers().AddProtoBufNet(); builder.Services.AddDbContext(option => @@ -58,6 +60,10 @@ using (var scope = app.Services.CreateScope()) db.Database.Migrate(); } +var gameDataService = app.Services.GetService(); +gameDataService.ThrowIfNull(); +await gameDataService.InitializeAsync(); + // For reverse proxy app.UseForwardedHeaders(new ForwardedHeadersOptions { diff --git a/TaikoLocalServer/Services/GameDataService.cs b/TaikoLocalServer/Services/GameDataService.cs new file mode 100644 index 0000000..f7fd748 --- /dev/null +++ b/TaikoLocalServer/Services/GameDataService.cs @@ -0,0 +1,130 @@ +using System.Collections.Immutable; +using System.Text.Json; +using SharedProject.Models; +using Swan.Mapping; +using TaikoLocalServer.Services.Interfaces; +using Throw; + +namespace TaikoLocalServer.Services; + +public class GameDataService : IGameDataService +{ + private ImmutableDictionary danDataDictionary = + ImmutableDictionary.Empty; + + private ImmutableDictionary introDataDictionary = + ImmutableDictionary.Empty; + + private ImmutableDictionary musicAttributes = + ImmutableDictionary.Empty; + + private List musics = new(); + + private List musicsWithUra = new(); + + public List GetMusicList() + { + return musics; + } + + public List GetMusicWithUraList() + { + return musicsWithUra; + } + + public ImmutableDictionary GetMusicAttributes() + { + return musicAttributes; + } + + public ImmutableDictionary GetDanDataDictionary() + { + return danDataDictionary; + } + + public ImmutableDictionary GetSongIntroDictionary() + { + return introDataDictionary; + } + + public async Task InitializeAsync() + { + var dataPath = PathHelper.GetDataPath(); + var musicAttributePath = Path.Combine(dataPath, Constants.MUSIC_ATTRIBUTE_FILE_NAME); + var danDataPath = Path.Combine(dataPath, Constants.DAN_DATA_FILE_NAME); + var songIntroDataPath = Path.Combine(dataPath, Constants.INTRO_DATA_FILE_NAME); + + await using var musicAttributeFile = File.OpenRead(musicAttributePath); + await using var danDataFile = File.OpenRead(danDataPath); + await using var songIntroDataFile = File.OpenRead(songIntroDataPath); + + var attributesData = await JsonSerializer.DeserializeAsync(musicAttributeFile); + var danData = await JsonSerializer.DeserializeAsync>(danDataFile); + var introData = await JsonSerializer.DeserializeAsync>(songIntroDataFile); + + InitializeMusicAttributes(attributesData); + + InitializeDanData(danData); + + InitializeIntroData(introData); + } + + private void InitializeIntroData(List? introData) + { + introData.ThrowIfNull("Shouldn't happen!"); + introDataDictionary = introData.ToImmutableDictionary(data => data.SetId, ToResponseIntroData); + } + + private void InitializeDanData(List? danData) + { + danData.ThrowIfNull("Shouldn't happen!"); + danDataDictionary = danData.ToImmutableDictionary(data => data.DanId, ToResponseOdaiData); + } + + private void InitializeMusicAttributes(MusicAttributes? attributesData) + { + attributesData.ThrowIfNull("Shouldn't happen!"); + + musicAttributes = attributesData.MusicAttributeEntries.ToImmutableDictionary(attribute => attribute.MusicId); + + musics = musicAttributes.Select(pair => pair.Key) + .ToList(); + musics.Sort(); + + musicsWithUra = musicAttributes.Where(attribute => attribute.Value.HasUra) + .Select(pair => pair.Key) + .ToList(); + musicsWithUra.Sort(); + } + + private static GetDanOdaiResponse.OdaiData ToResponseOdaiData(DanData data) + { + var responseOdaiData = new GetDanOdaiResponse.OdaiData + { + DanId = data.DanId, + Title = data.Title, + VerupNo = data.VerupNo + }; + + var odaiSongs = data.OdaiSongList.Select(song => song.CopyPropertiesToNew()); + responseOdaiData.AryOdaiSongs.AddRange(odaiSongs); + + var odaiBorders = data.OdaiBorderList.Select(border => border.CopyPropertiesToNew()); + responseOdaiData.AryOdaiBorders.AddRange(odaiBorders); + + return responseOdaiData; + } + + private static GetSongIntroductionResponse.SongIntroductionData ToResponseIntroData(SongIntroductionData data) + { + var responseOdaiData = new GetSongIntroductionResponse.SongIntroductionData + { + SetId = data.SetId, + VerupNo = data.VerupNo, + MainSongNo = data.MainSongNo, + SubSongNoes = data.SubSongNo + }; + + return responseOdaiData; + } +} \ No newline at end of file diff --git a/TaikoLocalServer/Services/Interfaces/IGameDataService.cs b/TaikoLocalServer/Services/Interfaces/IGameDataService.cs new file mode 100644 index 0000000..f633c33 --- /dev/null +++ b/TaikoLocalServer/Services/Interfaces/IGameDataService.cs @@ -0,0 +1,18 @@ +using System.Collections.Immutable; + +namespace TaikoLocalServer.Services.Interfaces; + +public interface IGameDataService +{ + public Task InitializeAsync(); + + public List GetMusicList(); + + public List GetMusicWithUraList(); + + public ImmutableDictionary GetMusicAttributes(); + + public ImmutableDictionary GetDanDataDictionary(); + + public ImmutableDictionary GetSongIntroDictionary(); +} \ No newline at end of file diff --git a/TaikoLocalServer/TaikoLocalServer.csproj b/TaikoLocalServer/TaikoLocalServer.csproj index a2bcdb8..76728c3 100644 --- a/TaikoLocalServer/TaikoLocalServer.csproj +++ b/TaikoLocalServer/TaikoLocalServer.csproj @@ -41,6 +41,9 @@ PreserveNewest + + PreserveNewest + diff --git a/TaikoLocalServer/wwwroot/data/intro_data.json b/TaikoLocalServer/wwwroot/data/intro_data.json new file mode 100644 index 0000000..ac08a5c --- /dev/null +++ b/TaikoLocalServer/wwwroot/data/intro_data.json @@ -0,0 +1,74 @@ +[ + { + "setId":1, + "verupNo":1, + "mainSongNo":895, + "subSongNo":[894,732,44,921] + }, + { + "setId":2, + "verupNo":1, + "mainSongNo":912, + "subSongNo":[827,871,36,227] + }, + { + "setId":3, + "verupNo":1, + "mainSongNo":913, + "subSongNo":[460,916,430,872] + }, + { + "setId":4, + "verupNo":1, + "mainSongNo":842, + "subSongNo":[7,233,256,831] + }, + { + "setId":5, + "verupNo":1, + "mainSongNo":947, + "subSongNo":[926,882,730,695] + }, + { + "setId":6, + "verupNo":1, + "mainSongNo":937, + "subSongNo":[828,925,474,924] + }, + { + "setId":7, + "verupNo":1, + "mainSongNo":956, + "subSongNo":[839,255,285,187] + }, + { + "setId":8, + "verupNo":1, + "mainSongNo":923, + "subSongNo":[729,873,789,893] + }, + { + "setId":9, + "verupNo":1, + "mainSongNo":915, + "subSongNo":[726,811,711,303] + }, + { + "setId":10, + "verupNo":1, + "mainSongNo":885, + "subSongNo":[837,464,801,18] + }, + { + "setId":11, + "verupNo":1, + "mainSongNo":898, + "subSongNo":[47,135,374,792] + }, + { + "setId":12, + "verupNo":1, + "mainSongNo":948, + "subSongNo":[412,538,411,413] + } +] \ No newline at end of file