diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
index 3defeef..1b46568 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Setting/SettingKeys.cs
@@ -74,6 +74,8 @@ internal static class SettingKeys
public const string CultivationWeapon90LevelCurrent = "Snap::Hutao::Cultivation::Weapon90::Level::Current";
public const string CultivationWeapon90LevelTarget = "Snap::Hutao::Cultivation::Weapon90::Level::Target";
public const string ResinStatisticsSelectedDropDistribution = "Snap::Hutao::Cultivation::ResinStatistics::DropDistribution";
+ public const string CultivationStatisticsMergeUpgradeMaterials = "Snap::Hutao::Cultivation::Statistics::MergeUpgradeMaterials";
+ public const string CultivationStatisticsTalentSynthCritTenPercent = "Snap::Hutao::Cultivation::Statistics::TalentSynthCritTenPercent";
// GachaLog
public const string IsEmptyHistoryWishVisible = "Snap::Hutao::GachaLog::HistoryWish::EmptyVisible";
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx
index 3f188c1..3dcd1bf 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.en.resx
@@ -2420,6 +2420,12 @@ Space Available: {2}
Uncollected First
+
+ Merge upgrade materials
+
+
+ Character talent (10%)
+
Resin Estimation
diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
index 7464282..5f30949 100644
--- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
+++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx
@@ -2471,6 +2471,12 @@
未集齐优先
+
+ 升级材料合并
+
+
+ 人物天赋(10%)
+
树脂预估
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationResinStatisticsService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationResinStatisticsService.cs
index bb59e9b..0b2076d 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationResinStatisticsService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationResinStatisticsService.cs
@@ -124,6 +124,6 @@ internal sealed partial class CultivationResinStatisticsService : ICultivationRe
private static double GetStatisticsCultivateItemTimes(StatisticsCultivateItem item)
{
- return item.Count - (long)item.Current;
+ return item.Count - (long)item.DisplayCurrent;
}
}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
index 03dde79..6c71220 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs
@@ -92,12 +92,12 @@ internal sealed partial class CultivationService : ICultivationService
}
}
- public async ValueTask GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token)
+ public async ValueTask GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CultivationStatisticsMergeOptions mergeOptions, CancellationToken token)
{
await taskContext.SwitchToBackgroundAsync();
- return SynchronizedGetStatisticsCultivateItemCollection(cultivateProject, context);
+ return SynchronizedGetStatisticsCultivateItemCollection(cultivateProject, context, mergeOptions);
- StatisticsCultivateItemCollection SynchronizedGetStatisticsCultivateItemCollection(CultivateProject cultivateProject, ICultivationMetadataContext context)
+ StatisticsCultivateItemCollection SynchronizedGetStatisticsCultivateItemCollection(CultivateProject cultivateProject, ICultivationMetadataContext context, CultivationStatisticsMergeOptions mergeOptions)
{
Dictionary* ItemId */ uint, StatisticsCultivateItem> resultItems = [];
Guid projectId = cultivateProject.InnerId;
@@ -129,6 +129,8 @@ internal sealed partial class CultivationService : ICultivationService
}
}
+ CultivationStatisticsSurplusMerge.Apply(resultItems, context, mergeOptions);
+
return new(resultItems);
}
}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsMergeOptions.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsMergeOptions.cs
new file mode 100644
index 0000000..48d2aa7
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsMergeOptions.cs
@@ -0,0 +1,6 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+namespace Snap.Hutao.Service.Cultivation;
+
+internal readonly record struct CultivationStatisticsMergeOptions(bool MergeUpgradeMaterials, bool TalentSynthCritTenPercent);
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsSurplusMerge.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsSurplusMerge.cs
new file mode 100644
index 0000000..c129a1c
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationStatisticsSurplusMerge.cs
@@ -0,0 +1,120 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Snap.Hutao.Model.Intrinsic;
+using Snap.Hutao.Model.Metadata;
+using Snap.Hutao.Service.Metadata.ContextAbstraction;
+using Snap.Hutao.ViewModel.Cultivation;
+
+namespace Snap.Hutao.Service.Cultivation;
+
+internal static class CultivationStatisticsSurplusMerge
+{
+ private const uint TalentCombineType = 3U;
+
+ public static void Apply(Dictionary items, ICultivationMetadataContext context, CultivationStatisticsMergeOptions options)
+ {
+ if (!options.MergeUpgradeMaterials || items.Count is 0)
+ {
+ return;
+ }
+
+ bool talentCrit = options.TalentSynthCritTenPercent;
+
+ List eligibleCombines = [];
+ foreach (Combine combine in context.ResultMaterialIdCombineMap.Values)
+ {
+ if (combine.RecipeType is not RecipeType.RECIPE_TYPE_COMBINE)
+ {
+ continue;
+ }
+
+ if (combine.Materials.Length is not 1 || combine.Materials[0].Count is not 3 || combine.Result.Count is not 1)
+ {
+ continue;
+ }
+
+ eligibleCombines.Add(combine);
+ }
+
+ Dictionary virtualAmount = new(items.Count);
+ foreach ((uint id, StatisticsCultivateItem item) in items)
+ {
+ virtualAmount[id] = item.Current;
+ }
+
+ foreach (IGrouping group in eligibleCombines.GroupBy(static c => c.SubType))
+ {
+ List groupCombines = [.. group];
+ HashSet ids = [];
+ foreach (Combine combine in groupCombines)
+ {
+ _ = ids.Add(combine.Result.Id);
+ _ = ids.Add(combine.Materials[0].Id);
+ }
+
+ uint[] sortedIds = [.. ids.OrderBy(id => GetRankLevel(context, id)).ThenBy(id => id)];
+
+ foreach (uint id in sortedIds)
+ {
+ Combine? upward = FindUpwardCombine(groupCombines, id);
+ if (upward is null)
+ {
+ continue;
+ }
+
+ if (!virtualAmount.TryGetValue(id, out double virt))
+ {
+ continue;
+ }
+
+ uint need = items.TryGetValue(id, out StatisticsCultivateItem? row) ? row.Count : 0U;
+ double surplus = Math.Max(0D, virt - need);
+ long crafts = (long)(surplus / 3D);
+ if (crafts <= 0L)
+ {
+ continue;
+ }
+
+ double multiplier = talentCrit && upward.Type == TalentCombineType ? 1.1D : 1D;
+ double produced = crafts * multiplier;
+ uint resultId = upward.Result.Id;
+
+ virtualAmount[id] = virt - (crafts * 3L);
+
+ if (!virtualAmount.TryGetValue(resultId, out double atResult))
+ {
+ atResult = items.TryGetValue(resultId, out StatisticsCultivateItem? r) ? r.Current : 0D;
+ }
+
+ virtualAmount[resultId] = atResult + produced;
+ }
+ }
+
+ foreach ((uint id, StatisticsCultivateItem item) in items)
+ {
+ if (virtualAmount.TryGetValue(id, out double v))
+ {
+ item.MergeAdjustedCurrent = (uint)Math.Clamp(Math.Floor(v + 1e-6), 0D, uint.MaxValue);
+ }
+ }
+ }
+
+ private static Combine? FindUpwardCombine(List groupCombines, uint ingredientId)
+ {
+ foreach (Combine combine in groupCombines)
+ {
+ if (combine.Materials is [{ Id: var mid, Count: 3 }] && mid == ingredientId)
+ {
+ return combine;
+ }
+ }
+
+ return null;
+ }
+
+ private static QualityType GetRankLevel(ICultivationMetadataContext context, uint materialId)
+ {
+ return context.GetMaterial(materialId).RankLevel;
+ }
+}
diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
index 7563710..423f898 100644
--- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs
@@ -17,7 +17,7 @@ internal interface ICultivationService
ValueTask> GetCultivateEntryCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context);
- ValueTask GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token);
+ ValueTask GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CultivationStatisticsMergeOptions mergeOptions, CancellationToken token);
ValueTask GetResinStatisticsAsync(StatisticsCultivateItemCollection statisticsCultivateItems, CancellationToken token);
diff --git a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml
index 0778846..56fd22c 100644
--- a/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml
+++ b/src/Snap.Hutao/Snap.Hutao/UI/Xaml/View/Page/CultivationPage.xaml
@@ -482,6 +482,19 @@
IsChecked="{Binding IncompleteFirst, Mode=TwoWay}"
Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListIncompleteFirstLabel}"
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
+
+
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs
index 1bc4cb7..da96cf3 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs
@@ -7,6 +7,7 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.Logging;
+using Snap.Hutao.Core.Setting;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Cultivation;
@@ -70,6 +71,12 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
[ObservableProperty]
public partial bool IncompleteFirst { get; set; }
+ [ObservableProperty]
+ public partial bool MergeUpgradeMaterials { get; set; } = LocalSetting.Get(SettingKeys.CultivationStatisticsMergeUpgradeMaterials, false);
+
+ [ObservableProperty]
+ public partial bool TalentSynthCritTenPercent { get; set; } = LocalSetting.Get(SettingKeys.CultivationStatisticsTalentSynthCritTenPercent, false);
+
[ObservableProperty]
public partial ObservableCollection? StatisticsItems { get; set; }
@@ -356,12 +363,19 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
await taskContext.SwitchToBackgroundAsync();
+ bool merge = MergeUpgradeMaterials;
+ bool talentCrit = merge && TalentSynthCritTenPercent;
+ LocalSetting.Set(SettingKeys.CultivationStatisticsMergeUpgradeMaterials, merge);
+ LocalSetting.Set(SettingKeys.CultivationStatisticsTalentSynthCritTenPercent, talentCrit);
+
CancellationToken token = exclusiveTokenProvider.GetNewToken();
StatisticsCultivateItemCollection statistics;
ResinStatistics resinStatistics;
try
{
- statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(Projects.CurrentItem, metadataContext, token).ConfigureAwait(false);
+ statistics = await cultivationService
+ .GetStatisticsCultivateItemCollectionAsync(Projects.CurrentItem, metadataContext, new CultivationStatisticsMergeOptions(merge, talentCrit), token)
+ .ConfigureAwait(false);
resinStatistics = await cultivationService.GetResinStatisticsAsync(statistics, token).ConfigureAwait(false);
}
catch (OperationCanceledException)
diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs
index dbdd4b9..ae44bcf 100644
--- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs
+++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs
@@ -29,9 +29,16 @@ internal sealed class StatisticsCultivateItem
public uint Current { get; set; }
- public bool IsFinished { get => Current >= Count; }
+ ///
+ /// 升级材料合并后的展示用持有量;未启用合并时为 。
+ ///
+ public uint? MergeAdjustedCurrent { get; set; }
- public string FormattedCount { get => $"{Current}/{Count}"; }
+ public uint DisplayCurrent { get => MergeAdjustedCurrent ?? Current; }
+
+ public bool IsFinished { get => DisplayCurrent >= Count; }
+
+ public string FormattedCount { get => $"{DisplayCurrent}/{Count}"; }
public bool IsToday { get => Inner.IsItemOfToday(offset, true); }