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> /// </summary>
public const string OverrideElevationRequirement = "OverrideElevationRequirement"; 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); Span<TSource> span = CollectionsMarshal.AsSpan(list);
List<TResult> results = new(span.Length); List<TResult> results = new(span.Length);
foreach (TSource item in span) foreach (ref readonly TSource item in span)
{ {
results.Add(selector(item)); results.Add(selector(item));
} }
@@ -111,6 +111,21 @@ internal static partial class EnumerableExtension
return results; 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) public static async ValueTask<List<TResult>> SelectListAsync<TSource, TResult>(this List<TSource> list, Func<TSource, ValueTask<TResult>> selector)
{ {
List<TResult> results = new(list.Count); List<TResult> results = new(list.Count);

View File

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

View File

@@ -3,6 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
@@ -18,44 +19,33 @@ namespace Snap.Hutao.Model.Calculable;
internal sealed class CalculableSkill internal sealed class CalculableSkill
: ObservableObject, : ObservableObject,
ICalculableSkill, ICalculableSkill,
IMappingFrom<CalculableSkill, ProudableSkill>, IMappingFrom<CalculableSkill, ProudableSkill, SkillType>,
IMappingFrom<CalculableSkill, SkillView> IMappingFrom<CalculableSkill, SkillView, SkillType>
{ {
private uint levelCurrent; private readonly SkillType type;
private uint levelTarget;
/// <summary> private CalculableSkill(ProudableSkill skill, SkillType type)
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
private CalculableSkill(ProudableSkill skill)
{ {
this.type = type;
GroupId = skill.GroupId; GroupId = skill.GroupId;
LevelMin = 1; LevelMin = 1;
LevelMax = 10; // hard coded 10 here LevelMax = ProudableSkill.GetMaxLevel();
Name = skill.Name; Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon); Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = QualityType.QUALITY_NONE; Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
} }
/// <summary> private CalculableSkill(SkillView skill, SkillType type)
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
private CalculableSkill(SkillView skill)
{ {
this.type = type;
GroupId = skill.GroupId; GroupId = skill.GroupId;
LevelMin = skill.LevelNumber; LevelMin = skill.LevelNumber;
LevelMax = 10; // hard coded 10 here LevelMax = ProudableSkill.GetMaxLevel();
Name = skill.Name; Name = skill.Name;
Icon = skill.Icon; Icon = skill.Icon;
Quality = QualityType.QUALITY_NONE; Quality = QualityType.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -77,18 +67,48 @@ internal sealed class CalculableSkill
public QualityType Quality { get; } public QualityType Quality { get; }
/// <inheritdoc/> /// <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/> /// <inheritdoc/>
public uint LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } public uint LevelTarget
public static CalculableSkill From(ProudableSkill source)
{ {
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 CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Core.Abstraction; using Snap.Hutao.Core.Abstraction;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Metadata.Weapon;
@@ -77,10 +78,18 @@ internal sealed class CalculableWeapon
public QualityType Quality { get; } public QualityType Quality { get; }
/// <inheritdoc/> /// <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/> /// <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) public static CalculableWeapon From(Weapon source)
{ {
@@ -91,4 +100,18 @@ internal sealed class CalculableWeapon
{ {
return new(source); 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>
/// 可计算物品的源 /// 可计算物品的源
/// </summary> /// </summary>
/// <typeparam name="T">可计算类型</typeparam> /// <typeparam name="TResult">可计算类型</typeparam>
[HighQuality] [HighQuality]
internal interface ICalculableSource<T> internal interface ICalculableSource<TResult>
where T : ICalculable where TResult : ICalculable
{ {
/// <summary> /// <summary>
/// 转换到可计算的对象 /// 转换到可计算的对象
/// </summary> /// </summary>
/// <returns>可计算物品</returns> /// <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> /// </summary>
[SuppressMessage("", "CA1822")] [SuppressMessage("", "CA1822")]
public uint MaxLevel { get => 90U; } public uint MaxLevel { get => GetMaxLevel(); }
public static uint GetMaxLevel()
{
return 90U;
}
/// <inheritdoc/> /// <inheritdoc/>
public ICalculableAvatar ToCalculable() public ICalculableAvatar ToCalculable()

View File

@@ -8,11 +8,16 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary> /// <summary>
/// 技能信息的接口实现 /// 技能信息的接口实现
/// </summary> /// </summary>
internal sealed partial class ProudableSkill : ICalculableSource<ICalculableSkill> internal sealed partial class ProudableSkill : ITypedCalculableSource<ICalculableSkill, SkillType>
{ {
/// <inheritdoc/> public static uint GetMaxLevel()
public ICalculableSkill ToCalculable()
{ {
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>
/// 查找类似 绑定当前用户与角色 的本地化字符串。 /// 查找类似 绑定当前用户与角色 的本地化字符串。
/// </summary> /// </summary>
@@ -2284,7 +2329,16 @@ namespace Snap.Hutao.Resource.Localization {
} }
/// <summary> /// <summary>
/// 查找类似 添加到当前养成计划 的本地化字符串。 /// 查找类似 批量添加或更新到当前养成计划 的本地化字符串。
/// </summary>
internal static string ViewDialogCultivatePromotionDeltaBatchTitle {
get {
return ResourceManager.GetString("ViewDialogCultivatePromotionDeltaBatchTitle", resourceCulture);
}
}
/// <summary>
/// 查找类似 添加或更新到当前养成计划 的本地化字符串。
/// </summary> /// </summary>
internal static string ViewDialogCultivatePromotionDeltaTitle { internal static string ViewDialogCultivatePromotionDeltaTitle {
get { get {
@@ -2895,6 +2949,15 @@ namespace Snap.Hutao.Resource.Localization {
} }
} }
/// <summary>
/// 查找类似 获取培养材料中,请稍候... 的本地化字符串。
/// </summary>
internal static string ViewModelAvatarPropertyBatchCultivateProgressTitle {
get {
return ResourceManager.GetString("ViewModelAvatarPropertyBatchCultivateProgressTitle", resourceCulture);
}
}
/// <summary> /// <summary>
/// 查找类似 当前角色无法计算,请同步信息后再试 的本地化字符串。 /// 查找类似 当前角色无法计算,请同步信息后再试 的本地化字符串。
/// </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>
/// 查找类似 已成功添加至当前养成计划 的本地化字符串。 /// 查找类似 已成功添加至当前养成计划 的本地化字符串。
/// </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>
/// 查找类似 双暴评分 的本地化字符串。 /// 查找类似 双暴评分 的本地化字符串。
/// </summary> /// </summary>

View File

@@ -905,6 +905,21 @@
<data name="ViewDialogAchievementArchiveImportTitle" xml:space="preserve"> <data name="ViewDialogAchievementArchiveImportTitle" xml:space="preserve">
<value>为当前存档导入成就</value> <value>为当前存档导入成就</value>
</data> </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"> <data name="ViewDialogCultivateProjectAttachUid" xml:space="preserve">
<value>绑定当前用户与角色</value> <value>绑定当前用户与角色</value>
</data> </data>
@@ -914,8 +929,11 @@
<data name="ViewDialogCultivateProjectTitle" xml:space="preserve"> <data name="ViewDialogCultivateProjectTitle" xml:space="preserve">
<value>创建新的养成计划</value> <value>创建新的养成计划</value>
</data> </data>
<data name="ViewDialogCultivatePromotionDeltaBatchTitle" xml:space="preserve">
<value>批量添加或更新到当前养成计划</value>
</data>
<data name="ViewDialogCultivatePromotionDeltaTitle" xml:space="preserve"> <data name="ViewDialogCultivatePromotionDeltaTitle" xml:space="preserve">
<value>添加到当前养成计划</value> <value>添加或更新到当前养成计划</value>
</data> </data>
<data name="ViewDialogDailyNoteNotificationDailyTaskNotify" xml:space="preserve"> <data name="ViewDialogDailyNoteNotificationDailyTaskNotify" xml:space="preserve">
<value>每日委托上线提醒</value> <value>每日委托上线提醒</value>
@@ -1118,6 +1136,9 @@
<data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve"> <data name="ViewModelAchievementRemoveArchiveTitle" xml:space="preserve">
<value>确定要删除存档 {0} 吗?</value> <value>确定要删除存档 {0} 吗?</value>
</data> </data>
<data name="ViewModelAvatarPropertyBatchCultivateProgressTitle" xml:space="preserve">
<value>获取培养材料中,请稍候...</value>
</data>
<data name="ViewModelAvatarPropertyCalculateWeaponNull" xml:space="preserve"> <data name="ViewModelAvatarPropertyCalculateWeaponNull" xml:space="preserve">
<value>当前角色无法计算,请同步信息后再试</value> <value>当前角色无法计算,请同步信息后再试</value>
</data> </data>
@@ -1142,6 +1163,12 @@
<data name="ViewModelCultivationAddWarning" xml:space="preserve"> <data name="ViewModelCultivationAddWarning" xml:space="preserve">
<value>养成计划添加失败</value> <value>养成计划添加失败</value>
</data> </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"> <data name="ViewModelCultivationEntryAddSuccess" xml:space="preserve">
<value>已成功添加至当前养成计划</value> <value>已成功添加至当前养成计划</value>
</data> </data>
@@ -1367,6 +1394,12 @@
<data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve"> <data name="ViewPageAvatarPropertyArtifactScore" xml:space="preserve">
<value>圣遗物评分</value> <value>圣遗物评分</value>
</data> </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"> <data name="ViewPageAvatarPropertyCritScore" xml:space="preserve">
<value>双暴评分</value> <value>双暴评分</value>
</data> </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()) 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()) using (IServiceScope scope = serviceProvider.CreateScope())
{ {

View File

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

View File

@@ -28,9 +28,9 @@ internal interface ICultivationDbService
ValueTask<List<InventoryItem>> GetInventoryItemListByProjectIdAsync(Guid projectId); 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); void UpdateCultivateItem(CultivateItem item);

View File

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

View File

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

View File

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

View File

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

View File

@@ -44,9 +44,9 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
private readonly IAvatarInfoService avatarInfoService; private readonly IAvatarInfoService avatarInfoService;
private readonly IClipboardInterop clipboardInterop; private readonly IClipboardInterop clipboardInterop;
private readonly CalculatorClient calculatorClient; private readonly CalculatorClient calculatorClient;
private readonly IInfoBarService infoBarService;
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly IUserService userService; private readonly IUserService userService;
private readonly IInfoBarService infoBarService;
private Summary? summary; private Summary? summary;
private AvatarView? selectedAvatar; private AvatarView? selectedAvatar;
@@ -161,66 +161,168 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
[Command("CultivateCommand")] [Command("CultivateCommand")]
private async Task CultivateAsync(AvatarView? avatar) 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());
CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable()); CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false); (bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk) if (!isOk)
{ {
return; return;
} }
Response<CalculatorConsumption> consumptionResponse = await calculatorClient Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, delta) .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); .ConfigureAwait(false);
if (!consumptionResponse.IsOk()) if (avatarAndWeaponSaved)
{ {
return; infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
} }
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. infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
bool avatarAndWeaponSaved = avatarSaved && await cultivationService }
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull()) }
.ConfigureAwait(false); catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
}
}
if (avatarAndWeaponSaved) [Command("BatchCultivateCommand")]
{ private async Task BatchCultivateAsync()
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); {
} if (summary is { Avatars: { } avatars })
else {
{ if (userService.Current is null)
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); {
} infoBarService.Warning(SH.MustSelectUserAndUid);
}
catch (Core.ExceptionService.UserdataCorruptedException ex)
{
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
}
} }
else 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>
/// 武器 /// 武器
/// </summary> /// </summary>
public WeaponView? Weapon { get; set; } = default!; public WeaponView? Weapon { get; set; }
/// <summary> /// <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> /// </summary>
[HighQuality] [HighQuality]
internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalculableSkill> internal sealed class SkillView : NameIconDescription, ITypedCalculableSource<ICalculableSkill, SkillType>
{ {
/// <summary> /// <summary>
/// 技能属性 /// 技能属性
@@ -34,8 +34,8 @@ internal sealed class SkillView : NameIconDescription, ICalculableSource<ICalcul
internal SkillGroupId GroupId { get; set; } internal SkillGroupId GroupId { get; set; }
/// <inheritdoc/> /// <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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
@@ -46,4 +47,19 @@ internal sealed class AvatarPromotionDelta
/// </summary> /// </summary>
[JsonPropertyName("weapon")] [JsonPropertyName("weapon")]
public PromotionDelta? Weapon { get; set; } 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), },
};
}
} }