batch add for avatarinfo cultivation

This commit is contained in:
DismissedLight
2023-09-09 17:22:38 +08:00
committed by Lightczx
parent ecd535f9a0
commit 84f629c113
26 changed files with 618 additions and 136 deletions

View File

@@ -63,4 +63,17 @@ internal static class SettingKeys
/// 覆盖管理员权限执行命令
/// </summary>
public const string OverrideElevationRequirement = "OverrideElevationRequirement";
public const string CultivationAvatarLevelCurrent = "CultivationAvatarLevelCurrent";
public const string CultivationAvatarLevelTarget = "CultivationAvatarLevelTarget";
public const string CultivationAvatarSkillACurrent = "CultivationAvatarSkillACurrent";
public const string CultivationAvatarSkillATarget = "CultivationAvatarSkillATarget";
public const string CultivationAvatarSkillECurrent = "CultivationAvatarSkillECurrent";
public const string CultivationAvatarSkillETarget = "CultivationAvatarSkillETarget";
public const string CultivationAvatarSkillQCurrent = "CultivationAvatarSkillQCurrent";
public const string CultivationAvatarSkillQTarget = "CultivationAvatarSkillQTarget";
public const string CultivationWeapon90LevelCurrent = "CultivationWeapon90LevelCurrent";
public const string CultivationWeapon90LevelTarget = "CultivationWeapon90LevelTarget";
public const string CultivationWeapon70LevelCurrent = "CultivationWeapon70LevelCurrent";
public const string CultivationWeapon70LevelTarget = "CultivationWeapon70LevelTarget";
}

View File

