Stage changes
This commit is contained in:
parent
adb101261a
commit
07c92c2eef
@ -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; }
|
||||
|
138
GameDatabase/Migrations/20240316064531_SplitCostumeUnlocks.cs
Normal file
138
GameDatabase/Migrations/20240316064531_SplitCostumeUnlocks.cs
Normal 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");
|
||||
}
|
||||
}
|
||||
}
|
@ -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 =>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using System.Text.Json.Serialization;
|
||||
|
||||
namespace SharedProject.Models;
|
||||
namespace SharedProject.Models;
|
||||
|
||||
public interface IVerupNo
|
||||
{
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Immutable;
|
||||
using System.Collections.Specialized;
|
||||
using System.Runtime.InteropServices;
|
||||
using GameDatabase.Entities;
|
||||
|
@ -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"
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
using SharedProject.Models.Requests;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
namespace TaikoLocalServer.Controllers.Api;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
|
@ -1,6 +1,4 @@
|
||||
using MediatR;
|
||||
|
||||
namespace TaikoLocalServer.Controllers;
|
||||
namespace TaikoLocalServer.Controllers;
|
||||
|
||||
public abstract class BaseController<T> : ControllerBase where T : BaseController<T>
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Models.Application;
|
||||
using AddTokenCountRequestMapper = TaikoLocalServer.Mappers.AddTokenCountRequestMapper;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
@ -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);
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using TaikoLocalServer.Handlers;
|
||||
using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
||||
|
@ -1,6 +1,5 @@
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Mappers;
|
||||
using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
||||
|
@ -1,7 +1,5 @@
|
||||
using GameDatabase.Entities;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Mappers;
|
||||
using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
19
TaikoLocalServer/Controllers/Game/HeadClerk2Controller.cs
Normal file
19
TaikoLocalServer/Controllers/Game/HeadClerk2Controller.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Mappers;
|
||||
using TaikoLocalServer.Settings;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
using GameDatabase.Entities;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Mappers;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -1,7 +1,5 @@
|
||||
using GameDatabase.Entities;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Handlers;
|
||||
using TaikoLocalServer.Mappers;
|
||||
using Throw;
|
||||
|
||||
namespace TaikoLocalServer.Controllers.Game;
|
||||
|
||||
|
@ -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;
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
|
@ -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
|
||||
{
|
||||
|
@ -1,6 +1,5 @@
|
||||
using GameDatabase.Context;
|
||||
using GameDatabase.Entities;
|
||||
using MediatR;
|
||||
using TaikoLocalServer.Models.Application;
|
||||
using Throw;
|
||||
|
||||
|
@ -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>())
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using GameDatabase.Context;
|
||||
using MediatR;
|
||||
using TaikoLocalServer.Models.Application;
|
||||
using Throw;
|
||||
|
||||
|
@ -1,6 +1,4 @@
|
||||
using GameDatabase.Context;
|
||||
using MudBlazor.Extensions;
|
||||
using SharedProject.Models;
|
||||
using TaikoLocalServer.Models.Application;
|
||||
using Throw;
|
||||
|
||||
|
@ -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);
|
||||
|
283
TaikoLocalServer/Handlers/UpdatePlayResultCommand.cs
Normal file
283
TaikoLocalServer/Handlers/UpdatePlayResultCommand.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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,
|
||||
|
12
TaikoLocalServer/Mappers/PlayResultMappers.cs
Normal file
12
TaikoLocalServer/Mappers/PlayResultMappers.cs
Normal 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);
|
||||
}
|
@ -1,6 +1,4 @@
|
||||
using GameDatabase.Entities;
|
||||
|
||||
namespace TaikoLocalServer.Models.Application;
|
||||
namespace TaikoLocalServer.Models.Application;
|
||||
|
||||
public class CommonBaidResponse
|
||||
{
|
||||
|
108
TaikoLocalServer/Models/Application/CommonPlayResultData.cs
Normal file
108
TaikoLocalServer/Models/Application/CommonPlayResultData.cs
Normal 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; }
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
using System.Reflection;
|
||||
using System.Security.Authentication;
|
||||
using Serilog.Sinks.File.Header;
|
||||
using TaikoLocalServer.Logging;
|
||||
using GameDatabase.Context;
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
@ -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>
|
||||
|
@ -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>
|
||||
|
@ -1,6 +1,4 @@
|
||||
using static MudBlazor.Colors;
|
||||
using System;
|
||||
using Microsoft.JSInterop;
|
||||
using Microsoft.JSInterop;
|
||||
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
|
@ -1,7 +1,4 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
using TaikoWebUI.Pages.Dialogs;
|
||||
|
||||
namespace TaikoWebUI.Pages;
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
public partial class Login
|
||||
{
|
||||
|
@ -1,5 +1,4 @@
|
||||
using Microsoft.Extensions.Options;
|
||||
namespace TaikoWebUI.Pages;
|
||||
namespace TaikoWebUI.Pages;
|
||||
|
||||
public partial class Users
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
using TaikoWebUI.Localization;
|
||||
using Microsoft.Extensions.Localization;
|
||||
using MudBlazor;
|
||||
|
||||
namespace TaikoWebUI;
|
||||
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user