1
0
mirror of synced 2025-01-31 04:13:50 +01:00

Stage changes

This commit is contained in:
asesidaa 2024-03-16 17:46:06 +08:00
parent adb101261a
commit 07c92c2eef
45 changed files with 680 additions and 525 deletions

View File

@ -9,10 +9,15 @@ namespace GameDatabase.Entities
public uint MyDonNameLanguage { get; set; }
public string Title { get; set; } = string.Empty;
public uint TitlePlateId { get; set; }
public uint[] FavoriteSongsArray { get; set; } = Array.Empty<uint>();
public uint[] ToneFlgArray { get; set; } = Array.Empty<uint>();
public uint[] TitleFlgArray { get; set; } = Array.Empty<uint>();
public List<uint> FavoriteSongsArray { get; set; } = [];
public List<uint> ToneFlgArray { get; set; } = [];
public List<uint> TitleFlgArray { get; set; } = [];
public string CostumeFlgArray { get; set; } = "[[],[],[],[],[]]";
public List<uint> UnlockedKigurumi { get; set; } = [0];
public List<uint> UnlockedHead { get; set; } = [0];
public List<uint> UnlockedBody { get; set; }= [0];
public List<uint> UnlockedFace { get; set; }= [0];
public List<uint> UnlockedPuchi{ get; set; }= [0];
public uint[] GenericInfoFlgArray { get; set; } = Array.Empty<uint>();
public short OptionSetting { get; set; }
public int NotesPosition { get; set; }

View File

@ -0,0 +1,138 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GameDatabase.Migrations
{
/// <inheritdoc />
public partial class SplitCostumeUnlocks : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "UnlockedBody",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedFace",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedHead",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedKigurumi",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
migrationBuilder.AddColumn<string>(
name: "UnlockedPuchi",
table: "UserData",
type: "TEXT",
nullable: false,
defaultValue: "");
// Split the costumeflgarray into separate fields
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedKigurumi = json_extract(CostumeFlgArray, '$[0]'),
UnlockedHead = json_extract(CostumeFlgArray, '$[1]'),
UnlockedBody = json_extract(CostumeFlgArray, '$[2]'),
UnlockedFace = json_extract(CostumeFlgArray, '$[3]'),
UnlockedPuchi = json_extract(CostumeFlgArray, '$[4]')");
// Deduplicate values
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedKigurumi = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedKigurumi)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedHead = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedHead)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedBody = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedBody)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedFace = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedFace)
WHERE ud.Baid = UserData.Baid
)
)");
migrationBuilder.Sql(@"
UPDATE UserData
SET UnlockedPuchi = (
SELECT json_group_array(DISTINCT value)
FROM (
SELECT value
FROM UserData AS ud
CROSS JOIN json_each(ud.UnlockedPuchi)
WHERE ud.Baid = UserData.Baid
)
)");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "UnlockedBody",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedFace",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedHead",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedKigurumi",
table: "UserData");
migrationBuilder.DropColumn(
name: "UnlockedPuchi",
table: "UserData");
}
}
}

View File

@ -33,7 +33,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("AiScoreData", (string)null);
b.ToTable("AiScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
@ -73,7 +73,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex");
b.ToTable("AiSectionScoreData", (string)null);
b.ToTable("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
@ -138,7 +138,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "DanId", "DanType");
b.ToTable("DanScoreData", (string)null);
b.ToTable("DanScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
@ -183,7 +183,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "DanId", "DanType", "SongNumber");
b.ToTable("DanStageScoreData", (string)null);
b.ToTable("DanStageScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
@ -211,7 +211,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("SongBestData", (string)null);
b.ToTable("SongBestData");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
@ -272,7 +272,7 @@ namespace TaikoLocalServer.Migrations
b.HasIndex("Baid");
b.ToTable("SongPlayData", (string)null);
b.ToTable("SongPlayData");
});
modelBuilder.Entity("GameDatabase.Entities.Token", b =>
@ -288,7 +288,7 @@ namespace TaikoLocalServer.Migrations
b.HasKey("Baid", "Id");
b.ToTable("Tokens", (string)null);
b.ToTable("Tokens");
});
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
@ -388,13 +388,33 @@ namespace TaikoLocalServer.Migrations
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedBody")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedFace")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedHead")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedKigurumi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedPuchi")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedSongIdList")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("UserData", (string)null);
b.ToTable("UserData");
});
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>

View File

@ -1,6 +1,4 @@
using System.Text.Json.Serialization;
namespace SharedProject.Models;
namespace SharedProject.Models;
public interface IVerupNo
{

View File

@ -1,6 +1,4 @@
using System.Buffers.Binary;
using System.Collections;
using System.Collections.Specialized;
using System.Collections.Specialized;
using SharedProject.Enums;
using SharedProject.Models;
using Throw;

View File

@ -1,5 +1,6 @@
using GameDatabase.Entities;
using SharedProject.Utils;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Common.Utils;
@ -21,4 +22,21 @@ public static class Extensions
datum.MissCount = ValueHelpers.Min(sectionData.NgCnt, datum.MissCount);
datum.DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, datum.DrumrollCount);
}
public static void UpdateBest(this AiSectionScoreDatum datum,CommonPlayResultData.AiStageSectionData sectionData)
{
var crown = (CrownType)sectionData.Crown;
if (crown == CrownType.Gold && sectionData.OkCnt == 0)
{
crown = CrownType.Dondaful;
}
datum.IsWin = sectionData.IsWin ? sectionData.IsWin : datum.IsWin;
datum.Crown = ValueHelpers.Max(crown, datum.Crown);
datum.Score = ValueHelpers.Max(sectionData.Score, datum.Score);
datum.GoodCount = ValueHelpers.Max(sectionData.GoodCnt, datum.GoodCount);
datum.OkCount = ValueHelpers.Min(sectionData.OkCnt, datum.OkCount);
datum.MissCount = ValueHelpers.Min(sectionData.NgCnt, datum.MissCount);
datum.DrumrollCount = ValueHelpers.Max(sectionData.PoundCnt, datum.DrumrollCount);
}
}