@@ -103,7 +103,7 @@ internal static partial class EnumerableExtension
Span<TSource> span = CollectionsMarshal.AsSpan(list);
List<TResult> results = new(span.Length);
foreach (TSource item in span)
foreach (ref readonly TSource item in span)
{
results.Add(selector(item));
}
@@ -111,6 +111,21 @@ internal static partial class EnumerableExtension
return results;
}
[Pure]
public static List<TResult> SelectList<TSource, TResult>(this List<TSource> list, Func<TSource, int, TResult> selector)
{
Span<TSource> span = CollectionsMarshal.AsSpan(list);
List<TResult> results = new(span.Length);
int index = -1;
foreach (ref readonly TSource item in span)
{
results.Add(selector(item, ++index));
}
return results;
}
public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, ValueTask<TResult>> selector)
{
List<TResult> results = new(list.Count);

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
@@ -21,9 +22,6 @@ internal sealed class CalculableAvatar
IMappingFrom<CalculableAvatar, Avatar>,
IMappingFrom<CalculableAvatar, AvatarView>
{
private uint levelCurrent;
private uint levelTarget;
/// <summary>
/// 构造一个新的可计算角色
/// </summary>
@@ -32,14 +30,11 @@ internal sealed class CalculableAvatar
{
AvatarId = avatar.Id;
LevelMin = 1;
LevelMax = 90;
Skills = avatar.SkillDepot.CompositeSkillsNoInherents().SelectList(p => p.ToCalculable());
LevelMax = avatar.MaxLevel;
Skills = avatar.SkillDepot.CompositeSkillsNoInherents().SelectList((p, i) => p.ToCalculable((SkillType)i));
Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
@@ -50,14 +45,11 @@ internal sealed class CalculableAvatar
{
AvatarId = avatar.Id;
LevelMin = avatar.LevelNumber;
LevelMax = 90; // hard coded 90
Skills = avatar.Skills.SelectList(s => s.ToCalculable());
LevelMax = Avatar.GetMaxLevel();
Skills = avatar.Skills.SelectList((s, i) => s.ToCalculable((SkillType)i));
Name = avatar.Name;
Icon = avatar.Icon;
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
@@ -82,10 +74,18 @@ internal sealed class CalculableAvatar
public QualityType Quality { get; }
/// <inheritdoc/>
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent
{
get => LocalSetting.Get(SettingKeys.CultivationAvatarLevelCurrent, LevelMin);
set => SetProperty(LevelCurrent, value, v => LocalSetting.Set(SettingKeys.CultivationAvatarLevelCurrent, v));
}
/// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public uint LevelTarget
{
get => LocalSetting.Get(SettingKeys.CultivationAvatarLevelTarget, LevelMax);
set => SetProperty(LevelTarget, value, v => LocalSetting.Set(SettingKeys.CultivationAvatarLevelTarget, v));
}
public static CalculableAvatar From(Avatar source)
{

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
@@ -18,44 +19,33 @@ namespace Snap.Hutao.Model.Calculable;
internal sealed class CalculableSkill
: ObservableObject,
ICalculableSkill,
IMappingFrom<CalculableSkill, ProudableSkill>,
IMappingFrom<CalculableSkill, SkillView>
IMappingFrom<CalculableSkill, ProudableSkill, SkillType>,
IMappingFrom<CalculableSkill, SkillView, SkillType>
{
private uint levelCurrent;
private uint levelTarget;
private readonly SkillType type;
/// <summary>
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
private CalculableSkill(ProudableSkill skill)
private CalculableSkill(ProudableSkill skill, SkillType type)
{
this.type = type;
GroupId = skill.GroupId;
LevelMin = 1;
LevelMax = 10; // hard coded 10 here
LevelMax = ProudableSkill.GetMaxLevel();
Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
private CalculableSkill(SkillView skill)
private CalculableSkill(SkillView skill, SkillType type)
{
this.type = type;
GroupId = skill.GroupId;
LevelMin = skill.LevelNumber;
LevelMax = 10; // hard coded 10 here
LevelMax = ProudableSkill.GetMaxLevel();
Name = skill.Name;
Icon = skill.Icon;
Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
@@ -77,18 +67,48 @@ internal sealed class CalculableSkill
public QualityType Quality { get; }
/// <inheritdoc/>
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent
{
get => LocalSetting.Get(SettingKeyCurrentFromSkillType(type), LevelMin);
set => SetProperty(LevelCurrent, value, v => LocalSetting.Set(SettingKeyCurrentFromSkillType(type), v));
}
/// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public static CalculableSkill From(ProudableSkill source)
public uint LevelTarget
{
return new(source);
get => LocalSetting.Get(SettingKeyTargetFromSkillType(type), LevelMax);
set => SetProperty(LevelTarget, value, v => LocalSetting.Set(SettingKeyTargetFromSkillType(type), v));
}
public static CalculableSkill From(SkillView source)
public static CalculableSkill From(ProudableSkill source, SkillType type)
{
return new(source);
return new(source, type);
}
public static CalculableSkill From(SkillView source, SkillType type)
{
return new(source, type);
}
public static string SettingKeyCurrentFromSkillType(SkillType type)
{
return type switch
{
SkillType.A => SettingKeys.CultivationAvatarSkillACurrent,
SkillType.E => SettingKeys.CultivationAvatarSkillECurrent,
SkillType.Q => SettingKeys.CultivationAvatarSkillQCurrent,
_ => throw Must.NeverHappen(),
};
}
public static string SettingKeyTargetFromSkillType(SkillType type)
{
return type switch
{
SkillType.A => SettingKeys.CultivationAvatarSkillATarget,
SkillType.E => SettingKeys.CultivationAvatarSkillETarget,
SkillType.Q => SettingKeys.CultivationAvatarSkillQTarget,
_ => throw Must.NeverHappen(),
};
}
}

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Weapon;
@@ -77,10 +78,18 @@ internal sealed class CalculableWeapon
public QualityType Quality { get; }
/// <inheritdoc/>
public uint LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
public uint LevelCurrent
{
get => LocalSetting.Get(SettingKeyCurrentFromQualityType(Quality), LevelMin);
set => SetProperty(LevelCurrent, value, v => LocalSetting.Set(SettingKeyCurrentFromQualityType(Quality), v));
}
/// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
public uint LevelTarget
{
get => LocalSetting.Get(SettingKeyTargetFromQualityType(Quality), LevelMax);
set => SetProperty(LevelTarget, value, v => LocalSetting.Set(SettingKeyTargetFromQualityType(Quality), v));
}
public static CalculableWeapon From(Weapon source)
{
@@ -91,4 +100,18 @@ internal sealed class CalculableWeapon
{
return new(source);
}
public static string SettingKeyCurrentFromQualityType(QualityType quality)
{
return quality >= QualityType.QUALITY_BLUE
? SettingKeys.CultivationWeapon90LevelCurrent
: SettingKeys.CultivationWeapon70LevelCurrent;
}
public static string SettingKeyTargetFromQualityType(QualityType quality)
{
return quality >= QualityType.QUALITY_BLUE
? SettingKeys.CultivationWeapon90LevelTarget
: SettingKeys.CultivationWeapon70LevelTarget;
}
}

View File

@@ -6,14 +6,25 @@ namespace Snap.Hutao.Model.Calculable;
/// <summary>
/// 可计算物品的源
/// </summary>
/// <typeparam name="T">可计算类型</typeparam>
/// <typeparam name="TResult">可计算类型</typeparam>
[HighQuality]
internal interface ICalculableSource<T>
where T : ICalculable
internal interface ICalculableSource<TResult>
where TResult : ICalculable
{
/// <summary>
/// 转换到可计算的对象
/// </summary>
/// <returns>可计算物品</returns>
public T ToCalculable();
public TResult ToCalculable();
}
internal interface ITypedCalculableSource<TResult, T>
where TResult : ICalculable
{
/// <summary>
/// 转换到可计算的对象
/// </summary>
/// <param name="param">索引</param>
/// <returns>可计算物品</returns>
public TResult ToCalculable(T param);
}

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Calculable;
internal enum SkillType
{
A,
E,
Q,
}

View File

@@ -39,7 +39,12 @@ internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IItem
/// 最大等级
/// </summary>
[SuppressMessage("", "CA1822")]
public uint MaxLevel { get => 90U; }
public uint MaxLevel { get => GetMaxLevel(); }
public static uint GetMaxLevel()
{
return 90U;
}
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()

View File

@@ -8,11 +8,16 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能信息的接口实现
/// </summary>
internal sealed partial class ProudableSkill : ICalculableSource<ICalculableSkill>
internal sealed partial class ProudableSkill : ITypedCalculableSource<ICalculableSkill, SkillType>
{
/// <inheritdoc/>
public ICalculableSkill ToCalculable()
public static uint GetMaxLevel()
{
return CalculableSkill.From(this);
return 10U;
}
/// <inheritdoc/>
public ICalculableSkill ToCalculable(SkillType type)
{
return CalculableSkill.From(this, type);
}
}

View File

@@ -2256,6 +2256,51 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 角色目标等级 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivateBatchAvatarLevelTarget {
get {
return ResourceManager.GetString("ViewDialogCultivateBatchAvatarLevelTarget", resourceCulture);
}
}
/// <summary>
/// 查找类似 普通攻击目标等级 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivateBatchSkillATarget {
get {
return ResourceManager.GetString("ViewDialogCultivateBatchSkillATarget", resourceCulture);
}
}
/// <summary>
/// 查找类似 元素战技目标等级 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivateBatchSkillETarget {
get {
return ResourceManager.GetString("ViewDialogCultivateBatchSkillETarget", resourceCulture);
}
}
/// <summary>
/// 查找类似 元素爆发目标等级 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivateBatchSkillQTarget {
get {
return ResourceManager.GetString("ViewDialogCultivateBatchSkillQTarget", resourceCulture);
}
}
/// <summary>
/// 查找类似 武器目标等级 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivateBatchWeaponLevelTarget {
get {
return ResourceManager.GetString("ViewDialogCultivateBatchWeaponLevelTarget", resourceCulture);
}
}
/// <summary>
/// 查找类似 绑定当前用户与角色 的本地化字符串。
/// </summary>
@@ -2284,7 +2329,16 @@ namespace Snap.Hutao.Resource.Localization {
}
/// <summary>
/// 查找类似 添加到当前养成计划 的本地化字符串。
/// 查找类似 批量添加或更新到当前养成计划 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivatePromotionDeltaBatchTitle {
get {
return ResourceManager.GetString("ViewDialogCultivatePromotionDeltaBatchTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 添加或更新到当前养成计划 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivatePromotionDeltaTitle {
get {
@@ -2895,6 +2949,15 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 获取培养材料中,请稍候... 的本地化字符串。
/// </summary>
internal static string ViewModelAvatarPropertyBatchCultivateProgressTitle {
get {
return ResourceManager.GetString("ViewModelAvatarPropertyBatchCultivateProgressTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 当前角色无法计算,请同步信息后再试 的本地化字符串。
/// </summary>
@@ -2967,6 +3030,24 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 操作完成:添加/更新:{0} 个,跳过 {1} 个 的本地化字符串。
/// </summary>
internal static string ViewModelCultivationBatchAddCompletedFormat {
get {
return ResourceManager.GetString("ViewModelCultivationBatchAddCompletedFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 操作未全部完成:添加/更新:{0} 个,跳过 {1} 个 的本地化字符串。
/// </summary>
internal static string ViewModelCultivationBatchAddIncompletedFormat {
get {
return ResourceManager.GetString("ViewModelCultivationBatchAddIncompletedFormat", resourceCulture);
}
}
/// <summary>
/// 查找类似 已成功添加至当前养成计划 的本地化字符串。
/// </summary>
@@ -3642,6 +3723,24 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 所有角色与武器 的本地化字符串。
/// </summary>
internal static string ViewPageAvatarPropertyCalculateAll {
get {
return ResourceManager.GetString("ViewPageAvatarPropertyCalculateAll", resourceCulture);
}
}
/// <summary>
/// 查找类似 当前角色与武器 的本地化字符串。
/// </summary>
internal static string ViewPageAvatarPropertyCalculateCurrent {
get {
return ResourceManager.GetString("ViewPageAvatarPropertyCalculateCurrent", resourceCulture);
}
}
/// <summary>
/// 查找类似 双暴评分 的本地化字符串。
/// </summary>

View File

@@ -905,6 +905,21 @@
<data name="ViewDialogAchievementArchiveImportTitle" xml:space="preserve">
<value>为当前存档导入成就</value>
</data>
<data name="ViewDialogCultivateBatchAvatarLevelTarget" xml:space="preserve">
<value>角色目标等级</value>
</data>
<data name="ViewDialogCultivateBatchSkillATarget" xml:space="preserve">
<value>普通攻击目标等级</value>
</data>
<data name="ViewDialogCultivateBatchSkillETarget" xml:space="preserve">
<value>元素战技目标等级</value>
</data>
<data name="ViewDialogCultivateBatchSkillQTarget" xml:space="preserve">
<value>元素爆发目标等级</value>
</data>
<data name="ViewDialogCultivateBatchWeaponLevelTarget" xml:space="preserve">
<value>武器目标等级</value>
</data>
<data name="ViewDialogCultivateProjectAttachUid" xml:space="preserve">
<value>绑定当前用户与角色</value>
</data>
@@ -914,8 +929,11 @@
<data name="ViewDialogCultivateProjectTitle" xml:space="preserve">
<value>创建新的养成计划</value>
</data>
<data name="ViewDialogCultivatePromotionDeltaBatchTitle" xml:space="preserve">
<value>批量添加或更新到当前养成计划</value>
</data>
<data name="ViewDialogCultivatePromotionDeltaTitle" xml:space="preserve">
<value>添加到当前养成计划</value>
<value>添加或更新到当前养成计划</value>
</data>
<data name="ViewDialogDailyNoteNotificationDailyTaskNotify" xml:space="preserve">
<value>每日委托上线提醒</value>
@@ -1118,6 +1136,9 @@
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
<value>确定要删除存档 {0} 吗?</value>
</data>
<data name="ViewModelAvatarPropertyBatchCultivateProgressTitle" xml:space="preserve">
<value>获取培养材料中,请稍候...</value>
</data>
<data name="ViewModelAvatarPropertyCalculateWeaponNull" xml:space="preserve">
<value>当前角色无法计算,请同步信息后再试</value>
</data>
@@ -1142,6 +1163,12 @@
<data name="ViewModelCultivationAddWarning" xml:space="preserve">
<value>养成计划添加失败</value>
</data>
<data name="ViewModelCultivationBatchAddCompletedFormat" xml:space="preserve">
<value>操作完成:添加/更新:{0} 个,跳过 {1} 个</value>
</data>
<data name="ViewModelCultivationBatchAddIncompletedFormat" xml:space="preserve">
<value>操作未全部完成:添加/更新:{0} 个,跳过 {1} 个</value>
</data>
<data name="ViewModelCultivationEntryAddSuccess" xml:space="preserve">
<value>已成功添加至当前养成计划</value>
</data>
@@ -1367,6 +1394,12 @@
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
<value>圣遗物评分</value>
</data>
<data name="ViewPageAvatarPropertyCalculateAll" xml:space="preserve">
<value>所有角色与武器</value>
</data>
<data name="ViewPageAvatarPropertyCalculateCurrent" xml:space="preserve">
<value>当前角色与武器</value>
</data>
<data name="ViewPageAvatarPropertyCritScore" xml:space="preserve">
<value>双暴评分</value>
</data>

View File

@@ -106,7 +106,7 @@ internal sealed partial class CultivationDbService : ICultivationDbService
}
}
public async ValueTask InsertCultivateEntryAsync(CultivateEntry entry)
public async ValueTask AddCultivateEntryAsync(CultivateEntry entry)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -126,7 +126,7 @@ internal sealed partial class CultivationDbService : ICultivationDbService
}
}
public async ValueTask InsertCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd)
public async ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{

View File

@@ -177,14 +177,14 @@ internal sealed partial class CultivationService : ICultivationService
if (entry is null)
{
entry = CultivateEntry.From(Current.InnerId, type, itemId);
await cultivationDbService.InsertCultivateEntryAsync(entry).ConfigureAwait(false);
await cultivationDbService.AddCultivateEntryAsync(entry).ConfigureAwait(false);
}
Guid entryId = entry.InnerId;
await cultivationDbService.DeleteCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false);
IEnumerable<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item));
await cultivationDbService.InsertCultivateItemRangeAsync(toAdd).ConfigureAwait(false);
await cultivationDbService.AddCultivateItemRangeAsync(toAdd).ConfigureAwait(false);
return true;
}

View File

@@ -28,9 +28,9 @@ internal interface ICultivationDbService
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId);
ValueTask InsertCultivateEntryAsync(CultivateEntry entry);
ValueTask AddCultivateEntryAsync(CultivateEntry entry);
ValueTask InsertCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd);
ValueTask AddCultivateItemRangeAsync(IEnumerable<CultivateItem> toAdd);
void UpdateCultivateItem(CultivateItem item);

View File

@@ -124,6 +124,7 @@
<None Remove="View\Dialog\AchievementArchiveCreateDialog.xaml" />
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
<None Remove="View\Dialog\CultivateProjectDialog.xaml" />
<None Remove="View\Dialog\CultivatePromotionDeltaBatchDialog.xaml" />
<None Remove="View\Dialog\CultivatePromotionDeltaDialog.xaml" />
<None Remove="View\Dialog\DailyNoteNotificationDialog.xaml" />
<None Remove="View\Dialog\GachaLogImportDialog.xaml" />
@@ -299,6 +300,12 @@
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\CultivatePromotionDeltaBatchDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Control\StatisticsSegmented.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -51,7 +51,7 @@
</Grid>
<ScrollViewer Grid.Row="1">
<StackPanel Margin="16,0,16,16">
<StackPanel Margin="16,0,12,16">
<TextBlock
Margin="0,16,0,8"
Style="{StaticResource BaseTextBlockStyle}"

View File

@@ -0,0 +1,77 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.CultivatePromotionDeltaBatchDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwc="using:CommunityToolkit.WinUI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvd="using:Snap.Hutao.View.Dialog"
Title="{shcm:ResourceString Name=ViewDialogCultivatePromotionDeltaBatchTitle}"
d:DataContext="{d:DesignInstance shvd:CultivatePromotionDeltaDialog}"
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
DefaultButton="Primary"
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<ContentDialog.Resources>
<x:Double x:Key="NumberBoxMinWidth">180</x:Double>
<x:Double x:Key="SettingsCardSpacing">3</x:Double>
<x:Double x:Key="SettingsCardMinHeight">0</x:Double>
<x:Double x:Key="SettingsCardWrapThreshold">0</x:Double>
<x:Double x:Key="SettingsCardWrapNoIconThreshold">0</x:Double>
</ContentDialog.Resources>
<StackPanel Spacing="{StaticResource SettingsCardSpacing}">
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewDialogCultivateBatchAvatarLevelTarget}">
<NumberBox
Grid.Column="2"
MinWidth="{StaticResource NumberBoxMinWidth}"
VerticalAlignment="Center"
Maximum="90"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.AvatarLevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewDialogCultivateBatchSkillATarget}">
<NumberBox
Grid.Column="2"
MinWidth="{StaticResource NumberBoxMinWidth}"
VerticalAlignment="Center"
Maximum="10"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.SkillList[0].LevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewDialogCultivateBatchSkillETarget}">
<NumberBox
Grid.Column="2"
MinWidth="{StaticResource NumberBoxMinWidth}"
VerticalAlignment="Center"
Maximum="10"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.SkillList[1].LevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewDialogCultivateBatchSkillQTarget}">
<NumberBox
Grid.Column="2"
MinWidth="{StaticResource NumberBoxMinWidth}"
VerticalAlignment="Center"
Maximum="10"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.SkillList[2].LevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
<cwc:SettingsCard Header="{shcm:ResourceString Name=ViewDialogCultivateBatchWeaponLevelTarget}">
<NumberBox
Grid.Column="2"
MinWidth="{StaticResource NumberBoxMinWidth}"
VerticalAlignment="Center"
Maximum="90"
Minimum="1"
SpinButtonPlacementMode="Inline"
Value="{x:Bind PromotionDelta.Weapon.LevelTarget, Mode=TwoWay}"/>
</cwc:SettingsCard>
</StackPanel>
</ContentDialog>

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.View.Dialog;
[DependencyProperty("PromotionDelta", typeof(AvatarPromotionDelta))]
internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog
{
private readonly ITaskContext taskContext;
public CultivatePromotionDeltaBatchDialog(IServiceProvider serviceProvider)
{
InitializeComponent();
XamlRoot = serviceProvider.GetRequiredService<MainWindow>().Content.XamlRoot;
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
PromotionDelta = AvatarPromotionDelta.CreateForBaseline();
}
public async ValueTask<ValueResult<bool, AvatarPromotionDelta>> GetPromotionDeltaBaselineAsync()
{
await taskContext.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync();
return new(result == ContentDialogResult.Primary, PromotionDelta);
}
}

