diff --git a/SharedProject/Utils/ValueHelpers.cs b/SharedProject/Utils/ValueHelpers.cs new file mode 100644 index 0000000..ecfc8e8 --- /dev/null +++ b/SharedProject/Utils/ValueHelpers.cs @@ -0,0 +1,14 @@ +namespace SharedProject.Utils; + +public static class ValueHelpers +{ + public static T Min(T a, T b) where T : IComparable + { + return Comparer.Default.Compare(a, b) <= 0 ? a : b; + } + + public static T Max(T a, T b) where T : IComparable + { + return Comparer.Default.Compare(a, b) >= 0 ? a : b; + } +} \ No newline at end of file diff --git a/TaikoLocalServer/Controllers/Game/BaidController.cs b/TaikoLocalServer/Controllers/Game/BaidController.cs index 934d53e..733b6a3 100644 --- a/TaikoLocalServer/Controllers/Game/BaidController.cs +++ b/TaikoLocalServer/Controllers/Game/BaidController.cs @@ -16,13 +16,16 @@ public class BaidController : BaseController private readonly IDanScoreDatumService danScoreDatumService; + private readonly IAiDatumService aiDatumService; + public BaidController(IUserDatumService userDatumService, ICardService cardService, - ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService) + ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService) { this.userDatumService = userDatumService; this.cardService = cardService; this.songBestDatumService = songBestDatumService; this.danScoreDatumService = danScoreDatumService; + this.aiDatumService = aiDatumService; } @@ -112,6 +115,9 @@ public class BaidController : BaseController var genericInfoFlgLength = genericInfoFlg.Any()? genericInfoFlg.Max() + 1 : 0; var genericInfoFlgArray = FlagCalculator.GetBitArrayFromIds(genericInfoFlg, (int)genericInfoFlgLength, Logger); + var aiScores = await aiDatumService.GetAllAiScoreById(baid); + var totalWin = aiScores.Count(datum => datum.IsWin); + response = new BAIDResponse { Result = 1, @@ -159,7 +165,7 @@ public class BaidController : BaseController LastPlayMode = userData.LastPlayMode, IsDispSouuchiOn = true, AiRank = 0, - AiTotalWin = 0, + AiTotalWin = (uint)totalWin, Accesstoken = "123456", ContentInfo = GZipBytesUtil.GetGZipBytes(new byte[10]) }; diff --git a/TaikoLocalServer/Controllers/Game/GetAiDataController.cs b/TaikoLocalServer/Controllers/Game/GetAiDataController.cs index 148875f..0613254 100644 --- a/TaikoLocalServer/Controllers/Game/GetAiDataController.cs +++ b/TaikoLocalServer/Controllers/Game/GetAiDataController.cs @@ -1,19 +1,30 @@ -namespace TaikoLocalServer.Controllers.Game; +using TaikoLocalServer.Services.Interfaces; + +namespace TaikoLocalServer.Controllers.Game; [Route("/v12r03/chassis/getaidata.php")] [ApiController] public class GetAiDataController : BaseController { + private readonly IAiDatumService aiDatumService; + + public GetAiDataController(IAiDatumService aiDatumService) + { + this.aiDatumService = aiDatumService; + } + [HttpPost] [Produces("application/protobuf")] - public IActionResult GetAiData([FromBody] GetAiDataRequest request) + public async Task GetAiData([FromBody] GetAiDataRequest request) { Logger.LogInformation("GetAiData request : {Request}", request.Stringify()); + var aiScoreData = await aiDatumService.GetAllAiScoreById(request.Baid); + var totalWin = aiScoreData.Count(datum => datum.IsWin); var response = new GetAiDataResponse { Result = 1, - TotalWinnings = 0 + TotalWinnings = (uint)totalWin }; return Ok(response); diff --git a/TaikoLocalServer/Controllers/Game/GetAiScoreController.cs b/TaikoLocalServer/Controllers/Game/GetAiScoreController.cs index 0d56035..33447cf 100644 --- a/TaikoLocalServer/Controllers/Game/GetAiScoreController.cs +++ b/TaikoLocalServer/Controllers/Game/GetAiScoreController.cs @@ -1,12 +1,22 @@ -namespace TaikoLocalServer.Controllers.Game; +using TaikoLocalServer.Services.Interfaces; +using Throw; + +namespace TaikoLocalServer.Controllers.Game; [Route("/v12r03/chassis/getaiscore.php")] [ApiController] public class GetAiScoreController : BaseController { + private readonly IAiDatumService aiDatumService; + + public GetAiScoreController(IAiDatumService aiDatumService) + { + this.aiDatumService = aiDatumService; + } + [HttpPost] [Produces("application/protobuf")] - public IActionResult GetAiScore([FromBody] GetAiScoreRequest request) + public async Task GetAiScore([FromBody] GetAiScoreRequest request) { Logger.LogInformation("GetAiScore request : {Request}", request.Stringify()); @@ -15,6 +25,30 @@ public class GetAiScoreController : BaseController Result = 1 }; + var difficulty = (Difficulty)request.Level; + difficulty.Throw().IfOutOfRange(); + + var aiData = await aiDatumService.GetSongAiScore(request.Baid, request.SongNo, difficulty); + if (aiData is null) + { + return Ok(response); + } + + for (var index = 0; index < aiData.AiSectionScoreData.Count; index++) + { + var sectionScoreDatum = aiData.AiSectionScoreData[index]; + response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData + { + Crown = (uint)sectionScoreDatum.Crown, + GoodCnt = sectionScoreDatum.GoodCount, + OkCnt = sectionScoreDatum.OkCount, + NgCnt = sectionScoreDatum.MissCount, + PoundCnt = sectionScoreDatum.DrumrollCount, + Score = sectionScoreDatum.Score, + SectionNo = (uint)index + }); + } + // There's either 3 or 5 total sections // SectionNo doesn't seem to actually affect which section is being assigned to, only the List order matters /*response.AryBestSectionDatas.Add(new GetAiScoreResponse.AiBestSectionData() diff --git a/TaikoLocalServer/Controllers/Game/PlayResultController.cs b/TaikoLocalServer/Controllers/Game/PlayResultController.cs index a767160..770fac5 100644 --- a/TaikoLocalServer/Controllers/Game/PlayResultController.cs +++ b/TaikoLocalServer/Controllers/Game/PlayResultController.cs @@ -20,13 +20,16 @@ public class PlayResultController : BaseController private readonly IDanScoreDatumService danScoreDatumService; + private readonly IAiDatumService aiDatumService; + public PlayResultController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService, - ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService) + ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService) { this.userDatumService = userDatumService; this.songPlayDatumService = songPlayDatumService; this.songBestDatumService = songBestDatumService; this.danScoreDatumService = danScoreDatumService; + this.aiDatumService = aiDatumService; } [HttpPost] @@ -77,9 +80,7 @@ public class PlayResultController : BaseController if (playMode == PlayMode.AiBattle) { - // await UpdateAiBattleData(request, stageData); - // Update AI win count here somewhere, or in UpdatePlayData? - // I have no clue how to update input median or variance + await UpdateAiBattleData(request, stageData); } await UpdateBestData(request, stageData, bestData); @@ -163,7 +164,7 @@ public class PlayResultController : BaseController ComboCount = stageData.ComboCnt, HitCount = stageData.HitCnt, DrumrollCount = stageData.PoundCnt, - Crown = PlayResultToCrown(stageData), + Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt), Score = stageData.PlayScore, ScoreRate = stageData.ScoreRate, ScoreRank = (ScoreRank)stageData.ScoreRank, @@ -294,7 +295,7 @@ public class PlayResultController : BaseController }); // Determine whether it is dondaful crown as this is not reflected by play result - var crown = PlayResultToCrown(stageData); + var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt); bestDatum.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate); @@ -302,23 +303,67 @@ public class PlayResultController : BaseController } // TODO: AI battle - /*private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData) + private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData) { - for (int i = 0; i < stageData.ArySectionDatas.Count; i++) + var difficulty = (Difficulty)stageData.Level; + difficulty.Throw().IfOutOfRange(); + var existing = await aiDatumService.GetSongAiScore(request.BaidConf, + stageData.SongNo, difficulty); + + if (existing is null) { - // Only update crown if it's a higher crown than the previous best crown + var aiScoreDatum = new AiScoreDatum + { + Baid = request.BaidConf, + SongId = stageData.SongNo, + Difficulty = difficulty, + IsWin = stageData.IsWin + }; + for (var index = 0; index < stageData.ArySectionDatas.Count; index++) + { + AddNewAiSectionScore(request, stageData, index, difficulty, aiScoreDatum); + } - // Maybe have a "SectionNo" variable for which section number it is on the DB - // compare DB.SectionNo == i - // if any aspect of the section is higher than the previous best, update it - // Similar to Dan best play updates + await aiDatumService.InsertSongAiScore(aiScoreDatum); + return; } - }*/ - private static CrownType PlayResultToCrown(StageData stageData) + for (var index = 0; index < stageData.ArySectionDatas.Count; index++) + { + var sectionData = stageData.ArySectionDatas[index]; + if (index < existing.AiSectionScoreData.Count) + { + existing.AiSectionScoreData[index].UpdateBest(sectionData); + } + else + { + AddNewAiSectionScore(request,stageData,index,difficulty,existing); + } + } + + await aiDatumService.UpdateSongAiScore(existing); + } + + private static void AddNewAiSectionScore(PlayResultRequest request, StageData stageData, int index, Difficulty difficulty, + AiScoreDatum aiScoreDatum) { - var crown = (CrownType)stageData.PlayResult; - if (crown == CrownType.Gold && stageData.OkCnt == 0) + var sectionData = stageData.ArySectionDatas[index]; + var aiSectionScoreDatum = new AiSectionScoreDatum + { + Baid = request.BaidConf, + SongId = stageData.SongNo, + Difficulty = difficulty, + SectionIndex = index + }; + aiSectionScoreDatum.UpdateBest(sectionData); + aiScoreDatum.AiSectionScoreData.Add(aiSectionScoreDatum); + } + + + private static CrownType PlayResultToCrown(uint playResult, uint okCount) + { + var crown = (CrownType)playResult; + if (crown == CrownType.Gold && okCount == 0) { crown = CrownType.Dondaful; } diff --git a/TaikoLocalServer/Entities/AiSectionScoreDatum.cs b/TaikoLocalServer/Entities/AiSectionScoreDatum.cs index c6b65e8..f2cf04f 100644 --- a/TaikoLocalServer/Entities/AiSectionScoreDatum.cs +++ b/TaikoLocalServer/Entities/AiSectionScoreDatum.cs @@ -1,4 +1,6 @@ -namespace TaikoLocalServer.Entities; +using SharedProject.Utils; + +namespace TaikoLocalServer.Entities; public class AiSectionScoreDatum { @@ -25,4 +27,21 @@ public class AiSectionScoreDatum public uint DrumrollCount { get; set; } public AiScoreDatum Parent { get; set; } = null!; + + public void UpdateBest(PlayResultDataRequest.StageData.AiStageSectionData sectionData) + { + var crown = (CrownType)sectionData.Crown; + if (crown == CrownType.Gold && sectionData.OkCnt == 0) + { + crown = CrownType.Dondaful; + } + + IsWin = sectionData.IsWin ? sectionData.IsWin : IsWin; + Crown = ValueHelpers.Max(crown, Crown); + Score = ValueHelpers.Max(sectionData.Score, Score); + GoodCount = ValueHelpers.Max(sectionData.GoodCnt, GoodCount); + OkCount = ValueHelpers.Min(sectionData.OkCnt, OkCount); + MissCount = ValueHelpers.Min(sectionData.NgCnt, MissCount); + DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, DrumrollCount); + } } \ No newline at end of file diff --git a/TaikoLocalServer/Services/AiDatumService.cs b/TaikoLocalServer/Services/AiDatumService.cs new file mode 100644 index 0000000..1b74374 --- /dev/null +++ b/TaikoLocalServer/Services/AiDatumService.cs @@ -0,0 +1,50 @@ +using TaikoLocalServer.Services.Interfaces; +using Throw; + +namespace TaikoLocalServer.Services; + +public class AiDatumService : IAiDatumService +{ + private readonly TaikoDbContext context; + + public AiDatumService(TaikoDbContext context) + { + this.context = context; + } + + public async Task> GetAllAiScoreById(uint baid) + { + return await context.AiScoreData.Where(datum => datum.Baid == baid) + .Include(datum => datum.AiSectionScoreData) + .ToListAsync(); + } + + public async Task GetSongAiScore(uint baid, uint songId, Difficulty difficulty) + { + return await context.AiScoreData.Where(datum => datum.Baid == baid && + datum.SongId == songId && + datum.Difficulty == difficulty) + .Include(datum => datum.AiSectionScoreData) + .FirstOrDefaultAsync(); + } + + public async Task UpdateSongAiScore(AiScoreDatum datum) + { + var existing = await context.AiScoreData.FindAsync(datum.Baid, datum.SongId, datum.Difficulty); + existing.ThrowIfNull("Cannot update a non-existing ai score!"); + + context.AiScoreData.Update(datum); + await context.SaveChangesAsync(); + } + + public async Task InsertSongAiScore(AiScoreDatum datum) + { + var existing = await context.AiScoreData.FindAsync(datum.Baid, datum.SongId, datum.Difficulty); + if (existing is not null) + { + throw new ArgumentException("Ai score already exists!", nameof(datum)); + } + context.AiScoreData.Add(datum); + await context.SaveChangesAsync(); + } +} \ No newline at end of file diff --git a/TaikoLocalServer/Services/DanScoreDatumService.cs b/TaikoLocalServer/Services/DanScoreDatumService.cs index 01591d8..3e91419 100644 --- a/TaikoLocalServer/Services/DanScoreDatumService.cs +++ b/TaikoLocalServer/Services/DanScoreDatumService.cs @@ -30,7 +30,7 @@ public class DanScoreDatumService : IDanScoreDatumService var existing = await context.DanScoreData.FindAsync(datum.Baid, datum.DanId); if (existing is null) { - await context.DanScoreData.AddAsync(datum); + context.DanScoreData.Add(datum); await context.SaveChangesAsync(); return; } diff --git a/TaikoLocalServer/Services/Extentions/ServiceExtensions.cs b/TaikoLocalServer/Services/Extentions/ServiceExtensions.cs index d7be170..9886ad2 100644 --- a/TaikoLocalServer/Services/Extentions/ServiceExtensions.cs +++ b/TaikoLocalServer/Services/Extentions/ServiceExtensions.cs @@ -11,6 +11,7 @@ public static class ServiceExtensions services.AddScoped(); services.AddScoped(); services.AddScoped(); + services.AddScoped(); return services; } diff --git a/TaikoLocalServer/Services/Interfaces/IAiDatumService.cs b/TaikoLocalServer/Services/Interfaces/IAiDatumService.cs new file mode 100644 index 0000000..225e46a --- /dev/null +++ b/TaikoLocalServer/Services/Interfaces/IAiDatumService.cs @@ -0,0 +1,12 @@ +namespace TaikoLocalServer.Services.Interfaces; + +public interface IAiDatumService +{ + public Task> GetAllAiScoreById(uint baid); + + public Task GetSongAiScore(uint baid, uint songId, Difficulty difficulty); + + public Task UpdateSongAiScore(AiScoreDatum datum); + + public Task InsertSongAiScore(AiScoreDatum datum); +} \ No newline at end of file diff --git a/TaikoLocalServer/Services/SongBestDatumService.cs b/TaikoLocalServer/Services/SongBestDatumService.cs index b596e9e..0771092 100644 --- a/TaikoLocalServer/Services/SongBestDatumService.cs +++ b/TaikoLocalServer/Services/SongBestDatumService.cs @@ -32,7 +32,7 @@ public class SongBestDatumService : ISongBestDatumService return; } - await context.SongBestData.AddAsync(datum); + context.SongBestData.Add(datum); await context.SaveChangesAsync(); } diff --git a/TaikoLocalServer/Services/SongPlayDatumService.cs b/TaikoLocalServer/Services/SongPlayDatumService.cs index a93eb41..c07e15f 100644 --- a/TaikoLocalServer/Services/SongPlayDatumService.cs +++ b/TaikoLocalServer/Services/SongPlayDatumService.cs @@ -18,7 +18,7 @@ class SongPlayDatumService : ISongPlayDatumService public async Task AddSongPlayDatum(SongPlayDatum datum) { - await context.SongPlayData.AddAsync(datum); + context.SongPlayData.Add(datum); await context.SaveChangesAsync(); } } \ No newline at end of file