diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs index 0b62a1b3..03508599 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/AvatarBaseValue.cs @@ -1,10 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Core.Json.Annotation; using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Primitive; -using Snap.Hutao.ViewModel.Wiki; namespace Snap.Hutao.Model.Metadata.Avatar; @@ -13,4 +10,13 @@ namespace Snap.Hutao.Model.Metadata.Avatar; /// internal sealed class AvatarBaseValue : BaseValue { + public override float GetValue(FightProperty fightProperty) + { + return fightProperty switch + { + FightProperty.FIGHT_PROP_CRITICAL => 0.05F, + FightProperty.FIGHT_PROP_CRITICAL_HURT => 0.5F, + _ => base.GetValue(fightProperty), + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs index 3ddab034..b7af48ea 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Monster/Monster.cs @@ -12,6 +12,8 @@ namespace Snap.Hutao.Model.Metadata.Monster; /// internal sealed class Monster { + internal const uint MaxLevel = 100; + /// /// Id /// diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs index b61de4af..60368338 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Promote.cs @@ -29,11 +29,9 @@ internal sealed class Promote /// public List> AddProperties { get; set; } = default!; - /// - /// 属性映射 - /// - public Dictionary AddPropertyMap + public float GetValue(FightProperty property) { - get => addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value); + addPropertyMap ??= AddProperties.ToDictionary(a => a.Type, a => a.Value); + return addPropertyMap.GetValueOrDefault(property); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs new file mode 100644 index 00000000..ac519875 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs @@ -0,0 +1,15 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.Runtime.CompilerServices; + +namespace Snap.Hutao.Model.Primitive; + +internal static class LevelFormat +{ + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static string Format(uint value) + { + return $"Lv.{value}"; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs index 6809b4d0..f172b865 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/HutaoCache.cs @@ -45,10 +45,10 @@ internal sealed partial class HutaoCache : IHutaoCache public Overview? Overview { get; set; } /// - public List? AvatarCollocations { get; set; } + public Dictionary? AvatarCollocations { get; set; } /// - public List? WeaponCollocations { get; set; } + public Dictionary? WeaponCollocations { get; set; } /// public async ValueTask InitializeForDatabaseViewModelAsync() @@ -153,7 +153,7 @@ internal sealed partial class HutaoCache : IHutaoCache Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)), Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)), ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)), - }); + }).ToDictionary(a => a.AvatarId); } private async ValueTask WeaponCollocationsAsync(Dictionary idAvatarMap) @@ -169,7 +169,7 @@ internal sealed partial class HutaoCache : IHutaoCache { WeaponId = co.WeaponId, Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)), - }); + }).ToDictionary(w => w.WeaponId); } [SuppressMessage("", "SH003")] diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs index 5c3125db..79741833 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Hutao/IHutaoCache.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Binding.Hutao; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.ViewModel.Complex; using Snap.Hutao.Web.Hutao.Model; @@ -41,12 +42,12 @@ internal interface IHutaoCache /// /// 角色搭配 /// - List? AvatarCollocations { get; set; } + Dictionary? AvatarCollocations { get; set; } /// /// 武器搭配 /// - List? WeaponCollocations { get; set; } + Dictionary? WeaponCollocations { get; set; } /// /// 为数据库视图模型初始化 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs new file mode 100644 index 00000000..18b906e4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/IUserInitializationService.cs @@ -0,0 +1,13 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Web.Hoyolab; + +namespace Snap.Hutao.Service.User; + +internal interface IUserInitializationService +{ + ValueTask CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken)); + + ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken)); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs new file mode 100644 index 00000000..baa26503 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserInitializationService.cs @@ -0,0 +1,185 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.DependencyInjection.Abstraction; +using Snap.Hutao.Web.Hoyolab; +using Snap.Hutao.Web.Hoyolab.Bbs.User; +using Snap.Hutao.Web.Hoyolab.Passport; +using Snap.Hutao.Web.Hoyolab.Takumi.Binding; +using Snap.Hutao.Web.Response; + +namespace Snap.Hutao.Service.User; + +[ConstructorGenerated] +[Injection(InjectAs.Singleton, typeof(IUserInitializationService))] +internal sealed partial class UserInitializationService : IUserInitializationService +{ + private readonly IServiceProvider serviceProvider; + + public async ValueTask ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default) + { + ViewModel.User.User user = ViewModel.User.User.From(inner, serviceProvider); + + if (!await InitializeUserAsync(user, token).ConfigureAwait(false)) + { + user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed }; + user.UserGameRoles = new(); + } + + return user; + } + + public async ValueTask CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default) + { + // 这里只负责创建实体用户,稍后在用户服务中保存到数据库 + Model.Entity.User entity = Model.Entity.User.From(cookie, isOversea); + + entity.Aid = cookie.GetValueOrDefault(Cookie.STUID); + entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID); + entity.IsOversea = isOversea; + + if (entity.Aid is not null && entity.Mid is not null) + { + ViewModel.User.User user = ViewModel.User.User.From(entity, serviceProvider); + bool initialized = await InitializeUserAsync(user, token).ConfigureAwait(false); + + return initialized ? user : null; + } + else + { + return null; + } + } + + private async ValueTask InitializeUserAsync(ViewModel.User.User user, CancellationToken token = default) + { + if (user.IsInitialized) + { + // Prevent multiple initialization. + return true; + } + + if (user.SToken is null) + { + return false; + } + + bool isOversea = user.Entity.IsOversea; + + if (!await TrySetUserLTokenAsync(user, token).ConfigureAwait(false)) + { + return false; + } + + if (!await TrySetUserCookieTokenAsync(user, token).ConfigureAwait(false)) + { + return false; + } + + if (!await TrySetUserUserInfoAsync(user, token).ConfigureAwait(false)) + { + return false; + } + + if (!await TrySetUserUserGameRolesAsync(user, token).ConfigureAwait(false)) + { + return false; + } + + user.SelectedUserGameRole = user.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen); + return user.IsInitialized = true; + } + + private async ValueTask TrySetUserLTokenAsync(ViewModel.User.User user, CancellationToken token) + { + if (user.LToken is not null) + { + return true; + } + + Response lTokenResponse = await serviceProvider + .GetRequiredService>() + .Create(user.IsOversea) + .GetLTokenBySTokenAsync(user.Entity, token) + .ConfigureAwait(false); + + if (lTokenResponse.IsOk()) + { + user.LToken = new() + { + [Cookie.LTUID] = user.Entity.Aid ?? string.Empty, + [Cookie.LTOKEN] = lTokenResponse.Data.LToken, + }; + return true; + } + else + { + return false; + } + } + + private async ValueTask TrySetUserCookieTokenAsync(ViewModel.User.User user, CancellationToken token) + { + if (user.CookieToken is not null) + { + return true; + } + + Response cookieTokenResponse = await serviceProvider + .GetRequiredService>() + .Create(user.IsOversea) + .GetCookieAccountInfoBySTokenAsync(user.Entity, token) + .ConfigureAwait(false); + + if (cookieTokenResponse.IsOk()) + { + user.CookieToken = new() + { + [Cookie.ACCOUNT_ID] = user.Entity.Aid ?? string.Empty, + [Cookie.COOKIE_TOKEN] = cookieTokenResponse.Data.CookieToken, + }; + return true; + } + else + { + return false; + } + } + + private async ValueTask TrySetUserUserInfoAsync(ViewModel.User.User user, CancellationToken token) + { + Response response = await serviceProvider + .GetRequiredService>() + .Create(user.IsOversea) + .GetUserFullInfoAsync(user.Entity, token) + .ConfigureAwait(false); + + if (response.IsOk()) + { + user.UserInfo = response.Data.UserInfo; + return true; + } + else + { + return false; + } + } + + private async ValueTask TrySetUserUserGameRolesAsync(ViewModel.User.User user, CancellationToken token) + { + Response> userGameRolesResponse = await serviceProvider + .GetRequiredService() + .GetUserGameRolesOverseaAwareAsync(user.Entity, token) + .ConfigureAwait(false); + + if (userGameRolesResponse.IsOk()) + { + user.UserGameRoles = userGameRolesResponse.Data.List; + return user.UserGameRoles.Any(); + } + else + { + return false; + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index f3a1bd53..45963999 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -25,11 +25,12 @@ namespace Snap.Hutao.Service.User; [Injection(InjectAs.Singleton, typeof(IUserService))] internal sealed partial class UserService : IUserService { - private readonly ITaskContext taskContext; - private readonly IUserDbService userDbService; - private readonly IServiceProvider serviceProvider; - private readonly IMessenger messenger; private readonly ScopedDbCurrent dbCurrent; + private readonly IUserInitializationService userInitializationService; + private readonly IServiceProvider serviceProvider; + private readonly IUserDbService userDbService; + private readonly ITaskContext taskContext; + private readonly IMessenger messenger; private ObservableCollection? userCollection; private ObservableCollection? userAndUidCollection; @@ -63,7 +64,7 @@ internal sealed partial class UserService : IUserService if (userCollection is null) { List entities = await userDbService.GetUserListAsync().ConfigureAwait(false); - List users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false); + List users = await entities.SelectListAsync(userInitializationService.ResumeUserAsync, default).ConfigureAwait(false); userCollection = users.ToObservableCollection(); try @@ -72,7 +73,7 @@ internal sealed partial class UserService : IUserService } catch (InvalidOperationException ex) { - throw new UserdataCorruptedException(SH.ServiceUserCurrentMultiMatched, ex); + ThrowHelper.UserdataCorrupted(SH.ServiceUserCurrentMultiMatched, ex); } } @@ -184,7 +185,7 @@ internal sealed partial class UserService : IUserService private async ValueTask> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea) { await taskContext.SwitchToBackgroundAsync(); - BindingUser? newUser = await BindingUser.CreateAsync(cookie, isOversea).ConfigureAwait(false); + BindingUser? newUser = await userInitializationService.CreateOrDefaultUserFromCookieAsync(cookie, isOversea).ConfigureAwait(false); if (newUser is not null) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs index 4fd0e922..48d626f0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/User.cs @@ -3,6 +3,7 @@ using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.Messaging; +using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Database; using Snap.Hutao.Core.DependencyInjection.Abstraction; using Snap.Hutao.Model; @@ -17,34 +18,36 @@ namespace Snap.Hutao.ViewModel.User; /// /// 用于视图绑定的用户 -/// TODO: move initializaion part to service /// [HighQuality] -internal sealed class User : ObservableObject, IEntityOnly, ISelectable +internal sealed class User : ObservableObject, IEntityOnly, IMappingFrom, ISelectable { private readonly EntityUser inner; + private readonly IServiceProvider serviceProvider; private UserGameRole? selectedUserGameRole; - private bool isInitialized; /// /// 构造一个新的绑定视图用户 /// /// 用户实体 - private User(EntityUser user) + private User(EntityUser user, IServiceProvider serviceProvider) { inner = user; + this.serviceProvider = serviceProvider; } - /// - /// 用户信息 - /// - public UserInfo? UserInfo { get; private set; } + public bool IsInitialized { get; set; } /// /// 用户信息 /// - public List UserGameRoles { get; private set; } = default!; + public UserInfo? UserInfo { get; set; } + + /// + /// 用户信息 + /// + public List UserGameRoles { get; set; } = default!; /// /// 用户信息, 请勿访问set @@ -56,9 +59,8 @@ internal sealed class User : ObservableObject, IEntityOnly, ISelecta { if (SetProperty(ref selectedUserGameRole, value)) { - Ioc.Default - .GetRequiredService() - .Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this }); + IMessenger messenger = serviceProvider.GetRequiredService(); + messenger.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this }); } } } @@ -103,178 +105,8 @@ internal sealed class User : ObservableObject, IEntityOnly, ISelecta /// public EntityUser Entity { get => inner; } - /// - /// 从数据库恢复用户 - /// - /// 数据库实体 - /// 取消令牌 - /// 用户 - internal static async ValueTask ResumeAsync(EntityUser inner, CancellationToken token = default) + public static User From(EntityUser user, IServiceProvider provider) { - User user = new(inner); - - if (!await user.InitializeCoreAsync(token).ConfigureAwait(false)) - { - user.UserInfo = new() { Nickname = SH.ModelBindingUserInitializationFailed }; - user.UserGameRoles = new(); - } - - return user; - } - - /// - /// 创建并初始化用户 - /// - /// cookie - /// 是否为国际服 - /// 取消令牌 - /// 用户 - internal static async Task CreateAsync(Cookie cookie, bool isOversea, CancellationToken token = default) - { - // 这里只负责创建实体用户,稍后在用户服务中保存到数据库 - EntityUser entity = EntityUser.From(cookie, isOversea); - - entity.Aid = cookie.GetValueOrDefault(Cookie.STUID); - entity.Mid = isOversea ? entity.Aid : cookie.GetValueOrDefault(Cookie.MID); - entity.IsOversea = isOversea; - - if (entity.Aid != null && entity.Mid != null) - { - User user = new(entity); - bool initialized = await user.InitializeCoreAsync(token).ConfigureAwait(false); - - return initialized ? user : null; - } - else - { - return null; - } - } - - private async Task InitializeCoreAsync(CancellationToken token = default) - { - if (isInitialized) - { - // Prevent multiple initialization. - return true; - } - - if (SToken == null) - { - return false; - } - - using (IServiceScope scope = Ioc.Default.CreateScope()) - { - bool isOversea = Entity.IsOversea; - - if (!await TrySetLTokenAsync(scope.ServiceProvider, token).ConfigureAwait(false)) - { - return false; - } - - if (!await TrySetCookieTokenAsync(scope.ServiceProvider, token).ConfigureAwait(false)) - { - return false; - } - - if (!await TrySetUserInfoAsync(scope.ServiceProvider, token).ConfigureAwait(false)) - { - return false; - } - - if (!await TrySetUserGameRolesAsync(scope.ServiceProvider, token).ConfigureAwait(false)) - { - return false; - } - } - - SelectedUserGameRole = UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen); - return isInitialized = true; - } - - private async Task TrySetLTokenAsync(IServiceProvider provider, CancellationToken token) - { - if (LToken != null) - { - return true; - } - - Response lTokenResponse = await provider - .GetRequiredService>() - .Create(Entity.IsOversea) - .GetLTokenBySTokenAsync(Entity, token) - .ConfigureAwait(false); - - if (lTokenResponse.IsOk()) - { - LToken = Cookie.Parse($"{Cookie.LTUID}={Entity.Aid};{Cookie.LTOKEN}={lTokenResponse.Data.LToken}"); - return true; - } - else - { - return false; - } - } - - private async Task TrySetCookieTokenAsync(IServiceProvider provider, CancellationToken token) - { - if (CookieToken != null) - { - return true; - } - - Response cookieTokenResponse = await provider - .GetRequiredService>() - .Create(Entity.IsOversea) - .GetCookieAccountInfoBySTokenAsync(Entity, token) - .ConfigureAwait(false); - - if (cookieTokenResponse.IsOk()) - { - CookieToken = Cookie.Parse($"{Cookie.ACCOUNT_ID}={Entity.Aid};{Cookie.COOKIE_TOKEN}={cookieTokenResponse.Data.CookieToken}"); - return true; - } - else - { - return false; - } - } - - private async Task TrySetUserInfoAsync(IServiceProvider provider, CancellationToken token) - { - Response response = await provider - .GetRequiredService>() - .Create(Entity.IsOversea) - .GetUserFullInfoAsync(Entity, token) - .ConfigureAwait(false); - - if (response.IsOk()) - { - UserInfo = response.Data.UserInfo; - return true; - } - else - { - return false; - } - } - - private async Task TrySetUserGameRolesAsync(IServiceProvider provider, CancellationToken token) - { - Response> userGameRolesResponse = await provider - .GetRequiredService() - .GetUserGameRolesOverseaAwareAsync(Entity, token) - .ConfigureAwait(false); - - if (userGameRolesResponse.IsOk()) - { - UserGameRoles = userGameRolesResponse.Data.List; - return UserGameRoles.Any(); - } - else - { - return false; - } + return new(user, provider); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs index 436e914d..2003ace6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/User/UserViewModel.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using CommunityToolkit.Mvvm.ComponentModel; +using Snap.Hutao.Core; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.IO.DataTransfer; using Snap.Hutao.Service.Navigation; @@ -23,9 +24,10 @@ namespace Snap.Hutao.ViewModel.User; [Injection(InjectAs.Singleton)] internal sealed partial class UserViewModel : ObservableObject { + private readonly INavigationService navigationService; private readonly IServiceProvider serviceProvider; private readonly IInfoBarService infoBarService; - private readonly Core.RuntimeOptions hutaoOptions; + private readonly RuntimeOptions runtimeOptions; private readonly ITaskContext taskContext; private readonly IUserService userService; @@ -44,7 +46,7 @@ internal sealed partial class UserViewModel : ObservableObject { userService.Current = value; - if (value != null) + if (value is not null) { value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen); } @@ -63,12 +65,13 @@ internal sealed partial class UserViewModel : ObservableObject /// 操作结果 /// uid /// 任务 - public async Task HandleUserOptionResultAsync(UserOptionResult optionResult, string uid) + internal async ValueTask HandleUserOptionResultAsync(UserOptionResult optionResult, string uid) { switch (optionResult) { case UserOptionResult.Added: - if (Users!.Count == 1) + ArgumentNullException.ThrowIfNull(Users); + if (Users.Count == 1) { await taskContext.SwitchToMainThreadAsync(); SelectedUser = Users.Single(); @@ -107,16 +110,16 @@ internal sealed partial class UserViewModel : ObservableObject [Command("AddUserCommand")] private Task AddUserAsync() { - return AddUserCoreAsync(false); + return AddUserCoreAsync(false).AsTask(); } [Command("AddOverseaUserCommand")] private Task AddOverseaUserAsync() { - return AddUserCoreAsync(true); + return AddUserCoreAsync(true).AsTask(); } - private async Task AddUserCoreAsync(bool isOversea) + private async ValueTask AddUserCoreAsync(bool isOversea) { // ContentDialog must be created by main thread. await taskContext.SwitchToMainThreadAsync(); @@ -126,9 +129,9 @@ internal sealed partial class UserViewModel : ObservableObject ValueResult result = await dialog.GetInputCookieAsync().ConfigureAwait(false); // User confirms the input - if (result.IsOk) + if (result.TryGetValue(out string rawCookie)) { - Cookie cookie = Cookie.Parse(result.Value); + Cookie cookie = Cookie.Parse(rawCookie); (UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie, isOversea).ConfigureAwait(false); @@ -139,11 +142,9 @@ internal sealed partial class UserViewModel : ObservableObject [Command("LoginMihoyoUserCommand")] private void LoginMihoyoUser() { - if (hutaoOptions.IsWebView2Supported) + if (runtimeOptions.IsWebView2Supported) { - serviceProvider - .GetRequiredService() - .Navigate(INavigationAwaiter.Default); + navigationService.Navigate(INavigationAwaiter.Default); } else { @@ -154,11 +155,9 @@ internal sealed partial class UserViewModel : ObservableObject [Command("LoginHoyoverseUserCommand")] private void LoginHoyoverseUser() { - if (hutaoOptions.IsWebView2Supported) + if (runtimeOptions.IsWebView2Supported) { - serviceProvider - .GetRequiredService() - .Navigate(INavigationAwaiter.Default); + navigationService.Navigate(INavigationAwaiter.Default); } else { @@ -169,11 +168,11 @@ internal sealed partial class UserViewModel : ObservableObject [Command("RemoveUserCommand")] private async Task RemoveUserAsync(User? user) { - if (user != null) + if (user is not null) { try { - await userService.RemoveUserAsync(user!).ConfigureAwait(false); + await userService.RemoveUserAsync(user).ConfigureAwait(false); infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname)); } catch (UserdataCorruptedException ex) @@ -188,26 +187,29 @@ internal sealed partial class UserViewModel : ObservableObject { try { + ArgumentNullException.ThrowIfNull(user); string cookieString = new StringBuilder() - .Append(user!.SToken) - .AppendIf(user.SToken != null, ';') + .Append(user.SToken) + .AppendIf(user.SToken is not null, ';') .Append(user.LToken) - .AppendIf(user.LToken != null, ';') + .AppendIf(user.LToken is not null, ';') .Append(user.CookieToken) .ToString(); serviceProvider.GetRequiredService().SetText(cookieString); - infoBarService.Success(string.Format(SH.ViewModelUserCookieCopied, user.UserInfo!.Nickname)); + + ArgumentNullException.ThrowIfNull(user.UserInfo); + infoBarService.Success(string.Format(SH.ViewModelUserCookieCopied, user.UserInfo.Nickname)); } - catch (Exception e) + catch (Exception ex) { - infoBarService.Error(e); + infoBarService.Error(ex); } } [Command("RefreshCookieTokenCommand")] private async Task RefreshCookieTokenAsync() { - if (SelectedUser != null) + if (SelectedUser is not null) { if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false)) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs index 66dfeda7..33e67f79 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/AvatarFilter.cs @@ -26,6 +26,7 @@ internal static class AvatarFilter { List matches = new(); + // TODO: use Collection Literals foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' })) { string value = segment.ToString(); diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs index 19b5bc04..ede05192 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/BaseValueInfo.cs @@ -71,7 +71,7 @@ internal sealed class BaseValueInfo : ObservableObject /// public string CurrentLevelFormatted { - get => $"Lv.{CurrentLevel}"; + get => LevelFormat.Format(CurrentLevel); } /// @@ -95,10 +95,10 @@ internal sealed class BaseValueInfo : ObservableObject Values = propValues.SelectList(propValue => { float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type); - if (promoteMap != null) + if (promoteMap is not null) { PromoteLevel promoteLevel = GetPromoteLevel(level, promoted); - float addValue = promoteMap[promoteLevel].AddPropertyMap.GetValueOrDefault(propValue.Property, 0F); + float addValue = promoteMap[promoteLevel].GetValue(propValue.Property); value += addValue; } @@ -109,59 +109,36 @@ internal sealed class BaseValueInfo : ObservableObject private PromoteLevel GetPromoteLevel(in Level level, bool promoted) { - if (MaxLevel <= 70) + if (MaxLevel <= 70 && level.Value == 70U) { - if (promoted) + return 4; + } + + if (promoted) + { + return level.Value switch { - return level.Value switch - { - >= 60U => 4, - >= 50U => 3, - >= 40U => 2, - >= 20U => 1, - _ => 0, - }; - } - else - { - return level.Value switch - { - > 60U => 4, - > 50U => 3, - > 40U => 2, - > 20U => 1, - _ => 0, - }; - } + >= 80U => 6, + >= 70U => 5, + >= 60U => 4, + >= 50U => 3, + >= 40U => 2, + >= 20U => 1, + _ => 0, + }; } else { - if (promoted) + return level.Value switch { - return level.Value switch - { - >= 80U => 6, - >= 70U => 5, - >= 60U => 4, - >= 50U => 3, - >= 40U => 2, - >= 20U => 1, - _ => 0, - }; - } - else - { - return level.Value switch - { - > 80U => 6, - > 70U => 5, - > 60U => 4, - > 50U => 3, - > 40U => 2, - > 20U => 1, - _ => 0, - }; - } + > 80U => 6, + > 70U => 5, + > 60U => 4, + > 50U => 3, + > 40U => 2, + > 20U => 1, + _ => 0, + }; } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs index 8337de49..ebe6b8fe 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/CookBonusView.cs @@ -31,7 +31,7 @@ internal sealed class CookBonusView /// 新的料理奖励视图 public static CookBonusView? Create(CookBonus? cookBonus, Dictionary idMaterialMap) { - if (cookBonus == null) + if (cookBonus is null) { return null; } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs index 99088b81..d1cca049 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/PropertyCurveValue.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata; namespace Snap.Hutao.ViewModel.Wiki; @@ -23,6 +24,11 @@ internal sealed class PropertyCurveValue Value = value; } + public PropertyCurveValue(FightProperty property, Dictionary growCurve, BaseValue baseValue) + : this(property, growCurve.GetValueOrDefault(property), baseValue.GetValue(property)) + { + } + /// /// 战斗属性值 /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index f2ea8b37..d603d067 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -2,11 +2,9 @@ // Licensed under the MIT license. using CommunityToolkit.WinUI.UI; -using Microsoft.Extensions.Primitives; using Snap.Hutao.Model.Calculable; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Intrinsic.Immutable; using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Item; @@ -17,15 +15,14 @@ using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; -using Snap.Hutao.ViewModel.Complex; using Snap.Hutao.Web.Response; using System.Collections.Immutable; using System.Runtime.InteropServices; -using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; -using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; -using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; -using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; -using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper; +using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; +using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; +using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; +using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; +using CalculateItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper; namespace Snap.Hutao.ViewModel.Wiki; @@ -41,6 +38,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel private readonly IMetadataService metadataService; private readonly ITaskContext taskContext; private readonly IHutaoCache hutaoCache; + private readonly IInfoBarService infoBarService; + private readonly IUserService userService; private AdvancedCollectionView? avatars; private Avatar? selected; @@ -78,150 +77,159 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel /// public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); } - /// - protected override async Task OpenUIAsync() + protected override async ValueTask InitializeUIAsync() { - if (await metadataService.InitializeAsync().ConfigureAwait(false)) + if (!await metadataService.InitializeAsync().ConfigureAwait(false)) { - levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false); - promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false); - - Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); - List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false); - List sorted = avatars - .OrderByDescending(avatar => avatar.BeginTime) - .ThenByDescending(avatar => avatar.Sort) - .ToList(); - - await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false); - - await taskContext.SwitchToMainThreadAsync(); - Avatars = new AdvancedCollectionView(sorted, true); - Selected = Avatars.Cast().FirstOrDefault(); + return false; } + + levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false); + promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false); + + Dictionary idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false); + List avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false); + List sorted = avatars + .OrderByDescending(avatar => avatar.BeginTime) + .ThenByDescending(avatar => avatar.Sort) + .ToList(); + + await CombineComplexDataAsync(sorted, idMaterialMap).ConfigureAwait(false); + + await taskContext.SwitchToMainThreadAsync(); + Avatars = new AdvancedCollectionView(sorted, true); + Selected = Avatars.Cast().FirstOrDefault(); + return true; } - private async Task CombineComplexDataAsync(List avatars, Dictionary idMaterialMap) + private async ValueTask CombineComplexDataAsync(List avatars, Dictionary idMaterialMap) { - if (await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false)) + if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false)) { - Dictionary idCollocations = hutaoCache.AvatarCollocations!.ToDictionary(a => a.AvatarId); + return; + } - foreach (Avatar avatar in avatars) - { - avatar.Collocation = idCollocations.GetValueOrDefault(avatar.Id); - avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap); - avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default)!); - } + ArgumentNullException.ThrowIfNull(hutaoCache.AvatarCollocations); + + foreach (Avatar avatar in avatars) + { + avatar.Collocation = hutaoCache.AvatarCollocations.GetValueOrDefault(avatar.Id); + avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap); + avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default)); } } [Command("CultivateCommand")] private async Task CultivateAsync(Avatar? avatar) { - if (avatar != null) + if (avatar is null) { - IInfoBarService infoBarService = serviceProvider.GetRequiredService(); - IUserService userService = serviceProvider.GetRequiredService(); + return; + } - if (userService.Current != null) + if (userService.Current is null) + { + infoBarService.Warning(SH.MustSelectUserAndUid); + return; + } + + // ContentDialog must be created by main thread. + await taskContext.SwitchToMainThreadAsync(); + CalculableOptions options = new(avatar.ToCalculable(), null); + CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options); + (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + + if (!isOk) + { + return; + } + + Response consumptionResponse = await serviceProvider + .GetRequiredService() + .ComputeAsync(userService.Current.Entity, delta) + .ConfigureAwait(false); + + if (!consumptionResponse.IsOk()) + { + return; + } + + CalculateConsumption consumption = consumptionResponse.Data; + List items = CalculateItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); + try + { + bool saved = await serviceProvider + .GetRequiredService() + .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) + .ConfigureAwait(false); + + if (saved) { - // ContentDialog must be created by main thread. - await taskContext.SwitchToMainThreadAsync(); - CalculableOptions options = new(avatar.ToCalculable(), null); - CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options); - (bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); - - if (isOk) - { - Response consumptionResponse = await serviceProvider - .GetRequiredService() - .ComputeAsync(userService.Current.Entity, delta) - .ConfigureAwait(false); - - if (consumptionResponse.IsOk()) - { - CalcConsumption consumption = consumptionResponse.Data; - - List items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); - try - { - bool saved = await serviceProvider - .GetRequiredService() - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) - .ConfigureAwait(false); - - if (saved) - { - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); - } - else - { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); - } - } - catch (Core.ExceptionService.UserdataCorruptedException ex) - { - infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); - } - } - } + infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); } else { - infoBarService.Warning(SH.MustSelectUserAndUid); + infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); } } + catch (Core.ExceptionService.UserdataCorruptedException ex) + { + infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); + } } private void UpdateBaseValueInfo(Avatar? avatar) { - if (avatar == null) + if (avatar is null) { BaseValueInfo = null; + return; } - else + + ArgumentNullException.ThrowIfNull(promotes); + Dictionary avatarPromoteMap = promotes.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level); + Dictionary avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value); + FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type; + + List propertyCurveValues = new() { - Dictionary avatarPromoteMap = promotes!.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level); - Dictionary avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value); - FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type; + new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve, avatar.BaseValue), + new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve, avatar.BaseValue), + new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve, avatar.BaseValue), + new(promoteProperty, avatarGrowCurve, avatar.BaseValue), + }; - List propertyCurveValues = new() - { - new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_HP], avatar.BaseValue.HpBase), - new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_ATTACK], avatar.BaseValue.AttackBase), - new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve[FightProperty.FIGHT_PROP_BASE_DEFENSE], avatar.BaseValue.DefenseBase), - new(promoteProperty, GrowCurveType.GROW_CURVE_NONE, 0), - }; - - BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap!, avatarPromoteMap); - } + ArgumentNullException.ThrowIfNull(levelAvatarCurveMap); + BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap); } [Command("FilterCommand")] private void ApplyFilter(string? input) { - if (Avatars != null) + if (Avatars is null) { - if (!string.IsNullOrWhiteSpace(input)) - { - Avatars.Filter = AvatarFilter.Compile(input); + return; + } - if (!Avatars.Contains(Selected)) - { - try - { - Avatars.MoveCurrentToFirst(); - } - catch (COMException) - { - } - } - } - else - { - Avatars.Filter = null!; - } + if (string.IsNullOrWhiteSpace(input)) + { + Avatars.Filter = default!; + return; + } + + Avatars.Filter = AvatarFilter.Compile(input); + + if (Avatars.Contains(Selected)) + { + return; + } + + try + { + Avatars.MoveCurrentToFirst(); + } + catch (COMException) + { } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs index 9912e1ab..926f9402 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiMonsterViewModel.cs @@ -50,8 +50,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel /// public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); } - /// - protected override async Task OpenUIAsync() + protected override async ValueTask InitializeUIAsync() { if (await metadataService.InitializeAsync().ConfigureAwait(false)) { @@ -61,30 +60,33 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel Dictionary idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false); foreach (Monster monster in monsters) { - monster.DropsView ??= monster.Drops?.SelectList(i => idDisplayMap.GetValueOrDefault(i)!); + monster.DropsView ??= monster.Drops?.SelectList(i => idDisplayMap.GetValueOrDefault(i, Material.Default)); } - List ordered = monsters.OrderBy(m => m.Id.Value).ToList(); + List ordered = monsters.SortBy(m => m.Id.Value); await taskContext.SwitchToMainThreadAsync(); Monsters = new AdvancedCollectionView(ordered, true); Selected = Monsters.Cast().FirstOrDefault(); + return true; } + + return false; } private void UpdateBaseValueInfo(Monster? monster) { - if (monster == null) + if (monster is null) { BaseValueInfo = null; } else { List propertyCurveValues = monster.GrowCurves - .Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type))) - .ToList(); + .SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type))); - BaseValueInfo = new(100, propertyCurveValues, levelMonsterCurveMap!); + ArgumentNullException.ThrowIfNull(levelMonsterCurveMap); + BaseValueInfo = new(Monster.MaxLevel, propertyCurveValues, levelMonsterCurveMap); } } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 69e07f90..32a19dea 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -14,13 +14,12 @@ using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Notification; using Snap.Hutao.Service.User; using Snap.Hutao.View.Dialog; -using Snap.Hutao.ViewModel.Complex; using Snap.Hutao.Web.Response; using System.Collections.Immutable; using System.Runtime.InteropServices; -using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; -using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; -using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; +using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; +using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; +using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption; namespace Snap.Hutao.ViewModel.Wiki; @@ -31,16 +30,12 @@ namespace Snap.Hutao.ViewModel.Wiki; [Injection(InjectAs.Scoped)] internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel { - private static readonly List SkippedWeapons = new() - { - 12304, 14306, 15306, 13304, // 石英大剑, 琥珀玥, 黑檀弓, 「旗杆」 - 11419, 11420, 11421, // 「一心传」名刀 - }; - private readonly ITaskContext taskContext; private readonly IServiceProvider serviceProvider; private readonly IMetadataService metadataService; private readonly IHutaoCache hutaoCache; + private readonly IInfoBarService infoBarService; + private readonly IUserService userService; private AdvancedCollectionView? weapons; private Weapon? selected; @@ -88,7 +83,6 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel List weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false); List sorted = weapons - .Where(weapon => !SkippedWeapons.Contains(weapon.Id)) .OrderByDescending(weapon => weapon.RankLevel) .ThenBy(weapon => weapon.WeaponType) .ThenByDescending(weapon => weapon.Id.Value) @@ -103,114 +97,117 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel } } - private async Task CombineWithWeaponCollocationsAsync(List weapons) + private async ValueTask CombineWithWeaponCollocationsAsync(List weapons) { if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false)) { - Dictionary idCollocations = hutaoCache.WeaponCollocations!.ToDictionary(a => a.WeaponId); - weapons.ForEach(w => w.Collocation = idCollocations.GetValueOrDefault(w.Id)); + ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations); + weapons.ForEach(w => w.Collocation = hutaoCache.WeaponCollocations.GetValueOrDefault(w.Id)); } } [Command("CultivateCommand")] private async Task CultivateAsync(Weapon? weapon) { - if (weapon != null) + if (weapon is null) { - IInfoBarService infoBarService = serviceProvider.GetRequiredService(); - IUserService userService = serviceProvider.GetRequiredService(); + return; + } - if (userService.Current != null) + if (userService.Current is null) + { + infoBarService.Warning(SH.MustSelectUserAndUid); + return; + } + + // ContentDialog must be created by main thread. + await taskContext.SwitchToMainThreadAsync(); + CalculableOptions options = new(null, weapon.ToCalculable()); + CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options); + (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + + if (!isOk) + { + return; + } + + Response consumptionResponse = await serviceProvider + .GetRequiredService() + .ComputeAsync(userService.Current.Entity, delta) + .ConfigureAwait(false); + + if (!consumptionResponse.IsOk()) + { + return; + } + + CalculateConsumption consumption = consumptionResponse.Data; + try + { + bool saved = await serviceProvider + .GetRequiredService() + .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull()) + .ConfigureAwait(false); + + if (saved) { - // ContentDialog must be created by main thread. - await taskContext.SwitchToMainThreadAsync(); - CalculableOptions options = new(null, weapon.ToCalculable()); - CultivatePromotionDeltaDialog dialog = serviceProvider.CreateInstance(options); - (bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); - - if (isOk) - { - Response consumptionResponse = await serviceProvider - .GetRequiredService() - .ComputeAsync(userService.Current.Entity, delta) - .ConfigureAwait(false); - - if (consumptionResponse.IsOk()) - { - CalcConsumption consumption = consumptionResponse.Data; - - try - { - bool saved = await serviceProvider - .GetRequiredService() - .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull()) - .ConfigureAwait(false); - - if (saved) - { - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); - } - else - { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); - } - } - catch (Core.ExceptionService.UserdataCorruptedException ex) - { - infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); - } - } - } + infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); } else { - infoBarService.Warning(SH.MustSelectUserAndUid); + infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); } } + catch (Core.ExceptionService.UserdataCorruptedException ex) + { + infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); + } } private void UpdateBaseValueInfo(Weapon? weapon) { - if (weapon == null) + if (weapon is null) { BaseValueInfo = null; + return; } - else - { - Dictionary weaponPromoteMap = promotes!.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level); - List propertyCurveValues = weapon.GrowCurves - .Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue)) - .ToList(); + ArgumentNullException.ThrowIfNull(promotes); + Dictionary weaponPromoteMap = promotes.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level); + List propertyCurveValues = weapon.GrowCurves + .SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue)); - BaseValueInfo = new(weapon.MaxLevel, propertyCurveValues, levelWeaponCurveMap!, weaponPromoteMap); - } + ArgumentNullException.ThrowIfNull(levelWeaponCurveMap); + BaseValueInfo = new(weapon.MaxLevel, propertyCurveValues, levelWeaponCurveMap, weaponPromoteMap); } [Command("FilterCommand")] private void ApplyFilter(string? input) { - if (Weapons != null) + if (Weapons is null) { - if (!string.IsNullOrWhiteSpace(input)) - { - Weapons.Filter = WeaponFilter.Compile(input); + return; + } - if (!Weapons.Contains(Selected)) - { - try - { - Weapons.MoveCurrentToFirst(); - } - catch (COMException) - { - } - } - } - else - { - Weapons.Filter = null!; - } + if (string.IsNullOrWhiteSpace(input)) + { + Weapons.Filter = default!; + return; + } + + Weapons.Filter = WeaponFilter.Compile(input); + + if (Weapons.Contains(Selected)) + { + return; + } + + try + { + Weapons.MoveCurrentToFirst(); + } + catch (COMException) + { } } } \ No newline at end of file