View File

@@ -7,9 +7,7 @@
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvd="using:Snap.Hutao.View.Dialog"
Title="{shcm:ResourceString Name=ViewDialogCultivatePromotionDeltaTitle}"
d:DataContext="{d:DesignInstance shvd:CultivatePromotionDeltaDialog}"
CloseButtonText="{shcm:ResourceString Name=ContentDialogCancelCloseButtonText}"
DefaultButton="Primary"
PrimaryButtonText="{shcm:ResourceString Name=ContentDialogConfirmPrimaryButtonText}"
@@ -27,9 +25,9 @@
<StackPanel
Grid.Row="0"
Margin="0,8,0,0"
Visibility="{Binding Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
Visibility="{x:Bind Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Border Style="{StaticResource BorderCardStyle}">
<Grid Margin="8" DataContext="{Binding Avatar}">
<Grid Margin="8" DataContext="{x:Bind Avatar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
@@ -73,7 +71,7 @@
</Grid>
</Border>
<ItemsControl ItemsSource="{Binding Avatar.Skills}">
<ItemsControl ItemsSource="{x:Bind Avatar.Skills}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,2,0,0" Style="{StaticResource BorderCardStyle}">
@@ -128,7 +126,7 @@
Margin="0,8,0,0"
Visibility="{Binding Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Border Style="{StaticResource BorderCardStyle}">
<Grid Margin="8" DataContext="{Binding Weapon}">
<Grid Margin="8" DataContext="{x:Bind Weapon}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>

