1
0
mirror of synced 2025-01-19 00:04:05 +01:00

Merge pull request #30 from asesidaa/LoginRefactor

Login refactor
This commit is contained in:
shibe 2024-06-01 22:50:07 -04:00 committed by GitHub
commit c5e6626422
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
80 changed files with 1180 additions and 1395 deletions

View File

@ -1,491 +0,0 @@
// <auto-generated />
using System;
using GameDatabase.Context;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace GameDatabase.Migrations
{
[DbContext(typeof(TaikoDbContext))]
[Migration("20240203182355_AddUnlockedUraSongIdListToUserDatum")]
partial class AddUnlockedUraSongIdListToUserDatum
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "8.0.0-rc.2.23480.1");
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("AiScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<int>("SectionIndex")
.HasColumnType("INTEGER");
b.Property<int>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<bool>("IsWin")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty", "SectionIndex");
b.ToTable("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
{
b.Property<string>("AccessCode")
.HasColumnType("TEXT");
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.HasKey("AccessCode");
b.HasIndex("Baid");
b.ToTable("Card", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<string>("Password")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("Credential", (string)null);
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("ArrivalSongCount")
.HasColumnType("INTEGER");
b.Property<uint>("ClearState")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(0u);
b.Property<uint>("ComboCountTotal")
.HasColumnType("INTEGER");
b.Property<uint>("SoulGaugeTotal")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType");
b.ToTable("DanScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("DanId")
.HasColumnType("INTEGER");
b.Property<int>("DanType")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER")
.HasDefaultValue(1);
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.Property<uint>("BadCount")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HighScore")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<uint>("PlayScore")
.HasColumnType("INTEGER");
b.Property<uint>("TotalHitCount")
.HasColumnType("INTEGER");
b.HasKey("Baid", "DanId", "DanType", "SongNumber");
b.ToTable("DanStageScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
{
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("BestCrown")
.HasColumnType("INTEGER");
b.Property<uint>("BestRate")
.HasColumnType("INTEGER");
b.Property<uint>("BestScore")
.HasColumnType("INTEGER");
b.Property<uint>("BestScoreRank")
.HasColumnType("INTEGER");
b.HasKey("Baid", "SongId", "Difficulty");
b.ToTable("SongBestData");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
{
b.Property<long>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<ulong>("Baid")
.HasColumnType("INTEGER");
b.Property<uint>("ComboCount")
.HasColumnType("INTEGER");
b.Property<uint>("Crown")
.HasColumnType("INTEGER");
b.Property<uint>("Difficulty")
.HasColumnType("INTEGER");
b.Property<uint>("DrumrollCount")
.HasColumnType("INTEGER");
b.Property<uint>("GoodCount")
.HasColumnType("INTEGER");
b.Property<uint>("HitCount")
.HasColumnType("INTEGER");
b.Property<uint>("MissCount")
.HasColumnType("INTEGER");
b.Property<uint>("OkCount")
.HasColumnType("INTEGER");
b.Property<DateTime>("PlayTime")
.HasColumnType("datetime");
b.Property<uint>("Score")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRank")
.HasColumnType("INTEGER");
b.Property<uint>("ScoreRate")
.HasColumnType("INTEGER");
b.Property<bool>("Skipped")
.HasColumnType("INTEGER");
b.Property<uint>("SongId")
.HasColumnType("INTEGER");
b.Property<uint>("SongNumber")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("Baid");
b.ToTable("SongPlayData");
});
modelBuilder.Entity("GameDatabase.Entities.UserDatum", b =>
{
b.Property<ulong>("Baid")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<uint>("AchievementDisplayDifficulty")
.HasColumnType("INTEGER");
b.Property<int>("AiWinCount")
.HasColumnType("INTEGER");
b.Property<uint>("ColorBody")
.HasColumnType("INTEGER");
b.Property<uint>("ColorFace")
.HasColumnType("INTEGER");
b.Property<uint>("ColorLimb")
.HasColumnType("INTEGER");
b.Property<string>("CostumeData")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("CostumeFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DifficultyPlayedArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("DifficultySettingArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("DisplayAchievement")
.HasColumnType("INTEGER");
b.Property<bool>("DisplayDan")
.HasColumnType("INTEGER");
b.Property<string>("FavoriteSongsArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("GenericInfoFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<bool>("IsAdmin")
.HasColumnType("INTEGER");
b.Property<bool>("IsSkipOn")
.HasColumnType("INTEGER");
b.Property<bool>("IsVoiceOn")
.HasColumnType("INTEGER");
b.Property<DateTime>("LastPlayDatetime")
.HasColumnType("datetime");
b.Property<uint>("LastPlayMode")
.HasColumnType("INTEGER");
b.Property<string>("MyDonName")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("MyDonNameLanguage")
.HasColumnType("INTEGER");
b.Property<int>("NotesPosition")
.HasColumnType("INTEGER");
b.Property<short>("OptionSetting")
.HasColumnType("INTEGER");
b.Property<uint>("SelectedToneId")
.HasColumnType("INTEGER");
b.Property<string>("Title")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("TitleFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<uint>("TitlePlateId")
.HasColumnType("INTEGER");
b.Property<string>("TokenCountDict")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ToneFlgArray")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("UnlockedSongIdList")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("Baid");
b.ToTable("UserData");
});
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.AiSectionScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.AiScoreDatum", "Parent")
.WithMany("AiSectionScoreData")
.HasForeignKey("Baid", "SongId", "Difficulty")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Parent");
});
modelBuilder.Entity("GameDatabase.Entities.Card", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.Credential", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.DanStageScoreDatum", b =>
{
b.HasOne("GameDatabase.Entities.DanScoreDatum", "Parent")
.WithMany("DanStageScoreData")
.HasForeignKey("Baid", "DanId", "DanType")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Parent");
});
modelBuilder.Entity("GameDatabase.Entities.SongBestDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.SongPlayDatum", b =>
{
b.HasOne("GameDatabase.Entities.UserDatum", "Ba")
.WithMany()
.HasForeignKey("Baid")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Ba");
});
modelBuilder.Entity("GameDatabase.Entities.AiScoreDatum", b =>
{
b.Navigation("AiSectionScoreData");
});
modelBuilder.Entity("GameDatabase.Entities.DanScoreDatum", b =>
{
b.Navigation("DanStageScoreData");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -1,22 +0,0 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace GameDatabase.Migrations
{
/// <inheritdoc />
public partial class AddUnlockedUraSongIdListToUserDatum : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
}
}
}

View File

@ -40,7 +40,12 @@ namespace GameDatabase.Migrations
});
foreach (var (baid, tokenCountDict) in tokenJsons)
{
var tokenDict = JsonSerializer.Deserialize<Dictionary<int, int>>(tokenCountDict);
Dictionary<int, int> tokenDict;
if (string.IsNullOrEmpty(tokenCountDict)) {
tokenDict = new();
} else {
tokenDict = JsonSerializer.Deserialize<Dictionary<int, int>>(tokenCountDict);
}
foreach (var (key, value) in tokenDict)
{
migrationBuilder.InsertData(

View File

@ -48,11 +48,11 @@ namespace GameDatabase.Migrations
// 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]')");
SET UnlockedKigurumi = coalesce(json_extract(CostumeFlgArray, '$[0]'), '[]'),
UnlockedHead = coalesce(json_extract(CostumeFlgArray, '$[1]'), '[]'),
UnlockedBody = coalesce(json_extract(CostumeFlgArray, '$[2]'), '[]'),
UnlockedFace = coalesce(json_extract(CostumeFlgArray, '$[3]'), '[]'),
UnlockedPuchi = coalesce(json_extract(CostumeFlgArray, '$[4]'), '[]')");
// Deduplicate values
migrationBuilder.Sql(@"
UPDATE UserData

View File

@ -47,11 +47,12 @@ namespace GameDatabase.Migrations
// Split CostumeData (json array) into the new fields
migrationBuilder.Sql(@"
UPDATE UserData
SET CurrentKigurumi = json_extract(CostumeData, '$[0]'),
CurrentHead = json_extract(CostumeData, '$[1]'),
CurrentBody = json_extract(CostumeData, '$[2]'),
CurrentFace = json_extract(CostumeData, '$[3]'),
CurrentPuchi = json_extract(CostumeData, '$[4]')");
SET CurrentKigurumi = COALESCE(json_extract(CostumeData, '$[0]'), 0),
CurrentHead = COALESCE(json_extract(CostumeData, '$[1]'), 0),
CurrentBody = COALESCE(json_extract(CostumeData, '$[2]'), 0),
CurrentFace = COALESCE(json_extract(CostumeData, '$[3]'), 0),
CurrentPuchi = COALESCE(json_extract(CostumeData, '$[4]'), 0);
");
}
/// <inheritdoc />

View File

@ -52,15 +52,17 @@ namespace GameDatabase.Migrations
nullable: false,
defaultValue: 0u);
// Extract from json arrays
migrationBuilder.Sql(@"
UPDATE UserData
SET DifficultyPlayedCourse = json_extract(DifficultyPlayedArray, '$[0]'),
DifficultyPlayedStar = json_extract(DifficultyPlayedArray, '$[1]'),
DifficultyPlayedSort = json_extract(DifficultyPlayedArray, '$[2]') ,
DifficultySettingCourse = json_extract(DifficultySettingArray, '$[0]'),
DifficultySettingStar = json_extract(DifficultySettingArray, '$[1]'),
DifficultySettingSort = json_extract(DifficultySettingArray, '$[2]');
SET
DifficultyPlayedCourse = COALESCE(json_extract(DifficultyPlayedArray, '$[0]'), 0),
DifficultyPlayedStar = COALESCE(json_extract(DifficultyPlayedArray, '$[1]'), 0),
DifficultyPlayedSort = COALESCE(json_extract(DifficultyPlayedArray, '$[2]'), 0),
DifficultySettingCourse = COALESCE(json_extract(DifficultySettingArray, '$[0]'), 0),
DifficultySettingStar = COALESCE(json_extract(DifficultySettingArray, '$[1]'), 0),
DifficultySettingSort = COALESCE(json_extract(DifficultySettingArray, '$[2]'), 0);
");
}

View File

@ -0,0 +1,25 @@
namespace SharedProject.Models;
public class Costume
{
public uint CostumeId { get; set; }
public string CostumeType { get; init; } = string.Empty;
public string CostumeName { get; init; } = string.Empty;
public override bool Equals(object? obj)
{
if (obj is Costume costume)
{
return costume.CostumeName.Equals(CostumeName) && costume.CostumeType.Equals(CostumeType);
}
return false;
}
public override int GetHashCode()
{
return CostumeName.GetHashCode();
}
}

View File

@ -1,4 +1,6 @@
namespace TaikoWebUI.Shared.Models;
using SharedProject.Enums;
namespace SharedProject.Models;
public class MusicDetail
{

View File

@ -1,4 +1,4 @@
namespace TaikoWebUI.Shared.Models;
namespace SharedProject.Models;
public class Title
{

View File

@ -4,6 +4,8 @@ namespace SharedProject.Models;
public class UserSetting
{
public uint Baid { get; set; }
public uint ToneId { get; set; }
public bool IsDisplayAchievement { get; set; }

View File

@ -2,27 +2,27 @@
public static class Constants
{
public const string DATE_TIME_FORMAT = "yyyyMMddHHmmss";
public const string DateTimeFormat = "yyyyMMddHHmmss";
public const int MUSIC_ID_MAX = 1600;
public const int MusicIdMax = 1600;
public const int MUSIC_ID_MAX_EXPANDED = 9000;
public const int MusicIdMaxExpanded = 9000;
public const string DEFAULT_DB_NAME = "taiko.db3";
public const string DefaultDbName = "taiko.db3";
public const string MUSIC_INFO_BASE_NAME = "musicinfo";
public const string WORDLIST_BASE_NAME = "wordlist";
public const string MUSIC_ORDER_BASE_NAME = "music_order";
public const string DON_COS_REWARD_BASE_NAME = "don_cos_reward";
public const string SHOUGOU_BASE_NAME = "shougou";
public const string NEIRO_BASE_NAME = "neiro";
public const string MusicInfoBaseName = "musicinfo";
public const string WordlistBaseName = "wordlist";
public const string MusicOrderBaseName = "music_order";
public const string DonCosRewardBaseName = "don_cos_reward";
public const string ShougouBaseName = "shougou";
public const string NeiroBaseName = "neiro";
public const uint DAN_VERUP_MASTER_TYPE = 101;
public const uint GAIDEN_VERUP_MASTER_TYPE = 102;
public const uint FOLDER_VERUP_MASTER_TYPE = 103;
public const uint INTRO_VERUP_MASTER_TYPE = 105;
public const uint DanVerupMasterType = 101;
public const uint GaidenVerupMasterType = 102;
public const uint FolderVerupMasterType = 103;
public const uint IntroVerupMasterType = 105;
public const uint FUNCTION_ID_DANI_FOLDER_AVAILABLE = 1;
public const uint FUNCTION_ID_DANI_AVAILABLE = 2;
public const uint FUNCTION_ID_AI_BATTLE_AVAILABLE = 3;
public const uint FunctionIdDaniFolderAvailable = 1;
public const uint FunctionIdDaniAvailable = 2;
public const uint FunctionIdAiBattleAvailable = 3;
}

View File

@ -1,6 +1,4 @@
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Authorization;
using Microsoft.Extensions.Options;
using SharedProject.Models.Requests;
using TaikoLocalServer.Filters;

View File

@ -0,0 +1,93 @@
using Microsoft.Extensions.Options;
using TaikoLocalServer.Filters;
using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("api/[controller]")]
public class GameDataController(IGameDataService gameDataService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<UsersController>
{
private readonly AuthSettings authSettings = settings.Value;
[HttpGet("MusicDetails")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GetMusicDetails()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
}
return Ok(gameDataService.GetMusicDetailDictionary());
}
[HttpGet("Costumes")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GetCostumes()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
}
return Ok(gameDataService.GetCostumeList());
}
[HttpGet("Titles")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GetTitles()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
}
return Ok(gameDataService.GetTitleDictionary());
}
[HttpGet("LockedCostumes")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GetLockedCostumes()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
}
return Ok(gameDataService.GetLockedCostumeDataDictionary());
}
[HttpGet("LockedTitles")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public IActionResult GetLockedTitles()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
}
return Ok(gameDataService.GetLockedTitleDataDictionary());
}
}

View File

@ -7,14 +7,93 @@ using TaikoLocalServer.Settings;
namespace TaikoLocalServer.Controllers.Api;
[ApiController]
[Route("/api/[controller]/{baid}")]
[Route("/api/[controller]")]
public class UserSettingsController(IUserDatumService userDatumService, IAuthService authService,
IOptions<AuthSettings> settings) : BaseController<UserSettingsController>
{
private readonly AuthSettings authSettings = settings.Value;
[HttpGet]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<List<UserSetting>>> GetAllUserSetting()
{
if (authSettings.LoginRequired)
{
var tokenInfo = authService.ExtractTokenInfo(HttpContext);
if (tokenInfo is null)
{
return Unauthorized();
}
if (!tokenInfo.Value.isAdmin)
{
return Forbid();
}
}
var users = await userDatumService.GetAllUserDatum();
var response = new List<UserSetting>();
foreach (var user in users)
{
List<List<uint>> costumeUnlockData =
[user.UnlockedKigurumi, user.UnlockedHead, user.UnlockedBody, user.UnlockedFace, user.UnlockedPuchi];
var unlockedTitle = user.TitleFlgArray
.ToList();
for (var i = 0; i < 5; i++)
{
if (!costumeUnlockData[i].Contains(0))
{
costumeUnlockData[i].Add(0);
}
}
var userSetting = new UserSetting
{
Baid = user.Baid,
AchievementDisplayDifficulty = user.AchievementDisplayDifficulty,
IsDisplayAchievement = user.DisplayAchievement,
IsDisplayDanOnNamePlate = user.DisplayDan,
DifficultySettingCourse = user.DifficultySettingCourse,
DifficultySettingStar = user.DifficultySettingStar,
DifficultySettingSort = user.DifficultySettingSort,
IsVoiceOn = user.IsVoiceOn,
IsSkipOn = user.IsSkipOn,
NotesPosition = user.NotesPosition,
PlaySetting = PlaySettingConverter.ShortToPlaySetting(user.OptionSetting),
ToneId = user.SelectedToneId,
MyDonName = user.MyDonName,
MyDonNameLanguage = user.MyDonNameLanguage,
Title = user.Title,
TitlePlateId = user.TitlePlateId,
Kigurumi = user.CurrentKigurumi,
Head = user.CurrentHead,
Body = user.CurrentBody,
Face = user.CurrentFace,
Puchi = user.CurrentPuchi,
UnlockedKigurumi = costumeUnlockData[0],
UnlockedHead = costumeUnlockData[1],
UnlockedBody = costumeUnlockData[2],
UnlockedFace = costumeUnlockData[3],
UnlockedPuchi = costumeUnlockData[4],
UnlockedTitle = unlockedTitle,
BodyColor = user.ColorBody,
FaceColor = user.ColorFace,
LimbColor = user.ColorLimb,
LastPlayDateTime = user.LastPlayDatetime
};
response.Add(userSetting);
}
return Ok(response);
}
[HttpGet("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<ActionResult<UserSetting>> GetUserSetting(uint baid)
{
if (authSettings.LoginRequired)
@ -54,6 +133,7 @@ public class UserSettingsController(IUserDatumService userDatumService, IAuthSer
var response = new UserSetting
{
Baid = user.Baid,
AchievementDisplayDifficulty = user.AchievementDisplayDifficulty,
IsDisplayAchievement = user.DisplayAchievement,
IsDisplayDanOnNamePlate = user.DisplayDan,
@ -88,7 +168,7 @@ public class UserSettingsController(IUserDatumService userDatumService, IAuthSer
return Ok(response);
}
[HttpPost]
[HttpPost("{baid}")]
[ServiceFilter(typeof(AuthorizeIfRequiredAttribute))]
public async Task<IActionResult> SaveUserSetting(uint baid, UserSetting userSetting)
{

View File

@ -58,7 +58,7 @@ public class CrownsDataController : BaseController<CrownsDataController>
{
var songBestData = await songBestDatumService.GetAllSongBestData(baid);
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var songIdMax = settings.EnableMoreSongs ? Constants.MusicIdMaxExpanded : Constants.MusicIdMax;
var crown = new ushort[songIdMax + 1];
var dondafulCrown = new byte[songIdMax + 1];

View File

@ -49,7 +49,7 @@ public class GetScoreRankController(ISongBestDatumService songBestDatumService,
private async Task<ScoreRankData> Handle(uint baid)
{
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var songIdMax = settings.EnableMoreSongs ? Constants.MusicIdMaxExpanded : Constants.MusicIdMax;
var kiwamiScores = new byte[songIdMax + 1];
var miyabiScores = new ushort[songIdMax + 1];
var ikiScores = new ushort[songIdMax + 1];

View File

@ -15,8 +15,8 @@ public class GetTelopController : BaseController<GetTelopController>
var response = new GettelopResponse
{
Result = 1,
StartDatetime = startDateTime.ToString(Constants.DATE_TIME_FORMAT),
EndDatetime = endDateTime.ToString(Constants.DATE_TIME_FORMAT),
StartDatetime = startDateTime.ToString(Constants.DateTimeFormat),
EndDatetime = endDateTime.ToString(Constants.DateTimeFormat),
Telop = "Hello 3906",
VerupNo = 1
};
@ -36,8 +36,8 @@ public class GetTelopController : BaseController<GetTelopController>
var response = new Models.v3209.GettelopResponse
{
Result = 1,
StartDatetime = startDateTime.ToString(Constants.DATE_TIME_FORMAT),
EndDatetime = endDateTime.ToString(Constants.DATE_TIME_FORMAT),
StartDatetime = startDateTime.ToString(Constants.DateTimeFormat),
EndDatetime = endDateTime.ToString(Constants.DateTimeFormat),
Telop = "Hello 3209",
VerupNo = 1
};

View File

@ -127,7 +127,7 @@ public class BaidQueryHandler(
GotDanMax = maxDan,
GotGaidenFlg = gotGaidenFlagArray,
IsDispAchievementOn = userData.DisplayAchievement,
LastPlayDatetime = userData.LastPlayDatetime.ToString(Constants.DATE_TIME_FORMAT),
LastPlayDatetime = userData.LastPlayDatetime.ToString(Constants.DateTimeFormat),
LastPlayMode = userData.LastPlayMode,
SelectedToneId = userData.SelectedToneId,
Title = userData.Title,

View File

@ -17,7 +17,7 @@ public class GetInitialDataQueryHandler(IGameDataService gameDataService,
public Task<CommonInitialDataCheckResponse> Handle(GetInitialDataQuery request, CancellationToken cancellationToken)
{
var songIdMax = settings.EnableMoreSongs ? Constants.MUSIC_ID_MAX_EXPANDED : Constants.MUSIC_ID_MAX;
var songIdMax = settings.EnableMoreSongs ? Constants.MusicIdMaxExpanded : Constants.MusicIdMax;
var musicList = gameDataService.GetMusicList();
var lockedSongsList = gameDataService.GetLockedSongsList();
@ -39,7 +39,7 @@ public class GetInitialDataQueryHandler(IGameDataService gameDataService,
DefaultSongFlg = defaultSongFlg,
AchievementSongBit = enabledArray,
UraReleaseBit = uraReleaseBit,
SongIntroductionEndDatetime = DateTime.Now.AddYears(10).ToString(Constants.DATE_TIME_FORMAT),
SongIntroductionEndDatetime = DateTime.Now.AddYears(10).ToString(Constants.DateTimeFormat),
ServerCurrentDatetime = (ulong)DateTimeOffset.Now.ToUnixTimeSeconds()
};
@ -65,18 +65,18 @@ public class GetInitialDataQueryHandler(IGameDataService gameDataService,
CommonInitialDataCheckResponse.VerupNoData2[] verupNo2List =
[
GetVerupNoData2(Constants.DAN_VERUP_MASTER_TYPE, commonDanDataDictionary),
GetVerupNoData2(Constants.GAIDEN_VERUP_MASTER_TYPE, commonGaidenDataDictionary),
GetVerupNoData2(Constants.FOLDER_VERUP_MASTER_TYPE, eventFolderDictionary),
GetVerupNoData2(Constants.INTRO_VERUP_MASTER_TYPE, songIntroDictionary)
GetVerupNoData2(Constants.DanVerupMasterType, commonDanDataDictionary),
GetVerupNoData2(Constants.GaidenVerupMasterType, commonGaidenDataDictionary),
GetVerupNoData2(Constants.FolderVerupMasterType, eventFolderDictionary),
GetVerupNoData2(Constants.IntroVerupMasterType, songIntroDictionary)
];
response.AryVerupNoData2s.AddRange(verupNo2List);
response.AryChassisFunctionIds =
[
Constants.FUNCTION_ID_DANI_AVAILABLE,
Constants.FUNCTION_ID_DANI_FOLDER_AVAILABLE,
Constants.FUNCTION_ID_AI_BATTLE_AVAILABLE
Constants.FunctionIdDaniAvailable,
Constants.FunctionIdDaniFolderAvailable,
Constants.FunctionIdAiBattleAvailable
];
return Task.FromResult(response);

View File

@ -5,7 +5,7 @@ namespace TaikoLocalServer.Handlers;
public record GetSelfBestQuery(uint Baid, uint Difficulty, uint[] SongIdList) : IRequest<CommonSelfBestResponse>;
public class GetSelfBestQueryHandler(IGameDataService gameDataService, TaikoDbContext context, ILogger<GetSelfBestQueryHandler> logger)
public class GetSelfBestQueryHandler(IGameDataService gameDataService, TaikoDbContext context, ILogger<GetSelfBestQueryHandler> logger)
: IRequestHandler<GetSelfBestQuery, CommonSelfBestResponse>
{
public async Task<CommonSelfBestResponse> Handle(GetSelfBestQuery request, CancellationToken cancellationToken)
@ -21,28 +21,38 @@ public class GetSelfBestQueryHandler(IGameDataService gameDataService, TaikoDbCo
logger.LogWarning("Invalid song IDs: {InvalidSongIds}", invalidSongIds.Stringify());
requestSet.ExceptWith(invalidSongIds);
}
var selfbestScores = await context.SongBestData
.Where(datum => datum.Baid == request.Baid &&
var selfBestScores = await context.SongBestData
.Where(datum => datum.Baid == request.Baid &&
requestSet.Contains(datum.SongId) &&
(datum.Difficulty == requestDifficulty ||
(datum.Difficulty == requestDifficulty ||
(datum.Difficulty == Difficulty.UraOni && requestDifficulty == Difficulty.Oni)))
.OrderBy(datum => datum.SongId)
.ToListAsync(cancellationToken);
var selfBestList = selfbestScores.ConvertAll(datum => new CommonSelfBestResponse.SelfBestData
var selfBestList = new List<CommonSelfBestResponse.SelfBestData>();
foreach (var songId in request.SongIdList)
{
SongNo = datum.SongId,
SelfBestScore = datum.BestScore,
UraBestScore = datum.Difficulty == Difficulty.UraOni ? datum.BestScore : 0,
SelfBestScoreRate = datum.BestRate,
UraBestScoreRate = datum.Difficulty == Difficulty.UraOni ? datum.BestRate : 0
});
// For songs that don't have a score, add them to the response with 0s
var missingSongs = requestSet.Except(selfBestList.Select(datum => datum.SongNo));
selfBestList.AddRange(missingSongs.Select(songNo => new CommonSelfBestResponse.SelfBestData
{
SongNo = songNo
}));
var selfBest = new CommonSelfBestResponse.SelfBestData();
var selfBestScore = selfBestScores
.FirstOrDefault(datum => datum.SongId == songId &&
datum.Difficulty == requestDifficulty);
var uraSelfBestScore = selfBestScores
.FirstOrDefault(datum => datum.SongId == songId &&
datum.Difficulty == Difficulty.UraOni && requestDifficulty == Difficulty.Oni);
selfBest.SongNo = songId;
if (selfBestScore is not null)
{
selfBest.SelfBestScore = selfBestScore.BestScore;
selfBest.SelfBestScoreRate = selfBestScore.BestRate;
}
if (uraSelfBestScore is not null)
{
selfBest.UraBestScore = uraSelfBestScore.BestScore;
selfBest.UraBestScoreRate = uraSelfBestScore.BestRate;
}
selfBestList.Add(selfBest);
}
var response = new CommonSelfBestResponse
{

View File

@ -18,7 +18,7 @@ public class UpdatePlayResultCommandHandler(TaikoDbContext context, ILogger<Upda
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);
logger.LogWarning("Game uploading a non existing user with baid {Baid}", request.Baid);
return 1;
}
@ -29,6 +29,11 @@ public class UpdatePlayResultCommandHandler(TaikoDbContext context, ILogger<Upda
var playMode = (PlayMode)playResultData.PlayMode;
if (playMode is PlayMode.DanMode or PlayMode.GaidenMode)
{
if (playResultData.IsNotRecordedDan)
{
return 1;
}
var danType = playMode == PlayMode.DanMode ? DanType.Normal : DanType.Gaiden;
var danPlayData = await context.DanScoreData
.Include(datum => datum.DanStageScoreData)

View File

@ -22,12 +22,12 @@ public class UserDataQueryHandler(TaikoDbContext context, IGameDataService gameD
lockedSongsList = lockedSongsList.Except(unlockedSongIdList).ToList();
var enabledMusicList = musicList.Except(lockedSongsList);
var releaseSongArray =
FlagCalculator.GetBitArrayFromIds(enabledMusicList, Constants.MUSIC_ID_MAX, logger);
FlagCalculator.GetBitArrayFromIds(enabledMusicList, Constants.MusicIdMax, logger);
var defaultSongWithUraList = gameDataService.GetMusicWithUraList();
var enabledUraMusicList = defaultSongWithUraList.Except(lockedSongsList);
var uraSongArray =
FlagCalculator.GetBitArrayFromIds(enabledUraMusicList, Constants.MUSIC_ID_MAX, logger);
FlagCalculator.GetBitArrayFromIds(enabledUraMusicList, Constants.MusicIdMax, logger);
if (userData.ToneFlgArray.Count == 0)
{

View File

@ -16,7 +16,7 @@ public class CommonBaidResponse
public List<byte[]> CostumeFlagArrays { get; set; }
= new() { Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>(), Array.Empty<byte>() };
public string LastPlayDatetime { get; set; } = DateTime.Now.ToString(Constants.DATE_TIME_FORMAT);
public string LastPlayDatetime { get; set; } = DateTime.Now.ToString(Constants.DateTimeFormat);
public bool DisplayDan { get; set; }
public uint GotDanMax { get; set; }
public byte[] GotDanFlg { get; set; } = Array.Empty<byte>();

View File

@ -10,7 +10,7 @@ public class CommonInitialDataCheckResponse
public byte[] UraReleaseBit { get; set; } = [];
public string SongIntroductionEndDatetime { get; set; } =
DateTime.Now.AddYears(10).ToString(Constants.DATE_TIME_FORMAT);
DateTime.Now.AddYears(10).ToString(Constants.DateTimeFormat);
public List<MovieData> AryMovieInfoes { get; set; } = [];
public List<AiEventData> AryAiEventDatas { get; set; } = [];

View File

@ -4,9 +4,27 @@ namespace TaikoLocalServer.Models;
public class MusicInfoEntry
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("uniqueId")]
public uint MusicId { get; set; }
[JsonPropertyName("genreNo")]
public SongGenre Genre { get; set; }
[JsonPropertyName("starEasy")]
public int StarEasy { get; set; }
[JsonPropertyName("starNormal")]
public int StarNormal { get; set; }
[JsonPropertyName("starHard")]
public int StarHard { get; set; }
[JsonPropertyName("starMania")]
public int StarOni { get; set; }
[JsonPropertyName("starUra")]
public uint StarUra { get; set; }
public int StarUra { get; set; }
}

View File

@ -15,6 +15,8 @@ using Serilog;
using SharedProject.Utils;
using TaikoLocalServer.Controllers.Api;
using TaikoLocalServer.Filters;
using Microsoft.AspNetCore.ResponseCompression;
using System.IO.Compression;
Log.Logger = new LoggerConfiguration()
.WriteTo.Console()
@ -29,14 +31,14 @@ Log.Information("Server starting up...");
try
{
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpLogging(options =>
{
options.LoggingFields = HttpLoggingFields.All;
options.RequestBodyLogLimit = 32768;
options.ResponseBodyLogLimit = 32768;
});
const string configurationsDirectory = "Configurations";
builder.Configuration.AddJsonFile($"{configurationsDirectory}/Kestrel.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile($"{configurationsDirectory}/Logging.json", optional: false, reloadOnChange: false);
@ -44,8 +46,8 @@ try
builder.Configuration.AddJsonFile($"{configurationsDirectory}/ServerSettings.json", optional: false, reloadOnChange: false);
builder.Configuration.AddJsonFile($"{configurationsDirectory}/DataSettings.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile($"{configurationsDirectory}/AuthSettings.json", optional: true, reloadOnChange: false);
builder.Configuration.AddJsonFile("wwwroot/appsettings.json", optional: true, reloadOnChange: true); // Add appsettings.json
builder.Configuration.AddJsonFile("wwwroot/appsettings.json", optional: false, reloadOnChange: false);
builder.Host.UseSerilog((context, configuration) =>
{
configuration
@ -65,6 +67,19 @@ try
Log.Warning("Song limit expanded! Use at your own risk!");
}
// Add response compression services
builder.Services.AddResponseCompression(options =>
{
options.EnableForHttps = true;
options.Providers.Add<GzipCompressionProvider>();
options.Providers.Add<BrotliCompressionProvider>();
});
builder.Services.Configure<GzipCompressionProviderOptions>(options =>
{
options.Level = CompressionLevel.Fastest;
});
// Add services to the container.
builder.Services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(Program).Assembly));
builder.Services.AddOptions();
@ -73,39 +88,38 @@ try
builder.Services.Configure<DataSettings>(builder.Configuration.GetSection(nameof(DataSettings)));
builder.Services.Configure<AuthSettings>(builder.Configuration.GetSection(nameof(AuthSettings)));
// Read LoginRequired setting from appsettings.json
var loginRequired = builder.Configuration.GetValue<bool>("LoginRequired");
var loginRequired = builder.Configuration.GetSection("WebUiSettings").GetValue<bool>("LoginRequired");
builder.Services.Configure<AuthSettings>(options => { options.LoginRequired = loginRequired; });
// Add Authentication with JWT
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration.GetSection(nameof(AuthSettings))["JwtIssuer"],
ValidAudience = builder.Configuration.GetSection(nameof(AuthSettings))["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection(nameof(AuthSettings))["JwtKey"] ?? throw new InvalidOperationException()))
};
});
ValidateIssuer = true,
ValidateAudience = true,
ValidateLifetime = true,
ValidateIssuerSigningKey = true,
ValidIssuer = builder.Configuration.GetSection(nameof(AuthSettings))["JwtIssuer"],
ValidAudience = builder.Configuration.GetSection(nameof(AuthSettings))["JwtAudience"],
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration.GetSection(nameof(AuthSettings))["JwtKey"] ?? throw new InvalidOperationException()))
};
});
builder.Services.AddScoped<AuthorizeIfRequiredAttribute>(); // Register the custom attribute
builder.Services.AddControllers().AddProtoBufNet();
builder.Services.AddDbContext<TaikoDbContext>(option =>
{
var dbName = builder.Configuration["DbFileName"];
if (string.IsNullOrEmpty(dbName))
{
dbName = Constants.DEFAULT_DB_NAME;
dbName = Constants.DefaultDbName;
}
var path = Path.Combine(PathHelper.GetRootPath(), dbName);
@ -148,6 +162,9 @@ try
gameDataService.ThrowIfNull();
await gameDataService.InitializeAsync();
// Use response compression
app.UseResponseCompression();
// For reverse proxy
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
@ -159,22 +176,28 @@ try
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
// Enable Authentication and Authorization middleware
app.UseAuthentication();
app.UseAuthorization();
app.UseHttpLogging();
app.Use(async (context, next) =>
{
await next();
if (context.Response.StatusCode >= 400)
if (context.Response.StatusCode == StatusCodes.Status404NotFound)
{
Log.Error("Unknown request from: {RemoteIpAddress} {Method} {Path} {StatusCode}",
context.Connection.RemoteIpAddress, context.Request.Method, context.Request.Path, context.Response.StatusCode);
Log.Error("Request headers: {Headers}", context.Request.Headers);
}
else if (context.Response.StatusCode != StatusCodes.Status200OK)
{
Log.Warning("Unsuccessful request from: {RemoteIpAddress} {Method} {Path} {StatusCode}",
context.Connection.RemoteIpAddress, context.Request.Method, context.Request.Path, context.Response.StatusCode);
Log.Warning("Request headers: {Headers}", context.Request.Headers);
}
});
app.MapControllers();
app.MapFallbackToFile("index.html");

View File

@ -6,11 +6,12 @@ using System.IO.Compression;
using System.Security.Cryptography;
using System.Text.Json;
using TaikoLocalServer.Settings;
using TaikoWebUI.Shared.Models;
using Throw;
namespace TaikoLocalServer.Services;
public class GameDataService : IGameDataService
public class GameDataService(IOptions<DataSettings> dataSettings) : IGameDataService
{
private ImmutableDictionary<uint, DanData> commonDanDataDictionary =
ImmutableDictionary<uint, DanData>.Empty;
@ -18,7 +19,7 @@ public class GameDataService : IGameDataService
private ImmutableDictionary<uint, DanData> commonGaidenDataDictionary =
ImmutableDictionary<uint, DanData>.Empty;
private ImmutableDictionary<uint, MusicInfoEntry> musicInfoes =
private ImmutableDictionary<uint, MusicInfoEntry> musicInfos =
ImmutableDictionary<uint, MusicInfoEntry>.Empty;
private ImmutableDictionary<uint, MovieData> movieDataDictionary =
@ -32,37 +33,42 @@ public class GameDataService : IGameDataService
private ImmutableDictionary<uint, EventFolderData> eventFolderDictionary =
ImmutableDictionary<uint, EventFolderData>.Empty;
private List<ShopFolderData> shopFolderList = new();
private List<ShopFolderData> shopFolderList = [];
private List<uint> musics = new();
private List<uint> musicUniqueIdList = [];
private List<uint> musicsWithUra = new();
private List<uint> musicWithUraUniqueIdList = [];
private List<uint> lockedSongsList = new();
private List<uint> lockedSongsList = [];
private readonly Dictionary<uint, MusicDetail> musicDetailDictionary = new();
private List<int> costumeFlagArraySizes = new();
private readonly List<Costume> costumeList = [];
private readonly Dictionary<uint, Title> titleDictionary = new();
private Dictionary<string, List<uint>> lockedCostumeDataDictionary = new();
private Dictionary<string, List<uint>> lockedTitleDataDictionary = new();
private List<int> costumeFlagArraySize = [];
private int titleFlagArraySize;
private int toneFlagArraySize;
private Dictionary<string, int> tokenDataDictionary = new();
private readonly DataSettings settings;
public GameDataService(IOptions<DataSettings> settings)
{
this.settings = settings.Value;
}
private readonly DataSettings settings = dataSettings.Value;
public List<uint> GetMusicList()
{
return musics;
return musicUniqueIdList;
}
public List<uint> GetMusicWithUraList()
{
return musicsWithUra;
return musicWithUraUniqueIdList;
}
public ImmutableDictionary<uint, MovieData> GetMovieDataDictionary()
@ -104,10 +110,35 @@ public class GameDataService : IGameDataService
{
return lockedSongsList;
}
public Dictionary<uint, MusicDetail> GetMusicDetailDictionary()
{
return musicDetailDictionary;
}
public List<Costume> GetCostumeList()
{
return costumeList;
}
public Dictionary<uint, Title> GetTitleDictionary()
{
return titleDictionary;
}
public Dictionary<string, List<uint>> GetLockedCostumeDataDictionary()
{
return lockedCostumeDataDictionary;
}
public Dictionary<string, List<uint>> GetLockedTitleDataDictionary()
{
return lockedTitleDataDictionary;
}
public List<int> GetCostumeFlagArraySizes()
{
return costumeFlagArraySizes;
return costumeFlagArraySize;
}
public int GetTitleFlagArraySize()
@ -130,23 +161,23 @@ public class GameDataService : IGameDataService
var dataPath = PathHelper.GetDataPath();
var datatablePath = PathHelper.GetDatatablePath();
var musicInfoPath = Path.Combine(datatablePath, $"{Constants.MUSIC_INFO_BASE_NAME}.json");
var encryptedInfo = Path.Combine(datatablePath, $"{Constants.MUSIC_INFO_BASE_NAME}.bin");
var musicInfoPath = Path.Combine(datatablePath, $"{Constants.MusicInfoBaseName}.json");
var encryptedInfo = Path.Combine(datatablePath, $"{Constants.MusicInfoBaseName}.bin");
var wordlistPath = Path.Combine(datatablePath, $"{Constants.WORDLIST_BASE_NAME}.json");
var encryptedWordlist = Path.Combine(datatablePath, $"{Constants.WORDLIST_BASE_NAME}.bin");
var wordlistPath = Path.Combine(datatablePath, $"{Constants.WordlistBaseName}.json");
var encryptedWordlist = Path.Combine(datatablePath, $"{Constants.WordlistBaseName}.bin");
var musicOrderPath = Path.Combine(datatablePath, $"{Constants.MUSIC_ORDER_BASE_NAME}.json");
var encryptedMusicOrder = Path.Combine(datatablePath, $"{Constants.MUSIC_ORDER_BASE_NAME}.bin");
var musicOrderPath = Path.Combine(datatablePath, $"{Constants.MusicOrderBaseName}.json");
var encryptedMusicOrder = Path.Combine(datatablePath, $"{Constants.MusicOrderBaseName}.bin");
var donCosRewardPath = Path.Combine(datatablePath, $"{Constants.DON_COS_REWARD_BASE_NAME}.json");
var encryptedDonCosReward = Path.Combine(datatablePath, $"{Constants.DON_COS_REWARD_BASE_NAME}.bin");
var donCosRewardPath = Path.Combine(datatablePath, $"{Constants.DonCosRewardBaseName}.json");
var encryptedDonCosReward = Path.Combine(datatablePath, $"{Constants.DonCosRewardBaseName}.bin");
var shougouPath = Path.Combine(datatablePath, $"{Constants.SHOUGOU_BASE_NAME}.json");
var encryptedShougou = Path.Combine(datatablePath, $"{Constants.SHOUGOU_BASE_NAME}.bin");
var shougouPath = Path.Combine(datatablePath, $"{Constants.ShougouBaseName}.json");
var encryptedShougou = Path.Combine(datatablePath, $"{Constants.ShougouBaseName}.bin");
var neiroPath = Path.Combine(datatablePath, $"{Constants.NEIRO_BASE_NAME}.json");
var encryptedNeiro = Path.Combine(datatablePath, $"{Constants.NEIRO_BASE_NAME}.bin");
var neiroPath = Path.Combine(datatablePath, $"{Constants.NeiroBaseName}.json");
var encryptedNeiro = Path.Combine(datatablePath, $"{Constants.NeiroBaseName}.bin");
var danDataPath = Path.Combine(dataPath, settings.DanDataFileName);
var gaidenDataPath = Path.Combine(dataPath, settings.GaidenDataFileName);
@ -157,6 +188,8 @@ public class GameDataService : IGameDataService
var tokenDataPath = Path.Combine(dataPath, settings.TokenDataFileName);
var lockedSongsDataPath = Path.Combine(dataPath, settings.LockedSongsDataFileName);
var qrCodeDataPath = Path.Combine(dataPath, settings.QrCodeDataFileName);
var lockedCostumeDataPath = Path.Combine(dataPath, settings.LockedCostumeDataFileName);
var lockedTitleDataPath = Path.Combine(dataPath, settings.LockedTitleDataFileName);
var encryptedFiles = new List<string>
{
@ -186,12 +219,9 @@ public class GameDataService : IGameDataService
}
}
foreach (var filePath in outputPaths)
foreach (var filePath in outputPaths.Where(filePath => !File.Exists(filePath)))
{
if (!File.Exists(filePath))
{
throw new FileNotFoundException($"{Path.GetFileName(filePath)} file not found!");
}
throw new FileNotFoundException($"{Path.GetFileName(filePath)} file not found!");
}
await using var musicInfoFile = File.OpenRead(musicInfoPath);
@ -207,8 +237,12 @@ public class GameDataService : IGameDataService
await using var shougouFile = File.OpenRead(shougouPath);
await using var neiroFile = File.OpenRead(neiroPath);
await using var qrCodeDataFile = File.OpenRead(qrCodeDataPath);
await using var wordlistFile = File.OpenRead(wordlistPath);
await using var musicOrderFile = File.OpenRead(musicOrderPath);
await using var lockedCostumeDataFile = File.OpenRead(lockedCostumeDataPath);
await using var lockedTitleDataFile = File.OpenRead(lockedTitleDataPath);
var infosData = await JsonSerializer.DeserializeAsync<MusicInfos>(musicInfoFile);
var musicInfoData = await JsonSerializer.DeserializeAsync<MusicInfos>(musicInfoFile);
var danData = await JsonSerializer.DeserializeAsync<List<DanData>>(danDataFile);
var gaidenData = await JsonSerializer.DeserializeAsync<List<DanData>>(gaidenDataFile);
var introData = await JsonSerializer.DeserializeAsync<List<SongIntroductionData>>(songIntroDataFile);
@ -221,8 +255,12 @@ public class GameDataService : IGameDataService
var shougouData = await JsonSerializer.DeserializeAsync<Shougous>(shougouFile);
var neiroData = await JsonSerializer.DeserializeAsync<Neiros>(neiroFile);
var qrCodeData = await JsonSerializer.DeserializeAsync<List<QRCodeData>>(qrCodeDataFile);
var wordlistData = await JsonSerializer.DeserializeAsync<WordList>(wordlistFile);
var musicOrderData = await JsonSerializer.DeserializeAsync<MusicOrder>(musicOrderFile);
var lockedCostumeData = await JsonSerializer.DeserializeAsync<Dictionary<string, uint[]>>(lockedCostumeDataFile);
var lockedTitleData = await JsonSerializer.DeserializeAsync<Dictionary<string, uint[]>>(lockedTitleDataFile);
InitializeMusicInfos(infosData);
InitializeMusicInfos(musicInfoData);
InitializeDanData(danData);
@ -239,10 +277,16 @@ public class GameDataService : IGameDataService
InitializeTokenData(tokenData);
InitializeLockedSongsData(lockedSongsData);
InitializeMusicDetails(musicInfoData, musicOrderData, wordlistData);
InitializeCostumeFlagArraySizes(donCosRewardData);
InitializeCostumes(donCosRewardData, wordlistData);
InitializeTitleFlagArraySize(shougouData);
InitializeTitles(shougouData, wordlistData);
InitializeLockedCostumeData(lockedCostumeData);
InitializeLockedTitleData(lockedTitleData);
InitializeToneFlagArraySize(neiroData);
@ -304,16 +348,16 @@ public class GameDataService : IGameDataService
{
infosData.ThrowIfNull("Shouldn't happen!");
musicInfoes = infosData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
musicInfos = infosData.MusicInfoEntries.ToImmutableDictionary(info => info.MusicId);
musics = musicInfoes.Select(pair => pair.Key)
musicUniqueIdList = musicInfos.Select(pair => pair.Key)
.ToList();
musics.Sort();
musicUniqueIdList.Sort();
musicsWithUra = musicInfoes.Where(info => info.Value.StarUra > 0)
musicWithUraUniqueIdList = musicInfos.Where(info => info.Value.StarUra > 0)
.Select(pair => pair.Key)
.ToList();
musicsWithUra.Sort();
musicWithUraUniqueIdList.Sort();
}
private void InitializeShopFolderData(List<ShopFolderData>? shopFolderData)
@ -333,40 +377,132 @@ public class GameDataService : IGameDataService
lockedSongsData.ThrowIfNull("Shouldn't happen!");
lockedSongsList = lockedSongsData["songNo"].ToList();
}
private void InitializeCostumeFlagArraySizes(DonCosRewards? donCosRewardData)
private void InitializeMusicDetails(MusicInfos? musicInfoData, MusicOrder? musicOrderData, WordList? wordlistData)
{
donCosRewardData.ThrowIfNull("Shouldn't happen!");
var kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "kigurumi")
.Select(entry => entry.UniqueId);
var headUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "head")
.Select(entry => entry.UniqueId);
var bodyUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "body")
.Select(entry => entry.UniqueId);
var faceUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "face")
.Select(entry => entry.UniqueId);
var puchiUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "puchi")
.Select(entry => entry.UniqueId);
costumeFlagArraySizes = new List<int>
musicInfoData.ThrowIfNull("Shouldn't happen!");
musicOrderData.ThrowIfNull("Shouldn't happen!");
wordlistData.ThrowIfNull("Shouldn't happen!");
foreach (var musicInfo in musicInfoData.MusicInfoEntries)
{
(int)kigurumiUniqueIdList.Max() + 1,
(int)headUniqueIdList.Max() + 1,
(int)bodyUniqueIdList.Max() + 1,
(int)faceUniqueIdList.Max() + 1,
(int)puchiUniqueIdList.Max() + 1
};
var musicUniqueId = musicInfo.MusicId;
if (musicUniqueId == 0)
{
continue;
}
var musicId = musicInfo.Id;
var musicNameKey = $"song_{musicId}";
var musicArtistKey = $"song_sub_{musicId}";
var musicName = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).JapaneseText;
var musicArtist = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).JapaneseText;
var musicNameEn = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).EnglishUsText;
var musicArtistEn = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).EnglishUsText;
var musicNameCn = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).ChineseTText;
var musicArtistCn = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).ChineseTText;
var musicNameKo = wordlistData.WordListEntries.First(entry => entry.Key == musicNameKey).KoreanText;
var musicArtistKo = wordlistData.WordListEntries.First(entry => entry.Key == musicArtistKey).KoreanText;
var musicGenre = musicInfo.Genre;
var musicStarEasy = musicInfo.StarEasy;
var musicStarNormal = musicInfo.StarNormal;
var musicStarHard = musicInfo.StarHard;
var musicStarOni = musicInfo.StarOni;
var musicStarUra = musicInfo.StarUra;
var musicDetail = new MusicDetail
{
SongId = musicUniqueId,
SongName = musicName,
SongNameEN = musicNameEn,
SongNameCN = musicNameCn,
SongNameKO = musicNameKo,
ArtistName = musicArtist,
ArtistNameEN = musicArtistEn,
ArtistNameCN = musicArtistCn,
ArtistNameKO = musicArtistKo,
Genre = musicGenre,
StarEasy = musicStarEasy,
StarNormal = musicStarNormal,
StarHard = musicStarHard,
StarOni = musicStarOni,
StarUra = musicStarUra
};
musicDetailDictionary.TryAdd(musicUniqueId, musicDetail);
}
for (var index = 0; index < musicOrderData.Order.Count; index++)
{
var musicOrderEntry = musicOrderData.Order[index];
var musicUniqueId = musicOrderEntry.SongId;
if (musicDetailDictionary.TryGetValue(musicUniqueId, out var musicDetail))
{
musicDetail.Index = index;
}
}
}
private void InitializeTitleFlagArraySize(Shougous? shougouData)
private void InitializeCostumes(DonCosRewards? donCosRewardData, WordList? wordlistData)
{
donCosRewardData.ThrowIfNull("Shouldn't happen!");
wordlistData.ThrowIfNull("Shouldn't happen!");
foreach (var donCosReward in donCosRewardData.DonCosRewardEntries)
{
var cosType = donCosReward.CosType;
var costumeId = donCosReward.UniqueId;
var costumeNameKey = $"costume_{cosType}_{costumeId}";
var costumeName = wordlistData.WordListEntries.First(entry => entry.Key == costumeNameKey).JapaneseText;
var costume = new Costume
{
CostumeId = costumeId,
CostumeType = cosType,
CostumeName = costumeName
};
costumeList.Add(costume);
}
var kigurumiMaxArraySize = (int)costumeList.Where(costume => costume.CostumeType == "kigurumi").Max(costume => costume.CostumeId) + 1;
var headMaxArraySize = (int)costumeList.Where(costume => costume.CostumeType == "head").Max(costume => costume.CostumeId) + 1;
var bodyMaxArraySize = (int)costumeList.Where(costume => costume.CostumeType == "body").Max(costume => costume.CostumeId) + 1;
var faceMaxArraySize = (int)costumeList.Where(costume => costume.CostumeType == "face").Max(costume => costume.CostumeId) + 1;
var puchiMaxArraySize = (int)costumeList.Where(costume => costume.CostumeType == "puchi").Max(costume => costume.CostumeId) + 1;
costumeFlagArraySize =
[kigurumiMaxArraySize, headMaxArraySize, bodyMaxArraySize, faceMaxArraySize, puchiMaxArraySize];
}
private void InitializeTitles(Shougous? shougouData, WordList? wordlistData)
{
shougouData.ThrowIfNull("Shouldn't happen!");
titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.UniqueId) + 1;
wordlistData.ThrowIfNull("Shouldn't happen!");
foreach (var shougou in shougouData.ShougouEntries)
{
var titleId = shougou.UniqueId;
var titleNameKey = $"syougou_{titleId}";
var titleName = wordlistData.WordListEntries.First(entry => entry.Key == titleNameKey).JapaneseText;
var title = new Title
{
TitleId = titleId,
TitleName = titleName,
TitleRarity = shougou.Rarity
};
titleDictionary.TryAdd(titleId, title);
}
titleFlagArraySize = (int)titleDictionary.Max(title => title.Key) + 1;
}
private void InitializeLockedCostumeData(Dictionary<string, uint[]>? lockedCostumeData)
{
lockedCostumeData.ThrowIfNull("Shouldn't happen!");
lockedCostumeDataDictionary = lockedCostumeData.ToDictionary(pair => pair.Key, pair => pair.Value.ToList());
}
private void InitializeLockedTitleData(Dictionary<string, uint[]>? lockedTitleData)
{
lockedTitleData.ThrowIfNull("Shouldn't happen!");
lockedTitleDataDictionary = lockedTitleData.ToDictionary(pair => pair.Key, pair => pair.Value.ToList());
}
private void InitializeToneFlagArraySize(Neiros? neiroData)

View File

@ -26,9 +26,19 @@ public interface IGameDataService
public Dictionary<string, int> GetTokenDataDictionary();
public List<uint> GetLockedSongsList();
public Dictionary<uint, MusicDetail> GetMusicDetailDictionary();
public List<Costume> GetCostumeList();
public Dictionary<uint, Title> GetTitleDictionary();
public Dictionary<string, List<uint>> GetLockedCostumeDataDictionary();
public Dictionary<string, List<uint>> GetLockedTitleDataDictionary();
public List<int> GetCostumeFlagArraySizes();
public int GetTitleFlagArraySize();
public int GetToneFlagArraySize();

View File

@ -2,6 +2,8 @@
public interface IUserDatumService
{
public Task<List<UserDatum>> GetAllUserDatum();
public Task<UserDatum?> GetFirstUserDatumOrNull(uint baid);
public Task UpdateUserDatum(UserDatum userDatum);

View File

@ -5,6 +5,11 @@ namespace TaikoLocalServer.Services;
public class UserDatumService(TaikoDbContext context) : IUserDatumService
{
public async Task<List<UserDatum>> GetAllUserDatum()
{
return await context.UserData.Include(d => d.Tokens).ToListAsync();
}
public async Task<UserDatum?> GetFirstUserDatumOrNull(uint baid)
{
return await context.UserData

View File

@ -19,6 +19,7 @@
<PackageReference Include="MediatR" Version="12.2.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.Server" Version="8.0.4" />
<PackageReference Include="Microsoft.AspNetCore.ResponseCompression" Version="2.2.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="8.0.4" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="8.0.4">

View File

@ -1,17 +1,17 @@
{
"Kigurumi": [
"kigurumi": [
],
"Head": [
"head": [
],
"Body": [
"body": [
],
"Face": [
"face": [
],
"Puchi": [
"puchi": [
]
}

View File

@ -1,8 +1,8 @@
{
"TitleNo": [
"title": [
],
"TitlePlateNo": [
"titlePlate": [
]
}

View File

@ -0,0 +1,54 @@
@using Blazored.LocalStorage
@using Microsoft.Extensions.Options
@using TaikoWebUI.Settings
@inject NavigationManager NavigationManager
@inject IOptions<WebUiSettings> Settings
@inject ILocalStorageService LocalStorage
<MudMenu Icon="@Icons.Material.Filled.Translate" Color="Color.Inherit" Size="Size.Small" Dense="true" AnchorOrigin="Origin.BottomCenter" TransformOrigin="Origin.TopCenter">
<MudText Align="Align.Center" GutterBottom="true">@Localizer["Song Name"]</MudText>
<MudDivider />
@foreach (var culture in supportedCultures)
{
<MudMenuItem OnClick="() => RequestCultureChange(culture.Key)" OnTouch="() => RequestCultureChange(culture.Key)">@culture.Value</MudMenuItem>
}
</MudMenu>
@code {
private readonly Dictionary<CultureInfo, string> supportedCultures = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
foreach (var language in Settings.Value.SupportedLanguages)
{
supportedCultures.Add(new CultureInfo(language.CultureCode), language.DisplayName);
}
if (supportedCultures.Count == 0)
{
supportedCultures.Add(new CultureInfo("en-US"), "English");
}
var currentSongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
if (string.IsNullOrEmpty(currentSongNameLanguage))
{
await LocalStorage.SetItemAsync("songNameLanguage", "en-US");
}
}
private async Task RequestCultureChange(CultureInfo newCulture)
{
var currentSongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
if (currentSongNameLanguage == newCulture.Name)
{
return;
}
await LocalStorage.SetItemAsync("songNameLanguage", newCulture.Name);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
}

View File

@ -1,31 +1,32 @@
@using System.Globalization;
@using Microsoft.Extensions.Options
@using Microsoft.Extensions.Options
@using TaikoWebUI.Settings
@inject NavigationManager NavigationManager
@inject IJSRuntime JSRuntime
@inject IJSRuntime JsRuntime
@inject IOptions<WebUiSettings> Settings
<MudMenu Icon="@Icons.Material.Filled.Translate" Color="Color.Inherit" Size="Size.Small" Dense="true">
@foreach (var culture in SupportedCultures)
<MudMenu Icon="@Icons.Material.Filled.Language" Color="Color.Inherit" Size="Size.Small" Dense="true" AnchorOrigin="Origin.BottomCenter" TransformOrigin="Origin.TopCenter">
<MudText Align="Align.Center" GutterBottom="true">@Localizer["UI"]</MudText>
<MudDivider />
@foreach (var culture in supportedCultures)
{
<MudMenuItem OnClick="() => RequestCultureChange(culture.Key)" OnTouch="() => RequestCultureChange(culture.Key)">@culture.Value</MudMenuItem>
}
</MudMenu>
@code {
public Dictionary<CultureInfo, string> SupportedCultures = new();
private Dictionary<CultureInfo, string> supportedCultures = new();
protected override void OnInitialized()
{
base.OnInitialized();
foreach (var language in Settings.Value.SupportedLanguages)
{
SupportedCultures.Add(new CultureInfo(language.CultureCode), language.DisplayName);
supportedCultures.Add(new CultureInfo(language.CultureCode), language.DisplayName);
}
if (SupportedCultures.Count == 0)
if (supportedCultures.Count == 0)
{
SupportedCultures.Add(new CultureInfo("en-US"), "English");
supportedCultures.Add(new CultureInfo("en-US"), "English");
}
}
@ -36,9 +37,8 @@
return;
}
var js = (IJSInProcessRuntime)JSRuntime;
var js = (IJSInProcessRuntime)JsRuntime;
js.InvokeVoid("blazorCulture.set", newCulture.Name);
NavigationManager.NavigateTo(NavigationManager.Uri, forceLoad: true);
}
}

View File

@ -1,6 +1,5 @@
@inherits LayoutComponentBase
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@inject HttpClient Client
@inject AuthService AuthService
<MudThemeProvider IsDarkMode="@isDarkMode" Theme="@taikoWebUiTheme" />
@ -11,9 +10,10 @@
<MudAppBar Elevation="0">
<MudIconButton Icon="@Icons.Material.Filled.Menu" Color="Color.Inherit" Edge="Edge.Start" OnClick="DrawerToggle" Size="Size.Small" />
<MudSpacer />
<MudStack Spacing="2" Row="true">
<MudStack Spacing="3" Row="true">
<ChooseUILanguage />
<ChooseSongNameLanguage />
<MudIconButton Icon="@DarkModeIcon" Size="Size.Small" Color="Color.Inherit" OnClick="ToggleDarkMode" />
<ChooseLanguage />
</MudStack>
</MudAppBar>
<MudDrawer Elevation="0" Style="border-right:1px solid #ededf0" @bind-Open="drawerOpen">
@ -53,7 +53,7 @@
isDarkMode = await LocalStorage.GetItemAsync<bool>("isDarkMode");
}
if (AuthService.LoginRequired)
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
// If not logged in, attempt to use JwtToken from local storage to log in
await AuthService.LoginWithAuthToken();

View File

@ -7,7 +7,7 @@
<MudText Typo="Typo.h6">@Localizer["Play History"]</MudText>
</MudItem>
<MudItem xs="12" md="8">
<MudText Typo="Typo.h6">@Localizer["Total Plays"]:@Items.Count</MudText>
<MudText Typo="Typo.h6">@Localizer["Total Credits Played"]:@Items.Count</MudText>
</MudItem>
</MudGrid>
</MudCardHeader>

View File

@ -13,9 +13,9 @@
<MudCardHeader>
<CardHeaderContent>
<div style="display:flex;flex-wrap:wrap;align-items:center;gap:5px;">
@if (userSetting is not null)
@if (UserSetting is not null)
{
<MudText Typo="Typo.h6" Style="font-weight:bold;word-break:break-all">@userSetting?.MyDonName</MudText>
<MudText Typo="Typo.h6" Style="font-weight:bold;word-break:break-all">@UserSetting?.MyDonName</MudText>
} else
{
<MudSkeleton Width="35%" Height="32px" />
@ -137,14 +137,11 @@
@code {
[Parameter] public User? User { get; set; }
private UserSetting? userSetting;
[Parameter] public UserSetting? UserSetting { get; set; }
protected override async Task OnInitializedAsync()
{
if (User is not null)
{
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{User.Baid}");
}
await base.OnInitializedAsync();
}
private Task ShowQrCode(User user)

View File

@ -113,6 +113,15 @@ namespace TaikoWebUI.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string Add {
get {
return ResourceManager.GetString("Add", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
@ -815,6 +824,24 @@ namespace TaikoWebUI.Localization {
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string Total_Credits_Played {
get {
return ResourceManager.GetString("Total Credits Played", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>
internal static string UI {
get {
return ResourceManager.GetString("UI", resourceCulture);
}
}
/// <summary>
/// Looks up a localized string similar to .
/// </summary>

View File

@ -192,7 +192,7 @@
<data name="Last Played" xml:space="preserve">
<value>Last Played</value>
</data>
<data name="Total Plays" xml:space="preserve">
<data name="Total Credits Played" xml:space="preserve">
<value>Total Plays</value>
</data>
<data name="Total Clears" xml:space="preserve">
@ -594,4 +594,10 @@
<data name="Rows Per Page:" xml:space="preserve">
<value>Rows Per Page:</value>
</data>
<data name="UI" xml:space="preserve">
<value>UI</value>
</data>
<data name="Add" xml:space="preserve">
<value>Add</value>
</data>
</root>

View File

@ -192,7 +192,7 @@
<data name="Last Played" xml:space="preserve">
<value>ラストプレイ</value>
</data>
<data name="Total Plays" xml:space="preserve">
<data name="Total Credits Played" xml:space="preserve">
<value>プレイ回数</value>
</data>
<data name="Total Clears" xml:space="preserve">
@ -594,4 +594,10 @@
<data name="Rows Per Page:" xml:space="preserve">
<value>1ページ当たりの行数</value>
</data>
<data name="UI" xml:space="preserve">
<value>UI</value>
</data>
<data name="Add" xml:space="preserve">
<value>追加</value>
</data>
</root>

View File

@ -387,4 +387,13 @@
<data name="Rows Per Page:" xml:space="preserve">
<value />
</data>
<data name="Total Credits Played" xml:space="preserve">
<value />
</data>
<data name="UI" xml:space="preserve">
<value />
</data>
<data name="Add" xml:space="preserve">
<value />
</data>
</root>

View File

@ -192,7 +192,7 @@
<data name="Last Played" xml:space="preserve">
<value>最后游玩时间</value>
</data>
<data name="Total Plays" xml:space="preserve">
<data name="Total Credits Played" xml:space="preserve">
<value>总游玩次数</value>
</data>
<data name="Total Clears" xml:space="preserve">
@ -591,4 +591,10 @@
<data name="Rows Per Page:" xml:space="preserve">
<value>每页行数</value>
</data>
<data name="UI" xml:space="preserve">
<value>界面</value>
</data>
<data name="Add" xml:space="preserve">
<value>添加</value>
</data>
</root>

View File

@ -192,7 +192,7 @@
<data name="Last Played" xml:space="preserve">
<value>最後遊玩時間</value>
</data>
<data name="Total Plays" xml:space="preserve">
<data name="Total Credits Played" xml:space="preserve">
<value>總遊玩次數</value>
</data>
<data name="Total Clears" xml:space="preserve">
@ -591,4 +591,10 @@
<data name="Rows Per Page:" xml:space="preserve">
<value>每頁行數</value>
</data>
<data name="UI" xml:space="preserve">
<value>界面</value>
</data>
<data name="Add" xml:space="preserve">
<value>添加</value>
</data>
</root>

View File

@ -19,6 +19,12 @@ public partial class AccessCode
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
await InitializeUser();
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");

View File

@ -11,6 +11,11 @@ public partial class ChangePassword
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
}
private async Task OnChangePassword()

View File

@ -1,8 +1,11 @@
@inject IGameDataService GameDataService
@using Blazored.LocalStorage
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@inject ILocalStorageService LocalStorage
@page "/Users/{baid:int}/DaniDojo"
@ -143,17 +146,17 @@ else
</MudTooltip>
<MudStack Row="true" Spacing="1" Justify="Justify.Center" AlignItems="AlignItems.Center">
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(danDataOdaiSong.SongNo, difficulty)</MudText>
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(musicDetailDictionary, danDataOdaiSong.SongNo, difficulty)</MudText>
</MudStack>
</MudItem>
<MudItem xs="9" md="4" Style="display:flex;flex-direction:column;" Class="pl-4">
<MudText Typo="Typo.body1" Style="font-weight: bold;">@GameDataService.GetMusicNameBySongId(danDataOdaiSong.SongNo, @CurrentLanguage)</MudText>
<MudText Typo="Typo.caption">@GameDataService.GetMusicArtistBySongId(danDataOdaiSong.SongNo, @CurrentLanguage)</MudText>
<MudText Typo="Typo.body1" Style="font-weight: bold;">@GameDataService.GetMusicNameBySongId(musicDetailDictionary, danDataOdaiSong.SongNo, SongNameLanguage)</MudText>
<MudText Typo="Typo.caption">@GameDataService.GetMusicArtistBySongId(musicDetailDictionary, danDataOdaiSong.SongNo, SongNameLanguage)</MudText>
</MudItem>
@if (bestDataMap.TryGetValue(danId, out var danBestData))
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
if (danBestData.DanBestStageDataList.Count > index)
{
@ -218,7 +221,7 @@ else
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (bestDataMap.TryGetValue(danId, out var danBestData))
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
if (danBestData.SoulGaugeTotal >= redRequirement) {
barClass = "bar-pass-red";
@ -288,7 +291,7 @@ else
}
<MudStack Spacing="1">
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (bestDataMap.TryGetValue(danId, out var danBestData))
@if (_bestDataMap.TryGetValue(danId, out var danBestData))
{
var bestData = GetAllBestFromData((DanConditionType)border.OdaiType, danBestData);
@ -381,7 +384,7 @@ else
</MudCardHeader>
<MudCardContent>
<MudText Typo="Typo.subtitle2" Style="font-weight:bold;">@Localizer["Result"]</MudText>
@if (bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
@if (_bestDataMap.TryGetValue(danId, out var danBestData) && (danBestData.DanBestStageDataList.Count > songNumber))
{
var bestData = GetSongBestFromData((DanConditionType)border.OdaiType, danBestData, songNumber);
if ((DanConditionType)border.OdaiType is DanConditionType.BadCount or DanConditionType.OkCount)

View File

@ -6,28 +6,37 @@ public partial class DaniDojo
{
[Parameter]
public int Baid { get; set; }
public string CurrentLanguage { get; set; } = "ja";
private string? SongNameLanguage { get; set; }
private DanBestDataResponse? response;
private UserSetting? userSetting;
private static Dictionary<uint, DanBestData> bestDataMap = new();
private static Dictionary<uint, DanBestData> _bestDataMap = new();
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
private readonly List<BreadcrumbItem> breadcrumbs = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
response = await Client.GetFromJsonAsync<DanBestDataResponse>($"api/DanBestData/{Baid}");
response.ThrowIfNull();
response.DanBestDataList.ForEach(data => data.DanBestStageDataList
.Sort((stageData, otherStageData) => stageData.SongNumber.CompareTo(otherStageData.SongNumber)));
bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
_bestDataMap = response.DanBestDataList.ToDictionary(data => data.DanId);
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
SongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
@ -168,12 +177,12 @@ public partial class DaniDojo
string icon;
const string notClearIcon = "<image href='/images/dani_NotClear.png' width='24' height='24' style='filter: contrast(0.65)'/>";
if (!bestDataMap.ContainsKey(danId))
if (!_bestDataMap.ContainsKey(danId))
{
return notClearIcon;
}
var state = bestDataMap[danId].ClearState;
var state = _bestDataMap[danId].ClearState;
icon = state is DanClearState.NotClear ? notClearIcon : $"<image href='/images/dani_{state}.png' width='24' height='24' />";
@ -182,7 +191,7 @@ public partial class DaniDojo
private DanClearState GetDanResultState(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].ClearState : DanClearState.NotClear;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].ClearState : DanClearState.NotClear;
}
private static uint GetSoulGauge(DanData data, bool isGold)
@ -209,36 +218,36 @@ public partial class DaniDojo
private static long GetTotalScore(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.HighScore) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.HighScore) : 0;
}
private static long GetTotalGoodHits(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.GoodCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.GoodCount) : 0;
}
private static long GetTotalOkHits(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.OkCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.OkCount) : 0;
}
private static long GetTotalBadHits(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.BadCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.BadCount) : 0;
}
private static long GetTotalDrumrollHits(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.DrumrollCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.DrumrollCount) : 0;
}
private static long GetTotalMaxCombo(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.ComboCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.ComboCount) : 0;
}
private static long GetTotalHits(uint danId)
{
return bestDataMap.ContainsKey(danId) ? bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.TotalHitCount) : 0;
return _bestDataMap.ContainsKey(danId) ? _bestDataMap[danId].DanBestStageDataList.Sum(stageData => stageData.TotalHitCount) : 0;
}
}

View File

@ -1,11 +1,10 @@
@using TaikoWebUI.Shared.Models
@using System.Collections.Immutable
@using System.Collections.Immutable
@inject IGameDataService GameDataService
@inject IJSRuntime Js
<MudDialog>
<DialogContent>
<MudTable Items="@titles" Filter="@Filter" @bind-SelectedItem="@selectedTitle" Height="40vh" Hover="true">
<MudTable Items="@Titles" Filter="@Filter" @bind-SelectedItem="@selectedTitle" Height="40vh" Hover="true">
<ColGroup>
<col style="width: 50px;" />
<col />
@ -66,9 +65,7 @@
public bool AllowFreeProfileEditing { get; set; }
[Parameter]
public List<uint> TitleUniqueIdList { get; set; } = new();
private IEnumerable<Title> titles = new List<Title>();
public List<Title> Titles { get; set; } = new();
private Title? selectedTitle;
@ -77,28 +74,13 @@
protected override void OnInitialized()
{
base.OnInitialized();
var titleSet = GameDataService.GetTitles();
if (!AllowFreeProfileEditing)
{
var unlockedTitle = UserSetting.UnlockedTitle;
titleSet = titleSet.Where(title => unlockedTitle.Contains(title.TitleId)).ToImmutableHashSet();
}
else
{
// Only allow titles in titleUniqueIdList
titleSet = titleSet.Where(title => TitleUniqueIdList.Contains(title.TitleId)).ToImmutableHashSet();
}
titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId));
var currentTitle = new Title
{
TitleName = UserSetting.Title
};
if (titleSet.Contains(currentTitle))
if (Titles.Contains(currentTitle))
{
titleSet.TryGetValue(new Title
{
TitleName = UserSetting.Title
}, out selectedTitle);
selectedTitle = currentTitle;
}
}

View File

@ -1,10 +1,10 @@
@inject IGameDataService GameDataService
@using TaikoWebUI.Utilities;
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@inject Blazored.LocalStorage.ILocalStorageService LocalStorage
@using TaikoWebUI.Utilities;
@page "/Users/{baid:int}/HighScores"
@ -22,14 +22,7 @@
{
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{
if (!AuthService.IsLoggedIn)
{
NavigationManager.NavigateTo("/Login");
}
else
{
NavigationManager.NavigateTo("/");
}
NavigationManager.NavigateTo(AuthService.IsLoggedIn ? "/" : "/Login");
}
else
{
@ -50,7 +43,7 @@
<MudText Typo="Typo.body2" Style="font-weight:bold">@Localizer["Song Name"]</MudText>
</MudTh>
<MudTh>
<MudTableSortLabel T="SongBestData" SortBy="x => GameDataService.GetMusicStarLevel(x.SongId, difficulty)">
<MudTableSortLabel T="SongBestData" SortBy="x => GameDataService.GetMusicStarLevel(musicDetailDictionary, x.SongId, difficulty)">
<MudText>@Localizer["Level"]</MudText>
</MudTableSortLabel>
</MudTh>
@ -106,7 +99,7 @@
</MudTh>
<MudTh>
<MudTableSortLabel T="SongBestData" SortBy="x => x.PlayCount">
<MudText>@Localizer["Total Plays"]</MudText>
<MudText>@Localizer["Total Credits Played"]</MudText>
</MudTableSortLabel>
</MudTh>
<MudTh>
@ -148,7 +141,7 @@
<MudTd>
<MudStack Row="true" Spacing="1" AlignItems="AlignItems.Center">
<MudIcon Icon="@Icons.Material.Filled.Star" Size="Size.Small" />
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(context.SongId, difficulty)</MudText>
<MudText Typo="Typo.caption" Style="line-height:1;margin-top:2px;margin-right:2px;">@GameDataService.GetMusicStarLevel(musicDetailDictionary, context.SongId, difficulty)</MudText>
</MudStack>
</MudTd>
<MudTd>

View File

@ -1,9 +1,4 @@
using static MudBlazor.Colors;
using System;
using Microsoft.JSInterop;
namespace TaikoWebUI.Pages;
namespace TaikoWebUI.Pages;
public partial class HighScores
{
@ -15,24 +10,32 @@ public partial class HighScores
private Dictionary<Difficulty, List<SongBestData>> songBestDataMap = new();
private readonly List<BreadcrumbItem> breadcrumbs = new();
private int selectedDifficultyTab = 0;
private int selectedDifficultyTab;
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
response = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
response.ThrowIfNull();
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
var songNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
response.SongBestData.ForEach(data =>
{
var songId = data.SongId;
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(language) ? "ja" : language);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(language) ? "ja" : language);
data.Genre = GameDataService.GetMusicGenreBySongId(musicDetailDictionary, songId);
data.MusicName = GameDataService.GetMusicNameBySongId(musicDetailDictionary, songId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(musicDetailDictionary, songId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
});
songBestDataMap = response.SongBestData.GroupBy(data => data.Difficulty)
@ -40,8 +43,8 @@ public partial class HighScores
data => data.ToList());
foreach (var songBestDataList in songBestDataMap.Values)
{
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(data1.SongId)
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(musicDetailDictionary, data1.SongId)
.CompareTo(GameDataService.GetMusicIndexBySongId(musicDetailDictionary, data2.SongId)));
}
// Set last selected tab from local storage

View File

@ -9,6 +9,11 @@ public partial class Login
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
}
private async Task OnLogin()

View File

@ -1,11 +1,11 @@
@using Blazored.LocalStorage
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject AuthService AuthService
@inject IJSRuntime JSRuntime
@inject IJSRuntime JsRuntime
@inject NavigationManager NavigationManager
@using TaikoWebUI.Utilities;
@using TaikoWebUI.Shared.Models;
@using SharedProject.Enums;
@inject ILocalStorageService LocalStorage
@page "/Users/{baid:int}/PlayHistory"
@ -113,7 +113,7 @@
ToggledSize="Size.Small"
Title="Add to favorites" ToggledTitle="Remove from favorites" />
</div>
</MudStack>
</MudStack>
</MudTd>
@* Genre display *@

View File

@ -1,9 +1,5 @@
using static MudBlazor.Colors;
using System;
using static MudBlazor.CategoryTypes;
using System.Globalization;
using System.Globalization;
using Microsoft.JSInterop;
using TaikoWebUI.Shared.Models;
namespace TaikoWebUI.Pages;
@ -16,7 +12,7 @@ public partial class PlayHistory
private string Search { get; set; } = string.Empty;
private string? currentLanguage;
private string? songNameLanguage;
private SongHistoryResponse? response;
@ -24,22 +20,32 @@ public partial class PlayHistory
private readonly List<BreadcrumbItem> breadcrumbs = new();
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
response.ThrowIfNull();
currentLanguage = await JSRuntime.InvokeAsync<string>("blazorCulture.get");
songNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
response.SongHistoryData.ForEach(data =>
{
var songId = data.SongId;
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
data.MusicName = GameDataService.GetMusicNameBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId, string.IsNullOrEmpty(currentLanguage) ? "ja" : currentLanguage);
data.Stars = GameDataService.GetMusicStarLevel(songId, data.Difficulty);
data.Genre = GameDataService.GetMusicGenreBySongId(musicDetailDictionary, songId);
data.MusicName = GameDataService.GetMusicNameBySongId(musicDetailDictionary, songId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(musicDetailDictionary, songId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
data.Stars = GameDataService.GetMusicStarLevel(musicDetailDictionary, songId, data.Difficulty);
data.ShowDetails = false;
});
@ -134,7 +140,7 @@ public partial class PlayHistory
return true;
}
var language = currentLanguage ?? "ja";
var language = songNameLanguage ?? "ja";
if (songHistoryDataList[0].PlayTime
.ToString("dddd d MMMM yyyy - HH:mm", CultureInfo.CreateSpecificCulture(language))

View File

@ -126,35 +126,35 @@
<MudSelect @bind-Value="@response.Head" Label=@Localizer["Head"]>
@foreach (var index in headUniqueIdList)
{
var costumeTitle = GameDataService.GetHeadTitle(index);
var costumeTitle = GameDataService.GetHeadTitle(costumeList, index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Body" Label=@Localizer["Body"]>
@foreach (var index in bodyUniqueIdList)
{
var costumeTitle = GameDataService.GetBodyTitle(index);
var costumeTitle = GameDataService.GetBodyTitle(costumeList, index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Face" Label=@Localizer["Face"]>
@foreach (var index in faceUniqueIdList)
{
var costumeTitle = GameDataService.GetFaceTitle(index);
var costumeTitle = GameDataService.GetFaceTitle(costumeList, index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Kigurumi" Label=@Localizer["Kigurumi"]>
@foreach (var index in kigurumiUniqueIdList)
{
var costumeTitle = GameDataService.GetKigurumiTitle(index);
var costumeTitle = GameDataService.GetKigurumiTitle(costumeList, index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Puchi" Label=@Localizer["Puchi"]>
@foreach (var index in puchiUniqueIdList)
{
var costumeTitle = GameDataService.GetPuchiTitle(index);
var costumeTitle = GameDataService.GetPuchiTitle(costumeList, index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>

View File

@ -179,15 +179,31 @@ public partial class Profile
private List<uint> puchiUniqueIdList = new();
private List<uint> titleUniqueIdList = new();
private List<uint> titlePlateIdList = new();
private List<Costume> costumeList = new();
private Dictionary<uint, Title> titleDictionary = new();
private Dictionary<string, List<uint>> lockedCostumeDataDictionary = new();
private Dictionary<string, List<uint>> lockedTitleDataDictionary = new();
private List<Title> unlockedTitles = new();
private int[] scoresArray = new int[10];
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
isSavingOptions = false;
response = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
response.ThrowIfNull();
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
@ -196,10 +212,15 @@ public partial class Profile
else
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
};
}
breadcrumbs.Add(new BreadcrumbItem($"{response.MyDonName}", href: null, disabled: true));
breadcrumbs.Add(new BreadcrumbItem(Localizer["Profile"], href: $"/Users/{Baid}/Profile", disabled: false));
costumeList = await GameDataService.GetCostumeList();
titleDictionary = await GameDataService.GetTitleDictionary();
lockedCostumeDataDictionary = await GameDataService.GetLockedCostumeDataDictionary();
lockedTitleDataDictionary = await GameDataService.GetLockedTitleDataDictionary();
InitializeAvailableCostumes();
InitializeAvailableTitles();
@ -209,9 +230,9 @@ public partial class Profile
songresponse.SongBestData.ForEach(data =>
{
var songId = data.SongId;
data.Genre = GameDataService.GetMusicGenreBySongId(songId);
data.MusicName = GameDataService.GetMusicNameBySongId(songId);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(songId);
data.Genre = GameDataService.GetMusicGenreBySongId(musicDetailDictionary, songId);
data.MusicName = GameDataService.GetMusicNameBySongId(musicDetailDictionary, songId);
data.MusicArtist = GameDataService.GetMusicArtistBySongId(musicDetailDictionary, songId);
});
songBestDataMap = songresponse.SongBestData.GroupBy(data => data.Difficulty)
@ -219,12 +240,12 @@ public partial class Profile
data => data.ToList());
foreach (var songBestDataList in songBestDataMap.Values)
{
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(data1.SongId)
.CompareTo(GameDataService.GetMusicIndexBySongId(data2.SongId)));
songBestDataList.Sort((data1, data2) => GameDataService.GetMusicIndexBySongId(musicDetailDictionary, data1.SongId)
.CompareTo(GameDataService.GetMusicIndexBySongId(musicDetailDictionary, data2.SongId)));
}
for (var i = 0; i < (int)Difficulty.UraOni; i++)
if (songBestDataMap.TryGetValue((Difficulty)i, out var values))
if (songBestDataMap.ContainsKey((Difficulty)i) && songBestDataMap[(Difficulty)i].Count > 0)
{
highestDifficulty = (Difficulty)i;
}
@ -242,18 +263,30 @@ public partial class Profile
if (AuthService.AllowFreeProfileEditing)
{
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList();
headUniqueIdList = GameDataService.GetHeadUniqueIdList();
bodyUniqueIdList = GameDataService.GetBodyUniqueIdList();
faceUniqueIdList = GameDataService.GetFaceUniqueIdList();
puchiUniqueIdList = GameDataService.GetPuchiUniqueIdList();
kigurumiUniqueIdList = costumeList.Where(costume => costume.CostumeType == "kigurumi").Select(costume => costume.CostumeId).ToList();
headUniqueIdList = costumeList.Where(costume => costume.CostumeType == "head").Select(costume => costume.CostumeId).ToList();
bodyUniqueIdList = costumeList.Where(costume => costume.CostumeType == "body").Select(costume => costume.CostumeId).ToList();
faceUniqueIdList = costumeList.Where(costume => costume.CostumeType == "face").Select(costume => costume.CostumeId).ToList();
puchiUniqueIdList = costumeList.Where(costume => costume.CostumeType == "puchi").Select(costume => costume.CostumeId).ToList();
// Lock costumes in LockedCostumesList but not in UnlockedCostumesList
var lockedKigurumiUniqueIdList = GameDataService.GetLockedKigurumiUniqueIdList().Except(unlockedKigurumi).ToList();
var lockedHeadUniqueIdList = GameDataService.GetLockedHeadUniqueIdList().Except(unlockedHead).ToList();
var lockedBodyUniqueIdList = GameDataService.GetLockedBodyUniqueIdList().Except(unlockedBody).ToList();
var lockedFaceUniqueIdList = GameDataService.GetLockedFaceUniqueIdList().Except(unlockedFace).ToList();
var lockedPuchiUniqueIdList = GameDataService.GetLockedPuchiUniqueIdList().Except(unlockedPuchi).ToList();
lockedCostumeDataDictionary.TryGetValue("kigurumi", out var lockedKigurumiUniqueIdList);
lockedCostumeDataDictionary.TryGetValue("head", out var lockedHeadUniqueIdList);
lockedCostumeDataDictionary.TryGetValue("body", out var lockedBodyUniqueIdList);
lockedCostumeDataDictionary.TryGetValue("face", out var lockedFaceUniqueIdList);
lockedCostumeDataDictionary.TryGetValue("puchi", out var lockedPuchiUniqueIdList);
lockedKigurumiUniqueIdList ??= new List<uint>();
lockedHeadUniqueIdList ??= new List<uint>();
lockedBodyUniqueIdList ??= new List<uint>();
lockedFaceUniqueIdList ??= new List<uint>();
lockedPuchiUniqueIdList ??= new List<uint>();
unlockedKigurumi.ForEach(id => kigurumiUniqueIdList.Add(id));
unlockedHead.ForEach(id => headUniqueIdList.Add(id));
unlockedBody.ForEach(id => bodyUniqueIdList.Add(id));
unlockedFace.ForEach(id => faceUniqueIdList.Add(id));
unlockedPuchi.ForEach(id => puchiUniqueIdList.Add(id));
lockedKigurumiUniqueIdList.ForEach(id => kigurumiUniqueIdList.Remove(id));
lockedHeadUniqueIdList.ForEach(id => headUniqueIdList.Remove(id));
@ -264,19 +297,33 @@ public partial class Profile
else
{
// Only unlock costumes that are in both UnlockedCostumesList and CostumeList
kigurumiUniqueIdList = GameDataService.GetKigurumiUniqueIdList().Intersect(unlockedKigurumi).ToList();
headUniqueIdList = GameDataService.GetHeadUniqueIdList().Intersect(unlockedHead).ToList();
bodyUniqueIdList = GameDataService.GetBodyUniqueIdList().Intersect(unlockedBody).ToList();
faceUniqueIdList = GameDataService.GetFaceUniqueIdList().Intersect(unlockedFace).ToList();
puchiUniqueIdList = GameDataService.GetPuchiUniqueIdList().Intersect(unlockedPuchi).ToList();
kigurumiUniqueIdList = costumeList.Where(costume => costume.CostumeType == "kigurumi").Select(costume => costume.CostumeId).Intersect(unlockedKigurumi).ToList();
headUniqueIdList = costumeList.Where(costume => costume.CostumeType == "head").Select(costume => costume.CostumeId).Intersect(unlockedHead).ToList();
bodyUniqueIdList = costumeList.Where(costume => costume.CostumeType == "body").Select(costume => costume.CostumeId).Intersect(unlockedBody).ToList();
faceUniqueIdList = costumeList.Where(costume => costume.CostumeType == "face").Select(costume => costume.CostumeId).Intersect(unlockedFace).ToList();
puchiUniqueIdList = costumeList.Where(costume => costume.CostumeType == "puchi").Select(costume => costume.CostumeId).Intersect(unlockedPuchi).ToList();
}
// Take unique values and sort
kigurumiUniqueIdList = kigurumiUniqueIdList.Distinct().OrderBy(id => id).ToList();
headUniqueIdList = headUniqueIdList.Distinct().OrderBy(id => id).ToList();
bodyUniqueIdList = bodyUniqueIdList.Distinct().OrderBy(id => id).ToList();
faceUniqueIdList = faceUniqueIdList.Distinct().OrderBy(id => id).ToList();
puchiUniqueIdList = puchiUniqueIdList.Distinct().OrderBy(id => id).ToList();
}
private void InitializeAvailableTitlePlates()
{
titlePlateIdList = GameDataService.GetTitlePlateIdList().ToList();
titlePlateIdList = titleDictionary.Values.Select(title => title.TitleRarity).ToList();
lockedTitleDataDictionary.TryGetValue("titlePlate", out var lockedTitlePlateIdList);
lockedTitlePlateIdList ??= new List<uint>();
// Cut off ids longer than TitlePlateStrings
titlePlateIdList = titlePlateIdList.Where(id => id < TitlePlateStrings.Length).Except(GameDataService.GetLockedTitlePlateIdList()).ToList();
titlePlateIdList = titlePlateIdList.Where(id => id < TitlePlateStrings.Length).Except(lockedTitlePlateIdList).ToList();
// Take unique values and sort
titlePlateIdList = titlePlateIdList.Distinct().OrderBy(id => id).ToList();
}
private void InitializeAvailableTitles()
@ -287,23 +334,32 @@ public partial class Profile
if (AuthService.AllowFreeProfileEditing)
{
titleUniqueIdList = GameDataService.GetTitleUniqueIdList();
titleUniqueIdList = titleDictionary.Values.Select(title => title.TitleId).ToList();
var titles = GameDataService.GetTitles();
// Lock titles in LockedTitlesList but not in UnlockedTitle
var lockedTitleUniqueIdList = GameDataService.GetLockedTitleUniqueIdList().ToList();
var lockedTitlePlateIdList = GameDataService.GetLockedTitlePlateIdList().ToList();
lockedTitleDataDictionary.TryGetValue("title", out var lockedTitleUniqueIdList);
lockedTitleDataDictionary.TryGetValue("titlePlate", out var lockedTitlePlateIdList);
lockedTitleUniqueIdList ??= new List<uint>();
lockedTitlePlateIdList ??= new List<uint>();
// Unlock titles in UnlockedTitlesList
lockedTitleUniqueIdList = lockedTitleUniqueIdList.Except(unlockedTitle).ToList();
// Find uniqueIds of titles with rarity in lockedTitlePlateIdList
lockedTitleUniqueIdList.AddRange(titles.Where(title => lockedTitlePlateIdList.Contains(title.TitleRarity)).Select(title => title.TitleId));
lockedTitleUniqueIdList.AddRange(titleDictionary.Values.Where(title => lockedTitlePlateIdList.Contains(title.TitleRarity)).Select(title => title.TitleId));
titleUniqueIdList = titleUniqueIdList.Except(lockedTitleUniqueIdList).ToList();
}
else
{
// Only unlock titles that are in both UnlockedTitlesList and TitleList
titleUniqueIdList = GameDataService.GetTitleUniqueIdList().Intersect(unlockedTitle).ToList();
titleUniqueIdList = titleDictionary.Values.Select(title => title.TitleId).ToList();
titleUniqueIdList = titleUniqueIdList.Intersect(unlockedTitle).ToList();
}
unlockedTitles = titleDictionary.Values.Where(title => titleUniqueIdList.Contains(title.TitleId)).ToList();
// Take unique values and sort
titleUniqueIdList = titleUniqueIdList.Distinct().OrderBy(id => id).ToList();
}
private async Task SaveOptions()
@ -405,7 +461,7 @@ public partial class Profile
{
{x => x.UserSetting, response},
{x => x.AllowFreeProfileEditing, AuthService.AllowFreeProfileEditing},
{x => x.TitleUniqueIdList, titleUniqueIdList}
{x => x.Titles, unlockedTitles},
};
var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options);
var result = await dialog.Result;

View File

@ -1,5 +1,4 @@
@inject HttpClient Client
@inject IDialogService DialogService
@inject IDialogService DialogService
@inject AuthService AuthService
@inject NavigationManager NavigationManager

View File

@ -1,12 +1,13 @@
@page "/Users/{baid:int}/Songs/{songId:int}"
@using Blazored.LocalStorage
@using TaikoWebUI.Components.Song
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject AuthService AuthService
@inject NavigationManager NavigationManager
@inject IJSRuntime JsRuntime
@using TaikoWebUI.Components.Song;
@inject ILocalStorageService LocalStorage
@if (AuthService.LoginRequired && (!AuthService.IsLoggedIn || (AuthService.GetLoggedInBaid() != Baid && !AuthService.IsAdmin)))
{

View File

@ -1,58 +1,62 @@
using Microsoft.JSInterop;
namespace TaikoWebUI.Pages;
namespace TaikoWebUI.Pages
public partial class Song
{
public partial class Song
[Parameter]
public int SongId { get; set; }
[Parameter]
public int Baid { get; set; }
private UserSetting? userSetting;
private SongHistoryResponse? response;
private List<SongHistoryData>? songHistoryData;
private readonly List<BreadcrumbItem> breadcrumbs = new();
private string songTitle = string.Empty;
private string songArtist = string.Empty;
protected override async Task OnInitializedAsync()
{
[Parameter]
public int SongId { get; set; }
[Parameter]
public int Baid { get; set; }
private UserSetting? userSetting;
private SongHistoryResponse? response;
private List<SongHistoryData>? songHistoryData;
private readonly List<BreadcrumbItem> breadcrumbs = new();
private string songTitle = string.Empty;
private string songArtist = string.Empty;
protected override async Task OnInitializedAsync()
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await base.OnInitializedAsync();
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
response.ThrowIfNull();
// Get all song best data with SongId
songHistoryData = response.SongHistoryData.Where(data => data.SongId == (uint)SongId).ToList();
// Get user settings
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
// Get song title and artist
var language = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
songTitle = GameDataService.GetMusicNameBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
songArtist = GameDataService.GetMusicArtistBySongId((uint)SongId, string.IsNullOrEmpty(language) ? "ja" : language);
// Breadcrumbs
var formattedSongTitle = songTitle;
if (formattedSongTitle.Length > 20)
{
formattedSongTitle = string.Concat(formattedSongTitle.AsSpan(0, 20), "...");
}
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
else
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
};
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
breadcrumbs.Add(new BreadcrumbItem(formattedSongTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
await AuthService.LoginWithAuthToken();
}
response = await Client.GetFromJsonAsync<SongHistoryResponse>($"api/PlayHistory/{(uint)Baid}");
response.ThrowIfNull();
// Get all song best data with SongId
songHistoryData = response.SongHistoryData.Where(data => data.SongId == (uint)SongId).ToList();
// Get user settings
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
var musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
// Get song title and artist
var songNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
songTitle = GameDataService.GetMusicNameBySongId(musicDetailDictionary, (uint)SongId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
songArtist = GameDataService.GetMusicArtistBySongId(musicDetailDictionary, (uint)SongId, string.IsNullOrEmpty(songNameLanguage) ? "ja" : songNameLanguage);
// Breadcrumbs
var formattedSongTitle = songTitle;
if (formattedSongTitle.Length > 20)
{
formattedSongTitle = string.Concat(formattedSongTitle.AsSpan(0, 20), "...");
}
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Dashboard"], href: "/"));
}
else
{
breadcrumbs.Add(new BreadcrumbItem(Localizer["Users"], href: "/Users"));
};
breadcrumbs.Add(new BreadcrumbItem($"{userSetting?.MyDonName}", href: null, disabled: true));
breadcrumbs.Add(new BreadcrumbItem(Localizer["Song List"], href: $"/Users/{Baid}/Songs", disabled: false));
breadcrumbs.Add(new BreadcrumbItem(formattedSongTitle, href: $"/Users/{Baid}/Songs/{SongId}", disabled: false));
}
}
}

View File

@ -1,10 +1,11 @@
@inject IGameDataService GameDataService
@using Blazored.LocalStorage
@using TaikoWebUI.Utilities;
@inject IGameDataService GameDataService
@inject HttpClient Client
@inject AuthService AuthService
@inject IJSRuntime JsRuntime
@inject ILocalStorageService LocalStorage
@inject NavigationManager NavigationManager
@using TaikoWebUI.Utilities;
@using TaikoWebUI.Shared.Models;
@page "/Users/{baid:int}/Songs"
@ -27,7 +28,7 @@
else
{
<MudItem xs="12">
<MudTable Items="musicMap" Elevation="0" Outlined="true" Filter="@FilterSongs">
<MudTable Items="musicDetailDictionary.Values" Elevation="0" Outlined="true" Filter="@FilterSongs">
<ToolBarContent>
<MudGrid Spacing="2">
<MudItem xs="12" md="8">
@ -56,7 +57,7 @@
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicNameBySongId(context.SongId, CurrentLanguage)">
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicNameBySongId(musicDetailDictionary, context.SongId, SongNameLanguage)">
@Localizer["Song Title / Artist"]
</MudTableSortLabel>
</MudTh>
@ -70,7 +71,7 @@
@if (difficulty is not Difficulty.None)
{
<MudTh>
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicStarLevel(context.SongId, difficulty)">
<MudTableSortLabel T="MusicDetail" SortBy="context => GameDataService.GetMusicStarLevel(musicDetailDictionary, context.SongId, difficulty)">
<img src="@ScoreUtils.GetDifficultyIcon(difficulty)" alt="@ScoreUtils.GetDifficultyTitle(difficulty)" style="@Constants.ICON_STYLE" />
</MudTableSortLabel>
</MudTh>
@ -83,10 +84,10 @@
<div>
<a href="@($"/Users/{Baid}/Songs/{context.SongId}")">
<MudText Typo="Typo.body2" Style="font-weight:bold">
@GameDataService.GetMusicNameBySongId(context.SongId, CurrentLanguage)
@GameDataService.GetMusicNameBySongId(musicDetailDictionary, context.SongId, SongNameLanguage)
</MudText>
<MudText Typo="Typo.caption">
@GameDataService.GetMusicArtistBySongId(context.SongId, CurrentLanguage)
@GameDataService.GetMusicArtistBySongId(musicDetailDictionary, context.SongId, SongNameLanguage)
</MudText>
</a>
</div>
@ -110,7 +111,7 @@
{
@if (difficulty is not Difficulty.None)
{
var starLevel = GameDataService.GetMusicStarLevel(context.SongId, difficulty);
var starLevel = GameDataService.GetMusicStarLevel(musicDetailDictionary, context.SongId, difficulty);
<MudTd>
@if (starLevel > 0)
{

View File

@ -1,9 +1,4 @@
using System.Reflection.Emit;
using Microsoft.JSInterop;
using TaikoWebUI.Shared.Models;
namespace TaikoWebUI.Pages;
namespace TaikoWebUI.Pages;
public partial class SongList
{
@ -12,25 +7,33 @@ public partial class SongList
private string Search { get; set; } = string.Empty;
private string GenreFilter { get; set; } = string.Empty;
private string CurrentLanguage { get; set; } = "ja";
private string? SongNameLanguage { get; set; }
private SongBestResponse? response;
private UserSetting? userSetting;
private readonly List<BreadcrumbItem> breadcrumbs = new();
private List<MusicDetail> musicMap = new();
private Dictionary<uint, MusicDetail> musicDetailDictionary = new();
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
response = await Client.GetFromJsonAsync<SongBestResponse>($"api/PlayData/{Baid}");
response.ThrowIfNull();
userSetting = await Client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{Baid}");
musicMap = GameDataService.GetMusicList();
musicDetailDictionary = await GameDataService.GetMusicDetailDictionary();
CurrentLanguage = await JsRuntime.InvokeAsync<string>("blazorCulture.get");
SongNameLanguage = await LocalStorage.GetItemAsync<string>("songNameLanguage");
Console.WriteLine("Language: " + SongNameLanguage);
if (AuthService.IsLoggedIn && !AuthService.IsAdmin)
{

View File

@ -9,7 +9,7 @@
<MudText Typo="Typo.h4">@Localizer["Users"]</MudText>
<MudGrid Class="my-8">
@if (!AuthService.LoginRequired || (AuthService.LoginRequired && AuthService.IsAdmin)) {
if (users == null) {
if (usersWithSettings == null) {
// Loading...
for (uint i = 0; i < 6; i++) {
<MudItem xs="12" md="6" lg="4">
@ -28,11 +28,11 @@
</MudCard>
</MudItem>
}
} else if (users.Count > 0) {
foreach (var user in users)
} else if (usersWithSettings.Count > 0) {
foreach (var (user, userSetting) in usersWithSettings)
{
<MudItem xs="12" md="6" lg="4">
<UserCard User="user" />
<UserCard User="user" UserSetting="userSetting" />
</MudItem>
}
} else { // No users in the database

View File

@ -2,14 +2,30 @@
public partial class Users
{
private List<User>? users;
// Tuple of User and UserSetting
private List<(User, UserSetting)>? usersWithSettings;
protected override async Task OnInitializedAsync()
{
await base.OnInitializedAsync();
if (AuthService.LoginRequired && !AuthService.IsLoggedIn)
{
await AuthService.LoginWithAuthToken();
}
if (AuthService.IsAdmin || !AuthService.LoginRequired)
{
users = await Client.GetFromJsonAsync<List<User>>("api/Users");
var users = await Client.GetFromJsonAsync<List<User>>("api/Users");
var userSettings = await Client.GetFromJsonAsync<List<UserSetting>>("api/UserSettings");
if (users != null && userSettings != null)
{
// Combine User and UserSetting with the same Baid
usersWithSettings = users.Join(userSettings,
user => user.Baid,
setting => setting.Baid,
(user, setting) => (user, setting))
.ToList();
}
}
}
}

View File

@ -10,6 +10,16 @@ var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after");
// Create a temporary HttpClient to fetch the appsettings.json file
using var httpClient = new HttpClient();
httpClient.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress);
var configurationStream = await httpClient.GetStreamAsync("appsettings.json");
// Load the configuration from the stream
var configuration = new ConfigurationBuilder()
.AddJsonStream(configurationStream)
.Build();
builder.Services.AddSingleton(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
@ -17,18 +27,16 @@ builder.Services.AddSingleton(sp => new HttpClient
builder.Services.AddMudServices();
builder.Services.AddSingleton<IGameDataService, GameDataService>();
builder.Services.Configure<WebUiSettings>(builder.Configuration.GetSection(nameof(WebUiSettings)));
// Configure WebUiSettings using the loaded configuration
builder.Services.Configure<WebUiSettings>(configuration.GetSection(nameof(WebUiSettings)));
builder.Services.AddScoped<AuthService>();
builder.Services.AddLocalization();
builder.Services.AddSingleton<MudLocalizer, ResXMudLocalizer>();
builder.Services.AddSingleton<ScoreUtils>();
builder.Services.AddSingleton<StringUtil>();
builder.Services.AddBlazoredLocalStorage();
var host = builder.Build();
var gameDataService = host.Services.GetRequiredService<IGameDataService>();
@ -51,4 +59,4 @@ else
CultureInfo.DefaultThreadCurrentCulture = culture;
CultureInfo.DefaultThreadCurrentUICulture = culture;
await host.RunAsync();
await host.RunAsync();

View File

@ -1,5 +1,4 @@
using System.Diagnostics;
using System.IdentityModel.Tokens.Jwt;
using System.IdentityModel.Tokens.Jwt;
using System.Net.Http.Headers;
using System.Security.Claims;
using System.Text.Json;
@ -23,8 +22,10 @@ public sealed class AuthService
public bool IsAdmin { get; private set; }
private readonly ILocalStorageService localStorage;
private readonly HttpClient client;
private readonly NavigationManager navigationManager;
public AuthService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage, HttpClient client)
public AuthService(IOptions<WebUiSettings> settings, ILocalStorageService localStorage, HttpClient client,
NavigationManager navigationManager)
{
this.localStorage = localStorage;
IsLoggedIn = false;
@ -37,6 +38,7 @@ public sealed class AuthService
AllowUserDelete = webUiSettings.AllowUserDelete;
AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing;
this.client = client;
this.navigationManager = navigationManager;
}
private void OnLoginStatusChanged()
@ -105,11 +107,19 @@ public sealed class AuthService
public async Task LoginWithAuthToken()
{
var hasAuthToken = await localStorage.ContainKeyAsync("authToken");
if (!hasAuthToken) return;
if (!hasAuthToken)
{
navigationManager.NavigateTo("/Login");
return;
}
// Attempt to get JWT token from local storage
var authToken = await localStorage.GetItemAsync<string>("authToken");
if (authToken == null) return;
if (authToken == null)
{
navigationManager.NavigateTo("/Login");
return;
}
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", authToken);
var responseMessage = await client.PostAsync("api/Auth/LoginWithToken", null);
@ -117,6 +127,7 @@ public sealed class AuthService
{
// Clear JWT token
await localStorage.RemoveItemAsync("authToken");
navigationManager.NavigateTo("/Login");
return;
}

View File

@ -1,38 +1,21 @@
using System.Collections.Immutable;
using Swan.Mapping;
using TaikoWebUI.Shared.Models;
namespace TaikoWebUI.Services;
public class GameDataService : IGameDataService
{
private readonly HttpClient client;
private readonly Dictionary<uint, MusicDetail> musicMap = new();
private ImmutableDictionary<uint, DanData> danMap = ImmutableDictionary<uint, DanData>.Empty;
private ImmutableHashSet<Title> titles = ImmutableHashSet<Title>.Empty;
private string[] bodyTitles = { };
private string[] faceTitles = { };
private string[] headTitles = { };
private string[] kigurumiTitles = { };
private string[] puchiTitles = { };
private Dictionary<uint, MusicDetail>? musicDetailDictionary = new();
private List<Costume>? costumeList;
private Dictionary<uint,Title>? titleDictionary = new();
private List<uint> kigurumiUniqueIdList = new();
private List<uint> headUniqueIdList = new();
private List<uint> bodyUniqueIdList = new();
private List<uint> faceUniqueIdList = new();
private List<uint> puchiUniqueIdList = new();
private List<uint> titleUniqueIdList = new();
private List<uint> titlePlateIdList = new();
private bool musicDetailInitialized;
private bool costumesInitialized;
private bool titlesInitialized;
private List<uint> lockedKigurumiUniqueIdList = new();
private List<uint> lockedHeadUniqueIdList = new();
private List<uint> lockedBodyUniqueIdList = new();
private List<uint> lockedFaceUniqueIdList = new();
private List<uint> lockedPuchiUniqueIdList = new();
private List<uint> lockedTitleUniqueIdList = new();
private List<uint> lockedTitlePlateIdList = new();
private Dictionary<string, List<uint>>? lockedCostumeDataDictionary = new();
private Dictionary<string, List<uint>>? lockedTitleDataDictionary = new();
public GameDataService(HttpClient client)
{
@ -42,58 +25,64 @@ public class GameDataService : IGameDataService
public async Task InitializeAsync(string dataBaseUrl)
{
dataBaseUrl = dataBaseUrl.TrimEnd('/');
var musicInfo = await GetData<MusicInfo>(dataBaseUrl, Constants.MUSIC_INFO_BASE_NAME);
var wordList = await GetData<WordList>(dataBaseUrl, Constants.WORDLIST_BASE_NAME);
var musicOrder = await GetData<MusicOrder>(dataBaseUrl, Constants.MUSIC_ORDER_BASE_NAME);
var donCosRewardData = await GetData<DonCosRewards>(dataBaseUrl, Constants.DON_COS_REWARD_BASE_NAME);
var shougouData = await GetData<Shougous>(dataBaseUrl, Constants.SHOUGOU_BASE_NAME);
var danData = await client.GetFromJsonAsync<List<DanData>>($"{dataBaseUrl}/data/dan_data.json");
danData.ThrowIfNull();
danMap = danData.ToImmutableDictionary(data => data.DanId);
}
public async Task<Dictionary<uint, MusicDetail>> GetMusicDetailDictionary()
{
if (!musicDetailInitialized)
{
await InitializeMusicDetailAsync();
}
// To prevent duplicate entries in wordlist
var wordlistDict = wordList.WordListEntries.GroupBy(entry => entry.Key)
.ToImmutableDictionary(group => group.Key, group => group.First());
await Task.Run(() => InitializeMusicMap(musicInfo, wordlistDict, musicOrder));
return musicDetailDictionary ?? new Dictionary<uint, MusicDetail>();
}
public async Task<List<Costume>> GetCostumeList()
{
if (!costumesInitialized)
{
await InitializeCostumesAsync();
}
await Task.Run(() => InitializeCostumeIdLists(donCosRewardData));
await Task.Run(() => InitializeTitleIdList(shougouData));
await Task.Run(() => InitializeHeadTitles(wordlistDict));
await Task.Run(() => InitializeFaceTitles(wordlistDict));
await Task.Run(() => InitializeBodyTitles(wordlistDict));
await Task.Run(() => InitializePuchiTitles(wordlistDict));
await Task.Run(() => InitializeKigurumiTitles(wordlistDict));
await Task.Run(() => InitializeTitles(wordlistDict, shougouData));
return costumeList ?? new List<Costume>();
}
public async Task<Dictionary<uint, Title>> GetTitleDictionary()
{
if (!titlesInitialized)
{
await InitializeTitlesAsync();
}
var lockedCostumeDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>($"{dataBaseUrl}/data/locked_costume_data.json") ?? throw new InvalidOperationException();
lockedKigurumiUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Kigurumi") ?? new List<uint>();
lockedHeadUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Head") ?? new List<uint>();
lockedBodyUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Body") ?? new List<uint>();
lockedFaceUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Face") ?? new List<uint>();
lockedPuchiUniqueIdList = lockedCostumeDataDictionary.GetValueOrDefault("Puchi") ?? new List<uint>();
return titleDictionary ?? new Dictionary<uint, Title>();
}
public async Task<Dictionary<string, List<uint>>> GetLockedCostumeDataDictionary()
{
if (!costumesInitialized)
{
await InitializeCostumesAsync();
}
var lockedTitleDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>($"{dataBaseUrl}/data/locked_title_data.json") ?? throw new InvalidOperationException();
lockedTitleUniqueIdList = lockedTitleDataDictionary.GetValueOrDefault("TitleNo") ?? new List<uint>();
lockedTitlePlateIdList = lockedTitleDataDictionary.GetValueOrDefault("TitlePlateNo") ?? new List<uint>();
return lockedCostumeDataDictionary ?? new Dictionary<string, List<uint>>();
}
public async Task<Dictionary<string, List<uint>>> GetLockedTitleDataDictionary()
{
if (!titlesInitialized)
{
await InitializeTitlesAsync();
}
return lockedTitleDataDictionary ?? new Dictionary<string, List<uint>>();
}
private async Task<T> GetData<T>(string dataBaseUrl, string fileBaseName) where T : notnull
public string GetMusicNameBySongId(Dictionary<uint, MusicDetail> musicDetails, uint songId, string? language = "ja")
{
var data = await client.GetFromJsonAsync<T>($"{dataBaseUrl}/data/datatable/{fileBaseName}.json");
data.ThrowIfNull();
return data;
}
public List<MusicDetail> GetMusicList()
{
return musicMap.Values.Where(musicDetail => musicDetail.SongId != 0).ToList();
}
public string GetMusicNameBySongId(uint songId, string? language = "ja")
{
return musicMap.TryGetValue(songId, out var musicDetail) ? language switch
return musicDetails.TryGetValue(songId, out var musicDetail) ? language switch
{
"ja" => musicDetail.SongName,
"en-US" => musicDetail.SongNameEN,
@ -104,9 +93,9 @@ public class GameDataService : IGameDataService
} : string.Empty;
}
public string GetMusicArtistBySongId(uint songId, string? language = "ja")
public string GetMusicArtistBySongId(Dictionary<uint, MusicDetail> musicDetails, uint songId, string? language = "ja")
{
return musicMap.TryGetValue(songId, out var musicDetail) ? language switch
return musicDetails.TryGetValue(songId, out var musicDetail) ? language switch
{
"jp" => musicDetail.ArtistName,
"en-US" => musicDetail.ArtistNameEN,
@ -117,14 +106,14 @@ public class GameDataService : IGameDataService
} : string.Empty;
}
public SongGenre GetMusicGenreBySongId(uint songId)
public SongGenre GetMusicGenreBySongId(Dictionary<uint, MusicDetail> musicDetails, uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Genre : SongGenre.Variety;
return musicDetails.TryGetValue(songId, out var musicDetail) ? musicDetail.Genre : SongGenre.Variety;
}
public int GetMusicIndexBySongId(uint songId)
public int GetMusicIndexBySongId(Dictionary<uint, MusicDetail> musicDetails, uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Index : int.MaxValue;
return musicDetails.TryGetValue(songId, out var musicDetail) ? musicDetail.Index : int.MaxValue;
}
public DanData GetDanDataById(uint danId)
@ -132,9 +121,9 @@ public class GameDataService : IGameDataService
return danMap.GetValueOrDefault(danId, new DanData());
}
public int GetMusicStarLevel(uint songId, Difficulty difficulty)
public int GetMusicStarLevel(Dictionary<uint, MusicDetail> musicDetails, uint songId, Difficulty difficulty)
{
var success = musicMap.TryGetValue(songId, out var musicDetail);
var success = musicDetails.TryGetValue(songId, out var musicDetail);
return difficulty switch
{
Difficulty.None => throw new ArgumentException("Difficulty cannot be none"),
@ -147,261 +136,48 @@ public class GameDataService : IGameDataService
};
}
public string GetHeadTitle(uint index)
public string GetHeadTitle(IEnumerable<Costume> costumes, uint index)
{
return index < headTitles.Length ? headTitles[index] : string.Empty;
return costumes.FirstOrDefault(costume => costume.CostumeType == "head" && costume.CostumeId == index)?.CostumeName ?? string.Empty;
}
public string GetKigurumiTitle(uint index)
public string GetKigurumiTitle(IEnumerable<Costume> costumes, uint index)
{
return index < kigurumiTitles.Length ? kigurumiTitles[index] : string.Empty;
return costumes.FirstOrDefault(costume => costume.CostumeType == "kigurumi" && costume.CostumeId == index)?.CostumeName ?? string.Empty;
}
public string GetBodyTitle(uint index)
public string GetBodyTitle(IEnumerable<Costume> costumes, uint index)
{
return index < bodyTitles.Length ? bodyTitles[index] : string.Empty;
return costumes.FirstOrDefault(costume => costume.CostumeType == "body" && costume.CostumeId == index)?.CostumeName ?? string.Empty;
}
public string GetFaceTitle(uint index)
public string GetFaceTitle(IEnumerable<Costume> costumes, uint index)
{
return index < faceTitles.Length ? faceTitles[index] : string.Empty;
return costumes.FirstOrDefault(costume => costume.CostumeType == "face" && costume.CostumeId == index)?.CostumeName ?? string.Empty;
}
public string GetPuchiTitle(uint index)
public string GetPuchiTitle(IEnumerable<Costume> costumes, uint index)
{
return index < puchiTitles.Length ? puchiTitles[index] : string.Empty;
}
public ImmutableHashSet<Title> GetTitles()
{
return titles;
return costumes.FirstOrDefault(costume => costume.CostumeType == "puchi" && costume.CostumeId == index)?.CostumeName ?? string.Empty;
}
public List<uint> GetKigurumiUniqueIdList()
private async Task InitializeMusicDetailAsync()
{
return kigurumiUniqueIdList;
musicDetailDictionary = await client.GetFromJsonAsync<Dictionary<uint, MusicDetail>>("api/GameData/MusicDetails");
musicDetailInitialized = true;
}
public List<uint> GetHeadUniqueIdList()
private async Task InitializeCostumesAsync()
{
return headUniqueIdList;
costumeList = await client.GetFromJsonAsync<List<Costume>>("api/GameData/Costumes");
lockedCostumeDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>("api/GameData/LockedCostumes");
costumesInitialized = true;
}
public List<uint> GetBodyUniqueIdList()
private async Task InitializeTitlesAsync()
{
return bodyUniqueIdList;
}
public List<uint> GetFaceUniqueIdList()
{
return faceUniqueIdList;
}
public List<uint> GetPuchiUniqueIdList()
{
return puchiUniqueIdList;
}
public List<uint> GetTitleUniqueIdList()
{
return titleUniqueIdList;
}
public List<uint> GetTitlePlateIdList()
{
return titlePlateIdList;
}
private void InitializeTitleIdList(Shougous? shougouData)
{
shougouData.ThrowIfNull("Shouldn't happen!");
titleUniqueIdList = shougouData.ShougouEntries.Select(entry => entry.UniqueId).ToList();
}
private void InitializeTitles(ImmutableDictionary<string, WordListEntry> dict, Shougous? shougouData)
{
shougouData.ThrowIfNull("Shouldn't happen!");
var set = ImmutableHashSet.CreateBuilder<Title>();
foreach (var i in titleUniqueIdList)
{
var key = $"syougou_{i}";
var titleWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
var titleRarity = shougouData.ShougouEntries
.Where(entry => entry.UniqueId == i)
.Select(entry => entry.Rarity)
.FirstOrDefault();
if (!titlePlateIdList.Contains(titleRarity))
{
titlePlateIdList.Add(titleRarity);
}
set.Add(new Title
{
TitleName = titleWordlistItem.JapaneseText,
TitleId = i,
TitleRarity = titleRarity
});
}
titles = set.ToImmutable();
}
private void InitializeCostumeIdLists(DonCosRewards? donCosRewardData)
{
donCosRewardData.ThrowIfNull("Shouldn't happen!");
kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "kigurumi")
.Select(entry => entry.UniqueId).ToList();
headUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "head")
.Select(entry => entry.UniqueId).ToList();
bodyUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "body")
.Select(entry => entry.UniqueId).ToList();
faceUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "face")
.Select(entry => entry.UniqueId).ToList();
puchiUniqueIdList = donCosRewardData.DonCosRewardEntries
.Where(entry => entry.CosType == "puchi")
.Select(entry => entry.UniqueId).ToList();
}
private void InitializeKigurumiTitles(ImmutableDictionary<string, WordListEntry> dict)
{
kigurumiTitles = new string[kigurumiUniqueIdList.Max() + 1];
foreach (var i in kigurumiUniqueIdList)
{
var key = $"costume_kigurumi_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
kigurumiTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeHeadTitles(ImmutableDictionary<string, WordListEntry> dict)
{
headTitles = new string[headUniqueIdList.Max() + 1];
foreach (var i in headUniqueIdList)
{
var key = $"costume_head_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
headTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeBodyTitles(ImmutableDictionary<string, WordListEntry> dict)
{
bodyTitles = new string[bodyUniqueIdList.Max() + 1];
foreach (var i in bodyUniqueIdList)
{
var key = $"costume_body_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
bodyTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeFaceTitles(ImmutableDictionary<string, WordListEntry> dict)
{
faceTitles = new string[faceUniqueIdList.Max() + 1];
foreach (var i in faceUniqueIdList)
{
var key = $"costume_face_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
faceTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializePuchiTitles(ImmutableDictionary<string, WordListEntry> dict)
{
puchiTitles = new string[puchiUniqueIdList.Max() + 1];
foreach (var i in puchiUniqueIdList)
{
var key = $"costume_puchi_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
puchiTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeMusicMap(MusicInfo musicInfo, ImmutableDictionary<string, WordListEntry> dict,
MusicOrder musicOrder)
{
foreach (var music in musicInfo.Items)
{
var songNameKey = $"song_{music.Id}";
var songArtistKey = $"song_sub_{music.Id}";
var musicName = dict.GetValueOrDefault(songNameKey, new WordListEntry());
var musicArtist = dict.GetValueOrDefault(songArtistKey, new WordListEntry());
var musicSongId = music.SongId;
var musicDetail = music.CopyPropertiesToNew<MusicDetail>();
musicDetail.SongName = musicName.JapaneseText;
musicDetail.ArtistName = musicArtist.JapaneseText;
// Add localized names
musicDetail.SongNameEN = musicName.EnglishUsText;
musicDetail.ArtistNameEN = musicArtist.EnglishUsText;
musicDetail.SongNameCN = musicName.ChineseTText;
musicDetail.ArtistNameCN = musicArtist.ChineseTText;
musicDetail.SongNameKO = musicName.KoreanText;
musicDetail.ArtistNameKO = musicArtist.KoreanText;
musicMap.TryAdd(musicSongId, musicDetail);
}
for (var index = 0; index < musicOrder.Order.Count; index++)
{
var musicOrderEntry = musicOrder.Order[index];
var songId = musicOrderEntry.SongId;
if (musicMap.TryGetValue(songId, out var value))
{
value.Index = index;
}
}
}
public List<uint> GetLockedKigurumiUniqueIdList()
{
return lockedKigurumiUniqueIdList;
}
public List<uint> GetLockedHeadUniqueIdList()
{
return lockedHeadUniqueIdList;
}
public List<uint> GetLockedBodyUniqueIdList()
{
return lockedBodyUniqueIdList;
}
public List<uint> GetLockedFaceUniqueIdList()
{
return lockedFaceUniqueIdList;
}
public List<uint> GetLockedPuchiUniqueIdList()
{
return lockedPuchiUniqueIdList;
}
public List<uint> GetLockedTitleUniqueIdList()
{
return lockedTitleUniqueIdList;
}
public List<uint> GetLockedTitlePlateIdList()
{
return lockedTitlePlateIdList;
titleDictionary = await client.GetFromJsonAsync<Dictionary<uint, Title>>("api/GameData/Titles");
lockedTitleDataDictionary = await client.GetFromJsonAsync<Dictionary<string, List<uint>>>("api/GameData/LockedTitles");
titlesInitialized = true;
}
}

View File

@ -1,47 +1,34 @@
using System.Collections.Immutable;
using TaikoWebUI.Shared.Models;
namespace TaikoWebUI.Services;
namespace TaikoWebUI.Services;
public interface IGameDataService
{
public Task InitializeAsync(string dataBaseUrl);
public List<MusicDetail> GetMusicList();
public Task<Dictionary<uint, MusicDetail>> GetMusicDetailDictionary();
public Task<Dictionary<uint, Title>> GetTitleDictionary();
public Task<List<Costume>> GetCostumeList();
public Task<Dictionary<string, List<uint>>> GetLockedCostumeDataDictionary();
public Task<Dictionary<string, List<uint>>> GetLockedTitleDataDictionary();
public string GetMusicNameBySongId(uint songId, string? language = null);
public string GetMusicNameBySongId(Dictionary<uint, MusicDetail> musicDetails,uint songId, string? language = null);
public string GetMusicArtistBySongId(uint songId, string? language = null);
public string GetMusicArtistBySongId(Dictionary<uint, MusicDetail> musicDetails,uint songId, string? language = null);
public SongGenre GetMusicGenreBySongId(uint songId);
public SongGenre GetMusicGenreBySongId(Dictionary<uint, MusicDetail> musicDetails,uint songId);
public int GetMusicIndexBySongId(uint songId);
public int GetMusicIndexBySongId(Dictionary<uint, MusicDetail> musicDetails,uint songId);
public DanData GetDanDataById(uint danId);
public int GetMusicStarLevel(uint songId, Difficulty difficulty);
public int GetMusicStarLevel(Dictionary<uint, MusicDetail> musicDetails, uint songId, Difficulty difficulty);
public string GetHeadTitle(uint index);
public string GetKigurumiTitle(uint index);
public string GetBodyTitle(uint index);
public string GetFaceTitle(uint index);
public string GetPuchiTitle(uint index);
public List<uint> GetKigurumiUniqueIdList();
public List<uint> GetHeadUniqueIdList();
public List<uint> GetBodyUniqueIdList();
public List<uint> GetFaceUniqueIdList();
public List<uint> GetPuchiUniqueIdList();
public List<uint> GetTitleUniqueIdList();
public List<uint> GetTitlePlateIdList();
public List<uint> GetLockedKigurumiUniqueIdList();
public List<uint> GetLockedHeadUniqueIdList();
public List<uint> GetLockedBodyUniqueIdList();
public List<uint> GetLockedFaceUniqueIdList();
public List<uint> GetLockedPuchiUniqueIdList();
public List<uint> GetLockedTitleUniqueIdList();
public List<uint> GetLockedTitlePlateIdList();
public ImmutableHashSet<Title> GetTitles();
public string GetHeadTitle(IEnumerable<Costume> costumes, uint index);
public string GetKigurumiTitle(IEnumerable<Costume> costumes, uint index);
public string GetBodyTitle(IEnumerable<Costume> costumes, uint index);
public string GetFaceTitle(IEnumerable<Costume> costumes, uint index);
public string GetPuchiTitle(IEnumerable<Costume> costumes, uint index);
}

View File

@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class DonCosRewardEntry
{
[JsonPropertyName("cosType")]
public string CosType { get; set; } = null!;
[JsonPropertyName("uniqueId")]
public uint UniqueId { get; set; }
}

View File

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class DonCosRewards
{
[JsonPropertyName("items")]
public List<DonCosRewardEntry> DonCosRewardEntries { get; set; } = new();
}

View File

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class MusicInfo
{
[JsonPropertyName("items")]
public List<MusicInfoEntry> Items { get; set; } = new();
}

View File

@ -1,30 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class MusicInfoEntry
{
[JsonPropertyName("id")]
public string Id { get; set; } = string.Empty;
[JsonPropertyName("uniqueId")]
public uint SongId { get; set; }
[JsonPropertyName("genreNo")]
public SongGenre Genre { get; set; }
[JsonPropertyName("starEasy")]
public int StarEasy { get; set; }
[JsonPropertyName("starNormal")]
public int StarNormal { get; set; }
[JsonPropertyName("starHard")]
public int StarHard { get; set; }
[JsonPropertyName("starMania")]
public int StarOni { get; set; }
[JsonPropertyName("starUra")]
public int StarUra { get; set; }
}

View File

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class NeiroEntry
{
[JsonPropertyName("uniqueId")]
public uint UniqueId { get; set; }
}

View File

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class Neiros
{
[JsonPropertyName("items")]
public List<NeiroEntry> NeiroEntries { get; set; } = new();
}

View File

@ -1,12 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class ShougouEntry
{
[JsonPropertyName("uniqueId")]
public uint UniqueId { get; set; }
[JsonPropertyName("rarity")]
public uint Rarity { get; set; }
}

View File

@ -1,9 +0,0 @@
using System.Text.Json.Serialization;
namespace TaikoWebUI.Shared.Models;
public class Shougous
{
[JsonPropertyName("items")]
public List<ShougouEntry> ShougouEntries { get; set; } = new();
}

View File

@ -1,6 +0,0 @@
namespace TaikoWebUI.Shared.Models
{
public class SongListEntry
{
}
}