View File

@ -1,5 +1,4 @@
using System.Collections;
using System.Collections.Immutable;
using System.Collections.Specialized;
using System.Runtime.InteropServices;
using GameDatabase.Entities;

View File

@ -16,6 +16,9 @@
"GameServer2": {
"Url": "https://0.0.0.0:54431"
},
"GameServerCN": {
"Url": "https://0.0.0.0:57402"
},
"GarmcServer": {
"Url": "https://0.0.0.0:443"
}

View File

@ -1,7 +1,6 @@
using SharedProject.Models;
using SharedProject.Utils;
using System.Text.Json;
using Throw;
namespace TaikoLocalServer.Controllers.Api;
@ -125,7 +124,7 @@ public class UserSettingsController : BaseController<UserSettingsController>
user.CostumeData = JsonSerializer.Serialize(costumes);
// If a locked tone is selected, unlock it
uint[] toneFlg = user.ToneFlgArray;
var toneFlg = user.ToneFlgArray;
/*try
{
toneFlg = JsonSerializer.Deserialize<uint[]>(user.ToneFlgArray)!;
@ -135,7 +134,7 @@ public class UserSettingsController : BaseController<UserSettingsController>
Logger.LogError(e, "Parsing tone flg json data failed");
}
toneFlg.ThrowIfNull("Tone flg should never be null!");*/
toneFlg = toneFlg.Append(0u).Append(userSetting.ToneId).Distinct().ToArray();
toneFlg = toneFlg.Append(0u).Append(userSetting.ToneId).Distinct().ToList();
user.ToneFlgArray = toneFlg;

View File

@ -1,6 +1,4 @@
using SharedProject.Models.Requests;
namespace TaikoLocalServer.Controllers.Api;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]

View File