View File

@@ -33,7 +33,6 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog
Avatar = options.Avatar;
Weapon = options.Weapon;
DataContext = this;
}
/// <summary>
@@ -53,19 +52,19 @@ internal sealed partial class CultivatePromotionDeltaDialog : ContentDialog
AvatarPromotionDelta delta = new()
{
AvatarId = Avatar?.AvatarId ?? 0,
AvatarLevelCurrent = Avatar?.LevelCurrent ?? 0,
AvatarLevelTarget = Avatar?.LevelTarget ?? 0,
SkillList = Avatar?.Skills.SelectList(s => new PromotionDelta()
AvatarLevelCurrent = Avatar is not null ? Math.Clamp(Avatar.LevelCurrent, Avatar.LevelMin, Avatar.LevelMax) : 0,
AvatarLevelTarget = Avatar is not null ? Math.Clamp(Avatar.LevelTarget, Avatar.LevelMin, Avatar.LevelMax) : 0,
SkillList = Avatar?.Skills.SelectList(skill => new PromotionDelta()
{
Id = s.GroupId,
LevelCurrent = s.LevelCurrent,
LevelTarget = s.LevelTarget,
Id = skill.GroupId,
LevelCurrent = Math.Clamp(skill.LevelCurrent, skill.LevelMin, skill.LevelMax),
LevelTarget = Math.Clamp(skill.LevelTarget, skill.LevelMin, skill.LevelMax),
}),
Weapon = Weapon is null ? null : new PromotionDelta()
{
Id = Weapon.WeaponId,
LevelCurrent = Weapon.LevelCurrent,
LevelTarget = Weapon.LevelTarget,
LevelCurrent = Math.Clamp(Weapon.LevelCurrent, Weapon.LevelMin, Weapon.LevelMax),
LevelTarget = Math.Clamp(Weapon.LevelTarget, Weapon.LevelMin, Weapon.LevelMax),
},
};

View File

@@ -89,11 +89,17 @@
CommandParameter="{Binding ElementName=ItemsPanelSelector, Path=Current, Converter={StaticResource PanelSelectorModeConverter}}"
Icon="{shcm:FontIcon Glyph=&#xE91B;}"
Label="{shcm:ResourceString Name=ViewPageAvatarPropertyExportAsImage}"/>
<AppBarButton
Command="{Binding CultivateCommand}"
CommandParameter="{Binding SelectedAvatar}"
Icon="{shcm:FontIcon Glyph=&#xE8EF;}"
Label="{shcm:ResourceString Name=ViewPageCultivateCalculate}"/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8EF;}" Label="{shcm:ResourceString Name=ViewPageCultivateCalculate}">
<AppBarButton.Flyout>
<MenuFlyout>
<MenuFlyoutItem
Command="{Binding CultivateCommand}"
CommandParameter="{Binding SelectedAvatar}"
Text="{shcm:ResourceString Name=ViewPageAvatarPropertyCalculateCurrent}"/>
<MenuFlyoutItem Command="{Binding BatchCultivateCommand}" Text="{shcm:ResourceString Name=ViewPageAvatarPropertyCalculateAll}"/>
</MenuFlyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarSeparator/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE72C;}" Label="{shcm:ResourceString Name=ViewAvatarPropertySyncDataButtonLabel}">
<AppBarButton.Flyout>

