1
0
mirror of https://github.com/wangdage12/Snap.Hutao.git synced 2026-07-31 04:10:25 +08:00

在养成计划,同步背包物品前面,加个 同步所有角色与武器 。 效果同 我的角色 养成计划 所有角色与武器 。

This commit is contained in:
mfkvfhpdx
2026-05-04 12:14:44 +08:00
parent 94592c8498
commit 7a3f125846
9 changed files with 275 additions and 85 deletions
@@ -2453,6 +2453,9 @@ Space Available: {2}</value>
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>Go</value>
</data>
<data name="ViewPageCultivationSyncAllAvatarsAndWeapons" xml:space="preserve">
<value>Sync All Characters and Weapons</value>
</data>
<data name="ViewPageCultivationRefreshInventory" xml:space="preserve">
<value>Sync Inventory Items</value>
</data>
@@ -2504,6 +2504,9 @@
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>前往</value>
</data>
<data name="ViewPageCultivationSyncAllAvatarsAndWeapons" xml:space="preserve">
<value>同步所有角色与武器</value>
</data>
<data name="ViewPageCultivationRefreshInventory" xml:space="preserve">
<value>同步背包物品</value>
</data>
@@ -2429,6 +2429,9 @@
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>前往</value>
</data>
<data name="ViewPageCultivationSyncAllAvatarsAndWeapons" xml:space="preserve">
<value>同步所有角色與武器</value>
</data>
<data name="ViewPageCultivationRefreshInventory" xml:space="preserve">
<value>同步背包物品</value>
</data>
@@ -0,0 +1,179 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Cultivation;
using Snap.Hutao.Model.Entity.Primitive;
using Snap.Hutao.Service.AvatarInfo.Factory;
using Snap.Hutao.Service.Cultivation.Consumption;
using Snap.Hutao.Service.Cultivation.Offline;
using Snap.Hutao.UI.Xaml.View.Dialog;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
using System.Collections.Immutable;
using CalculatorAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalculatorBatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption;
using CalculatorConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
using CalculatorItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
namespace Snap.Hutao.Service.Cultivation;
[Service(ServiceLifetime.Singleton, typeof(IAvatarPropertyBatchCultivateService))]
internal sealed partial class AvatarPropertyBatchCultivateService : IAvatarPropertyBatchCultivateService
{
private readonly IContentDialogFactory contentDialogFactory;
private readonly ICultivationService cultivationService;
private readonly IServiceProvider serviceProvider;
[GeneratedConstructor]
public partial AvatarPropertyBatchCultivateService(IServiceProvider serviceProvider);
public async ValueTask<BatchCultivateResult?> ExecuteAsync(SummaryFactoryMetadataContext metadataContext, ImmutableArray<AvatarView> targetAvatars, CancellationToken cancellationToken)
{
CultivateProjectAvatarPropertyBatchPreferences? batchPrefs = await cultivationService
.GetAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync()
.ConfigureAwait(false);
CultivatePromotionDeltaBatchDialog dialog = batchPrefs is null
? await contentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>(serviceProvider)
.ConfigureAwait(false)
: await contentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>(serviceProvider, batchPrefs)
.ConfigureAwait(false);
if (await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false) is not (true, { } baseline))
{
return null;
}
await cultivationService
.SaveAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync(ToAvatarPropertyBatchPreferences(baseline))
.ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(baseline.Delta.Weapon);
ContentDialog progressDialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle)
.ConfigureAwait(false);
BatchCultivateResult result = default;
using (await contentDialogFactory.BlockAsync(progressDialog).ConfigureAwait(false))
{
if (baseline.ClearAvatarAndWeaponEntriesBeforeSync)
{
await cultivationService.RemoveAvatarAndWeaponEntriesForCurrentProjectAsync().ConfigureAwait(false);
}
ImmutableArray<CalculatorAvatarPromotionDelta>.Builder deltasBuilder = ImmutableArray.CreateBuilder<CalculatorAvatarPromotionDelta>();
foreach (AvatarView avatar in targetAvatars)
{
if (!baseline.Delta.TryGetNonErrorCopy(avatar, out CalculatorAvatarPromotionDelta? copy))
{
++result.SkippedCount;
continue;
}
deltasBuilder.Add(copy);
}
ImmutableArray<CalculatorAvatarPromotionDelta> deltas = deltasBuilder.ToImmutable();
CalculatorBatchConsumption batchConsumption = OfflineCalculator.CalculateBatchConsumption(deltas, metadataContext);
foreach ((CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) in batchConsumption.Items.Zip(deltas))
{
cancellationToken.ThrowIfCancellationRequested();
if (!await SaveCultivationAsync(consumption, new CultivatePromotionDeltaOptions(delta, baseline.Strategy)).ConfigureAwait(false))
{
break;
}
++result.SucceedCount;
}
}
return result;
}
private static CultivateProjectAvatarPropertyBatchPreferences ToAvatarPropertyBatchPreferences(CultivatePromotionDeltaOptions baseline)
{
CalculatorAvatarPromotionDelta d = baseline.Delta;
uint sa = 10;
uint se = 10;
uint sq = 10;
if (d.SkillList is [{ } a, { } e, { } q, ..])
{
sa = a.LevelTarget;
se = e.LevelTarget;
sq = q.LevelTarget;
}
return new CultivateProjectAvatarPropertyBatchPreferences
{
AvatarLevelTarget = d.AvatarLevelTarget,
SkillATarget = sa,
SkillETarget = se,
SkillQTarget = sq,
WeaponLevelTarget = d.Weapon?.LevelTarget ?? 90U,
ConsumptionSaveStrategyIndex = (int)baseline.Strategy,
ClearAvatarAndWeaponEntriesBeforeSync = baseline.ClearAvatarAndWeaponEntriesBeforeSync,
};
}
private async ValueTask<bool> SaveCultivationAsync(CalculatorConsumption consumption, CultivatePromotionDeltaOptions options)
{
LevelInformation levelInformation = LevelInformation.From(options.Delta);
InputConsumption avatarInput = new()
{
Type = CultivateType.AvatarAndSkill,
ItemId = options.Delta.AvatarId,
Items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume),
LevelInformation = levelInformation,
Strategy = options.Strategy,
};
ConsumptionSaveResult avatarSave = await cultivationService.SaveConsumptionAsync(avatarInput).ConfigureAwait(false);
if (avatarSave.Kind is ConsumptionSaveResultKind.NoProject)
{
return false;
}
ArgumentNullException.ThrowIfNull(options.Delta.Weapon);
Guid? relatedAvatarEntryId = avatarSave.CreatedEntryInnerId;
if (relatedAvatarEntryId is null && avatarSave.Kind is ConsumptionSaveResultKind.Skipped)
{
relatedAvatarEntryId = await cultivationService.TryGetAvatarCultivateEntryInnerIdAsync(options.Delta.AvatarId).ConfigureAwait(false);
}
if (relatedAvatarEntryId is null && avatarSave.Kind is ConsumptionSaveResultKind.NoItem)
{
relatedAvatarEntryId = await cultivationService.TryGetAvatarCultivateEntryInnerIdAsync(options.Delta.AvatarId).ConfigureAwait(false);
}
if (relatedAvatarEntryId is null && !consumption.WeaponConsume.IsEmpty)
{
relatedAvatarEntryId = await cultivationService.EnsureAvatarAssociationStubAsync(options.Delta.AvatarId, levelInformation).ConfigureAwait(false);
}
InputConsumption weaponInput = new()
{
Type = CultivateType.Weapon,
ItemId = options.Delta.Weapon.Id,
Items = consumption.WeaponConsume,
LevelInformation = levelInformation,
Strategy = options.Strategy,
RelatedEntryId = relatedAvatarEntryId,
};
ConsumptionSaveResult weaponSave = await cultivationService.SaveConsumptionAsync(weaponInput).ConfigureAwait(false);
return weaponSave.Kind is not ConsumptionSaveResultKind.NoProject;
}
}
@@ -1,10 +1,10 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.AvatarProperty;
namespace Snap.Hutao.Service.Cultivation;
internal struct BatchCultivateResult
{
public int SucceedCount;
public int SkippedCount;
}
}
@@ -0,0 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Service.AvatarInfo.Factory;
using Snap.Hutao.ViewModel.AvatarProperty;
using System.Collections.Immutable;
namespace Snap.Hutao.Service.Cultivation;
internal interface IAvatarPropertyBatchCultivateService
{
/// <summary>
/// <see langword="null"/> when the baseline dialog is dismissed without confirmation.
/// </summary>
ValueTask<BatchCultivateResult?> ExecuteAsync(SummaryFactoryMetadataContext metadataContext, ImmutableArray<AvatarView> targetAvatars, CancellationToken cancellationToken);
}
@@ -467,6 +467,10 @@
CommandParameter="{Binding Projects.CurrentItem, Mode=OneWay}"
Icon="{shuxm:FontIcon Glyph=&#xE894;}"
Label="{shuxm:ResourceString Name=ViewPageCultivationClearInventory}"/>
<AppBarButton
Command="{Binding SyncAllAvatarsAndWeaponsCommand}"
Icon="{shuxm:FontIcon Glyph=&#xE72C;}"
Label="{shuxm:ResourceString Name=ViewPageCultivationSyncAllAvatarsAndWeapons}"/>
<AppBarButton Icon="{shuxm:FontIcon Glyph=&#xE895;}" Label="{shuxm:ResourceString Name=ViewPageCultivationRefreshInventory}">
<AppBarButton.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight">
@@ -16,6 +16,7 @@ using Snap.Hutao.Service.AvatarInfo;
using Snap.Hutao.Service.AvatarInfo.Factory;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.Cultivation.Consumption;
using BatchCultivateResult = Snap.Hutao.Service.Cultivation.BatchCultivateResult;
using Snap.Hutao.Service.Cultivation.Offline;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.Service.Notification;
@@ -28,7 +29,6 @@ using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
using System.Collections.Immutable;
using System.Globalization;
using System.Runtime.CompilerServices;
using CalculatorAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalculatorBatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption;
using CalculatorConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
using CalculatorItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
@@ -41,6 +41,7 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
{
private readonly ExclusiveTokenProvider refreshTokenProvider = new();
private readonly AvatarPropertyViewModelScopeContext scopeContext;
private readonly IAvatarPropertyBatchCultivateService avatarPropertyBatchCultivateService;
private SummaryFactoryMetadataContext? metadataContext;
@@ -270,69 +271,16 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
targetAvatars = [.. avatars.Source];
}
CultivateProjectAvatarPropertyBatchPreferences? batchPrefs = await scopeContext.CultivationService
.GetAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync()
ArgumentNullException.ThrowIfNull(metadataContext);
BatchCultivateResult? batchResult = await avatarPropertyBatchCultivateService
.ExecuteAsync(metadataContext, targetAvatars, CancellationToken)
.ConfigureAwait(false);
CultivatePromotionDeltaBatchDialog dialog = batchPrefs is null
? await scopeContext.ContentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>(scopeContext.ServiceProvider)
.ConfigureAwait(false)
: await scopeContext.ContentDialogFactory
.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>(scopeContext.ServiceProvider, batchPrefs)
.ConfigureAwait(false);
if (await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false) is not (true, { } baseline))
if (batchResult is not { } result)
{
return;
}
await scopeContext.CultivationService
.SaveAvatarPropertyBatchCultivatePreferencesForCurrentProjectAsync(ToAvatarPropertyBatchPreferences(baseline))
.ConfigureAwait(false);
ArgumentNullException.ThrowIfNull(baseline.Delta.Weapon);
ContentDialog progressDialog = await scopeContext.ContentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle)
.ConfigureAwait(false);
BatchCultivateResult result = default;
using (await scopeContext.ContentDialogFactory.BlockAsync(progressDialog).ConfigureAwait(false))
{
if (baseline.ClearAvatarAndWeaponEntriesBeforeSync)
{
await scopeContext.CultivationService.RemoveAvatarAndWeaponEntriesForCurrentProjectAsync().ConfigureAwait(false);
}
ImmutableArray<CalculatorAvatarPromotionDelta>.Builder deltasBuilder = ImmutableArray.CreateBuilder<CalculatorAvatarPromotionDelta>();
foreach (AvatarView avatar in targetAvatars)
{
if (!baseline.Delta.TryGetNonErrorCopy(avatar, out CalculatorAvatarPromotionDelta? copy))
{
++result.SkippedCount;
continue;
}
deltasBuilder.Add(copy);
}
ImmutableArray<CalculatorAvatarPromotionDelta> deltas = deltasBuilder.ToImmutable();
ArgumentNullException.ThrowIfNull(metadataContext);
CalculatorBatchConsumption batchConsumption = OfflineCalculator.CalculateBatchConsumption(deltas, metadataContext);
foreach ((CalculatorConsumption consumption, CalculatorAvatarPromotionDelta delta) in batchConsumption.Items.Zip(deltas))
{
if (!await SaveCultivationAsync(consumption, new CultivatePromotionDeltaOptions(delta, baseline.Strategy), true).ConfigureAwait(false))
{
break;
}
++result.SucceedCount;
}
}
scopeContext.Messenger.Send(CultivationProjectEntriesChangedMessage.Empty);
InfoBarMessage message = result.SkippedCount > 0
@@ -342,31 +290,6 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
scopeContext.Messenger.Send(message);
}
private static CultivateProjectAvatarPropertyBatchPreferences ToAvatarPropertyBatchPreferences(CultivatePromotionDeltaOptions baseline)
{
CalculatorAvatarPromotionDelta d = baseline.Delta;
uint sa = 10;
uint se = 10;
uint sq = 10;
if (d.SkillList is [{ } a, { } e, { } q, ..])
{
sa = a.LevelTarget;
se = e.LevelTarget;
sq = q.LevelTarget;
}
return new CultivateProjectAvatarPropertyBatchPreferences
{
AvatarLevelTarget = d.AvatarLevelTarget,
SkillATarget = sa,
SkillETarget = se,
SkillQTarget = sq,
WeaponLevelTarget = d.Weapon?.LevelTarget ?? 90U,
ConsumptionSaveStrategyIndex = (int)baseline.Strategy,
ClearAvatarAndWeaponEntriesBeforeSync = baseline.ClearAvatarAndWeaponEntriesBeforeSync,
};
}
/// <returns><see langword="true"/> if we can continue saving consumptions, otherwise <see langword="false"/>.</returns>
private async ValueTask<bool> SaveCultivationAsync(CalculatorConsumption consumption, CultivatePromotionDeltaOptions options, bool isBatch = false)
{
@@ -11,19 +11,24 @@ using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.AvatarInfo;
using Snap.Hutao.Service.AvatarInfo.Factory;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.Inventory;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.Service.Notification;
using Snap.Hutao.Service.User;
using Snap.Hutao.Service.Yae;
using Snap.Hutao.ViewModel.AvatarProperty;
using Snap.Hutao.UI.Xaml.Control.AutoSuggestBox;
using Snap.Hutao.UI.Xaml.Data;
using Snap.Hutao.UI.Xaml.View.Dialog;
using Snap.Hutao.ViewModel.Game;
using System.Collections.Immutable;
using System.Collections.ObjectModel;
using BatchCultivateResult = Snap.Hutao.Service.Cultivation.BatchCultivateResult;
namespace Snap.Hutao.ViewModel.Cultivation;
@@ -38,6 +43,9 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel, IRec
private readonly ICultivationService cultivationService;
private readonly INavigationService navigationService;
private readonly IInventoryService inventoryService;
private readonly IAvatarInfoService avatarInfoService;
private readonly IAvatarPropertyBatchCultivateService avatarPropertyBatchCultivateService;
private readonly IUserService userService;
private readonly IServiceProvider serviceProvider;
private readonly IMetadataService metadataService;
private readonly ITaskContext taskContext;
@@ -322,6 +330,57 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel, IRec
}
}
[Command("SyncAllAvatarsAndWeaponsCommand")]
private async Task SyncAllAvatarsAndWeaponsAsync()
{
SentrySdk.AddBreadcrumb(BreadcrumbFactory2.CreateUI("Sync all avatars and weapons to cultivation", "CultivationViewModel.Command", []));
if (Projects?.CurrentItem is null)
{
return;
}
if (await userService.GetCurrentUserAndUidAsync().ConfigureAwait(false) is not { } userAndUid)
{
messenger.Send(InfoBarMessage.Warning(SH.MustSelectUserAndUid));
return;
}
SummaryFactoryMetadataContext summaryContext = await metadataService.GetContextAsync<SummaryFactoryMetadataContext>(CancellationToken).ConfigureAwait(false);
Summary? summary = await avatarInfoService.GetSummaryAsync(summaryContext, userAndUid, global::Snap.Hutao.Service.AvatarInfo.RefreshOptionKind.None, CancellationToken).ConfigureAwait(false);
if (summary is not { Avatars: { } avatars })
{
messenger.Send(InfoBarMessage.Warning(SH.ViewPageAvatarPropertyDefaultDescription));
return;
}
if (avatars.Source.Count < 1)
{
messenger.Send(InfoBarMessage.Warning(SH.ViewPageAvatarPropertyDefaultDescription));
return;
}
ImmutableArray<AvatarView> targetAvatars = [.. avatars.Source];
BatchCultivateResult? batchResult = await avatarPropertyBatchCultivateService
.ExecuteAsync(summaryContext, targetAvatars, CancellationToken)
.ConfigureAwait(false);
if (batchResult is not { } result)
{
return;
}
messenger.Send(CultivationProjectEntriesChangedMessage.Empty);
InfoBarMessage message = result.SkippedCount > 0
? InfoBarMessage.Warning(SH.FormatViewModelCultivationBatchAddIncompleted(result.SucceedCount, result.SkippedCount))
: InfoBarMessage.Success(SH.FormatViewModelCultivationBatchAddCompleted(result.SucceedCount, result.SkippedCount));
messenger.Send(message);
}
[Command("ClearInventoryCommand")]
private async Task ClearInventoryAsync(CultivateProject? project)
{