@ -1,6 +1,4 @@
using MediatR;
namespace TaikoLocalServer.Controllers;
namespace TaikoLocalServer.Controllers;
public abstract class BaseController<T> : ControllerBase where T : BaseController<T>
{

View File

@ -1,5 +1,4 @@
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Models.Application;
using AddTokenCountRequestMapper = TaikoLocalServer.Mappers.AddTokenCountRequestMapper;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -54,7 +54,7 @@ public class CrownsDataController : BaseController<CrownsDataController>
public record CrownData(byte[] CrownFlg, byte[] DondafulCrownFlg);
public async Task<CrownData> Handle(uint baid)
private async Task<CrownData> Handle(uint baid)
{
var songBestData = await songBestDatumService.GetAllSongBestData(baid);

View File

@ -1,5 +1,4 @@
using TaikoLocalServer.Handlers;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,6 +1,5 @@
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,7 +1,5 @@
using GameDatabase.Entities;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,8 +1,5 @@
using System.Text.Json;
using GameDatabase.Entities;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -0,0 +1,19 @@
using TaikoLocalServer.Models.v3209;
namespace TaikoLocalServer.Controllers.Game;
[ApiController]
public class HeadClerk2Controller : BaseController<HeadClerk2Controller>
{
[HttpPost("/v12r00_cn/chassis/headclerk2.php")]
[Produces("application/protobuf")]
public IActionResult UploadHeadClert2([FromBody] HeadClerk2Request request)
{
Logger.LogInformation("HeadClerk2 request : {Request}", request.Stringify());
var response = new HeadClerk2Response
{
Result = 1
};
return Ok(response);
}
}

View File

@ -1,7 +1,5 @@
using Microsoft.Extensions.Options;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,5 +1,4 @@
using GameDatabase.Entities;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,418 +1,47 @@
using GameDatabase.Entities;
using System.Globalization;
using System.Text.Json;
using Throw;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
namespace TaikoLocalServer.Controllers.Game;
using StageData = PlayResultDataRequest.StageData;
[Route("/v12r08_ww/chassis/playresult_r3ky4a4z.php")]
[ApiController]
public class PlayResultController : BaseController<PlayResultController>
{
private readonly IUserDatumService userDatumService;
private readonly ISongPlayDatumService songPlayDatumService;
private readonly ISongBestDatumService songBestDatumService;
private readonly IDanScoreDatumService danScoreDatumService;
private readonly IAiDatumService aiDatumService;
public PlayResultController(IUserDatumService userDatumService, ISongPlayDatumService songPlayDatumService,
ISongBestDatumService songBestDatumService, IDanScoreDatumService danScoreDatumService, IAiDatumService aiDatumService)
{
this.userDatumService = userDatumService;
this.songPlayDatumService = songPlayDatumService;
this.songBestDatumService = songBestDatumService;
this.danScoreDatumService = danScoreDatumService;
this.aiDatumService = aiDatumService;
}
[HttpPost]
[HttpPost("/v12r08_ww/chassis/playresult_r3ky4a4z.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> UploadPlayResult([FromBody] PlayResultRequest request)
{
Logger.LogInformation("PlayResult request : {Request}", request.Stringify());
var truncated = request.PlayresultData.Skip(32).ToArray();
var decompressed = GZipBytesUtil.DecompressGZipBytes(truncated);
var playResultData = Serializer.Deserialize<PlayResultDataRequest>(new ReadOnlySpan<byte>(decompressed));
Logger.LogInformation("Play result data {Data}", playResultData.Stringify());
var commonRequest = PlayResultMappers.Map(playResultData);
var commonResponse = await Mediator.Send(new UpdatePlayResultCommand(request.BaidConf, commonRequest));
var response = new PlayResultResponse
{
Result = 1
Result = commonResponse
};
// Fix issue caused by guest play, god knows why they send guest play data
if (request.BaidConf == 0)
{
return Ok(response);
}
if (await userDatumService.GetFirstUserDatumOrNull(request.BaidConf) is null)
{
Logger.LogWarning("Game uploading a non exisiting user with baid {Baid}", request.BaidConf);
return Ok(response);
}
var lastPlayDatetime = DateTime.ParseExact(playResultData.PlayDatetime, Constants.DATE_TIME_FORMAT,
CultureInfo.InvariantCulture);
await UpdateUserData(request, playResultData, lastPlayDatetime);
var playMode = (PlayMode)playResultData.PlayMode;
if (playMode is PlayMode.DanMode or PlayMode.GaidenMode)
{
var danType = playMode == PlayMode.DanMode ? DanType.Normal : DanType.Gaiden;
await UpdateDanPlayData(request, playResultData, danType);
return Ok(response);
}
for (var songNumber = 0; songNumber < playResultData.AryStageInfoes.Count; songNumber++)
{
var stageData = playResultData.AryStageInfoes[songNumber];
if (stageData.IsSkipUse)
{
await UpdatePlayData(request, songNumber, stageData, lastPlayDatetime);
continue;
}
if (playMode == PlayMode.AiBattle)
{
await UpdateAiBattleData(request, stageData);
}
await UpdateBestData(request, stageData);
await UpdatePlayData(request, songNumber, stageData, lastPlayDatetime);
}
return Ok(response);
}
private async Task UpdateDanPlayData(PlayResultRequest request, PlayResultDataRequest playResultDataRequest, DanType danType)
[HttpPost("/v12r00_cn/chassis/playresult.php")]
[Produces("application/protobuf")]
public async Task<IActionResult> UploadPlayResult3209([FromBody] Models.v3209.PlayResultRequest request)
{
if (playResultDataRequest.IsNotRecordedDan)
Logger.LogInformation("PlayResult3209 request : {Request}", request.Stringify());
var decompressed = GZipBytesUtil.DecompressGZipBytes(request.PlayresultData);
var playResultData =
Serializer.Deserialize<Models.v3209.PlayResultDataRequest>(new ReadOnlySpan<byte>(decompressed));
Logger.LogInformation("Play result data 3209 {Data}", playResultData.Stringify());
var commonRequest = PlayResultMappers.Map(playResultData);
var commonResponse = await Mediator.Send(new UpdatePlayResultCommand((uint) request.BaidConf, commonRequest));
var response = new Models.v3209.PlayResultResponse
{
Logger.LogInformation("Dan score will not be saved!");
return;
}
var danScoreData =
await danScoreDatumService.GetSingleDanScoreDatum(request.BaidConf, playResultDataRequest.DanId, danType) ??
new DanScoreDatum
{
Baid = request.BaidConf,
DanId = playResultDataRequest.DanId,
DanType = danType
};
danScoreData.ClearState =
(DanClearState)Math.Max(playResultDataRequest.DanResult, (uint)danScoreData.ClearState);
danScoreData.ArrivalSongCount =
Math.Max((uint)playResultDataRequest.AryStageInfoes.Count, danScoreData.ArrivalSongCount);
danScoreData.ComboCountTotal = Math.Max(playResultDataRequest.ComboCntTotal, danScoreData.ComboCountTotal);
danScoreData.SoulGaugeTotal = Math.Max(playResultDataRequest.SoulGaugeTotal, danScoreData.SoulGaugeTotal);
UpdateDanStageData(playResultDataRequest, danScoreData);
await danScoreDatumService.InsertOrUpdateDanScoreDatum(danScoreData);
}
private void UpdateDanStageData(PlayResultDataRequest playResultDataRequest, DanScoreDatum danScoreData)
{
for (var i = 0; i < playResultDataRequest.AryStageInfoes.Count; i++)
{
var stageData = playResultDataRequest.AryStageInfoes[i];
var songNumber = i;
var danStageData = danScoreData.DanStageScoreData.FirstOrDefault(datum => datum.SongNumber == songNumber,
new DanStageScoreDatum
{
Baid = danScoreData.Baid,
DanId = danScoreData.DanId,
DanType = danScoreData.DanType,
SongNumber = (uint)songNumber,
OkCount = stageData.OkCnt,
BadCount = stageData.NgCnt
});
danStageData.HighScore = Math.Max(danStageData.HighScore, stageData.PlayScore);
danStageData.ComboCount = Math.Max(danStageData.ComboCount, stageData.ComboCnt);
danStageData.DrumrollCount = Math.Max(danStageData.DrumrollCount, stageData.PoundCnt);
danStageData.GoodCount = Math.Max(danStageData.GoodCount, stageData.GoodCnt);
danStageData.TotalHitCount = Math.Max(danStageData.TotalHitCount, stageData.HitCnt);
danStageData.OkCount = Math.Min(danStageData.OkCount, stageData.OkCnt);
danStageData.BadCount = Math.Min(danStageData.BadCount, stageData.NgCnt);
var index = danScoreData.DanStageScoreData.IndexOf(danStageData);
if (index == -1)
{
danScoreDatumService.TrackDanStageData(danStageData);
}
}
}
private async Task UpdatePlayData(PlayResultRequest request, int songNumber, StageData stageData,
DateTime lastPlayDatetime)
{
var songPlayDatum = new SongPlayDatum
{
Baid = request.BaidConf,
SongNumber = (uint)songNumber,
GoodCount = stageData.GoodCnt,
OkCount = stageData.OkCnt,
MissCount = stageData.NgCnt,
ComboCount = stageData.ComboCnt,
HitCount = stageData.HitCnt,
DrumrollCount = stageData.PoundCnt,
Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt),
Score = stageData.PlayScore,
ScoreRate = stageData.ScoreRate,
ScoreRank = (ScoreRank)stageData.ScoreRank,
Skipped = stageData.IsSkipUse,
SongId = stageData.SongNo,
PlayTime = lastPlayDatetime,
Difficulty = (Difficulty)stageData.Level
Result = commonResponse
};
await songPlayDatumService.AddSongPlayDatum(songPlayDatum);
}
private async Task UpdateUserData(PlayResultRequest request, PlayResultDataRequest playResultData,
DateTime lastPlayDatetime)
{
var userdata = await userDatumService.GetFirstUserDatumOrNull(request.BaidConf);
userdata.ThrowIfNull($"User data is null! Baid: {request.BaidConf}");
var playMode = (PlayMode)playResultData.PlayMode;
userdata.Title = playResultData.Title;
userdata.TitlePlateId = playResultData.TitleplateId;
var costumeData = new List<uint>
{
playResultData.AryCurrentCostume.Costume1,
playResultData.AryCurrentCostume.Costume2,
playResultData.AryCurrentCostume.Costume3,
playResultData.AryCurrentCostume.Costume4,
playResultData.AryCurrentCostume.Costume5
};
userdata.CostumeData = JsonSerializer.Serialize(costumeData);
// Skip user setting altogether following official logic
// Skip user setting saving when in dan mode as dan mode uses its own default setting
// if (playMode != PlayMode.DanMode)
// {
// var lastStage = playResultData.AryStageInfoes.Last();
// var option = BinaryPrimitives.ReadInt16LittleEndian(lastStage.OptionFlg);
// userdata.OptionSetting = option;
// userdata.IsSkipOn = lastStage.IsSkipOn;
// userdata.IsVoiceOn = !lastStage.IsVoiceOn;
// userdata.NotesPosition = lastStage.NotesPosition;
// }
userdata.LastPlayDatetime = lastPlayDatetime;
userdata.LastPlayMode = playResultData.PlayMode;
var tones = userdata.ToneFlgArray.ToList();
tones.AddRange(playResultData.GetToneNoes);
userdata.ToneFlgArray = tones.ToArray();
//UpdateJsonUintFlagArray(userdata.ToneFlgArray, playResultData.GetToneNoes, nameof(userdata.ToneFlgArray));
/*userdata.TitleFlgArray =
UpdateJsonUintFlagArray(userdata.TitleFlgArray, playResultData.GetTitleNoes, nameof(userdata.TitleFlgArray));*/
var titles = userdata.TitleFlgArray.ToList();
titles.AddRange(playResultData.GetTitleNoes);
userdata.TitleFlgArray = titles.ToArray();
userdata.CostumeFlgArray = UpdateJsonCostumeFlagArray(userdata.CostumeFlgArray,
new[]
{
playResultData.GetCostumeNo1s,
playResultData.GetCostumeNo2s,
playResultData.GetCostumeNo3s,
playResultData.GetCostumeNo4s,
playResultData.GetCostumeNo5s
});
/*userdata.GenericInfoFlgArray =
UpdateJsonUintFlagArray(userdata.GenericInfoFlgArray, playResultData.GetGenericInfoNoes, nameof(userdata.GenericInfoFlgArray));*/
var genericInfo = userdata.GenericInfoFlgArray.ToList();
genericInfo.AddRange(playResultData.GetGenericInfoNoes);
userdata.GenericInfoFlgArray = genericInfo.ToArray();
var difficultyPlayedArray = new List<uint>
{
playResultData.DifficultyPlayedCourse,
playResultData.DifficultyPlayedStar,
playResultData.DifficultyPlayedSort
};
userdata.DifficultyPlayedArray = JsonSerializer.Serialize(difficultyPlayedArray);
userdata.AiWinCount += playResultData.AryStageInfoes.Count(data => data.IsWin);
await userDatumService.UpdateUserDatum(userdata);
}
private string UpdateJsonUintFlagArray(string originalValue, IReadOnlyCollection<uint>? newValue, string fieldName)
{
var flgData = new List<uint>();
try
{
flgData = JsonSerializer.Deserialize<List<uint>>(originalValue);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing {FieldName} json data failed", fieldName);
}
flgData?.AddRange(newValue ?? Array.Empty<uint>());
var flgArray = flgData ?? new List<uint>();
return JsonSerializer.Serialize(flgArray);
}
private string UpdateJsonCostumeFlagArray(string originalValue, IReadOnlyList<IReadOnlyCollection<uint>?>? newValue)
{
var flgData = new List<List<uint>>();
try
{
flgData = JsonSerializer.Deserialize<List<List<uint>>>(originalValue);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing Costume flag json data failed");
}
if (flgData is null)
{
flgData = new List<List<uint>>();
}
for (var index = 0; index < flgData.Count; index++)
{
var subFlgData = flgData[index];
subFlgData.AddRange(newValue?[index] ?? Array.Empty<uint>());
}
if (flgData.Count >= 5)
{
return JsonSerializer.Serialize(flgData);
}
Logger.LogWarning("Costume flag array count less than 5!");
flgData = new List<List<uint>>
{
new(), new(), new(), new(), new()
};
return JsonSerializer.Serialize(flgData);
}
private async Task UpdateBestData(PlayResultRequest request, StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await songBestDatumService.GetSongBestData(request.BaidConf, stageData.SongNo, difficulty);
// Determine whether it is dondaful crown as this is not reflected by play result
var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt);
if (existing is null)
{
var songBestDatum = new SongBestDatum
{
Baid = request.BaidConf,
SongId = stageData.SongNo,
Difficulty = difficulty,
BestScore = stageData.PlayScore,
BestRate = stageData.ScoreRate,
BestCrown = crown,
BestScoreRank = (ScoreRank)stageData.ScoreRank
};
await songBestDatumService.InsertSongBestData(songBestDatum);
return;
}
existing.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
await songBestDatumService.UpdateSongBestData(existing);
}
private async Task UpdateAiBattleData(PlayResultRequest request, StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await aiDatumService.GetSongAiScore(request.BaidConf,
stageData.SongNo, difficulty);
if (existing is null)
{
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);
}
await aiDatumService.InsertSongAiScore(aiScoreDatum);
return;
}
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 sectionData = stageData.ArySectionDatas[index];
var aiSectionScoreDatum = new AiSectionScoreDatum
{
Baid = request.BaidConf,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = index,
OkCount = sectionData.OkCnt,
MissCount = sectionData.NgCnt
};
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;
}
return crown;
return Ok(response);
}
}