View File

@@ -44,9 +44,9 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
private readonly IAvatarInfoService avatarInfoService;
private readonly IClipboardInterop clipboardInterop;
private readonly CalculatorClient calculatorClient;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext;
private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private Summary? summary;
private AvatarView? selectedAvatar;
@@ -161,66 +161,168 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
[Command("CultivateCommand")]
private async Task CultivateAsync(AvatarView? avatar)
{
if (avatar is not null)
if (avatar is null)
{
if (userService.Current is not null)
return;
}
if (userService.Current is null)
{
infoBarService.Warning(SH.MustSelectUserAndUid);
}
else
{
if (avatar.Weapon is null)
{
if (avatar.Weapon is null)
{
infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull);
return;
}
infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull);
return;
}
// ContentDialog must be created by main thread.
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable());
CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable());
CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
if (!isOk)
{
return;
}
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, delta)
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
{
return;
}
CalculatorConsumption consumption = consumptionResponse.Data;
List<CalculatorItem> items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
bool avatarSaved = await cultivationService
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
try
{
// take a hot path if avatar is not saved.
bool avatarAndWeaponSaved = avatarSaved && await cultivationService
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
if (avatarAndWeaponSaved)
{
return;
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
CalculatorConsumption consumption = consumptionResponse.Data;
List<CalculatorItem> items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
bool avatarSaved = await cultivationService
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
try
else
{
// take a hot path if avatar is not saved.
bool avatarAndWeaponSaved = avatarSaved && await cultivationService
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
}
}
if (avatarAndWeaponSaved)
{
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
else
{
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
}
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
[Command("BatchCultivateCommand")]
private async Task BatchCultivateAsync()
{
if (summary is { Avatars: { } avatars })
{
if (userService.Current is null)
{
infoBarService.Warning(SH.MustSelectUserAndUid);
}
else
{
infoBarService.Warning(SH.MustSelectUserAndUid);
CultivatePromotionDeltaBatchDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>().ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta baseline) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false);
if (isOk)
{
ArgumentNullException.ThrowIfNull(baseline.SkillList);
ArgumentNullException.ThrowIfNull(baseline.Weapon);
ContentDialog progressDialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle)
.ConfigureAwait(false);
using (await progressDialog.BlockAsync(taskContext).ConfigureAwait(false))
{
BatchCultivateResult result = default;
foreach (AvatarView avatar in avatars)
{
baseline.AvatarId = avatar.Id;
baseline.AvatarLevelCurrent = Math.Min(avatar.LevelNumber, baseline.AvatarLevelTarget);
baseline.SkillList[0].Id = avatar.Skills[0].GroupId;
baseline.SkillList[0].LevelCurrent = Math.Min(avatar.Skills[0].LevelNumber, baseline.SkillList[0].LevelTarget);
baseline.SkillList[1].Id = avatar.Skills[1].GroupId;
baseline.SkillList[1].LevelCurrent = Math.Min(avatar.Skills[1].LevelNumber, baseline.SkillList[1].LevelTarget);
baseline.SkillList[2].Id = avatar.Skills[2].GroupId;
baseline.SkillList[2].LevelCurrent = Math.Min(avatar.Skills[2].LevelNumber, baseline.SkillList[2].LevelTarget);
if (avatar.Weapon is null)
{
result.SkippedCount++;
continue;
}
baseline.Weapon.Id = avatar.Weapon.Id;
baseline.Weapon.LevelCurrent = Math.Min(avatar.Weapon.LevelNumber, baseline.Weapon.LevelTarget);
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, baseline)
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
{
result.Interrupted = true;
break;
}
else
{
CalculatorConsumption consumption = consumptionResponse.Data;
List<CalculatorItem> items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
bool avatarSaved = await cultivationService
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
try
{
// take a hot path if avatar is not saved.
bool avatarAndWeaponSaved = avatarSaved && await cultivationService
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
if (avatarAndWeaponSaved)
{
result.SucceedCount++;
}
else
{
result.Interrupted = true;
break;
}
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
}
}
if (result.Interrupted)
{
infoBarService.Warning(SH.ViewModelCultivationBatchAddIncompletedFormat.Format(result.SucceedCount, result.SkippedCount));
}
else
{
infoBarService.Success(SH.ViewModelCultivationBatchAddCompletedFormat.Format(result.SucceedCount, result.SkippedCount));
}
}
}
}
}
}

