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-03 17:20:00 +08:00
parent d3865b7992
commit be9f3f6a25
11 changed files with 184 additions and 8 deletions
@@ -74,6 +74,8 @@ internal static class SettingKeys
public const string CultivationWeapon90LevelCurrent = "Snap::Hutao::Cultivation::Weapon90::Level::Current"; public const string CultivationWeapon90LevelCurrent = "Snap::Hutao::Cultivation::Weapon90::Level::Current";
public const string CultivationWeapon90LevelTarget = "Snap::Hutao::Cultivation::Weapon90::Level::Target"; public const string CultivationWeapon90LevelTarget = "Snap::Hutao::Cultivation::Weapon90::Level::Target";
public const string ResinStatisticsSelectedDropDistribution = "Snap::Hutao::Cultivation::ResinStatistics::DropDistribution"; 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 // GachaLog
public const string IsEmptyHistoryWishVisible = "Snap::Hutao::GachaLog::HistoryWish::EmptyVisible"; public const string IsEmptyHistoryWishVisible = "Snap::Hutao::GachaLog::HistoryWish::EmptyVisible";
@@ -2420,6 +2420,12 @@ Space Available: {2}</value>
<data name="ViewPageCultivationMaterialListIncompleteFirstLabel" xml:space="preserve"> <data name="ViewPageCultivationMaterialListIncompleteFirstLabel" xml:space="preserve">
<value>Uncollected First</value> <value>Uncollected First</value>
</data> </data>
<data name="ViewPageCultivationMergeUpgradeMaterialsLabel" xml:space="preserve">
<value>Merge upgrade materials</value>
</data>
<data name="ViewPageCultivationTalentSynthCritTenPercentLabel" xml:space="preserve">
<value>Character talent (10%)</value>
</data>
<data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve"> <data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve">
<value>Resin Estimation</value> <value>Resin Estimation</value>
</data> </data>
@@ -2471,6 +2471,12 @@
<data name="ViewPageCultivationMaterialListIncompleteFirstLabel" xml:space="preserve"> <data name="ViewPageCultivationMaterialListIncompleteFirstLabel" xml:space="preserve">
<value>未集齐优先</value> <value>未集齐优先</value>
</data> </data>
<data name="ViewPageCultivationMergeUpgradeMaterialsLabel" xml:space="preserve">
<value>升级材料合并</value>
</data>
<data name="ViewPageCultivationTalentSynthCritTenPercentLabel" xml:space="preserve">
<value>人物天赋(10%</value>
</data>
<data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve"> <data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve">
<value>树脂预估</value> <value>树脂预估</value>
</data> </data>
@@ -124,6 +124,6 @@ internal sealed partial class CultivationResinStatisticsService : ICultivationRe
private static double GetStatisticsCultivateItemTimes(StatisticsCultivateItem item) private static double GetStatisticsCultivateItemTimes(StatisticsCultivateItem item)
{ {
return item.Count - (long)item.Current; return item.Count - (long)item.DisplayCurrent;
} }
} }
@@ -92,12 +92,12 @@ internal sealed partial class CultivationService : ICultivationService
} }
} }
public async ValueTask<StatisticsCultivateItemCollection> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token) public async ValueTask<StatisticsCultivateItemCollection> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CultivationStatisticsMergeOptions mergeOptions, CancellationToken token)
{ {
await taskContext.SwitchToBackgroundAsync(); 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 = []; Dictionary</* ItemId */ uint, StatisticsCultivateItem> resultItems = [];
Guid projectId = cultivateProject.InnerId; Guid projectId = cultivateProject.InnerId;
@@ -129,6 +129,8 @@ internal sealed partial class CultivationService : ICultivationService
} }
} }
CultivationStatisticsSurplusMerge.Apply(resultItems, context, mergeOptions);
return new(resultItems); return new(resultItems);
} }
} }
@@ -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);
@@ -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<uint, StatisticsCultivateItem> items, ICultivationMetadataContext context, CultivationStatisticsMergeOptions options)
{
if (!options.MergeUpgradeMaterials || items.Count is 0)
{
return;
}
bool talentCrit = options.TalentSynthCritTenPercent;
List<Combine> 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<uint, double> virtualAmount = new(items.Count);
foreach ((uint id, StatisticsCultivateItem item) in items)
{
virtualAmount[id] = item.Current;
}
foreach (IGrouping<uint, Combine> group in eligibleCombines.GroupBy(static c => c.SubType))
{
List<Combine> groupCombines = [.. group];
HashSet<uint> 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<Combine> 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;
}
}
@@ -17,7 +17,7 @@ internal interface ICultivationService
ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntryCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context); ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntryCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context);
ValueTask<StatisticsCultivateItemCollection> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token); ValueTask<StatisticsCultivateItemCollection> GetStatisticsCultivateItemCollectionAsync(CultivateProject cultivateProject, ICultivationMetadataContext context, CultivationStatisticsMergeOptions mergeOptions, CancellationToken token);
ValueTask<ResinStatistics> GetResinStatisticsAsync(StatisticsCultivateItemCollection statisticsCultivateItems, CancellationToken token); ValueTask<ResinStatistics> GetResinStatisticsAsync(StatisticsCultivateItemCollection statisticsCultivateItems, CancellationToken token);
@@ -482,6 +482,19 @@
IsChecked="{Binding IncompleteFirst, Mode=TwoWay}" IsChecked="{Binding IncompleteFirst, Mode=TwoWay}"
Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListIncompleteFirstLabel}" Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListIncompleteFirstLabel}"
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/> Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
<AppBarToggleButton
Command="{Binding RefreshStatisticsItemsCommand}"
Icon="{shuxm:FontIcon Glyph=&#xE7C3;}"
IsChecked="{Binding MergeUpgradeMaterials, Mode=TwoWay}"
Label="{shuxm:ResourceString Name=ViewPageCultivationMergeUpgradeMaterialsLabel}"
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
<AppBarToggleButton
Command="{Binding RefreshStatisticsItemsCommand}"
Icon="{shuxm:FontIcon Glyph=&#xE9A1;}"
IsChecked="{Binding TalentSynthCritTenPercent, Mode=TwoWay}"
IsEnabled="{Binding MergeUpgradeMaterials}"
Label="{shuxm:ResourceString Name=ViewPageCultivationTalentSynthCritTenPercentLabel}"
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
<AppBarButton Icon="{shuxm:FontIcon Glyph=&#xE946;}" Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListResinStatisticsLabel}"> <AppBarButton Icon="{shuxm:FontIcon Glyph=&#xE946;}" Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListResinStatisticsLabel}">
<AppBarButton.Flyout> <AppBarButton.Flyout>
<Flyout FlyoutPresenterStyle="{ThemeResource FlyoutPresenterPadding0Style}" Placement="BottomEdgeAlignedRight"> <Flyout FlyoutPresenterStyle="{ThemeResource FlyoutPresenterPadding0Style}" Placement="BottomEdgeAlignedRight">
@@ -7,6 +7,7 @@ using Snap.Hutao.Core;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Core.Setting;
using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Service.Cultivation;
@@ -70,6 +71,12 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
[ObservableProperty] [ObservableProperty]
public partial bool IncompleteFirst { get; set; } 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] [ObservableProperty]
public partial ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get; set; } public partial ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get; set; }
@@ -356,12 +363,19 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
bool merge = MergeUpgradeMaterials;
bool talentCrit = merge && TalentSynthCritTenPercent;
LocalSetting.Set(SettingKeys.CultivationStatisticsMergeUpgradeMaterials, merge);
LocalSetting.Set(SettingKeys.CultivationStatisticsTalentSynthCritTenPercent, talentCrit);
CancellationToken token = exclusiveTokenProvider.GetNewToken(); CancellationToken token = exclusiveTokenProvider.GetNewToken();
StatisticsCultivateItemCollection statistics; StatisticsCultivateItemCollection statistics;
ResinStatistics resinStatistics; ResinStatistics resinStatistics;
try 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); resinStatistics = await cultivationService.GetResinStatisticsAsync(statistics, token).ConfigureAwait(false);
} }
catch (OperationCanceledException) catch (OperationCanceledException)
@@ -29,9 +29,16 @@ internal sealed class StatisticsCultivateItem
public uint Current { get; set; } public uint Current { get; set; }
public bool IsFinished { get => Current >= Count; } /// <summary>
/// 升级材料合并后的展示用持有量;未启用合并时为 <see langword="null"/>。
/// </summary>
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); } public bool IsToday { get => Inner.IsItemOfToday(offset, true); }