View File

@ -1,7 +1,5 @@
using GameDatabase.Entities;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,10 +1,5 @@
using Microsoft.Extensions.Options;
using System.Buffers.Binary;
using System.Text.Json;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Handlers;
using TaikoLocalServer.Mappers;
using TaikoLocalServer.Settings;
using Throw;
namespace TaikoLocalServer.Controllers.Game;

View File

@ -1,8 +1,4 @@
using System.Buffers;
using System.Security.Cryptography;
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
using System.Text.Json;
using System.Text;
using System.Text.Json.Serialization;

View File

@ -1,7 +1,4 @@
using System.Buffers;
using Microsoft.Extensions.Options;
using TaikoLocalServer.Settings;
using System.Text.Json;
using System.Text;
using System.Text.Json.Serialization;

View File

@ -11,7 +11,7 @@ public class AddMyDonEntryCommandHandler(TaikoDbContext context, ILogger<AddMyDo
public async Task<CommonMyDonEntryResponse> Handle(AddMyDonEntryCommand request, CancellationToken cancellationToken)
{
var nextBaid = await context.Cards.Select(card => card.Baid)
.DefaultIfEmpty(0u)
.DefaultIfEmpty()
.MaxAsync(cancellationToken) + 1;
var newUser = new UserDatum
{

View File

@ -1,6 +1,5 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using MediatR;
using TaikoLocalServer.Models.Application;
using Throw;

View File

@ -1,6 +1,4 @@
using System.Text.Json;
using GameDatabase.Context;
using MediatR;
using GameDatabase.Context;
using TaikoLocalServer.Models.Application;
using Throw;
@ -40,7 +38,7 @@ public class BaidQueryHandler : IRequestHandler<BaidQuery, CommonBaidResponse>
var userData = await context.UserData.FindAsync(baid);
userData.ThrowIfNull($"User not found for card with Baid {baid}!");
var songBestData = context.SongBestData.Where(datum => datum.Baid == baid);
var songBestData = context.SongBestData.Where(datum => datum.Baid == baid).ToList();
var achievementDisplayDifficulty = userData.AchievementDisplayDifficulty;
if (achievementDisplayDifficulty == Difficulty.None)
{
@ -51,10 +49,10 @@ public class BaidQueryHandler : IRequestHandler<BaidQuery, CommonBaidResponse>
.Max();
}
// For each crown type, calculate how many songs have that crown type
var crownCountData = await songBestData
var crownCountData = songBestData
.Where(datum => datum.BestCrown >= CrownType.Clear)
.GroupBy(datum => datum.BestCrown)
.ToDictionaryAsync(datums => datums.Key, datums => (uint)datums.Count(), cancellationToken);
.ToDictionary(datums => datums.Key, datums => (uint)datums.Count());
var crownCount = new uint[3];
foreach (var crownType in Enum.GetValues<CrownType>())
{
@ -64,10 +62,10 @@ public class BaidQueryHandler : IRequestHandler<BaidQuery, CommonBaidResponse>
}
}
var scoreRankData = await songBestData
var scoreRankData = songBestData
.Where(datum => datum.BestCrown >= CrownType.Clear)
.GroupBy(datum => datum.BestScoreRank)
.ToDictionaryAsync(datums => datums.Key, datums => (uint)datums.Count(), cancellationToken);
.ToDictionary(datums => datums.Key, datums => (uint)datums.Count());
var scoreRankCount = new uint[7];
foreach (var scoreRank in Enum.GetValues<ScoreRank>())
{

View File

@ -1,5 +1,4 @@
using GameDatabase.Context;
using MediatR;
using TaikoLocalServer.Models.Application;
using Throw;

View File

@ -1,6 +1,4 @@
using GameDatabase.Context;
using MudBlazor.Extensions;
using SharedProject.Models;
using TaikoLocalServer.Models.Application;
using Throw;

View File

@ -29,19 +29,24 @@ public class GetTokenCountQueryHandler(IGameDataService gameDataService,
tokenDataDictionary.TryGetValue(tokenName, out var tokenId);
if (tokenId <= 0) continue;
var token = await context.Tokens.FirstOrDefaultAsync(t => t.Baid == request.Baid &&
t.Id == tokenId,
cancellationToken) ?? new Token
t.Id == tokenId,
cancellationToken);
if (token is null)
{
Id = tokenId,
Count = 0
};
token = new Token
{
Baid = request.Baid,
Id = tokenId,
Count = 0,
};
context.Tokens.Add(token);
}
response.AryTokenCountDatas.Add(new CommonTokenCountData
{
TokenId = (uint)token.Id,
TokenCount = token.Count
});
context.Tokens.Update(token);
}
await context.SaveChangesAsync(cancellationToken);

View File

@ -0,0 +1,283 @@
using System.Text.Json;
using GameDatabase.Context;
using GameDatabase.Entities;
using TaikoLocalServer.Models.Application;
using Throw;
namespace TaikoLocalServer.Handlers;
public record UpdatePlayResultCommand(uint Baid, CommonPlayResultData PlayResultData) : IRequest<uint>;
public class UpdatePlayResultCommandHandler(TaikoDbContext context, ILogger<UpdatePlayResultCommandHandler> logger)
: IRequestHandler<UpdatePlayResultCommand, uint>
{
public async Task<uint> Handle(UpdatePlayResultCommand request, CancellationToken cancellationToken)
{
if (request.Baid == 0)
{
return 1;
}
var user = await context.UserData.FindAsync(request.Baid, cancellationToken);
if (user is null)
{
logger.LogWarning("Game uploading a non exisiting user with baid {Baid}", request.Baid);
return 1;
}
var lastPlayDateTime = DateTime.Now;
var playResultData = request.PlayResultData;
UpdateUserData(user, playResultData, lastPlayDateTime);
var playMode = (PlayMode)playResultData.PlayMode;
if (playMode is PlayMode.DanMode or PlayMode.GaidenMode)
{
var danType = playMode == PlayMode.DanMode ? DanType.Normal : DanType.Gaiden;
var danPlayData = await context.DanScoreData
.Include(datum => datum.DanStageScoreData)
.FirstOrDefaultAsync(datum => datum.Baid == request.Baid &&
datum.DanId == playResultData.DanId &&
datum.DanType == danType, cancellationToken);
if (danPlayData is null)
{
danPlayData = new DanScoreDatum
{
Baid = request.Baid,
DanId = playResultData.DanId,
DanType = danType
};
UpdateDanPlayData(danPlayData, playResultData);
context.DanScoreData.Add(danPlayData);
}
else
{
UpdateDanPlayData(danPlayData, playResultData);
context.DanScoreData.Update(danPlayData);
}
return 1;
}
for (var songNumber = 0; songNumber < playResultData.AryStageInfoes.Count; songNumber++)
{
var stageData = playResultData.AryStageInfoes[songNumber];
if (playMode == PlayMode.AiBattle)
{
await UpdateAiBattleData(playResultData, stageData);
}
await UpdateBestData(playResultData, stageData);
var songPlayDatum = new SongPlayDatum
{
Baid = request.Baid,
SongNumber = (uint)songNumber,
GoodCount = stageData.GoodCnt,
OkCount = stageData.OkCnt,
MissCount = stageData.NgCnt,
ComboCount = stageData.ComboCnt,
HitCount = stageData.HitCnt,
DrumrollCount = stageData.PoundCnt,
Crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt),
Score = stageData.PlayScore,
ScoreRate = stageData.ScoreRate,
ScoreRank = (ScoreRank)stageData.ScoreRank,
Skipped = stageData.IsSkipUse,
SongId = stageData.SongNo,
PlayTime = lastPlayDateTime,
Difficulty = (Difficulty)stageData.Level
};
context.SongPlayData.Add(songPlayDatum);
}
await context.SaveChangesAsync(cancellationToken);
return 1;
}
private async Task UpdateBestData(CommonPlayResultData playResultData, CommonPlayResultData.StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await context.SongBestData.FindAsync(playResultData.Baid, stageData.SongNo, difficulty);
// Determine whether it is dondaful crown as this is not reflected by play result
var crown = PlayResultToCrown(stageData.PlayResult, stageData.OkCnt);
if (existing is null)
{
existing = new SongBestDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
BestScore = stageData.PlayScore,
BestRate = stageData.ScoreRate,
BestCrown = crown,
BestScoreRank = (ScoreRank)stageData.ScoreRank
};
context.SongBestData.Add(existing);
return;
}
existing.UpdateBestData(crown, stageData.ScoreRank, stageData.PlayScore, stageData.ScoreRate);
context.SongBestData.Update(existing);
}
private async Task UpdateAiBattleData(CommonPlayResultData playResultData, CommonPlayResultData.StageData stageData)
{
var difficulty = (Difficulty)stageData.Level;
difficulty.Throw().IfOutOfRange();
var existing = await context.AiScoreData
.Include(datum => datum.AiSectionScoreData)
.FirstOrDefaultAsync(datum => datum.Baid == playResultData.Baid &&
datum.SongId == stageData.SongNo &&
datum.Difficulty == difficulty);
if (existing is null)
{
existing = new AiScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
IsWin = stageData.IsWin
};
var aiSections = stageData.ArySectionDatas.Select((data, i) =>
{
var section = new AiSectionScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = i,
OkCount = data.OkCnt,
MissCount = data.NgCnt
};
section.UpdateBest(data);
return section;
}
);
existing.AiSectionScoreData.AddRange(aiSections);
context.AiScoreData.Add(existing);
return;
}
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
{
var aiSectionScoreDatum = new AiSectionScoreDatum
{
Baid = playResultData.Baid,
SongId = stageData.SongNo,
Difficulty = difficulty,
SectionIndex = index,
OkCount = sectionData.OkCnt,
MissCount = sectionData.NgCnt
};
aiSectionScoreDatum.UpdateBest(sectionData);
existing.AiSectionScoreData.Add(aiSectionScoreDatum);
context.AiScoreData.Update(existing);
}
}
}
private void UpdateDanPlayData(DanScoreDatum danPlayData, CommonPlayResultData playResultData)
{
danPlayData.ClearState =
(DanClearState)Math.Max(playResultData.DanResult, (uint)danPlayData.ClearState);
danPlayData.ArrivalSongCount =
Math.Max((uint)playResultData.AryStageInfoes.Count, danPlayData.ArrivalSongCount);
danPlayData.ComboCountTotal = Math.Max(playResultData.ComboCntTotal, danPlayData.ComboCountTotal);
danPlayData.SoulGaugeTotal = Math.Max(playResultData.SoulGaugeTotal, danPlayData.SoulGaugeTotal);
for (var i = 0; i < playResultData.AryStageInfoes.Count; i++)
{
var stageData = playResultData.AryStageInfoes[i];
var songNumber = i;
var danStageData = danPlayData.DanStageScoreData.FirstOrDefault(datum => datum.SongNumber == songNumber,
new DanStageScoreDatum
{
Baid = danPlayData.Baid,
DanId = danPlayData.DanId,
DanType = danPlayData.DanType,
SongNumber = (uint)songNumber,
OkCount = stageData.OkCnt,
BadCount = stageData.NgCnt
});
danStageData.HighScore = Math.Max(danStageData.HighScore, stageData.PlayScore);
danStageData.ComboCount = Math.Max(danStageData.ComboCount, stageData.ComboCnt);
danStageData.DrumrollCount = Math.Max(danStageData.DrumrollCount, stageData.PoundCnt);
danStageData.GoodCount = Math.Max(danStageData.GoodCount, stageData.GoodCnt);
danStageData.TotalHitCount = Math.Max(danStageData.TotalHitCount, stageData.HitCnt);
danStageData.OkCount = Math.Min(danStageData.OkCount, stageData.OkCnt);
danStageData.BadCount = Math.Min(danStageData.BadCount, stageData.NgCnt);
var index = danPlayData.DanStageScoreData.IndexOf(danStageData);
if (index == -1)
{
context.DanStageScoreData.Add(danStageData);
}
}
}
private void UpdateUserData(UserDatum user, CommonPlayResultData playResultData, DateTime lastPlayDateTime)
{
user.Title = playResultData.Title;
user.TitlePlateId = playResultData.TitleplateId;
var costumeData = new List<uint>
{
playResultData.AryCurrentCostume.Costume1,
playResultData.AryCurrentCostume.Costume2,
playResultData.AryCurrentCostume.Costume3,
playResultData.AryCurrentCostume.Costume4,
playResultData.AryCurrentCostume.Costume5
};
user.CostumeData = JsonSerializer.Serialize(costumeData);
user.LastPlayDatetime = lastPlayDateTime;
user.LastPlayMode = playResultData.PlayMode;
user.ToneFlgArray.AddRange(playResultData.GetToneNoes);
user.TitleFlgArray.AddRange(playResultData.GetTitleNoes);
user.UnlockedKigurumi.AddRange(playResultData.GetCostumeNo1s);
user.UnlockedHead.AddRange(playResultData.GetCostumeNo2s);
user.UnlockedBody.AddRange(playResultData.GetCostumeNo3s);
user.UnlockedFace.AddRange(playResultData.GetCostumeNo4s);
user.UnlockedPuchi.AddRange(playResultData.GetCostumeNo5s);
var genericInfo = user.GenericInfoFlgArray.ToList();
genericInfo.AddRange(playResultData.GetGenericInfoNoes);
user.GenericInfoFlgArray = genericInfo.ToArray();
var difficultyPlayedArray = new List<uint>
{
playResultData.DifficultyPlayedCourse,
playResultData.DifficultyPlayedStar,
playResultData.DifficultyPlayedSort
};
user.DifficultyPlayedArray = JsonSerializer.Serialize(difficultyPlayedArray);
user.AiWinCount += playResultData.AryStageInfoes.Count(data => data.IsWin);
context.UserData.Update(user);
}
private static CrownType PlayResultToCrown(uint playResult, uint okCount)
{
var crown = (CrownType)playResult;
if (crown == CrownType.Gold && okCount == 0)
{
crown = CrownType.Dondaful;
}
return crown;
}
}

