1
0
mirror of synced 2025-01-30 11:57:23 +01:00

Merge pull request #5 from asesidaa/CostumeUI

Add costume menu to profile page, add player titles
This commit is contained in:
asesidaa 2022-09-16 00:08:48 +08:00 committed by GitHub
commit f9cefb6c1f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 694 additions and 183 deletions

View File

@ -20,9 +20,36 @@ public class UserSetting
public int NotesPosition { get; set; } public int NotesPosition { get; set; }
public string MyDonName { get; set; } = String.Empty; public string MyDonName { get; set; } = string.Empty;
public string Title { get; set; } = String.Empty; public string Title { get; set; } = string.Empty;
public uint TitlePlateId { get; set; } public uint TitlePlateId { get; set; }
public uint Kigurumi { get; set; }
public uint Head { get; set; }
public uint Body { get; set; }
public uint Face { get; set; }
public uint Puchi { get; set; }
public List<uint> UnlockedKigurumi { get; set; } = new();
public List<uint> UnlockedHead { get; set; } = new();
public List<uint> UnlockedBody { get; set; } = new();
public List<uint> UnlockedFace { get; set; } = new();
public List<uint> UnlockedPuchi { get; set; } = new();
public uint FaceColor { get; set; }
public uint BodyColor { get; set; }
public uint LimbColor { get; set; }
} }

View File

@ -1,4 +1,6 @@
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation"> <wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
<s:Boolean x:Key="/Default/UserDictionary/Words/=Kigurumi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=musicinfo/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=musicinfo/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Namco/@EntryIndexedValue">True</s:Boolean> <s:Boolean x:Key="/Default/UserDictionary/Words/=Namco/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Puchi/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Vocaloid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary> <s:Boolean x:Key="/Default/UserDictionary/Words/=Vocaloid/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

View File

@ -0,0 +1,52 @@
using System.Text.Json;
namespace TaikoLocalServer.Common.Utils;
public static class JsonHelper
{
public static List<uint> GetCostumeDataFromUserData(UserDatum userData, ILogger logger)
{
var costumeData = new List<uint> { 0, 0, 0, 0, 0 };
try
{
costumeData = JsonSerializer.Deserialize<List<uint>>(userData.CostumeData);
}
catch (JsonException e)
{
logger.LogError(e, "Parsing costume json data failed");
}
if (costumeData != null && costumeData.Count >= 5)
{
return costumeData;
}
logger.LogWarning("Costume data is null or count less than 5!");
costumeData = new List<uint> { 0, 0, 0, 0, 0 };
return costumeData;
}
public static List<List<uint>> GetCostumeUnlockDataFromUserData(UserDatum userData, ILogger logger)
{
var costumeUnlockData = new List<List<uint>> { new(), new(), new(), new(), new() };
try
{
costumeUnlockData = JsonSerializer.Deserialize<List<List<uint>>>(userData.CostumeFlgArray);
}
catch (JsonException e)
{
logger.LogError(e, "Parsing costume json data failed");
}
if (costumeUnlockData != null && costumeUnlockData.Count >= 5)
{
return costumeUnlockData;
}
logger.LogWarning("Costume unlock data is null or count less than 5!");
costumeUnlockData = new List<List<uint>> { new(), new(), new(), new(), new() };
return costumeUnlockData;
}
}

View File

@ -2,7 +2,7 @@
public static class PathHelper public static class PathHelper
{ {
public static string GetDataPath() public static string GetRootPath()
{ {
var path = Environment.ProcessPath; var path = Environment.ProcessPath;
if (path is null) if (path is null)
@ -14,6 +14,11 @@ public static class PathHelper
{ {
throw new ApplicationException(); throw new ApplicationException();
} }
return Path.Combine(parentPath.ToString(), "wwwroot", "data"); return Path.Combine(parentPath.ToString(), "wwwroot");
}
public static string GetDataPath()
{
return Path.Combine(GetRootPath(), "data");
} }
} }

View File

@ -22,7 +22,7 @@
{ {
return; return;
} }
var path = Path.Combine(PathHelper.GetDataPath(), Constants.DEFAULT_DB_NAME); var path = Path.Combine(PathHelper.GetRootPath(), Constants.DEFAULT_DB_NAME);
optionsBuilder.UseSqlite($"Data Source={path}"); optionsBuilder.UseSqlite($"Data Source={path}");
} }

View File

