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.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Core.Json.Annotation;
|
|
||||||
using Snap.Hutao.Model.Intrinsic;
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
using Snap.Hutao.Model.Primitive;
|
|
||||||
using Snap.Hutao.ViewModel.Wiki;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Model.Metadata.Avatar;
|
namespace Snap.Hutao.Model.Metadata.Avatar;
|
||||||
|
|
||||||
@@ -13,4 +10,13 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class AvatarBaseValue : BaseValue
|
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>
|
/// </summary>
|
||||||
internal sealed class Monster
|
internal sealed class Monster
|
||||||
{
|
{
|
||||||
|
internal const uint MaxLevel = 100;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Id
|
/// Id
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -29,11 +29,9 @@ internal sealed class Promote
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!;
|
public List<TypeValue<FightProperty, float>> AddProperties { get; set; } = default!;
|
||||||
|
|
||||||
/// <summary>
|
public float GetValue(FightProperty property)
|
||||||
/// 属性映射
|
|
||||||
/// </summary>
|
|
||||||
public Dictionary<FightProperty, float> AddPropertyMap
|
|
||||||
{
|
{
|
||||||
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; }
|
public Overview? Overview { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public List<AvatarCollocationView>? AvatarCollocations { get; set; }
|
public Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public List<WeaponCollocationView>? WeaponCollocations { get; set; }
|
public Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
/// <inheritdoc/>
|
||||||
public async ValueTask<bool> InitializeForDatabaseViewModelAsync()
|
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)),
|
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
|
||||||
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
|
Weapons = co.Weapons.SelectList(w => new WeaponView(idWeaponMap[w.Item], w.Rate)),
|
||||||
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
|
ReliquarySets = co.Reliquaries.SelectList(r => new ReliquarySetView(r, idReliquarySetMap)),
|
||||||
});
|
}).ToDictionary(a => a.AvatarId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
private async ValueTask WeaponCollocationsAsync(Dictionary<AvatarId, Avatar> idAvatarMap)
|
||||||
@@ -169,7 +169,7 @@ internal sealed partial class HutaoCache : IHutaoCache
|
|||||||
{
|
{
|
||||||
WeaponId = co.WeaponId,
|
WeaponId = co.WeaponId,
|
||||||
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
|
Avatars = co.Avatars.SelectList(a => new AvatarView(idAvatarMap[a.Item], a.Rate)),
|
||||||
});
|
}).ToDictionary(w => w.WeaponId);
|
||||||
}
|
}
|
||||||
|
|
||||||
[SuppressMessage("", "SH003")]
|
[SuppressMessage("", "SH003")]
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Model.Binding.Hutao;
|
using Snap.Hutao.Model.Binding.Hutao;
|
||||||
|
using Snap.Hutao.Model.Primitive;
|
||||||
using Snap.Hutao.ViewModel.Complex;
|
using Snap.Hutao.ViewModel.Complex;
|
||||||
using Snap.Hutao.Web.Hutao.Model;
|
using Snap.Hutao.Web.Hutao.Model;
|
||||||
|
|
||||||
@@ -41,12 +42,12 @@ internal interface IHutaoCache
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// 角色搭配
|
/// 角色搭配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<AvatarCollocationView>? AvatarCollocations { get; set; }
|
Dictionary<AvatarId, AvatarCollocationView>? AvatarCollocations { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 武器搭配
|
/// 武器搭配
|
||||||
/// </summary>
|
/// </summary>
|
||||||
List<WeaponCollocationView>? WeaponCollocations { get; set; }
|
Dictionary<WeaponId, WeaponCollocationView>? WeaponCollocations { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <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))]
|
[Injection(InjectAs.Singleton, typeof(IUserService))]
|
||||||
internal sealed partial class UserService : 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 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<BindingUser>? userCollection;
|
||||||
private ObservableCollection<UserAndUid>? userAndUidCollection;
|
private ObservableCollection<UserAndUid>? userAndUidCollection;
|
||||||
@@ -63,7 +64,7 @@ internal sealed partial class UserService : IUserService
|
|||||||
if (userCollection is null)
|
if (userCollection is null)
|
||||||
{
|
{
|
||||||
List<Model.Entity.User> entities = await userDbService.GetUserListAsync().ConfigureAwait(false);
|
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();
|
userCollection = users.ToObservableCollection();
|
||||||
|
|
||||||
try
|
try
|
||||||
@@ -72,7 +73,7 @@ internal sealed partial class UserService : IUserService
|
|||||||
}
|
}
|
||||||
catch (InvalidOperationException ex)
|
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)
|
private async ValueTask<ValueResult<UserOptionResult, string>> TryCreateUserAndAddAsync(Cookie cookie, bool isOversea)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToBackgroundAsync();
|
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)
|
if (newUser is not null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
using CommunityToolkit.Mvvm.Messaging;
|
using CommunityToolkit.Mvvm.Messaging;
|
||||||
|
using Snap.Hutao.Core.Abstraction;
|
||||||
using Snap.Hutao.Core.Database;
|
using Snap.Hutao.Core.Database;
|
||||||
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
using Snap.Hutao.Core.DependencyInjection.Abstraction;
|
||||||
using Snap.Hutao.Model;
|
using Snap.Hutao.Model;
|
||||||
@@ -17,34 +18,36 @@ namespace Snap.Hutao.ViewModel.User;
|
|||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用于视图绑定的用户
|
/// 用于视图绑定的用户
|
||||||
/// TODO: move initializaion part to service
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[HighQuality]
|
[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 EntityUser inner;
|
||||||
|
private readonly IServiceProvider serviceProvider;
|
||||||
|
|
||||||
private UserGameRole? selectedUserGameRole;
|
private UserGameRole? selectedUserGameRole;
|
||||||
private bool isInitialized;
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的绑定视图用户
|
/// 构造一个新的绑定视图用户
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="user">用户实体</param>
|
/// <param name="user">用户实体</param>
|
||||||
private User(EntityUser user)
|
private User(EntityUser user, IServiceProvider serviceProvider)
|
||||||
{
|
{
|
||||||
inner = user;
|
inner = user;
|
||||||
|
this.serviceProvider = serviceProvider;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
public bool IsInitialized { get; set; }
|
||||||
/// 用户信息
|
|
||||||
/// </summary>
|
|
||||||
public UserInfo? UserInfo { get; private set; }
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 用户信息
|
/// 用户信息
|
||||||
/// </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>
|
/// <summary>
|
||||||
/// 用户信息, 请勿访问set
|
/// 用户信息, 请勿访问set
|
||||||
@@ -56,9 +59,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
|
|||||||
{
|
{
|
||||||
if (SetProperty(ref selectedUserGameRole, value))
|
if (SetProperty(ref selectedUserGameRole, value))
|
||||||
{
|
{
|
||||||
Ioc.Default
|
IMessenger messenger = serviceProvider.GetRequiredService<IMessenger>();
|
||||||
.GetRequiredService<IMessenger>()
|
messenger.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
|
||||||
.Send(new Message.UserChangedMessage() { OldValue = this, NewValue = this });
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -103,178 +105,8 @@ internal sealed class User : ObservableObject, IEntityOnly<EntityUser>, ISelecta
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public EntityUser Entity { get => inner; }
|
public EntityUser Entity { get => inner; }
|
||||||
|
|
||||||
/// <summary>
|
public static User From(EntityUser user, IServiceProvider provider)
|
||||||
/// 从数据库恢复用户
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="inner">数据库实体</param>
|
|
||||||
/// <param name="token">取消令牌</param>
|
|
||||||
/// <returns>用户</returns>
|
|
||||||
internal static async ValueTask<User> ResumeAsync(EntityUser inner, CancellationToken token = default)
|
|
||||||
{
|
{
|
||||||
User user = new(inner);
|
return new(user, provider);
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.Mvvm.ComponentModel;
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
using Snap.Hutao.Core;
|
||||||
using Snap.Hutao.Core.ExceptionService;
|
using Snap.Hutao.Core.ExceptionService;
|
||||||
using Snap.Hutao.Core.IO.DataTransfer;
|
using Snap.Hutao.Core.IO.DataTransfer;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
@@ -23,9 +24,10 @@ namespace Snap.Hutao.ViewModel.User;
|
|||||||
[Injection(InjectAs.Singleton)]
|
[Injection(InjectAs.Singleton)]
|
||||||
internal sealed partial class UserViewModel : ObservableObject
|
internal sealed partial class UserViewModel : ObservableObject
|
||||||
{
|
{
|
||||||
|
private readonly INavigationService navigationService;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly IInfoBarService infoBarService;
|
private readonly IInfoBarService infoBarService;
|
||||||
private readonly Core.RuntimeOptions hutaoOptions;
|
private readonly RuntimeOptions runtimeOptions;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
private readonly IUserService userService;
|
private readonly IUserService userService;
|
||||||
|
|
||||||
@@ -44,7 +46,7 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
userService.Current = value;
|
userService.Current = value;
|
||||||
|
|
||||||
if (value != null)
|
if (value is not null)
|
||||||
{
|
{
|
||||||
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
value.SelectedUserGameRole = value.UserGameRoles.FirstOrFirstOrDefault(role => role.IsChosen);
|
||||||
}
|
}
|
||||||
@@ -63,12 +65,13 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
/// <param name="optionResult">操作结果</param>
|
/// <param name="optionResult">操作结果</param>
|
||||||
/// <param name="uid">uid</param>
|
/// <param name="uid">uid</param>
|
||||||
/// <returns>任务</returns>
|
/// <returns>任务</returns>
|
||||||
public async Task HandleUserOptionResultAsync(UserOptionResult optionResult, string uid)
|
internal async ValueTask HandleUserOptionResultAsync(UserOptionResult optionResult, string uid)
|
||||||
{
|
{
|
||||||
switch (optionResult)
|
switch (optionResult)
|
||||||
{
|
{
|
||||||
case UserOptionResult.Added:
|
case UserOptionResult.Added:
|
||||||
if (Users!.Count == 1)
|
ArgumentNullException.ThrowIfNull(Users);
|
||||||
|
if (Users.Count == 1)
|
||||||
{
|
{
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
SelectedUser = Users.Single();
|
SelectedUser = Users.Single();
|
||||||
@@ -107,16 +110,16 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
[Command("AddUserCommand")]
|
[Command("AddUserCommand")]
|
||||||
private Task AddUserAsync()
|
private Task AddUserAsync()
|
||||||
{
|
{
|
||||||
return AddUserCoreAsync(false);
|
return AddUserCoreAsync(false).AsTask();
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("AddOverseaUserCommand")]
|
[Command("AddOverseaUserCommand")]
|
||||||
private Task AddOverseaUserAsync()
|
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.
|
// ContentDialog must be created by main thread.
|
||||||
await taskContext.SwitchToMainThreadAsync();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
@@ -126,9 +129,9 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
ValueResult<bool, string> result = await dialog.GetInputCookieAsync().ConfigureAwait(false);
|
ValueResult<bool, string> result = await dialog.GetInputCookieAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
// User confirms the input
|
// 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);
|
(UserOptionResult optionResult, string uid) = await userService.ProcessInputCookieAsync(cookie, isOversea).ConfigureAwait(false);
|
||||||
|
|
||||||
@@ -139,11 +142,9 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
[Command("LoginMihoyoUserCommand")]
|
[Command("LoginMihoyoUserCommand")]
|
||||||
private void LoginMihoyoUser()
|
private void LoginMihoyoUser()
|
||||||
{
|
{
|
||||||
if (hutaoOptions.IsWebView2Supported)
|
if (runtimeOptions.IsWebView2Supported)
|
||||||
{
|
{
|
||||||
serviceProvider
|
navigationService.Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
|
||||||
.GetRequiredService<INavigationService>()
|
|
||||||
.Navigate<LoginMihoyoUserPage>(INavigationAwaiter.Default);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -154,11 +155,9 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
[Command("LoginHoyoverseUserCommand")]
|
[Command("LoginHoyoverseUserCommand")]
|
||||||
private void LoginHoyoverseUser()
|
private void LoginHoyoverseUser()
|
||||||
{
|
{
|
||||||
if (hutaoOptions.IsWebView2Supported)
|
if (runtimeOptions.IsWebView2Supported)
|
||||||
{
|
{
|
||||||
serviceProvider
|
navigationService.Navigate<LoginHoyoverseUserPage>(INavigationAwaiter.Default);
|
||||||
.GetRequiredService<INavigationService>()
|
|
||||||
.Navigate<LoginHoyoverseUserPage>(INavigationAwaiter.Default);
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -169,11 +168,11 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
[Command("RemoveUserCommand")]
|
[Command("RemoveUserCommand")]
|
||||||
private async Task RemoveUserAsync(User? user)
|
private async Task RemoveUserAsync(User? user)
|
||||||
{
|
{
|
||||||
if (user != null)
|
if (user is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await userService.RemoveUserAsync(user!).ConfigureAwait(false);
|
await userService.RemoveUserAsync(user).ConfigureAwait(false);
|
||||||
infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname));
|
infoBarService.Success(string.Format(SH.ViewModelUserRemoved, user.UserInfo?.Nickname));
|
||||||
}
|
}
|
||||||
catch (UserdataCorruptedException ex)
|
catch (UserdataCorruptedException ex)
|
||||||
@@ -188,26 +187,29 @@ internal sealed partial class UserViewModel : ObservableObject
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
ArgumentNullException.ThrowIfNull(user);
|
||||||
string cookieString = new StringBuilder()
|
string cookieString = new StringBuilder()
|
||||||
.Append(user!.SToken)
|
.Append(user.SToken)
|
||||||
.AppendIf(user.SToken != null, ';')
|
.AppendIf(user.SToken is not null, ';')
|
||||||
.Append(user.LToken)
|
.Append(user.LToken)
|
||||||
.AppendIf(user.LToken != null, ';')
|
.AppendIf(user.LToken is not null, ';')
|
||||||
.Append(user.CookieToken)
|
.Append(user.CookieToken)
|
||||||
.ToString();
|
.ToString();
|
||||||
serviceProvider.GetRequiredService<IClipboardInterop>().SetText(cookieString);
|
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")]
|
[Command("RefreshCookieTokenCommand")]
|
||||||
private async Task RefreshCookieTokenAsync()
|
private async Task RefreshCookieTokenAsync()
|
||||||
{
|
{
|
||||||
if (SelectedUser != null)
|
if (SelectedUser is not null)
|
||||||
{
|
{
|
||||||
if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false))
|
if (await userService.RefreshCookieTokenAsync(SelectedUser).ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ internal static class AvatarFilter
|
|||||||
{
|
{
|
||||||
List<bool> matches = new();
|
List<bool> matches = new();
|
||||||
|
|
||||||
|
// TODO: use Collection Literals
|
||||||
foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' }))
|
foreach (StringSegment segment in new StringTokenizer(input, new char[] { ' ' }))
|
||||||
{
|
{
|
||||||
string value = segment.ToString();
|
string value = segment.ToString();
|
||||||
|
|||||||
@@ -71,7 +71,7 @@ internal sealed class BaseValueInfo : ObservableObject
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string CurrentLevelFormatted
|
public string CurrentLevelFormatted
|
||||||
{
|
{
|
||||||
get => $"Lv.{CurrentLevel}";
|
get => LevelFormat.Format(CurrentLevel);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -95,10 +95,10 @@ internal sealed class BaseValueInfo : ObservableObject
|
|||||||
Values = propValues.SelectList(propValue =>
|
Values = propValues.SelectList(propValue =>
|
||||||
{
|
{
|
||||||
float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type);
|
float value = propValue.Value * growCurveMap[level].GetValueOrDefault(propValue.Type);
|
||||||
if (promoteMap != null)
|
if (promoteMap is not null)
|
||||||
{
|
{
|
||||||
PromoteLevel promoteLevel = GetPromoteLevel(level, promoted);
|
PromoteLevel promoteLevel = GetPromoteLevel(level, promoted);
|
||||||
float addValue = promoteMap[promoteLevel].AddPropertyMap.GetValueOrDefault(propValue.Property, 0F);
|
float addValue = promoteMap[promoteLevel].GetValue(propValue.Property);
|
||||||
|
|
||||||
value += addValue;
|
value += addValue;
|
||||||
}
|
}
|
||||||
@@ -109,59 +109,36 @@ internal sealed class BaseValueInfo : ObservableObject
|
|||||||
|
|
||||||
private PromoteLevel GetPromoteLevel(in Level level, bool promoted)
|
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
|
>= 80U => 6,
|
||||||
{
|
>= 70U => 5,
|
||||||
>= 60U => 4,
|
>= 60U => 4,
|
||||||
>= 50U => 3,
|
>= 50U => 3,
|
||||||
>= 40U => 2,
|
>= 40U => 2,
|
||||||
>= 20U => 1,
|
>= 20U => 1,
|
||||||
_ => 0,
|
_ => 0,
|
||||||
};
|
};
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return level.Value switch
|
|
||||||
{
|
|
||||||
> 60U => 4,
|
|
||||||
> 50U => 3,
|
|
||||||
> 40U => 2,
|
|
||||||
> 20U => 1,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (promoted)
|
return level.Value switch
|
||||||
{
|
{
|
||||||
return level.Value switch
|
> 80U => 6,
|
||||||
{
|
> 70U => 5,
|
||||||
>= 80U => 6,
|
> 60U => 4,
|
||||||
>= 70U => 5,
|
> 50U => 3,
|
||||||
>= 60U => 4,
|
> 40U => 2,
|
||||||
>= 50U => 3,
|
> 20U => 1,
|
||||||
>= 40U => 2,
|
_ => 0,
|
||||||
>= 20U => 1,
|
};
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return level.Value switch
|
|
||||||
{
|
|
||||||
> 80U => 6,
|
|
||||||
> 70U => 5,
|
|
||||||
> 60U => 4,
|
|
||||||
> 50U => 3,
|
|
||||||
> 40U => 2,
|
|
||||||
> 20U => 1,
|
|
||||||
_ => 0,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -31,7 +31,7 @@ internal sealed class CookBonusView
|
|||||||
/// <returns>新的料理奖励视图</returns>
|
/// <returns>新的料理奖励视图</returns>
|
||||||
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap)
|
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap)
|
||||||
{
|
{
|
||||||
if (cookBonus == null)
|
if (cookBonus is null)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using Snap.Hutao.Model.Intrinsic;
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
|
using Snap.Hutao.Model.Metadata;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Wiki;
|
namespace Snap.Hutao.ViewModel.Wiki;
|
||||||
|
|
||||||
@@ -23,6 +24,11 @@ internal sealed class PropertyCurveValue
|
|||||||
Value = value;
|
Value = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PropertyCurveValue(FightProperty property, Dictionary<FightProperty, GrowCurveType> growCurve, BaseValue baseValue)
|
||||||
|
: this(property, growCurve.GetValueOrDefault(property), baseValue.GetValue(property))
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 战斗属性值
|
/// 战斗属性值
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
|||||||
@@ -2,11 +2,9 @@
|
|||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
using CommunityToolkit.WinUI.UI;
|
using CommunityToolkit.WinUI.UI;
|
||||||
using Microsoft.Extensions.Primitives;
|
|
||||||
using Snap.Hutao.Model.Calculable;
|
using Snap.Hutao.Model.Calculable;
|
||||||
using Snap.Hutao.Model.Entity.Primitive;
|
using Snap.Hutao.Model.Entity.Primitive;
|
||||||
using Snap.Hutao.Model.Intrinsic;
|
using Snap.Hutao.Model.Intrinsic;
|
||||||
using Snap.Hutao.Model.Intrinsic.Immutable;
|
|
||||||
using Snap.Hutao.Model.Metadata;
|
using Snap.Hutao.Model.Metadata;
|
||||||
using Snap.Hutao.Model.Metadata.Avatar;
|
using Snap.Hutao.Model.Metadata.Avatar;
|
||||||
using Snap.Hutao.Model.Metadata.Item;
|
using Snap.Hutao.Model.Metadata.Item;
|
||||||
@@ -17,15 +15,14 @@ using Snap.Hutao.Service.Metadata;
|
|||||||
using Snap.Hutao.Service.Notification;
|
using Snap.Hutao.Service.Notification;
|
||||||
using Snap.Hutao.Service.User;
|
using Snap.Hutao.Service.User;
|
||||||
using Snap.Hutao.View.Dialog;
|
using Snap.Hutao.View.Dialog;
|
||||||
using Snap.Hutao.ViewModel.Complex;
|
|
||||||
using Snap.Hutao.Web.Response;
|
using Snap.Hutao.Web.Response;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
||||||
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
||||||
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
||||||
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
|
||||||
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
using CalculateItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Wiki;
|
namespace Snap.Hutao.ViewModel.Wiki;
|
||||||
|
|
||||||
@@ -41,6 +38,8 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
|
|||||||
private readonly IMetadataService metadataService;
|
private readonly IMetadataService metadataService;
|
||||||
private readonly ITaskContext taskContext;
|
private readonly ITaskContext taskContext;
|
||||||
private readonly IHutaoCache hutaoCache;
|
private readonly IHutaoCache hutaoCache;
|
||||||
|
private readonly IInfoBarService infoBarService;
|
||||||
|
private readonly IUserService userService;
|
||||||
|
|
||||||
private AdvancedCollectionView? avatars;
|
private AdvancedCollectionView? avatars;
|
||||||
private Avatar? selected;
|
private Avatar? selected;
|
||||||
@@ -78,150 +77,159 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
|
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
protected override async ValueTask<bool> InitializeUIAsync()
|
||||||
protected override async Task OpenUIAsync()
|
|
||||||
{
|
{
|
||||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
if (!await metadataService.InitializeAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
levelAvatarCurveMap = await metadataService.GetLevelToAvatarCurveMapAsync().ConfigureAwait(false);
|
return 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();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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)
|
ArgumentNullException.ThrowIfNull(hutaoCache.AvatarCollocations);
|
||||||
{
|
|
||||||
avatar.Collocation = idCollocations.GetValueOrDefault(avatar.Id);
|
foreach (Avatar avatar in avatars)
|
||||||
avatar.CookBonusView ??= CookBonusView.Create(avatar.FetterInfo.CookBonus, idMaterialMap);
|
{
|
||||||
avatar.CultivationItemsView ??= avatar.CultivationItems.SelectList(i => idMaterialMap.GetValueOrDefault(i, Material.Default)!);
|
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")]
|
[Command("CultivateCommand")]
|
||||||
private async Task CultivateAsync(Avatar? avatar)
|
private async Task CultivateAsync(Avatar? avatar)
|
||||||
{
|
{
|
||||||
if (avatar != null)
|
if (avatar is null)
|
||||||
{
|
{
|
||||||
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
return;
|
||||||
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
|
}
|
||||||
|
|
||||||
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.
|
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||||
|
{
|
||||||
|
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBaseValueInfo(Avatar? avatar)
|
private void UpdateBaseValueInfo(Avatar? avatar)
|
||||||
{
|
{
|
||||||
if (avatar == null)
|
if (avatar is null)
|
||||||
{
|
{
|
||||||
BaseValueInfo = 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);
|
new(FightProperty.FIGHT_PROP_BASE_HP, avatarGrowCurve, avatar.BaseValue),
|
||||||
Dictionary<FightProperty, GrowCurveType> avatarGrowCurve = avatar.GrowCurves.ToDictionary(g => g.Type, g => g.Value);
|
new(FightProperty.FIGHT_PROP_BASE_ATTACK, avatarGrowCurve, avatar.BaseValue),
|
||||||
FightProperty promoteProperty = avatarPromoteMap[0].AddProperties.Last().Type;
|
new(FightProperty.FIGHT_PROP_BASE_DEFENSE, avatarGrowCurve, avatar.BaseValue),
|
||||||
|
new(promoteProperty, avatarGrowCurve, avatar.BaseValue),
|
||||||
|
};
|
||||||
|
|
||||||
List<PropertyCurveValue> propertyCurveValues = new()
|
ArgumentNullException.ThrowIfNull(levelAvatarCurveMap);
|
||||||
{
|
BaseValueInfo = new(avatar.MaxLevel, propertyCurveValues, levelAvatarCurveMap, avatarPromoteMap);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("FilterCommand")]
|
[Command("FilterCommand")]
|
||||||
private void ApplyFilter(string? input)
|
private void ApplyFilter(string? input)
|
||||||
{
|
{
|
||||||
if (Avatars != null)
|
if (Avatars is null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(input))
|
return;
|
||||||
{
|
}
|
||||||
Avatars.Filter = AvatarFilter.Compile(input);
|
|
||||||
|
|
||||||
if (!Avatars.Contains(Selected))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
{
|
{
|
||||||
try
|
Avatars.Filter = default!;
|
||||||
{
|
return;
|
||||||
Avatars.MoveCurrentToFirst();
|
}
|
||||||
}
|
|
||||||
catch (COMException)
|
Avatars.Filter = AvatarFilter.Compile(input);
|
||||||
{
|
|
||||||
}
|
if (Avatars.Contains(Selected))
|
||||||
}
|
{
|
||||||
}
|
return;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
Avatars.Filter = null!;
|
try
|
||||||
}
|
{
|
||||||
|
Avatars.MoveCurrentToFirst();
|
||||||
|
}
|
||||||
|
catch (COMException)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -50,8 +50,7 @@ internal sealed partial class WikiMonsterViewModel : Abstraction.ViewModel
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
|
public BaseValueInfo? BaseValueInfo { get => baseValueInfo; set => SetProperty(ref baseValueInfo, value); }
|
||||||
|
|
||||||
/// <inheritdoc/>
|
protected override async ValueTask<bool> InitializeUIAsync()
|
||||||
protected override async Task OpenUIAsync()
|
|
||||||
{
|
{
|
||||||
if (await metadataService.InitializeAsync().ConfigureAwait(false))
|
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);
|
Dictionary<MaterialId, DisplayItem> idDisplayMap = await metadataService.GetIdToDisplayItemAndMaterialMapAsync().ConfigureAwait(false);
|
||||||
foreach (Monster monster in monsters)
|
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();
|
await taskContext.SwitchToMainThreadAsync();
|
||||||
|
|
||||||
Monsters = new AdvancedCollectionView(ordered, true);
|
Monsters = new AdvancedCollectionView(ordered, true);
|
||||||
Selected = Monsters.Cast<Monster>().FirstOrDefault();
|
Selected = Monsters.Cast<Monster>().FirstOrDefault();
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBaseValueInfo(Monster? monster)
|
private void UpdateBaseValueInfo(Monster? monster)
|
||||||
{
|
{
|
||||||
if (monster == null)
|
if (monster is null)
|
||||||
{
|
{
|
||||||
BaseValueInfo = null;
|
BaseValueInfo = null;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
List<PropertyCurveValue> propertyCurveValues = monster.GrowCurves
|
List<PropertyCurveValue> propertyCurveValues = monster.GrowCurves
|
||||||
.Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type)))
|
.SelectList(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, monster.BaseValue.GetValue(curveInfo.Type)));
|
||||||
.ToList();
|
|
||||||
|
|
||||||
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.Notification;
|
||||||
using Snap.Hutao.Service.User;
|
using Snap.Hutao.Service.User;
|
||||||
using Snap.Hutao.View.Dialog;
|
using Snap.Hutao.View.Dialog;
|
||||||
using Snap.Hutao.ViewModel.Complex;
|
|
||||||
using Snap.Hutao.Web.Response;
|
using Snap.Hutao.Web.Response;
|
||||||
using System.Collections.Immutable;
|
using System.Collections.Immutable;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
using CalculateAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
|
||||||
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
|
||||||
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
using CalculateConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
|
||||||
|
|
||||||
namespace Snap.Hutao.ViewModel.Wiki;
|
namespace Snap.Hutao.ViewModel.Wiki;
|
||||||
|
|
||||||
@@ -31,16 +30,12 @@ namespace Snap.Hutao.ViewModel.Wiki;
|
|||||||
[Injection(InjectAs.Scoped)]
|
[Injection(InjectAs.Scoped)]
|
||||||
internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
|
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 ITaskContext taskContext;
|
||||||
private readonly IServiceProvider serviceProvider;
|
private readonly IServiceProvider serviceProvider;
|
||||||
private readonly IMetadataService metadataService;
|
private readonly IMetadataService metadataService;
|
||||||
private readonly IHutaoCache hutaoCache;
|
private readonly IHutaoCache hutaoCache;
|
||||||
|
private readonly IInfoBarService infoBarService;
|
||||||
|
private readonly IUserService userService;
|
||||||
|
|
||||||
private AdvancedCollectionView? weapons;
|
private AdvancedCollectionView? weapons;
|
||||||
private Weapon? selected;
|
private Weapon? selected;
|
||||||
@@ -88,7 +83,6 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel
|
|||||||
|
|
||||||
List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
|
List<Weapon> weapons = await metadataService.GetWeaponsAsync().ConfigureAwait(false);
|
||||||
List<Weapon> sorted = weapons
|
List<Weapon> sorted = weapons
|
||||||
.Where(weapon => !SkippedWeapons.Contains(weapon.Id))
|
|
||||||
.OrderByDescending(weapon => weapon.RankLevel)
|
.OrderByDescending(weapon => weapon.RankLevel)
|
||||||
.ThenBy(weapon => weapon.WeaponType)
|
.ThenBy(weapon => weapon.WeaponType)
|
||||||
.ThenByDescending(weapon => weapon.Id.Value)
|
.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))
|
if (await hutaoCache.InitializeForWikiWeaponViewModelAsync().ConfigureAwait(false))
|
||||||
{
|
{
|
||||||
Dictionary<WeaponId, WeaponCollocationView> idCollocations = hutaoCache.WeaponCollocations!.ToDictionary(a => a.WeaponId);
|
ArgumentNullException.ThrowIfNull(hutaoCache.WeaponCollocations);
|
||||||
weapons.ForEach(w => w.Collocation = idCollocations.GetValueOrDefault(w.Id));
|
weapons.ForEach(w => w.Collocation = hutaoCache.WeaponCollocations.GetValueOrDefault(w.Id));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Command("CultivateCommand")]
|
[Command("CultivateCommand")]
|
||||||
private async Task CultivateAsync(Weapon? weapon)
|
private async Task CultivateAsync(Weapon? weapon)
|
||||||
{
|
{
|
||||||
if (weapon != null)
|
if (weapon is null)
|
||||||
{
|
{
|
||||||
IInfoBarService infoBarService = serviceProvider.GetRequiredService<IInfoBarService>();
|
return;
|
||||||
IUserService userService = serviceProvider.GetRequiredService<IUserService>();
|
}
|
||||||
|
|
||||||
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.
|
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||||
|
{
|
||||||
|
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void UpdateBaseValueInfo(Weapon? weapon)
|
private void UpdateBaseValueInfo(Weapon? weapon)
|
||||||
{
|
{
|
||||||
if (weapon == null)
|
if (weapon is null)
|
||||||
{
|
{
|
||||||
BaseValueInfo = 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
|
ArgumentNullException.ThrowIfNull(promotes);
|
||||||
.Select(curveInfo => new PropertyCurveValue(curveInfo.Type, curveInfo.Value, curveInfo.InitValue))
|
Dictionary<PromoteLevel, Promote> weaponPromoteMap = promotes.Where(p => p.Id == weapon.PromoteId).ToDictionary(p => p.Level);
|
||||||
.ToList();
|
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")]
|
[Command("FilterCommand")]
|
||||||
private void ApplyFilter(string? input)
|
private void ApplyFilter(string? input)
|
||||||
{
|
{
|
||||||
if (Weapons != null)
|
if (Weapons is null)
|
||||||
{
|
{
|
||||||
if (!string.IsNullOrWhiteSpace(input))
|
return;
|
||||||
{
|
}
|
||||||
Weapons.Filter = WeaponFilter.Compile(input);
|
|
||||||
|
|
||||||
if (!Weapons.Contains(Selected))
|
if (string.IsNullOrWhiteSpace(input))
|
||||||
{
|
{
|
||||||
try
|
Weapons.Filter = default!;
|
||||||
{
|
return;
|
||||||
Weapons.MoveCurrentToFirst();
|
}
|
||||||
}
|
|
||||||
catch (COMException)
|
Weapons.Filter = WeaponFilter.Compile(input);
|
||||||
{
|
|
||||||
}
|
if (Weapons.Contains(Selected))
|
||||||
}
|
{
|
||||||
}
|
return;
|
||||||
else
|
}
|
||||||
{
|
|
||||||
Weapons.Filter = null!;
|
try
|
||||||
}
|
{
|
||||||
|
Weapons.MoveCurrentToFirst();
|
||||||
|
}
|
||||||
|
catch (COMException)
|
||||||
|
{
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user