View File

@ -30,7 +30,7 @@ public class UserDataQueryHandler(TaikoDbContext context, IGameDataService gameD
var uraSongArray =
FlagCalculator.GetBitArrayFromIds(enabledUraMusicList, Constants.MUSIC_ID_MAX, logger);
if (userData.ToneFlgArray.Length == 0)
if (userData.ToneFlgArray.Count == 0)
{
userData.ToneFlgArray = [0];
context.UserData.Update(userData);
@ -82,7 +82,7 @@ public class UserDataQueryHandler(TaikoDbContext context, IGameDataService gameD
TitleFlg = titleArray,
ReleaseSongFlg = releaseSongArray,
UraReleaseSongFlg = uraSongArray,
AryFavoriteSongNoes = userData.FavoriteSongsArray,
AryFavoriteSongNoes = userData.FavoriteSongsArray.ToArray(),
AryRecentSongNoes = recentSongs,
DefaultOptionSetting = defaultOptions,
NotesPosition = userData.NotesPosition,

View File

@ -0,0 +1,12 @@
using Riok.Mapperly.Abstractions;
using TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Mappers;
[Mapper]
public static partial class PlayResultMappers
{
public static partial CommonPlayResultData Map(PlayResultDataRequest request);
public static partial CommonPlayResultData Map(Models.v3209.PlayResultDataRequest request);
}

View File

@ -1,6 +1,4 @@
using GameDatabase.Entities;
namespace TaikoLocalServer.Models.Application;
namespace TaikoLocalServer.Models.Application;
public class CommonBaidResponse
{

View File

@ -0,0 +1,108 @@
// ReSharper disable InconsistentNaming
namespace TaikoLocalServer.Models.Application;
public class CommonPlayResultData
{
public uint Baid { get; set; }
public string ChassisId { get; set; } = string.Empty;
public string ShopId { get; set; } = string.Empty;
public string PlayDatetime { get; set; } = string.Empty;
public bool IsRight { get; set; }
public uint CardType { get; set; }
public bool IsTwoPlayers { get; set; }
public List<StageData> AryStageInfoes { get; set; } = [];
public List<uint> ReleaseSongNoes { get; set; } = [];
public List<uint> UraReleaseSongNoes { get; set; } = [];
public List<uint> GetToneNoes { get; set; } = [];
public List<uint> GetCostumeNo1s { get; set; } = [];
public List<uint> GetCostumeNo2s { get; set; } = [];
public List<uint> GetCostumeNo3s { get; set; } = [];
public List<uint> GetCostumeNo4s { get; set; } = [];
public List<uint> GetCostumeNo5s { get; set; } = [];
public List<uint> GetTitleNoes { get; set; } = [];
public List<uint> GetGenericInfoNoes { get; set; } = [];
public CostumeData AryPlayCostume { get; set; } = new();
public CostumeData AryCurrentCostume { get; set; } = new();
public string Title { get; set; } = string.Empty;
public uint TitleplateId { get; set; }
public uint PlayMode { get; set; }
public uint CollaborationId { get; set; }
public uint DanId { get; set; }
public uint DanResult { get; set; }
public uint SoulGaugeTotal { get; set; }
public uint ComboCntTotal { get; set; }
public bool IsNotRecordedDan { get; set; }
public uint AreaCode { get; set; }
public byte[] Reserved { get; set; } = [];
public uint TournamentMode { get; set; }
public string Accesstoken { get; set; } = string.Empty;
public byte[] ContentInfo { get; set; } = [];
public uint DifficultyPlayedCourse { get; set; }
public uint DifficultyPlayedStar { get; set; }
public uint DifficultyPlayedSort { get; set; }
public uint IsRandomUsePlay { get; set; }
public string InputMedian { get; set; } = string.Empty;
public string InputVariance { get; set; } = string.Empty;
public class StageData
{
public uint SongNo { get; set; }
public uint Level { get; set; }
public uint StageMode { get; set; }
public uint PlayResult { get; set; }
public uint PlayScore { get; set; }
public uint ScoreRate { get; set; }
public uint ScoreRank { get; set; }
public uint GoodCnt { get; set; }
public uint OkCnt { get; set; }
public uint NgCnt { get; set; }
public uint PoundCnt { get; set; }
public uint ComboCnt { get; set; }
public uint HitCnt { get; set; }
public byte[] OptionFlg { get; set; } = [];
public byte[] ToneFlg { get; set; } = [];
public int NotesPosition { get; set; }
public bool IsVoiceOn { get; set; }
public bool IsSkipOn { get; set; }
public bool IsSkipUse { get; set; }
public uint SupportLevel { get; set; }
public List<ResultcompeData> AryChallengeIds { get; set; } = [];
public List<ResultcompeData> AryUserCompeIds { get; set; } = [];
public List<ResultcompeData> AryBngCompeIds { get; set; } = [];
public uint MusicCateg { get; set; }
public bool IsFavorite { get; set; }
public bool IsRecent { get; set; }
public uint SelectedFolderId { get; set; }
public uint? IsRandomUseStage { get; set; }
public bool IsPapamama { get; set; }
public uint StarLevel { get; set; }
public bool IsWin { get; set; }
public List<AiStageSectionData> ArySectionDatas { get; set; } = [];
}
public class ResultcompeData
{
public uint CompeId { get; set; }
public uint TrackNo { get; set; }
}
public class AiStageSectionData
{
public bool IsWin { get; set; }
public uint Crown { get; set; }
public uint Score { get; set; }
public uint GoodCnt { get; set; }
public uint OkCnt { get; set; }
public uint NgCnt { get; set; }
public uint PoundCnt { get; set; }
}
public class CostumeData
{
public uint Costume1 { get; set; }
public uint Costume2 { get; set; }
public uint Costume3 { get; set; }
public uint Costume4 { get; set; }
public uint Costume5 { get; set; }
}
}

View File

@ -1,5 +1,4 @@
using System.Reflection;
using System.Security.Authentication;
using Serilog.Sinks.File.Header;
using TaikoLocalServer.Logging;
using GameDatabase.Context;

View File

@ -1,7 +1,6 @@
using Microsoft.Extensions.Options;
using SharedProject.Models;
using SharedProject.Utils;
using Swan.Mapping;
using System.Collections.Immutable;
using System.IO.Compression;
using System.Security.Cryptography;

View File

@ -1,6 +1,5 @@
using GameDatabase.Context;
using GameDatabase.Entities;
using System.Text.Json;
using Throw;
namespace TaikoLocalServer.Services;
@ -76,39 +75,13 @@ public class UserDatumService : IUserDatumService
var userDatum = await context.UserData.FindAsync(baid);
userDatum.ThrowIfNull($"User with baid: {baid} not found!");
return userDatum.FavoriteSongsArray.ToList();
/*using var stringStream = GZipBytesUtil.GenerateStreamFromString(userDatum.FavoriteSongsArray);
List<uint>? result;
try
{
result = await JsonSerializer.DeserializeAsync<List<uint>>(stringStream);
}
catch (JsonException e)
{
logger.LogError(e, "Parse favorite song array json failed! Is the user initialized correctly?");
result = new List<uint>();
}
result.ThrowIfNull("Song favorite array should never be null!");
return result;*/
}
public async Task UpdateFavoriteSong(uint baid, uint songId, bool isFavorite)
{
var userDatum = await context.UserData.FindAsync(baid);
userDatum.ThrowIfNull($"User with baid: {baid} not found!");
/*using var stringStream = GZipBytesUtil.GenerateStreamFromString(userDatum.FavoriteSongsArray);
List<uint>? favoriteSongIds;
try
{
favoriteSongIds = await JsonSerializer.DeserializeAsync<List<uint>>(stringStream);
}
catch (JsonException e)
{
logger.LogError(e, "Parse favorite song array json failed! Is the user initialized correctly?");
favoriteSongIds = new List<uint>();
}
favoriteSongIds.ThrowIfNull("Song favorite array should never be null!");*/
var favoriteSet = new HashSet<uint>(userDatum.FavoriteSongsArray);
if (isFavorite)
{
@ -119,12 +92,7 @@ public class UserDatumService : IUserDatumService
favoriteSet.Remove(songId);
}
using var newFavoriteSongStream = new MemoryStream();
await JsonSerializer.SerializeAsync(newFavoriteSongStream, favoriteSet);
newFavoriteSongStream.Position = 0;
using var reader = new StreamReader(newFavoriteSongStream);
userDatum.FavoriteSongsArray = favoriteSet.ToArray();//await reader.ReadToEndAsync();
userDatum.FavoriteSongsArray = favoriteSet.ToList();
//logger.LogInformation("Favorite songs are: {Favorite}", userDatum.FavoriteSongsArray);
context.Update(userDatum);
await context.SaveChangesAsync();

View File

@ -4,7 +4,7 @@
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
<Version>1.0.0-beta</Version>
<Version>1.1.0</Version>
<LangVersion>12</LangVersion>
<EnableConfigurationBindingGenerator>false</EnableConfigurationBindingGenerator>
<ApplicationManifest>app.manifest</ApplicationManifest>

View File

@ -1,6 +1,4 @@
@using TaikoWebUI.Shared.Models
@using System.Collections.Immutable
@inject IGameDataService GameDataService
@inject IGameDataService GameDataService
<MudDialog Class="dialog-user-qr-code">
<DialogContent>

View File

@ -1,6 +1,4 @@
using static MudBlazor.Colors;
using System;
using Microsoft.JSInterop;
using Microsoft.JSInterop;
namespace TaikoWebUI.Pages;

View File

@ -1,7 +1,4 @@
using Microsoft.Extensions.Options;
using TaikoWebUI.Pages.Dialogs;
namespace TaikoWebUI.Pages;
namespace TaikoWebUI.Pages;
public partial class Login
{

View File

@ -1,5 +1,4 @@
using Microsoft.Extensions.Options;
namespace TaikoWebUI.Pages;
namespace TaikoWebUI.Pages;
public partial class Users
{

View File

@ -1,6 +1,6 @@
using TaikoWebUI.Localization;
using Microsoft.Extensions.Localization;
using MudBlazor;
namespace TaikoWebUI;