mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor wiki viewmodel
This commit is contained in:
@@ -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;
|
||||
/// </summary>
|
||||
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),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ namespace Snap.Hutao.Model.Metadata.Monster;
|
||||
/// </summary>
|
||||
internal sealed class Monster
|
||||
{
|
||||
internal const uint MaxLevel = 100;
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
|
||||
@@ -29,11 +29,9 @@ internal sealed class Promote
|
||||
/// </summary>
|
||||
public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 属性映射
|
||||
/// </summary>
|
||||
public Dictionary<FightProperty, float> 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);
|
||||
}
|
||||
}
|
||||
15
src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs
Normal file
15
src/Snap.Hutao/Snap.Hutao/Model/Primitive/LevelFormat.cs
Normal file
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -45,10 +45,10 @@ internal sealed partial class HutaoCache : IHutaoCache
|
||||
public Overview? Overview { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||
public Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public List<WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||
public Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async ValueTask<bool> 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<AvatarId, Avatar> 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")]
|
||||
|
||||
@@ -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
|
||||
/// <summary>
|
||||
/// 角色搭配
|
||||
/// </summary>
|
||||
List<AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||
Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器搭配
|
||||
/// </summary>
|
||||
List<WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||
Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 为数据库视图模型初始化
|
||||
|
||||
@@ -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<ViewModel.User.User?> CreateOrDefaultUserFromCookieAsync(Cookie cookie, bool isOversea, CancellationToken token = default(CancellationToken));
|
||||
|
||||
ValueTask<ViewModel.User.User> ResumeUserAsync(Model.Entity.User inner, CancellationToken token = default(CancellationToken));
|
||||
}
|
||||
@@ -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<ViewModel.User.User> 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<ViewModel.User.User?> 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<bool> 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<bool> TrySetUserLTokenAsync(ViewModel.User.User user, CancellationToken token)
|
||||
{
|
||||
if (user.LToken is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Response<LTokenWrapper> lTokenResponse = await serviceProvider
|
||||
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
|
||||
.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<bool> TrySetUserCookieTokenAsync(ViewModel.User.User user, CancellationToken token)
|
||||
{
|
||||
if (user.CookieToken is not null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Response<UidCookieToken> cookieTokenResponse = await serviceProvider
|
||||
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
|
||||
.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<bool> TrySetUserUserInfoAsync(ViewModel.User.User user, CancellationToken token)
|
||||
{
|
||||
Response<UserFullInfoWrapper> response = await serviceProvider
|
||||
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
|
||||
.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<bool> TrySetUserUserGameRolesAsync(ViewModel.User.User user, CancellationToken token)
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await serviceProvider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesOverseaAwareAsync(user.Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (userGameRolesResponse.IsOk())
|
||||
{
|
||||
user.UserGameRoles = userGameRolesResponse.Data.List;
|
||||
return user.UserGameRoles.Any();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<BindingUser, Model.Entity.User, UserChangedMessage> dbCurrent;
|
||||
private readonly IUserInitializationService userInitializationService;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
private readonly IUserDbService userDbService;
|
||||
private readonly ITaskContext taskContext;
|
||||
private readonly IMessenger messenger;
|
||||
|
||||
private ObservableCollection<BindingUser>? userCollection;
|
||||
private ObservableCollection<UserAndUid>? userAndUidCollection;
|
||||
@@ -63,7 +64,7 @@ internal sealed partial class UserService : IUserService
|
||||
if (userCollection is null)
|
||||
{
|
||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
||||
List<BindingUser> users = await entities.SelectListAsync(BindingUser.ResumeAsync, default).ConfigureAwait(false);
|
||||
List<BindingUser> 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<ValueResult<UserOptionResult, string>> 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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的用户
|
||||
/// TODO: move initializaion part to service
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelectable
|
||||
internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, IMappingFrom<User, EntityUser, IServiceProvider>, ISelectable
|
||||
{
|
||||
private readonly EntityUser inner;
|
||||
private readonly IServiceProvider serviceProvider;
|
||||
|
||||
private UserGameRole? selectedUserGameRole;
|
||||
private bool isInitialized;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的绑定视图用户
|
||||
/// </summary>
|
||||
/// <param name="user">用户实体</param>
|
||||
private User(EntityUser user)
|
||||
private User(EntityUser user, IServiceProvider serviceProvider)
|
||||
{
|
||||
inner = user;
|
||||
this.serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public UserInfo? UserInfo { get; private set; }
|
||||
public bool IsInitialized { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public List<UserGameRole> UserGameRoles { get; private set; } = default!;
|
||||
public UserInfo? UserInfo { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息
|
||||
/// </summary>
|
||||
public List<UserGameRole> UserGameRoles { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 用户信息, 请勿访问set
|
||||
@@ -56,9 +59,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
|
||||
{
|
||||
if (SetProperty(ref selectedUserGameRole, value))
|
||||
{
|
||||
Ioc.Default
|
||||
.GetRequiredService<IMessenger>()
|
||||
.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
|
||||
IMessenger messenger = serviceProvider.GetRequiredService<IMessenger>();
|
||||
messenger.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -103,178 +105,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
|
||||
/// </summary>
|
||||
public EntityUser Entity { get => inner; }
|
||||
|
||||
/// <summary>
|
||||
/// 从数据库恢复用户
|
||||
/// </summary>
|
||||
/// <param name="inner">数据库实体</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户</returns>
|
||||
internal static async ValueTask<User> 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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建并初始化用户
|
||||
/// </summary>
|
||||
/// <param name="cookie">cookie</param>
|
||||
/// <param name="isOversea">是否为国际服</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>用户</returns>
|
||||
internal static async Task<User?> 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<bool> 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<bool> TrySetLTokenAsync(IServiceProvider provider, CancellationToken token)
|
||||
{
|
||||
if (LToken != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Response<LTokenWrapper> lTokenResponse = await provider
|
||||
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
|
||||
.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<bool> TrySetCookieTokenAsync(IServiceProvider provider, CancellationToken token)
|
||||
{
|
||||
if (CookieToken != null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
Response<UidCookieToken> cookieTokenResponse = await provider
|
||||
.GetRequiredService<IOverseaSupportFactory<IPassportClient>>()
|
||||
.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<bool> TrySetUserInfoAsync(IServiceProvider provider, CancellationToken token)
|
||||
{
|
||||
Response<UserFullInfoWrapper> response = await provider
|
||||
.GetRequiredService<IOverseaSupportFactory<IUserClient>>()
|
||||
.Create(Entity.IsOversea)
|
||||
.GetUserFullInfoAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (response.IsOk())
|
||||
{
|
||||
UserInfo = response.Data.UserInfo;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private async Task<bool> TrySetUserGameRolesAsync(IServiceProvider provider, CancellationToken token)
|
||||
{
|
||||
Response<ListWrapper<UserGameRole>> userGameRolesResponse = await provider
|
||||
.GetRequiredService<BindingClient>()
|
||||
.GetUserGameRolesOverseaAwareAsync(Entity, token)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (userGameRolesResponse.IsOk())
|
||||
{
|
||||
UserGameRoles = userGameRolesResponse.Data.List;
|
||||
return UserGameRoles.Any();
|
||||
}
|
||||
else
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return new(user, provider);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
/// <param name="optionResult">操作结果</param>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>任务</returns>
|
||||
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<bool, string> 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<INavigationService>()
|
||||
.Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
|
||||
navigationService.Navigate<LoginMihoyoUserPage>(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<INavigationService>()
|
||||
.Navigate<LoginHoyoverseUserPage>(INavigationAwaiter.Default);
|
||||
navigationService.Navigate<LoginHoyoverseUserPage>(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<IClipboardInterop>().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))
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@ internal static class AvatarFilter
|
||||
{
|
||||
List<bool> matches = new();
|
||||
|
||||
// TODO: use Collection Literals
|
||||
foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' }))
|
||||
{
|
||||
string value = segment.ToString();
|
||||
|
||||
@@ -71,7 +71,7 @@ internal sealed class BaseValueInfo : ObservableObject
|
||||
/// </summary>
|
||||
public string CurrentLevelFormatted
|
||||
{
|
||||
get => $"Lv.{CurrentLevel}";
|
||||
get => LevelFormat.Format(CurrentLevel);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -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,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -31,7 +31,7 @@ internal sealed class CookBonusView
|
||||
/// <returns>新的料理奖励视图</returns>
|
||||
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap)
|
||||
{
|
||||
if (cookBonus == null)
|
||||
if (cookBonus is null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -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<FightProperty, GrowCurveType> growCurve, BaseValue baseValue)
|
||||
: this(property, growCurve.GetValueOrDefault(property), baseValue.GetValue(property))
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 战斗属性值
|
||||
/// </summary>
|
||||
|
||||
@@ -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
|
||||
/// </summary>
|
||||
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
protected override async ValueTask<bool> 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<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
|
||||
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
|
||||
List<Avatar> 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<Avatar>().FirstOrDefault();
|
||||
return false;
|
||||
}
|
||||
|
||||
levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
|
||||
promotes = await metadataService.GetAvatarPromotesAsync().ConfigureAwait(false);
|
||||
|
||||
Dictionary<MaterialId, Material> idMaterialMap = await metadataService.GetIdToMaterialMapAsync().ConfigureAwait(false);
|
||||
List<Avatar> avatars = await metadataService.GetAvatarsAsync().ConfigureAwait(false);
|
||||
List<Avatar> 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<Avatar>().FirstOrDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
private async Task CombineComplexDataAsync(List<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap)
|
||||
private async ValueTask CombineComplexDataAsync(List<Avatar> avatars, Dictionary<MaterialId, Material> idMaterialMap)
|
||||
{
|
||||
if (await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
|
||||
if (!await hutaoCache.InitializeForWikiAvatarViewModelAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<AvatarId, AvatarCollocationView> 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<IInfoBarService>();
|
||||
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
|
||||
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<CultivatePromotionDeltaDialog>(options);
|
||||
(bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Response<CalculateConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalculateClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!consumptionResponse.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateConsumption consumption = consumptionResponse.Data;
|
||||
List<CalculateItem> items = CalculateItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||
try
|
||||
{
|
||||
bool saved = await serviceProvider
|
||||
.GetRequiredService<ICultivationService>()
|
||||
.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<CultivatePromotionDeltaDialog>(options);
|
||||
(bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
Response<CalcConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalcClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
CalcConsumption consumption = consumptionResponse.Data;
|
||||
|
||||
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||
try
|
||||
{
|
||||
bool saved = await serviceProvider
|
||||
.GetRequiredService<ICultivationService>()
|
||||
.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<PromoteLevel, Promote> avatarPromoteMap = promotes.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level);
|
||||
Dictionary<FightProperty, GrowCurveType> avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value);
|
||||
FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type;
|
||||
|
||||
List<PropertyCurveValue> propertyCurveValues = new()
|
||||
{
|
||||
Dictionary<PromoteLevel, Promote> avatarPromoteMap = promotes!.Where(p => p.Id == avatar.PromoteId).ToDictionary(p => p.Level);
|
||||
Dictionary<FightProperty, GrowCurveType> 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<PropertyCurveValue> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -50,8 +50,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
||||
/// </summary>
|
||||
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
|
||||
|
||||
/// <inheritdoc/>
|
||||
protected override async Task OpenUIAsync()
|
||||
protected override async ValueTask<bool> InitializeUIAsync()
|
||||
{
|
||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||
{
|
||||
@@ -61,30 +60,33 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
||||
Dictionary<MaterialId, DisplayItem> 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<Monster> ordered = monsters.OrderBy(m => m.Id.Value).ToList();
|
||||
List<Monster> ordered = monsters.SortBy(m => m.Id.Value);
|
||||
await taskContext.SwitchToMainThreadAsync();
|
||||
|
||||
Monsters = new AdvancedCollectionView(ordered, true);
|
||||
Selected = Monsters.Cast<Monster>().FirstOrDefault();
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateBaseValueInfo(Monster? monster)
|
||||
{
|
||||
if (monster == null)
|
||||
if (monster is null)
|
||||
{
|
||||
BaseValueInfo = null;
|
||||
}
|
||||
else
|
||||
{
|
||||
List<PropertyCurveValue> 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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<WeaponId> 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<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
|
||||
List<Weapon> 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<Weapon> weapons)
|
||||
private async ValueTask CombineWithWeaponCollocationsAsync(List<Weapon> weapons)
|
||||
{
|
||||
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
|
||||
{
|
||||
Dictionary<WeaponId, WeaponCollocationView> 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<IInfoBarService>();
|
||||
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
|
||||
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<CultivatePromotionDeltaDialog>(options);
|
||||
(bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Response<CalculateConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalculateClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!consumptionResponse.IsOk())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculateConsumption consumption = consumptionResponse.Data;
|
||||
try
|
||||
{
|
||||
bool saved = await serviceProvider
|
||||
.GetRequiredService<ICultivationService>()
|
||||
.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<CultivatePromotionDeltaDialog>(options);
|
||||
(bool isOk, CalcAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
Response<CalcConsumption> consumptionResponse = await serviceProvider
|
||||
.GetRequiredService<CalcClient>()
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (consumptionResponse.IsOk())
|
||||
{
|
||||
CalcConsumption consumption = consumptionResponse.Data;
|
||||
|
||||
try
|
||||
{
|
||||
bool saved = await serviceProvider
|
||||
.GetRequiredService<ICultivationService>()
|
||||
.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<PromoteLevel, Promote> weaponPromoteMap = promotes!.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
|
||||
|
||||
List<PropertyCurveValue> propertyCurveValues = weapon.GrowCurves
|
||||
.Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue))
|
||||
.ToList();
|
||||
ArgumentNullException.ThrowIfNull(promotes);
|
||||
Dictionary<PromoteLevel, Promote> weaponPromoteMap = promotes.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
|
||||
List<PropertyCurveValue> 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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user