@ -1,9 +1,11 @@
using System.Buffers.Binary; using System.Buffers.Binary;
using System.Text.Json;
using SharedProject.Models; using SharedProject.Models;
using SharedProject.Models.Responses; using SharedProject.Models.Responses;
using SharedProject.Utils; using SharedProject.Utils;
using TaikoLocalServer.Services; using TaikoLocalServer.Services;
using TaikoLocalServer.Services.Interfaces; using TaikoLocalServer.Services.Interfaces;
using Throw;
namespace TaikoLocalServer.Controllers.Api; namespace TaikoLocalServer.Controllers.Api;
@ -28,6 +30,10 @@ public class UserSettingsController : BaseController<UserSettingsController>
return NotFound(); return NotFound();
} }
var costumeData = JsonHelper.GetCostumeDataFromUserData(user, Logger);
var costumeUnlockData = JsonHelper.GetCostumeUnlockDataFromUserData(user, Logger);
var response = new UserSetting var response = new UserSetting
{ {
AchievementDisplayDifficulty = user.AchievementDisplayDifficulty, AchievementDisplayDifficulty = user.AchievementDisplayDifficulty,
@ -40,7 +46,20 @@ public class UserSettingsController : BaseController<UserSettingsController>
ToneId = user.SelectedToneId, ToneId = user.SelectedToneId,
MyDonName = user.MyDonName, MyDonName = user.MyDonName,
Title = user.Title, Title = user.Title,
TitlePlateId = user.TitlePlateId TitlePlateId = user.TitlePlateId,
Kigurumi = costumeData[0],
Head = costumeData[1],
Body = costumeData[2],
Face = costumeData[3],
Puchi = costumeData[4],
UnlockedKigurumi = costumeUnlockData[0],
UnlockedHead = costumeUnlockData[1],
UnlockedBody = costumeUnlockData[2],
UnlockedFace = costumeUnlockData[3],
UnlockedPuchi = costumeUnlockData[4],
BodyColor = user.ColorBody,
FaceColor = user.ColorFace,
LimbColor = user.ColorLimb
}; };
return Ok(response); return Ok(response);
} }
@ -55,6 +74,15 @@ public class UserSettingsController : BaseController<UserSettingsController>
return NotFound(); return NotFound();
} }
var costumes = new List<uint>
{
userSetting.Kigurumi,
userSetting.Head,
userSetting.Body,
userSetting.Face,
userSetting.Puchi,
};
user.IsSkipOn = userSetting.IsSkipOn; user.IsSkipOn = userSetting.IsSkipOn;
user.IsVoiceOn = userSetting.IsVoiceOn; user.IsVoiceOn = userSetting.IsVoiceOn;
user.DisplayAchievement = userSetting.IsDisplayAchievement; user.DisplayAchievement = userSetting.IsDisplayAchievement;
@ -66,6 +94,11 @@ public class UserSettingsController : BaseController<UserSettingsController>
user.MyDonName = userSetting.MyDonName; user.MyDonName = userSetting.MyDonName;
user.Title = userSetting.Title; user.Title = userSetting.Title;
user.TitlePlateId = userSetting.TitlePlateId; user.TitlePlateId = userSetting.TitlePlateId;
user.ColorBody = userSetting.BodyColor;
user.ColorFace = userSetting.FaceColor;
user.ColorLimb = userSetting.LimbColor;
user.CostumeData = JsonSerializer.Serialize(costumes);
await userDatumService.UpdateUserDatum(user); await userDatumService.UpdateUserDatum(user);

View File

@ -1,6 +1,4 @@
using System.Text.Json; using TaikoLocalServer.Services.Interfaces;
using TaikoLocalServer.Services.Interfaces;
using Throw;
namespace TaikoLocalServer.Controllers.Game; namespace TaikoLocalServer.Controllers.Game;
@ -79,34 +77,9 @@ public class BaidController : BaseController<BaidController>
var scoreRankCount = CalculateScoreRankCount(songCountData); var scoreRankCount = CalculateScoreRankCount(songCountData);
var costumeData = new List<uint>{ 0, 0, 0, 0, 0 }; var costumeData = JsonHelper.GetCostumeDataFromUserData(userData, Logger);
try
{
costumeData = JsonSerializer.Deserialize<List<uint>>(userData.CostumeData);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing costume json data failed");
}
if (costumeData == null || costumeData.Count < 5)
{
Logger.LogWarning("Costume data is null or count less than 5!");
costumeData = new List<uint> { 0, 0, 0, 0, 0 };
}
var costumeArrays = Array.Empty<uint[]>(); var costumeArrays = JsonHelper.GetCostumeUnlockDataFromUserData(userData, Logger);
try
{
costumeArrays = JsonSerializer.Deserialize<uint[][]>(userData.CostumeFlgArray);
}
catch (JsonException e)
{
Logger.LogError(e, "Parsing costume flg json data failed");
}
// The only way to get a null is provide string "null" as input,
// which means database content need to be fixed, so better throw
costumeArrays.ThrowIfNull("Costume flg should never be null!");
var costumeFlagArrays = Constants.CostumeFlagArraySizes var costumeFlagArrays = Constants.CostumeFlagArraySizes
.Select((size, index) => FlagCalculator.GetBitArrayFromIds(costumeArrays[index], size, Logger)) .Select((size, index) => FlagCalculator.GetBitArrayFromIds(costumeArrays[index], size, Logger))

View File

