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 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); }