From 509ac85718bd346f704789587ee77bbba9760298 Mon Sep 17 00:00:00 2001 From: S-Sebb <794194678@qq.com> Date: Sun, 12 Nov 2023 23:12:54 +0000 Subject: [PATCH] Add registering with last play date time, add disabling free profile editing --- SharedProject/Models/UserSetting.cs | 5 +- .../Controllers/Api/UserSettingsController.cs | 18 +- .../Controllers/Game/MyDonEntryController.cs | 2 +- TaikoLocalServer/Models/DonCosRewardEntry.cs | 4 +- TaikoLocalServer/Models/MusicInfoEntry.cs | 2 +- TaikoLocalServer/Models/NeiroEntry.cs | 2 +- TaikoLocalServer/Models/ShougouEntry.cs | 5 +- TaikoLocalServer/Services/GameDataService.cs | 26 +-- .../Pages/Dialogs/ChooseTitleDialog.razor | 9 + TaikoWebUI/Pages/Profile.razor | 162 ++++++++++++------ TaikoWebUI/Pages/Profile.razor.cs | 20 ++- TaikoWebUI/Pages/Register.razor | 19 +- TaikoWebUI/Pages/Register.razor.cs | 15 +- TaikoWebUI/Pages/Users.razor | 30 ++-- TaikoWebUI/Pages/Users.razor.cs | 8 + TaikoWebUI/Services/GameDataService.cs | 36 ++-- TaikoWebUI/Services/LoginService.cs | 23 ++- TaikoWebUI/Settings/WebUiSettings.cs | 4 +- TaikoWebUI/Shared/Models/DonCosRewardEntry.cs | 4 +- TaikoWebUI/Shared/Models/NeiroEntry.cs | 2 +- TaikoWebUI/Shared/Models/ShougouEntry.cs | 5 +- TaikoWebUI/Shared/Models/Title.cs | 2 + TaikoWebUI/wwwroot/appsettings.json | 5 +- 23 files changed, 295 insertions(+), 113 deletions(-) diff --git a/SharedProject/Models/UserSetting.cs b/SharedProject/Models/UserSetting.cs index b05460e..5b23aaf 100644 --- a/SharedProject/Models/UserSetting.cs +++ b/SharedProject/Models/UserSetting.cs @@ -53,11 +53,14 @@ public class UserSetting public List UnlockedFace { get; set; } = new(); public List UnlockedPuchi { get; set; } = new(); + + public List UnlockedTitle { get; set; } = new(); public uint FaceColor { get; set; } public uint BodyColor { get; set; } public uint LimbColor { get; set; } - + + public DateTime LastPlayDateTime { get; set; } } \ No newline at end of file diff --git a/TaikoLocalServer/Controllers/Api/UserSettingsController.cs b/TaikoLocalServer/Controllers/Api/UserSettingsController.cs index 632a89e..ca790de 100644 --- a/TaikoLocalServer/Controllers/Api/UserSettingsController.cs +++ b/TaikoLocalServer/Controllers/Api/UserSettingsController.cs @@ -25,12 +25,24 @@ public class UserSettingsController : BaseController return NotFound(); } - var difficultySettingArray = JsonHelper.GetUIntArrayFromJson(user.DifficultySettingArray, 3, Logger, nameof(user.DifficultySettingArray)); + var difficultySettingArray = JsonHelper.GetUIntArrayFromJson(user.DifficultySettingArray, 3, Logger, + nameof(user.DifficultySettingArray)); var costumeData = JsonHelper.GetCostumeDataFromUserData(user, Logger); var costumeUnlockData = JsonHelper.GetCostumeUnlockDataFromUserData(user, Logger); + var unlockedTitle = JsonHelper.GetUIntArrayFromJson(user.TitleFlgArray, 0, Logger, nameof(user.TitleFlgArray)) + .ToList(); + + for (var i = 0; i < 5; i++) + { + if (!costumeUnlockData[i].Contains(0)) + { + costumeUnlockData[i].Add(0); + } + } + var response = new UserSetting { AchievementDisplayDifficulty = user.AchievementDisplayDifficulty, @@ -58,9 +70,11 @@ public class UserSettingsController : BaseController UnlockedBody = costumeUnlockData[2], UnlockedFace = costumeUnlockData[3], UnlockedPuchi = costumeUnlockData[4], + UnlockedTitle = unlockedTitle, BodyColor = user.ColorBody, FaceColor = user.ColorFace, - LimbColor = user.ColorLimb + LimbColor = user.ColorLimb, + LastPlayDateTime = user.LastPlayDatetime }; return Ok(response); } diff --git a/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs b/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs index b36e4a1..9c229ac 100644 --- a/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs +++ b/TaikoLocalServer/Controllers/Game/MyDonEntryController.cs @@ -53,7 +53,7 @@ public class MyDonEntryController : BaseController FavoriteSongsArray = "[]", ToneFlgArray = "[0]", TitleFlgArray = "[]", - CostumeFlgArray = "[[],[],[],[],[]]", + CostumeFlgArray = "[[0],[0],[0],[0],[0]]", GenericInfoFlgArray = "[]", TokenCountDict = "{}", UnlockedSongIdList = "[]" diff --git a/TaikoLocalServer/Models/DonCosRewardEntry.cs b/TaikoLocalServer/Models/DonCosRewardEntry.cs index b73b312..6229c30 100644 --- a/TaikoLocalServer/Models/DonCosRewardEntry.cs +++ b/TaikoLocalServer/Models/DonCosRewardEntry.cs @@ -5,8 +5,8 @@ namespace TaikoLocalServer.Models; public class DonCosRewardEntry { [JsonPropertyName("cosType")] - public string cosType { get; set; } = null!; + public string CosType { get; set; } = null!; [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } } \ No newline at end of file diff --git a/TaikoLocalServer/Models/MusicInfoEntry.cs b/TaikoLocalServer/Models/MusicInfoEntry.cs index b5e7142..75ab386 100644 --- a/TaikoLocalServer/Models/MusicInfoEntry.cs +++ b/TaikoLocalServer/Models/MusicInfoEntry.cs @@ -8,5 +8,5 @@ public class MusicInfoEntry public uint MusicId { get; set; } [JsonPropertyName("starUra")] - public uint starUra { get; set; } + public uint StarUra { get; set; } } \ No newline at end of file diff --git a/TaikoLocalServer/Models/NeiroEntry.cs b/TaikoLocalServer/Models/NeiroEntry.cs index fc82ec9..617bf94 100644 --- a/TaikoLocalServer/Models/NeiroEntry.cs +++ b/TaikoLocalServer/Models/NeiroEntry.cs @@ -5,5 +5,5 @@ namespace TaikoLocalServer.Models; public class NeiroEntry { [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } } \ No newline at end of file diff --git a/TaikoLocalServer/Models/ShougouEntry.cs b/TaikoLocalServer/Models/ShougouEntry.cs index 1c759db..099995a 100644 --- a/TaikoLocalServer/Models/ShougouEntry.cs +++ b/TaikoLocalServer/Models/ShougouEntry.cs @@ -5,5 +5,8 @@ namespace TaikoLocalServer.Models; public class ShougouEntry { [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } + + [JsonPropertyName("rarity")] + public uint Rarity { get; set; } } \ No newline at end of file diff --git a/TaikoLocalServer/Services/GameDataService.cs b/TaikoLocalServer/Services/GameDataService.cs index 975bb40..c1d29c5 100644 --- a/TaikoLocalServer/Services/GameDataService.cs +++ b/TaikoLocalServer/Services/GameDataService.cs @@ -331,7 +331,7 @@ public class GameDataService : IGameDataService .ToList(); musics.Sort(); - musicsWithUra = musicInfoes.Where(info => info.Value.starUra > 0) + musicsWithUra = musicInfoes.Where(info => info.Value.StarUra > 0) .Select(pair => pair.Key) .ToList(); musicsWithUra.Sort(); @@ -362,20 +362,20 @@ public class GameDataService : IGameDataService { donCosRewardData.ThrowIfNull("Shouldn't happen!"); var kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "kigurumi") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "kigurumi") + .Select(entry => entry.UniqueId); var headUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "head") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "head") + .Select(entry => entry.UniqueId); var bodyUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "body") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "body") + .Select(entry => entry.UniqueId); var faceUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "face") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "face") + .Select(entry => entry.UniqueId); var puchiUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "puchi") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "puchi") + .Select(entry => entry.UniqueId); costumeFlagArraySizes = new List { @@ -390,13 +390,13 @@ public class GameDataService : IGameDataService private void InitializeTitleFlagArraySize(Shougous? shougouData) { shougouData.ThrowIfNull("Shouldn't happen!"); - titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.uniqueId) + 1; + titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.UniqueId) + 1; } private void InitializeToneFlagArraySize(Neiros? neiroData) { neiroData.ThrowIfNull("Shouldn't happen!"); - toneFlagArraySize = (int)neiroData.NeiroEntries.Max(entry => entry.uniqueId) + 1; + toneFlagArraySize = (int)neiroData.NeiroEntries.Max(entry => entry.UniqueId) + 1; } private void InitializeQrCodeData(List? qrCodeData) diff --git a/TaikoWebUI/Pages/Dialogs/ChooseTitleDialog.razor b/TaikoWebUI/Pages/Dialogs/ChooseTitleDialog.razor index b40e7be..28694da 100644 --- a/TaikoWebUI/Pages/Dialogs/ChooseTitleDialog.razor +++ b/TaikoWebUI/Pages/Dialogs/ChooseTitleDialog.razor @@ -57,6 +57,9 @@ [Parameter] public UserSetting UserSetting { get; set; } = new(); + [Parameter] + public bool AllowFreeProfileEditing { get; set; } + private IEnumerable titles = new List<Title>(); private Title? selectedTitle; @@ -67,6 +70,11 @@ { base.OnInitialized(); var titleSet = GameDataService.GetTitles(); + if (!AllowFreeProfileEditing) + { + var unlockedTitle = UserSetting.UnlockedTitle; + titleSet = titleSet.Where(title => unlockedTitle.Contains((uint)title.TitleId)).ToImmutableHashSet(); + } titles = titleSet.ToImmutableList().Sort((title, title1) => title.TitleId.CompareTo(title1.TitleId)); var currentTitle = new Title { @@ -96,6 +104,7 @@ if (selectedTitle is not null) { UserSetting.Title = selectedTitle.TitleName; + UserSetting.TitlePlateId = selectedTitle.TitleRarity; } MudDialog.Close(DialogResult.Ok(true)); } diff --git a/TaikoWebUI/Pages/Profile.razor b/TaikoWebUI/Pages/Profile.razor index 60231ab..9e7c507 100644 --- a/TaikoWebUI/Pages/Profile.razor +++ b/TaikoWebUI/Pages/Profile.razor @@ -47,20 +47,30 @@ <MudGrid> <MudItem xs="12" md="8"> - <MudTextField TextChanged="UpdateTitle" @bind-Value="@response.Title" Label="Title" /> - <MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((_)=>OpenChooseTitleDialog())"> + @if (LoginService.AllowFreeProfileEditing) + { + <MudTextField TextChanged="UpdateTitle" @bind-Value="@response.Title" Label="Title"/> + } + else + { + <MudTextField ReadOnly="true" @bind-Value="@response.Title" Label="Title"/> + } + <MudButton Color="Color.Primary" Class="mt-1" Size="Size.Small" OnClick="@((_) => OpenChooseTitleDialog())"> Select a Title </MudButton> </MudItem> - <MudItem xs="12" md="4"> - <MudSelect @bind-Value="@response.TitlePlateId" Label="Title Plate"> - @for (uint i = 0; i < TitlePlateStrings.Length; i++) - { - var index = i; - <MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem> - } - </MudSelect> - </MudItem> + @if (LoginService.AllowFreeProfileEditing) + { + <MudItem xs="12" md="4"> + <MudSelect @bind-Value="@response.TitlePlateId" Label="Title Plate"> + @for (uint i = 0; i < TitlePlateStrings.Length; i++) + { + var index = i; + <MudSelectItem Value="@i">@TitlePlateStrings[index]</MudSelectItem> + } + </MudSelect> + </MudItem> + } </MudGrid> <MudSelect @bind-Value="@response.AchievementDisplayDifficulty" @@ -115,50 +125,100 @@ <MudGrid> <MudItem xs="12"> <MudStack Spacing="4" Class="mb-8"> - <MudSelect @bind-Value="@response.Head" Label="Head"> - @for (var i = 0; i < costumeFlagArraySizes[1]; i++) - { - var index = (uint)i; - var costumeTitle = GameDataService.GetHeadTitle(index); - <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> - } - </MudSelect> + @if (LoginService.AllowFreeProfileEditing) + { + <MudSelect @bind-Value="@response.Head" Label="Head"> + @for (var i = 0; i < costumeFlagArraySizes[1]; i++) + { + var index = (uint)i; + var costumeTitle = GameDataService.GetHeadTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> - <MudSelect @bind-Value="@response.Body" Label="Body"> - @for (var i = 0; i < costumeFlagArraySizes[2]; i++) - { - var index = (uint)i; - var costumeTitle = GameDataService.GetBodyTitle(index); - <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> - } - </MudSelect> + <MudSelect @bind-Value="@response.Body" Label="Body"> + @for (var i = 0; i < costumeFlagArraySizes[2]; i++) + { + var index = (uint)i; + var costumeTitle = GameDataService.GetBodyTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> - <MudSelect @bind-Value="@response.Face" Label="Face"> - @for (var i = 0; i < costumeFlagArraySizes[3]; i++) - { - var index = (uint)i; - var costumeTitle = GameDataService.GetFaceTitle(index); - <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> - } - </MudSelect> + <MudSelect @bind-Value="@response.Face" Label="Face"> + @for (var i = 0; i < costumeFlagArraySizes[3]; i++) + { + 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 < costumeFlagArraySizes[0]; i++) - { - var index = (uint)i; - var costumeTitle = GameDataService.GetKigurumiTitle(index); - <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> - } - </MudSelect> + <MudSelect @bind-Value="@response.Kigurumi" Label="Kigurumi"> + @for (var i = 0; i < costumeFlagArraySizes[0]; 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 < costumeFlagArraySizes[4]; i++) - { - var index = (uint)i; - var costumeTitle = GameDataService.GetPuchiTitle(index); - <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> - } - </MudSelect> + <MudSelect @bind-Value="@response.Puchi" Label="Puchi"> + @for (var i = 0; i < costumeFlagArraySizes[4]; i++) + { + var index = (uint)i; + var costumeTitle = GameDataService.GetPuchiTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + } + else + { + <MudSelect @bind-Value="@response.Head" Label="Head"> + @foreach (var i in unlockedHeadCostumes) + { + var index = i; + var costumeTitle = GameDataService.GetHeadTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + + <MudSelect @bind-Value="@response.Body" Label="Body"> + @foreach (var i in unlockedBodyCostumes) + { + var index = i; + var costumeTitle = GameDataService.GetBodyTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + + <MudSelect @bind-Value="@response.Face" Label="Face"> + @foreach (var i in unlockedFaceCostumes) + { + var index = i; + var costumeTitle = GameDataService.GetFaceTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + + <MudSelect @bind-Value="@response.Kigurumi" Label="Kigurumi"> + @foreach (var i in unlockedKigurumiCostumes) + { + var index = i; + var costumeTitle = GameDataService.GetKigurumiTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + + <MudSelect @bind-Value="@response.Puchi" Label="Puchi"> + @foreach (var i in unlockedPuchiCostumes) + { + var index = i; + var costumeTitle = GameDataService.GetPuchiTitle(index); + <MudSelectItem Value="@index">@index - @costumeTitle</MudSelectItem> + } + </MudSelect> + } </MudStack> <MudStack Row="true"> diff --git a/TaikoWebUI/Pages/Profile.razor.cs b/TaikoWebUI/Pages/Profile.razor.cs index c9fc558..22a77db 100644 --- a/TaikoWebUI/Pages/Profile.razor.cs +++ b/TaikoWebUI/Pages/Profile.razor.cs @@ -170,6 +170,12 @@ public partial class Profile }; private List<int> costumeFlagArraySizes = new(); + + private List<uint> unlockedHeadCostumes = new(); + private List<uint> unlockedBodyCostumes = new(); + private List<uint> unlockedFaceCostumes = new(); + private List<uint> unlockedKigurumiCostumes = new(); + private List<uint> unlockedPuchiCostumes = new(); protected override async Task OnInitializedAsync() { @@ -179,6 +185,15 @@ public partial class Profile breadcrumbs.Add(new BreadcrumbItem($"User: {Baid}", href: null, disabled: true)); breadcrumbs.Add(new BreadcrumbItem("Profile", href: $"/Users/{Baid}/Profile", disabled: false)); + + if (response != null) + { + unlockedHeadCostumes = response.UnlockedHead.Distinct().OrderBy(x => x).ToList(); + unlockedBodyCostumes = response.UnlockedBody.Distinct().OrderBy(x => x).ToList(); + unlockedFaceCostumes = response.UnlockedFace.Distinct().OrderBy(x => x).ToList(); + unlockedKigurumiCostumes = response.UnlockedKigurumi.Distinct().OrderBy(x => x).ToList(); + unlockedPuchiCostumes = response.UnlockedPuchi.Distinct().OrderBy(x => x).ToList(); + } costumeFlagArraySizes = GameDataService.GetCostumeFlagArraySizes(); } @@ -209,9 +224,10 @@ public partial class Profile MaxWidth = MaxWidth.Medium, FullWidth = true }; - var parameters = new DialogParameters + var parameters = new DialogParameters<ChooseTitleDialog> { - ["UserSetting"] = response + {x => x.UserSetting, response}, + {x => x.AllowFreeProfileEditing, LoginService.AllowFreeProfileEditing} }; var dialog = DialogService.Show<ChooseTitleDialog>("Player Titles", parameters, options); var result = await dialog.Result; diff --git a/TaikoWebUI/Pages/Register.razor b/TaikoWebUI/Pages/Register.razor index fcb614c..da73d69 100644 --- a/TaikoWebUI/Pages/Register.razor +++ b/TaikoWebUI/Pages/Register.razor @@ -23,6 +23,23 @@ else <MudTextField @bind-value="accessCode" InputType="InputType.Text" T="string" FullWidth="true" Required="@true" RequiredError="Access Code is required" Label="Access Code"/> + @if (LoginService.RegisterWithLastPlayTime) + { + <MudDatePicker @ref="datePicker" Label="Last Play Date" @bind-Date="date" AutoClose="false"> + <PickerActions> + <MudButton Class="mr-auto align-self-start" OnClick="@(() => datePicker.Clear())">Clear</MudButton> + <MudButton OnClick="@(() => datePicker.Close(false))">Cancel</MudButton> + <MudButton Color="Color.Primary" OnClick="@(() => datePicker.Close())">Ok</MudButton> + </PickerActions> + </MudDatePicker> + <MudTimePicker @ref="timePicker" AmPm="true" Label="Last Play Time(5 min within when credit ended)" @bind-Time="time" AutoClose="false"> + <PickerActions> + <MudButton Class="mr-auto align-self-start" OnClick="@(() => timePicker.Clear())">Clear</MudButton> + <MudButton OnClick="@(() => timePicker.Close(false))">Cancel</MudButton> + <MudButton Color="Color.Primary" OnClick="@(() => timePicker.Close())">Ok</MudButton> + </PickerActions> + </MudTimePicker> + } <MudTextField @bind-Value="password" InputType="InputType.Password" T="string" FullWidth="true" Required="@true" RequiredError="Password is required" @@ -40,4 +57,4 @@ else </MudItem> </MudGrid> </MudContainer> -} \ No newline at end of file +} diff --git a/TaikoWebUI/Pages/Register.razor.cs b/TaikoWebUI/Pages/Register.razor.cs index 2b76790..c7768bb 100644 --- a/TaikoWebUI/Pages/Register.razor.cs +++ b/TaikoWebUI/Pages/Register.razor.cs @@ -6,6 +6,11 @@ public partial class Register private string confirmPassword = ""; private string password = ""; private MudForm registerForm = default!; + + private MudDatePicker datePicker = new(); + private MudTimePicker timePicker = new(); + private DateTime? date = DateTime.Today; + private TimeSpan? time = new TimeSpan(00, 45, 00); private DashboardResponse? response; @@ -17,9 +22,10 @@ public partial class Register private async Task OnRegister() { + var inputDateTime = date!.Value.Date + time!.Value; if (response != null) { - var result = await LoginService.Register(accessCode, password, confirmPassword, response, Client); + var result = await LoginService.Register(accessCode, inputDateTime, password, confirmPassword, response, Client); switch (result) { case 0: @@ -57,6 +63,13 @@ public partial class Register "Ok"); NavigationManager.NavigateTo("/Users"); break; + case 5: + await DialogService.ShowMessageBox( + "Error", + (MarkupString) + "Wrong last play time.<br />If you have forgotten when you last played, please play another game with this access code.", + "Ok"); + break; } } } diff --git a/TaikoWebUI/Pages/Users.razor b/TaikoWebUI/Pages/Users.razor index 0819a80..038f4ea 100644 --- a/TaikoWebUI/Pages/Users.razor +++ b/TaikoWebUI/Pages/Users.razor @@ -54,12 +54,15 @@ IconColor="@Color.Primary"> Manage Access Codes </MudMenuItem> - <MudDivider/> - <MudMenuItem Icon="@Icons.Material.Filled.Delete" - OnClick="@(_ => DeleteUser(user))" - IconColor="@Color.Error"> - Delete User - </MudMenuItem> + @if (LoginService.AllowUserDelete) + { + <MudDivider/> + <MudMenuItem Icon="@Icons.Material.Filled.Delete" + OnClick="@(_ => DeleteUser(user))" + IconColor="@Color.Error"> + Delete User + </MudMenuItem> + } </MudMenu> </CardHeaderActions> </MudCardHeader> @@ -153,12 +156,15 @@ IconColor="@Color.Primary"> Manage Access Codes </MudMenuItem> - <MudDivider/> - <MudMenuItem Icon="@Icons.Material.Filled.Delete" - OnClick="@(_ => DeleteUser(user))" - IconColor="@Color.Error"> - Delete User - </MudMenuItem> + @if (LoginService.AllowUserDelete) + { + <MudDivider/> + <MudMenuItem Icon="@Icons.Material.Filled.Delete" + OnClick="@(_ => DeleteUser(user))" + IconColor="@Color.Error"> + Delete User + </MudMenuItem> + } </MudMenu> </CardHeaderActions> </MudCardHeader> diff --git a/TaikoWebUI/Pages/Users.razor.cs b/TaikoWebUI/Pages/Users.razor.cs index 681cfa1..c827d2e 100644 --- a/TaikoWebUI/Pages/Users.razor.cs +++ b/TaikoWebUI/Pages/Users.razor.cs @@ -17,6 +17,14 @@ public partial class Users private async Task DeleteUser(User user) { + if (!LoginService.AllowUserDelete) + { + await DialogService.ShowMessageBox( + "Error", + "User deletion is disabled by admin.", + "Ok"); + return; + } var parameters = new DialogParameters { ["user"] = user diff --git a/TaikoWebUI/Services/GameDataService.cs b/TaikoWebUI/Services/GameDataService.cs index a001130..ce88324 100644 --- a/TaikoWebUI/Services/GameDataService.cs +++ b/TaikoWebUI/Services/GameDataService.cs @@ -55,7 +55,7 @@ public class GameDataService : IGameDataService await Task.Run(() => InitializeBodyTitles(dict)); await Task.Run(() => InitializePuchiTitles(dict)); await Task.Run(() => InitializeKigurumiTitles(dict)); - await Task.Run(() => InitializeTitles(dict)); + await Task.Run(() => InitializeTitles(dict, shougouData)); } private async Task<T> GetData<T>(string dataBaseUrl, string fileBaseName) where T : notnull @@ -143,21 +143,29 @@ public class GameDataService : IGameDataService private void InitializeTitleFlagArraySize(Shougous? shougouData) { shougouData.ThrowIfNull("Shouldn't happen!"); - titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.uniqueId) + 1; + titleFlagArraySize = (int)shougouData.ShougouEntries.Max(entry => entry.UniqueId) + 1; } - private void InitializeTitles(ImmutableDictionary<string, WordListEntry> dict) + private void InitializeTitles(ImmutableDictionary<string, WordListEntry> dict, Shougous? shougouData) { + shougouData.ThrowIfNull("Shouldn't happen!"); + var set = ImmutableHashSet.CreateBuilder<Title>(); for (var i = 1; i < titleFlagArraySize; i++) { 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(); set.Add(new Title{ TitleName = titleWordlistItem.JapaneseText, - TitleId = i + TitleId = i, + TitleRarity = titleRarity }); } @@ -168,20 +176,20 @@ public class GameDataService : IGameDataService { donCosRewardData.ThrowIfNull("Shouldn't happen!"); var kigurumiUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "kigurumi") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "kigurumi") + .Select(entry => entry.UniqueId); var headUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "head") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "head") + .Select(entry => entry.UniqueId); var bodyUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "body") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "body") + .Select(entry => entry.UniqueId); var faceUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "face") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "face") + .Select(entry => entry.UniqueId); var puchiUniqueIdList = donCosRewardData.DonCosRewardEntries - .Where(entry => entry.cosType == "puchi") - .Select(entry => entry.uniqueId); + .Where(entry => entry.CosType == "puchi") + .Select(entry => entry.UniqueId); costumeFlagArraySizes = new List<int> { diff --git a/TaikoWebUI/Services/LoginService.cs b/TaikoWebUI/Services/LoginService.cs index 7cdbefc..0a7ce10 100644 --- a/TaikoWebUI/Services/LoginService.cs +++ b/TaikoWebUI/Services/LoginService.cs @@ -11,7 +11,10 @@ public class LoginService private readonly string adminUsername; public bool LoginRequired { get; } public bool OnlyAdmin { get; } - private int BoundAccessCodeUpperLimit; + private readonly int boundAccessCodeUpperLimit; + public bool RegisterWithLastPlayTime { get; } + public bool AllowUserDelete { get; } + public bool AllowFreeProfileEditing { get; } public LoginService(IOptions<WebUiSettings> settings) { @@ -22,7 +25,10 @@ public class LoginService adminPassword = webUiSettings.AdminPassword; LoginRequired = webUiSettings.LoginRequired; OnlyAdmin = webUiSettings.OnlyAdmin; - BoundAccessCodeUpperLimit = webUiSettings.BoundAccessCodeUpperLimit; + boundAccessCodeUpperLimit = webUiSettings.BoundAccessCodeUpperLimit; + RegisterWithLastPlayTime = webUiSettings.RegisterWithLastPlayTime; + AllowUserDelete = webUiSettings.AllowUserDelete; + AllowFreeProfileEditing = webUiSettings.AllowFreeProfileEditing; } public bool IsLoggedIn { get; private set; } @@ -55,14 +61,23 @@ public class LoginService return 3; } - public async Task<int> Register(string inputCardNum, string inputPassword, string inputConfirmPassword, + public async Task<int> Register(string inputCardNum, DateTime inputDateTime, string inputPassword, string inputConfirmPassword, DashboardResponse response, HttpClient client) { if (OnlyAdmin) return 0; + foreach (var user in response.Users.Where(user => user.AccessCodes.Contains(inputCardNum))) { foreach (var userCredential in response.UserCredentials.Where(userCredential => userCredential.Baid == user.Baid)) { + if (RegisterWithLastPlayTime) + { + var userSettingResponse = await client.GetFromJsonAsync<UserSetting>($"api/UserSettings/{user.Baid}"); + if (userSettingResponse is null) return 3; + var lastPlayDateTime = userSettingResponse.LastPlayDateTime; + var diffMinutes = (inputDateTime - lastPlayDateTime).Duration().TotalMinutes; + if (diffMinutes > 5) return 5; + } if (userCredential.Password != "") return 4; if (inputPassword != inputConfirmPassword) return 2; var salt = CreateSalt(); @@ -153,7 +168,7 @@ public class LoginService public async Task<int> BindAccessCode(string inputAccessCode, HttpClient client) { if (!IsLoggedIn) return 0; - if (LoggedInUser.AccessCodes.Count >= BoundAccessCodeUpperLimit) return 2; + if (LoggedInUser.AccessCodes.Count >= boundAccessCodeUpperLimit) return 2; var request = new BindAccessCodeRequest { AccessCode = inputAccessCode, diff --git a/TaikoWebUI/Settings/WebUiSettings.cs b/TaikoWebUI/Settings/WebUiSettings.cs index 7cf651c..5bbf4d5 100644 --- a/TaikoWebUI/Settings/WebUiSettings.cs +++ b/TaikoWebUI/Settings/WebUiSettings.cs @@ -6,6 +6,8 @@ public class WebUiSettings public string AdminUsername { get; set; } = string.Empty; public string AdminPassword { get; set; } = string.Empty; public bool OnlyAdmin { get; set; } - public int BoundAccessCodeUpperLimit { get; set; } + public bool RegisterWithLastPlayTime { get; set; } + public bool AllowUserDelete { get; set; } + public bool AllowFreeProfileEditing { get; set; } } \ No newline at end of file diff --git a/TaikoWebUI/Shared/Models/DonCosRewardEntry.cs b/TaikoWebUI/Shared/Models/DonCosRewardEntry.cs index 57db82e..3d2b57f 100644 --- a/TaikoWebUI/Shared/Models/DonCosRewardEntry.cs +++ b/TaikoWebUI/Shared/Models/DonCosRewardEntry.cs @@ -5,8 +5,8 @@ namespace TaikoWebUI.Shared.Models; public class DonCosRewardEntry { [JsonPropertyName("cosType")] - public string cosType { get; set; } = null!; + public string CosType { get; set; } = null!; [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } } \ No newline at end of file diff --git a/TaikoWebUI/Shared/Models/NeiroEntry.cs b/TaikoWebUI/Shared/Models/NeiroEntry.cs index 852e972..23d876c 100644 --- a/TaikoWebUI/Shared/Models/NeiroEntry.cs +++ b/TaikoWebUI/Shared/Models/NeiroEntry.cs @@ -5,5 +5,5 @@ namespace TaikoWebUI.Shared.Models; public class NeiroEntry { [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } } \ No newline at end of file diff --git a/TaikoWebUI/Shared/Models/ShougouEntry.cs b/TaikoWebUI/Shared/Models/ShougouEntry.cs index 1f1270e..a51112b 100644 --- a/TaikoWebUI/Shared/Models/ShougouEntry.cs +++ b/TaikoWebUI/Shared/Models/ShougouEntry.cs @@ -5,5 +5,8 @@ namespace TaikoWebUI.Shared.Models; public class ShougouEntry { [JsonPropertyName("uniqueId")] - public uint uniqueId { get; set; } + public uint UniqueId { get; set; } + + [JsonPropertyName("rarity")] + public uint Rarity { get; set; } } \ No newline at end of file diff --git a/TaikoWebUI/Shared/Models/Title.cs b/TaikoWebUI/Shared/Models/Title.cs index d6f68be..476492c 100644 --- a/TaikoWebUI/Shared/Models/Title.cs +++ b/TaikoWebUI/Shared/Models/Title.cs @@ -5,6 +5,8 @@ public class Title public int TitleId { get; set; } public string TitleName { get; init; } = string.Empty; + + public uint TitleRarity { get; init; } public override bool Equals(object? obj) { diff --git a/TaikoWebUI/wwwroot/appsettings.json b/TaikoWebUI/wwwroot/appsettings.json index 8b01379..a84a7b7 100644 --- a/TaikoWebUI/wwwroot/appsettings.json +++ b/TaikoWebUI/wwwroot/appsettings.json @@ -4,6 +4,9 @@ "AdminUserName": "admin", "AdminPassword": "admin", "OnlyAdmin": "false", - "BoundAccessCodeUpperLimit": "3" + "BoundAccessCodeUpperLimit": "3", + "RegisterWithLastPlayTime": "false", + "AllowUserDelete": "true", + "AllowFreeProfileEditing": "true" } } \ No newline at end of file