@ -6,10 +6,10 @@
public string MyDonName { get; set; } = string.Empty; public string MyDonName { get; set; } = string.Empty;
public string Title { get; set; } = string.Empty; public string Title { get; set; } = string.Empty;
public uint TitlePlateId { get; set; } public uint TitlePlateId { get; set; }
public string FavoriteSongsArray { get; set; } = string.Empty; public string FavoriteSongsArray { get; set; } = "[]";
public string ToneFlgArray { get; set; } = string.Empty; public string ToneFlgArray { get; set; } = "[]";
public string TitleFlgArray { get; set; } = string.Empty; public string TitleFlgArray { get; set; } = "[]";
public string CostumeFlgArray { get; set; } = string.Empty; public string CostumeFlgArray { get; set; } = "[]";
public short OptionSetting { get; set; } public short OptionSetting { get; set; }
public int NotesPosition { get; set; } public int NotesPosition { get; set; }
public bool IsVoiceOn { get; set; } public bool IsVoiceOn { get; set; }
@ -20,7 +20,7 @@
public uint ColorBody { get; set; } public uint ColorBody { get; set; }
public uint ColorFace { get; set; } public uint ColorFace { get; set; }
public uint ColorLimb { get; set; } public uint ColorLimb { get; set; }
public string CostumeData { get; set; } = string.Empty; public string CostumeData { get; set; } = "[[],[],[],[],[]]";
public bool DisplayDan { get; set; } public bool DisplayDan { get; set; }
public bool DisplayAchievement { get; set; } public bool DisplayAchievement { get; set; }
public Difficulty AchievementDisplayDifficulty { get; set; } public Difficulty AchievementDisplayDifficulty { get; set; }

View File

@ -30,7 +30,7 @@ builder.Services.AddDbContext<TaikoDbContext>(option =>
{ {
dbName = Constants.DEFAULT_DB_NAME; dbName = Constants.DEFAULT_DB_NAME;
} }
var path = Path.Combine(PathHelper.GetDataPath(), dbName); var path = Path.Combine(PathHelper.GetRootPath(), dbName);
option.UseSqlite($"Data Source={path}"); option.UseSqlite($"Data Source={path}");
}); });
builder.Services.AddHttpLogging(options => builder.Services.AddHttpLogging(options =>

View File

@ -17,4 +17,6 @@ public interface IUserDatumService
public Task<List<uint>> GetFavoriteSongIds(uint baid); public Task<List<uint>> GetFavoriteSongIds(uint baid);
public Task UpdateFavoriteSong(uint baid, uint songId, bool isFavorite); public Task UpdateFavoriteSong(uint baid, uint songId, bool isFavorite);
} }

View File

@ -6,6 +6,7 @@ global using Microsoft.AspNetCore.Components.Web;
global using MudBlazor; global using MudBlazor;
global using TaikoWebUI; global using TaikoWebUI;
global using TaikoWebUI.Services; global using TaikoWebUI.Services;
global using TaikoWebUI.Shared;
global using SharedProject.Models; global using SharedProject.Models;
global using SharedProject.Models.Requests; global using SharedProject.Models.Requests;
global using SharedProject.Models.Responses; global using SharedProject.Models.Responses;

View File

@ -0,0 +1,94 @@
@using TaikoWebUI.Shared.Models
@using System.Collections.Immutable
@inject IGameDataService GameDataService
<MudDialog>
<DialogContent>
<MudTable Items="@titles" Filter="@Filter" @bind-SelectedItem="@selectedTitle" Height="40vh" Hover="true">
<ColGroup>
<col style="width: 50px;" />
<col />
</ColGroup>
<ToolBarContent>
<MudTextField @bind-Value="searchString" Placeholder="Search" Adornment="Adornment.Start" Immediate="true"
AdornmentIcon="@Icons.Material.Filled.Search" IconSize="Size.Medium" Class="mt-0">
</MudTextField>
</ToolBarContent>
<HeaderContent>
<MudTh>
<MudTableSortLabel SortBy="@(new Func<Title, object>(x => x.TitleId))">
ID
</MudTableSortLabel>
</MudTh>
<MudTh>
<MudTableSortLabel SortBy="@(new Func<Title, object>(x => x.TitleName))">
Title
</MudTableSortLabel>
</MudTh>
</HeaderContent>
<RowTemplate>
<MudTd DataLabel="Id" Class="cursor-pointer">@context.TitleId</MudTd>
<MudTd DataLabel="Title" Class="cursor-pointer">@context.TitleName</MudTd>
</RowTemplate>
<PagerContent>
<MudTablePager/>
</PagerContent>
</MudTable>
<MudText Class="mt-4 d-block" Typo="Typo.caption"><b>Selected Title:</b> @selectedTitle?.TitleName</MudText>
</DialogContent>
<DialogActions>
<MudButton OnClick="Cancel">Cancel</MudButton>
<MudButton Color="Color.Primary" OnClick="Submit">Ok</MudButton>
</DialogActions>
</MudDialog>
@code {
[CascadingParameter]
MudDialogInstance MudDialog { get; set; } = null!;
[Parameter]
public UserSetting UserSetting { get; set; } = new();
private IEnumerable<Title> titles = new List<Title>();
private Title? selectedTitle;
private string searchString = string.Empty;
protected override void OnInitialized()
{
base.OnInitialized();
var titleSet = GameDataService.GetTitles();
titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId));
var currentTitle = new Title
{
TitleName = UserSetting.Title
};
if (titleSet.Contains(currentTitle))
{
titleSet.TryGetValue(new Title
{
TitleName = UserSetting.Title
}, out selectedTitle);
}
}
private bool Filter(Title title)
{
return string.IsNullOrEmpty(searchString) ||
title.TitleName.Contains(searchString, StringComparison.InvariantCultureIgnoreCase);
}
private void Submit()
{
if (selectedTitle is not null)
{
UserSetting.Title = selectedTitle.TitleName;
}
MudDialog.Close(DialogResult.Ok(true));
}
private void Cancel() => MudDialog.Cancel();
}