View File

@@ -51,7 +51,7 @@ internal sealed class AvatarView : INameIconSide, ICalculableSource<ICalculableA
/// <summary>
/// 武器
/// </summary>
public WeaponView? Weapon { get; set; } = default!;
public WeaponView? Weapon { get; set; }
/// <summary>
/// 圣遗物列表

View File

@@ -0,0 +1,11 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.AvatarProperty;
internal struct BatchCultivateResult
{
public int SucceedCount;
public int SkippedCount;
public bool Interrupted;
}

View File

@@ -11,7 +11,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
/// 天赋
/// </summary>
[HighQuality]
internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalculableSkill>
internal sealed class SkillView : NameIconDescription, ITypedCalculableSource<ICalculableSkill, SkillType>
{
/// <summary>
/// 技能属性
@@ -34,8 +34,8 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
internal SkillGroupId GroupId { get; set; }
/// <inheritdoc/>
public ICalculableSkill ToCalculable()
public ICalculableSkill ToCalculable(SkillType type)
{
return CalculableSkill.From(this);
return CalculableSkill.From(this, type);
}
}

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
@@ -46,4 +47,19 @@ internal sealed class AvatarPromotionDelta
/// </summary>
[JsonPropertyName("weapon")]
public PromotionDelta? Weapon { get; set; }
public static AvatarPromotionDelta CreateForBaseline()
{
return new()
{
AvatarLevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarLevelTarget, 90U),
SkillList = new()
{
new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillATarget, 10U), },
new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillETarget, 10U), },
new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationAvatarSkillQTarget, 10U), },
},
Weapon = new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationWeapon90LevelTarget, 90U), },
};
}
}