View File

@ -1,5 +1,7 @@
@page "/Cards/{baid:int}/Profile" @page "/Cards/{baid:int}/Profile"
@inject HttpClient Client @inject HttpClient Client
@inject IGameDataService GameDataService
@inject IDialogService DialogService
<MudBreadcrumbs Items="breadcrumbs" Class="px-0"></MudBreadcrumbs> <MudBreadcrumbs Items="breadcrumbs" Class="px-0"></MudBreadcrumbs>
@ -8,107 +10,205 @@
@if (response is not null) @if (response is not null)
{ {
<MudGrid> <MudGrid Class="my-4 pb-10">
<MudItem xs="12" md="8"> <MudItem xs="12" md="8">
<MudPaper Class="py-8 px-8 my-8" Outlined="true"> <MudPaper Elevation="0" Outlined="true">
<MudStack Spacing="4"> <MudTabs Rounded="true" Border="true" PanelClass="pa-8">
<h2>Profile Options</h2> <MudTabPanel Text="Profile">
<MudStack Spacing="4">
<h2>Profile Options</h2>
<MudTextField @bind-Value="@response.MyDonName" Label="Name"></MudTextField> <MudTextField @bind-Value="@response.MyDonName" Label="Name"></MudTextField>
<MudGrid> <MudGrid>
<MudItem xs="12" md="8"> <MudItem xs="12" md="8">
<MudTextField @bind-Value="@response.Title" Label="Title"></MudTextField> <MudTextField @bind-Value="@response.Title" Label="Title"/>
</MudItem> <MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((e)=>OpenChooseTitleDialog())">
<MudItem xs="12" md="4"> Select a Title
<MudSelect @bind-Value="@response.TitlePlateId" Label="Title Plate"> </MudButton>
@for (uint i = 0; i < 8; i++) </MudItem>
{ <MudItem xs="12" md="4">
var index = i; <MudSelect @bind-Value="@response.TitlePlateId" Label="Title Plate">
<MudSelectItem Value="@i">@titlePlateStrings[index]</MudSelectItem> @for (uint i = 0; i < 8; i++)
}
</MudSelect>
</MudItem>
</MudGrid>
<MudSelect @bind-Value="@response.AchievementDisplayDifficulty"
Label="Achievement Panel Difficulty">
@foreach (var item in Enum.GetValues<Difficulty>())
{
<MudSelectItem Value="@item"/>
}
</MudSelect>
<MudSwitch @bind-Checked="@response.IsDisplayAchievement" Label="Display Achievement Panel" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.IsDisplayDanOnNamePlate" Label="Display Dan Rank on Name Plate" Color="Color.Primary"/>
</MudStack>
</MudPaper>
<MudPaper Class="py-8 px-8 my-8" Outlined="true">
<MudStack Spacing="4">
<h2>Song Options</h2>
<MudGrid>
<MudItem xs="12" md="4">
<MudStack Spacing="4">
<MudSwitch @bind-Checked="@response.PlaySetting.IsVanishOn" Label="Vanish" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.PlaySetting.IsInverseOn" Label="Inverse" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.IsSkipOn" Label="Give Up" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.IsVoiceOn" Label="Voice" Color="Color.Primary"/>
</MudStack>
</MudItem>
<MudItem xs="12" md="8">
<MudStack Spacing="4">
<MudSelect @bind-Value="@response.PlaySetting.Speed" Label="Speed">
@for (uint i = 0; i < 15; i++)
{ {
var index = i; var index = i;
<MudSelectItem Value="@i">@speedStrings[index]</MudSelectItem> <MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem>
} }
</MudSelect> </MudSelect>
</MudItem>
</MudGrid>
<MudSelect @bind-Value="@response.PlaySetting.RandomType" <MudSelect @bind-Value="@response.AchievementDisplayDifficulty"
Label="Random"> Label="Achievement Panel Difficulty">
@foreach (var item in Enum.GetValues<RandomType>()) @foreach (var item in Enum.GetValues<Difficulty>())
{ {
<MudSelectItem Value="@item"/> <MudSelectItem Value="@item"/>
} }
</MudSelect> </MudSelect>
<MudSelect @bind-Value="@response.ToneId" Label="Tone"> <MudSwitch @bind-Checked="@response.IsDisplayAchievement" Label="Display Achievement Panel" Color="Color.Primary"/>
@for (uint i = 0; i < 19; i++) <MudSwitch @bind-Checked="@response.IsDisplayDanOnNamePlate" Label="Display Dan Rank on Name Plate" Color="Color.Primary"/>
{ </MudStack>
var index = i; </MudTabPanel>
<MudSelectItem Value="@i">@toneStrings[index]</MudSelectItem>
}
</MudSelect>
<MudSlider Class="mb-8" @bind-Value="@response.NotesPosition" Size="Size.Medium" Min="-5" Max="5" Step="1" TickMarks="true" TickMarkLabels="@notePositionStrings"> <MudTabPanel Text="Costume">
<MudText Typo="Typo.caption">Notes Position</MudText> <MudStack Spacing="4">
</MudSlider> <h2>Costume Options</h2>
</MudStack> <MudGrid>
</MudItem> <MudItem xs="12">
</MudGrid> <MudStack Spacing="4" Class="mb-8">
</MudStack> <MudSelect @bind-Value="@response.Head" Label="Head">
</MudPaper> @for (var i = 0; i < Constants.COSTUME_HEAD_MAX; i++)
</MudItem> {
<MudItem md="4" xs="12" Class="py-8 px-8 my-4 pt-8"> var index = (uint)i;
<MudStack Spacing="4" Style="top:100px" Class="sticky"> var costumeTitle = GameDataService.GetHeadTitle(index);
<MudButton Disabled="@isSavingOptions" <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
OnClick="SaveOptions" }
Variant="Variant.Filled" </MudSelect>
Color="Color.Primary">
@if (isSavingOptions) <MudSelect @bind-Value="@response.Body" Label="Body">
{ @for (var i = 0; i < Constants.COSTUME_BODY_MAX; i++)
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/> {
<MudText Class="ms-2">Saving...</MudText> var index = (uint)i;
} var costumeTitle = GameDataService.GetBodyTitle(index);
else <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
{ }
<MudIcon Icon="@Icons.Filled.Save" Class="mx-2"></MudIcon> </MudSelect>
<MudText>Save</MudText>
} <MudSelect @bind-Value="@response.Face" Label="Face">
</MudButton> @for (var i = 0; i < Constants.COSTUME_FACE_MAX; i++)
</MudStack> {
</MudItem> var index = (uint)i;
var costumeTitle = GameDataService.GetFaceTitle(index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Kigurumi" Label="Kigurumi">
@for (var i = 0; i < Constants.COSTUME_KIGURUMI_MAX; i++)
{
var index = (uint)i;
var costumeTitle = GameDataService.GetKigurumiTitle(index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.Puchi" Label="Puchi">
@for (var i = 0; i < Constants.COSTUME_PUCHI_MAX; i++)
{
var index = (uint)i;
var costumeTitle = GameDataService.GetPuchiTitle(index);
<MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem>
}
</MudSelect>
</MudStack>
<MudStack Row="true">
<MudSelect @bind-Value="@response.BodyColor" Label="Body Color">
@for (uint i = 0; i < Constants.COSTUME_COLOR_MAX; i++)
{
var index = i;
<MudSelectItem Value="@index">
<div class="color-box" style=@($"background: {CostumeColors[index]}")></div>
@index
</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.FaceColor" Label="Face Color">
@for (uint i = 0; i < Constants.COSTUME_COLOR_MAX; i++)
{
var index = i;
<MudSelectItem Value="@index">
<div class="color-box" style=@($"background: {CostumeColors[index]}")></div>
@index
</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.LimbColor" Label="Limb Color">
@for (uint i = 0; i < Constants.COSTUME_COLOR_MAX; i++)
{
var index = i;
<MudSelectItem Value="@index">
<div class="color-box" style=@($"background: {CostumeColors[index]}")></div>
@index
</MudSelectItem>
}
</MudSelect>
</MudStack>
</MudItem>
</MudGrid>
</MudStack>
</MudTabPanel>
<MudTabPanel Text="Song Options">
<MudStack Spacing="4">
<h2>Song Options</h2>
<MudGrid>
<MudItem xs="12" md="4">
<MudStack Spacing="4">
<MudSwitch @bind-Checked="@response.PlaySetting.IsVanishOn" Label="Vanish" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.PlaySetting.IsInverseOn" Label="Inverse" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.IsSkipOn" Label="Give Up" Color="Color.Primary"/>
<MudSwitch @bind-Checked="@response.IsVoiceOn" Label="Voice" Color="Color.Primary"/>
</MudStack>
</MudItem>
<MudItem xs="12" md="8">
<MudStack Spacing="4">
<MudSelect @bind-Value="@response.PlaySetting.Speed" Label="Speed">
@for (uint i = 0; i < 15; i++)
{
var index = i;
<MudSelectItem Value="@i">@SpeedStrings[index]</MudSelectItem>
}
</MudSelect>
<MudSelect @bind-Value="@response.PlaySetting.RandomType"
Label="Random">
@foreach (var item in Enum.GetValues<RandomType>())
{
<MudSelectItem Value="@item"/>
}
</MudSelect>
<MudSelect @bind-Value="@response.ToneId" Label="Tone">
@for (uint i = 0; i < 19; i++)
{
var index = i;
<MudSelectItem Value="@i">@ToneStrings[index]</MudSelectItem>
}
</MudSelect>
<MudSlider Class="mb-8" @bind-Value="@response.NotesPosition" Size="Size.Medium" Min="-5" Max="5" Step="1" TickMarks="true" TickMarkLabels="@NotePositionStrings">
<MudText Typo="Typo.caption">Notes Position</MudText>
</MudSlider>
</MudStack>
</MudItem>
</MudGrid>
</MudStack>
</MudTabPanel>
</MudTabs>
</MudPaper>
</MudItem>
<MudItem md="4" xs="12" Class="py-4 px-8">
<MudStack Style="top:100px" Class="sticky">
<MudButton Disabled="@isSavingOptions"
OnClick="SaveOptions"
Variant="Variant.Filled"
Color="Color.Primary">
@if (isSavingOptions)
{
<MudProgressCircular Class="ms-n1" Size="Size.Small" Indeterminate="true"/>
<MudText Class="ms-2">Saving...</MudText>
}
else
{
<MudIcon Icon="@Icons.Filled.Save" Class="mx-2"></MudIcon>
<MudText>Save</MudText>
}
</MudButton>
</MudStack>
</MudItem>
</MudGrid> </MudGrid>
} }

View File

@ -1,4 +1,6 @@
namespace TaikoWebUI.Pages; using TaikoWebUI.Pages.Dialogs;
namespace TaikoWebUI.Pages;
public partial class Profile public partial class Profile
{ {
@ -9,16 +11,31 @@ public partial class Profile
private bool isSavingOptions; private bool isSavingOptions;
private readonly string[] speedStrings = private static readonly string[] CostumeColors =
{
"#F84828", "#68C0C0", "#DC1500", "#F8F0E0", "#009687", "#00BF87",
"#00FF9A", "#66FFC2", "#FFFFFF", "#690000", "#FF0000", "#FF6666",
"#FFB3B3", "#00BCC2", "#00F7FF", "#66FAFF", "#B3FDFF", "#E4E4E4",
"#993800", "#FF5E00", "#FF9E78", "#FFCFB3", "#005199", "#0088FF",
"#66B8FF", "#B3DBFF", "#B9B9B9", "#B37700", "#FFAA00", "#FFCC66",
"#FFE2B3", "#000C80", "#0019FF", "#6675FF", "#B3BAFF", "#858585",
"#B39B00", "#FFDD00", "#FFFF00", "#FFFF71", "#2B0080", "#5500FF",
"#9966FF", "#CCB3FF", "#505050", "#38A100", "#78C900", "#B3FF00",
"#DCFF8A", "#610080", "#C400FF", "#DC66FF", "#EDB3FF", "#232323",
"#006600", "#00B800", "#00FF00", "#8AFF9E", "#990059", "#FF0095",
"#FF66BF", "#FFB3DF", "#000000"
};
private static readonly string[] SpeedStrings =
{ {
"1.0", "1.1", "1.2", "1.3", "1.4", "1.0", "1.1", "1.2", "1.3", "1.4",
"1.5", "1.6", "1.7", "1.8", "1.9", "1.5", "1.6", "1.7", "1.8", "1.9",
"2.0", "2.5", "3.0", "3.5", "4.0" "2.0", "2.5", "3.0", "3.5", "4.0"
}; };
private readonly string[] notePositionStrings = { "-5", "-4", "-3", "-2", "-1", "0", "+1", "+2", "+3", "+4", "+5" }; private static readonly string[] NotePositionStrings = { "-5", "-4", "-3", "-2", "-1", "0", "+1", "+2", "+3", "+4", "+5" };
private readonly string[] toneStrings = private static readonly string[] ToneStrings =
{ {
"Taiko", "Festival", "Dogs & Cats", "Deluxe", "Taiko", "Festival", "Dogs & Cats", "Deluxe",
"Drumset", "Tambourine", "Don Wada", "Clapping", "Drumset", "Tambourine", "Don Wada", "Clapping",
@ -27,13 +44,13 @@ public partial class Profile
"Synth Drum", "Shuriken", "Bubble Pop", "Electric Guitar" "Synth Drum", "Shuriken", "Bubble Pop", "Electric Guitar"
}; };
private readonly string[] titlePlateStrings = private static readonly string[] TitlePlateStrings =
{ {
"Wood", "Rainbow", "Gold", "Purple", "Wood", "Rainbow", "Gold", "Purple",
"AI 1", "AI 2", "AI 3", "AI 4" "AI 1", "AI 2", "AI 3", "AI 4"
}; };
private List<BreadcrumbItem> breadcrumbs = new() private readonly List<BreadcrumbItem> breadcrumbs = new()
{ {
new BreadcrumbItem("Cards", href: "/Cards"), new BreadcrumbItem("Cards", href: "/Cards"),
}; };
@ -55,4 +72,25 @@ public partial class Profile
isSavingOptions = false; isSavingOptions = false;
} }
private async Task OpenChooseTitleDialog()
{
var options = new DialogOptions
{
//CloseButton = false,
CloseOnEscapeKey = false,
DisableBackdropClick = true,
MaxWidth = MaxWidth.Medium,
FullWidth = true
};
var parameters = new DialogParameters
{
["UserSetting"] = response
};
var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options);
var result = await dialog.Result;
if (!result.Cancelled)
{
StateHasChanged();
}
}
} }

View File

@ -6,11 +6,19 @@ namespace TaikoWebUI.Services;
public class GameDataService : IGameDataService public class GameDataService : IGameDataService
{ {
private readonly string[] bodyTitles = new string[Constants.COSTUME_BODY_MAX];
private readonly HttpClient client; private readonly HttpClient client;
private readonly string[] faceTitles = new string[Constants.COSTUME_FACE_MAX];
private readonly string[] headTitles = new string[Constants.COSTUME_HEAD_MAX];
private readonly string[] kigurumiMTitles = new string[Constants.COSTUME_KIGURUMI_MAX];
private readonly Dictionary<uint, MusicDetail> musicMap = new(); private readonly Dictionary<uint, MusicDetail> musicMap = new();
private readonly string[] puchiTitles = new string[Constants.COSTUME_PUCHI_MAX];
private ImmutableDictionary<uint, DanData> danMap = null!; private ImmutableDictionary<uint, DanData> danMap = ImmutableDictionary<uint, DanData>.Empty;
private ImmutableHashSet<Title> titles = ImmutableHashSet<Title>.Empty;
public GameDataService(HttpClient client) public GameDataService(HttpClient client)
{ {
@ -34,6 +42,162 @@ public class GameDataService : IGameDataService
// To prevent duplicate entries in wordlist // To prevent duplicate entries in wordlist
var dict = wordList.WordListEntries.GroupBy(entry => entry.Key) var dict = wordList.WordListEntries.GroupBy(entry => entry.Key)
.ToImmutableDictionary(group => group.Key, group => group.First()); .ToImmutableDictionary(group => group.Key, group => group.First());
await Task.Run(() => InitializeMusicMap(musicInfo, dict, musicOrder));
await Task.Run(() => InitializeHeadTitles(dict));
await Task.Run(() => InitializeFaceTitles(dict));
await Task.Run(() => InitializeBodyTitles(dict));
await Task.Run(() => InitializePuchiTitles(dict));
await Task.Run(() => InitializeKigurumiTitles(dict));
await Task.Run(() => InitializeTitles(dict));
}
public string GetMusicNameBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.SongName : string.Empty;
}
public string GetMusicArtistBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.ArtistName : string.Empty;
}
public SongGenre GetMusicGenreBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Genre : SongGenre.Variety;
}
public int GetMusicIndexBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Index : int.MaxValue;
}
public DanData GetDanDataById(uint danId)
{
return danMap.GetValueOrDefault(danId, new DanData());
}
public int GetMusicStarLevel(uint songId, Difficulty difficulty)
{
var success = musicMap.TryGetValue(songId, out var musicDetail);
return difficulty switch
{
Difficulty.None => throw new ArgumentException("Difficulty cannot be none"),
Difficulty.Easy => success ? musicDetail!.StarEasy : 0,
Difficulty.Normal => success ? musicDetail!.StarNormal : 0,
Difficulty.Hard => success ? musicDetail!.StarHard : 0,
Difficulty.Oni => success ? musicDetail!.StarOni : 0,
Difficulty.UraOni => success ? musicDetail!.StarUra : 0,
_ => throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null)
};
}
public string GetHeadTitle(uint index)
{
return index < headTitles.Length ? headTitles[index] : string.Empty;
}
public string GetKigurumiTitle(uint index)
{
return index < kigurumiMTitles.Length ? kigurumiMTitles[index] : string.Empty;
}
public string GetBodyTitle(uint index)
{
return index < bodyTitles.Length ? bodyTitles[index] : string.Empty;
}
public string GetFaceTitle(uint index)
{
return index < faceTitles.Length ? faceTitles[index] : string.Empty;
}
public string GetPuchiTitle(uint index)
{
return index < puchiTitles.Length ? puchiTitles[index] : string.Empty;
}
public ImmutableHashSet<Title> GetTitles()
{
return titles;
}
private void InitializeTitles(ImmutableDictionary<string, WordListEntry> dict)
{
var set = ImmutableHashSet.CreateBuilder<Title>();
for (var i = 1; i < Constants.PLAYER_TITLE_MAX; i++)
{
var key = $"syougou_{i}";
var titleWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
set.Add(new Title{
TitleName = titleWordlistItem.JapaneseText,
TitleId = i
});
}
titles = set.ToImmutable();
}
private void InitializePuchiTitles(ImmutableDictionary<string, WordListEntry> dict)
{
for (var i = 0; i < Constants.COSTUME_PUCHI_MAX; i++)
{
var key = $"costume_puchi_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
puchiTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeKigurumiTitles(ImmutableDictionary<string, WordListEntry> dict)
{
for (var i = 0; i < Constants.COSTUME_KIGURUMI_MAX; i++)
{
var key = $"costume_kigurumi_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
kigurumiMTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeBodyTitles(ImmutableDictionary<string, WordListEntry> dict)
{
for (var i = 0; i < Constants.COSTUME_BODY_MAX; i++)
{
var key = $"costume_body_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
bodyTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeFaceTitles(ImmutableDictionary<string, WordListEntry> dict)
{
for (var i = 0; i < Constants.COSTUME_FACE_MAX; i++)
{
var key = $"costume_face_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
faceTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeHeadTitles(ImmutableDictionary<string, WordListEntry> dict)
{
for (var i = 0; i < Constants.COSTUME_HEAD_MAX; i++)
{
var key = $"costume_head_{i}";
var costumeWordlistItem = dict.GetValueOrDefault(key, new WordListEntry());
headTitles[i] = costumeWordlistItem.JapaneseText;
}
}
private void InitializeMusicMap(MusicInfo musicInfo, ImmutableDictionary<string, WordListEntry> dict,
MusicOrder musicOrder)
{
foreach (var music in musicInfo.Items) foreach (var music in musicInfo.Items)
{ {
var songNameKey = $"song_{music.Id}"; var songNameKey = $"song_{music.Id}";
@ -60,42 +224,4 @@ public class GameDataService : IGameDataService
} }
} }
} }
public string GetMusicNameBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.SongName : string.Empty;
}
public string GetMusicArtistBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.ArtistName : string.Empty;
}
public SongGenre GetMusicGenreBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Genre : SongGenre.Variety;
}
public int GetMusicIndexBySongId(uint songId)
{
return musicMap.TryGetValue(songId, out var musicDetail) ? musicDetail.Index : int.MaxValue;
}
public DanData GetDanDataById(uint danId)
{
return danMap.GetValueOrDefault(danId, new DanData());
}
public int GetMusicStarLevel(uint songId, Difficulty difficulty)
{
var success = musicMap.TryGetValue(songId, out var musicDetail);
return difficulty switch
{
Difficulty.None => throw new ArgumentException("Difficulty cannot be none"),
Difficulty.Easy => success ? musicDetail!.StarEasy : 0,
Difficulty.Normal => success ? musicDetail!.StarNormal : 0,
Difficulty.Hard => success ? musicDetail!.StarHard : 0,
Difficulty.Oni => success ? musicDetail!.StarOni : 0,
Difficulty.UraOni => success ? musicDetail!.StarUra : 0,
_ => throw new ArgumentOutOfRangeException(nameof(difficulty), difficulty, null)
};
}
} }

View File

@ -1,4 +1,7 @@
namespace TaikoWebUI.Services; using System.Collections.Immutable;
using TaikoWebUI.Shared.Models;
namespace TaikoWebUI.Services;
public interface IGameDataService public interface IGameDataService
{ {
@ -15,4 +18,12 @@ public interface IGameDataService
public DanData GetDanDataById(uint danId); public DanData GetDanDataById(uint danId);
public int GetMusicStarLevel(uint songId, Difficulty difficulty); public int GetMusicStarLevel(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 ImmutableHashSet<Title> GetTitles();
} }

View File

@ -0,0 +1,12 @@
namespace TaikoWebUI.Shared;
public static class Constants
{
public const int COSTUME_HEAD_MAX = 140;
public const int COSTUME_FACE_MAX = 58;
public const int COSTUME_BODY_MAX = 156;
public const int COSTUME_KIGURUMI_MAX = 154;
public const int COSTUME_PUCHI_MAX = 129;
public const int COSTUME_COLOR_MAX = 63;
public const int PLAYER_TITLE_MAX = 750;
}

View File

@ -0,0 +1,23 @@
namespace TaikoWebUI.Shared.Models;
public class Title
{
public int TitleId { get; set; }
public string TitleName { get; init; } = string.Empty;
public override bool Equals(object? obj)
{
if (obj is Title title)
{
return title.TitleName.Equals(TitleName);
}
return false;
}
public override int GetHashCode()
{
return TitleName.GetHashCode();
}
}

View File

@ -7,6 +7,7 @@
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Autocomplete.Clients" Version="1.1.0" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.7" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly" Version="6.0.7" />
<PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.7" PrivateAssets="all" /> <PackageReference Include="Microsoft.AspNetCore.Components.WebAssembly.DevServer" Version="6.0.7" PrivateAssets="all" />
<PackageReference Include="MudBlazor" Version="6.0.15" /> <PackageReference Include="MudBlazor" Version="6.0.15" />

View File

@ -19,3 +19,14 @@
font-weight: bold; font-weight: bold;
color: #333; color: #333;
} }
.color-box {
width: 16px;
height: 16px;
border-radius: 9999px;
display: inline-block;
margin-right: 10px;
border: 1px solid black;
position: relative;
top: 2px;
}