mirror of
https://github.com/wangdage12/Snap.Hutao.git
synced 2026-07-31 04:10:25 +08:00
材料统计支持周本材料转化后的结果
This commit is contained in:
@@ -76,6 +76,7 @@ internal static class SettingKeys
|
||||
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";
|
||||
public const string CultivationStatisticsWeeklyBossMaterialInterchange = "Snap::Hutao::Cultivation::Statistics::WeeklyBossMaterialInterchange";
|
||||
|
||||
// GachaLog
|
||||
public const string IsEmptyHistoryWishVisible = "Snap::Hutao::GachaLog::HistoryWish::EmptyVisible";
|
||||
|
||||
@@ -2429,6 +2429,9 @@ Space Available: {2}</value>
|
||||
<data name="ViewPageCultivationTalentSynthCritTenPercentLabel" xml:space="preserve">
|
||||
<value>Character talent (10%)</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationWeeklyBossMaterialInterchangeLabel" xml:space="preserve">
|
||||
<value>Weekly boss material interchange</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve">
|
||||
<value>Resin Estimation</value>
|
||||
</data>
|
||||
|
||||
@@ -2480,6 +2480,9 @@
|
||||
<data name="ViewPageCultivationTalentSynthCritTenPercentLabel" xml:space="preserve">
|
||||
<value>角色天赋(10%)</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationWeeklyBossMaterialInterchangeLabel" xml:space="preserve">
|
||||
<value>周本材料转化</value>
|
||||
</data>
|
||||
<data name="ViewPageCultivationMaterialListResinStatisticsLabel" xml:space="preserve">
|
||||
<value>树脂预估</value>
|
||||
</data>
|
||||
|
||||
@@ -23,6 +23,9 @@ internal class CultivationMetadataContext : ICultivationMetadataContext
|
||||
|
||||
public ImmutableDictionary<MaterialId, Combine> ResultMaterialIdCombineMap { get; set; } = default!;
|
||||
|
||||
public ImmutableArray<ImmutableArray<MaterialId>> WeeklyBossMaterialInterchangeGroups { get; set; }
|
||||
= ImmutableArray<ImmutableArray<MaterialId>>.Empty;
|
||||
|
||||
public Item GetAvatarItem(AvatarId avatarId)
|
||||
{
|
||||
return this.GetAvatar(avatarId).GetOrCreateItem();
|
||||
|
||||
@@ -152,6 +152,10 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
}
|
||||
|
||||
CultivationStatisticsSurplusMerge.Apply(resultItems, context, mergeOptions);
|
||||
CultivationStatisticsWeeklyBossInterchange.Apply(
|
||||
resultItems,
|
||||
context.WeeklyBossMaterialInterchangeGroups,
|
||||
mergeOptions.WeeklyBossMaterialInterchange);
|
||||
ApplyStatisticsConsumerMenuLines(resultItems, projectId, context, cultivationRepository, token);
|
||||
|
||||
return new(resultItems);
|
||||
|
||||
@@ -3,4 +3,7 @@
|
||||
|
||||
namespace Snap.Hutao.Service.Cultivation;
|
||||
|
||||
internal readonly record struct CultivationStatisticsMergeOptions(bool MergeUpgradeMaterials, bool TalentSynthCritTenPercent);
|
||||
internal readonly record struct CultivationStatisticsMergeOptions(
|
||||
bool MergeUpgradeMaterials,
|
||||
bool TalentSynthCritTenPercent,
|
||||
bool WeeklyBossMaterialInterchange);
|
||||
|
||||
+109
@@ -0,0 +1,109 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.Cultivation;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Service.Cultivation;
|
||||
|
||||
/// <summary>
|
||||
/// 材料统计:同一周本 Boss 材料池内,将超出需求的虚拟持有量 1:1 调配给池内缺口(不计异梦溶媒消耗)。
|
||||
/// </summary>
|
||||
internal static class CultivationStatisticsWeeklyBossInterchange
|
||||
{
|
||||
public static void Apply(
|
||||
Dictionary<uint, StatisticsCultivateItem> items,
|
||||
ImmutableArray<ImmutableArray<MaterialId>> interchangeGroups,
|
||||
bool enabled)
|
||||
{
|
||||
if (!enabled || interchangeGroups.IsDefault || interchangeGroups.IsEmpty)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (ImmutableArray<MaterialId> group in interchangeGroups)
|
||||
{
|
||||
ApplySingleGroup(items, group);
|
||||
}
|
||||
}
|
||||
|
||||
private static void ApplySingleGroup(Dictionary<uint, StatisticsCultivateItem> items, ImmutableArray<MaterialId> group)
|
||||
{
|
||||
List<uint> poolIds = [];
|
||||
foreach (MaterialId mid in group)
|
||||
{
|
||||
uint id = mid;
|
||||
if (items.ContainsKey(id))
|
||||
{
|
||||
poolIds.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
if (poolIds.Count < 2)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Dictionary<uint, uint> virt = new(poolIds.Count);
|
||||
foreach (uint id in poolIds)
|
||||
{
|
||||
StatisticsCultivateItem it = items[id];
|
||||
virt[id] = it.MergeAdjustedCurrent ?? it.Current;
|
||||
}
|
||||
|
||||
while (true)
|
||||
{
|
||||
uint? donor = null;
|
||||
uint maxSurplus = 0U;
|
||||
foreach (uint id in poolIds)
|
||||
{
|
||||
uint v = virt[id];
|
||||
uint need = items[id].Count;
|
||||
if (v > need && v - need > maxSurplus)
|
||||
{
|
||||
maxSurplus = v - need;
|
||||
donor = id;
|
||||
}
|
||||
}
|
||||
|
||||
uint? receiver = null;
|
||||
uint maxDeficit = 0U;
|
||||
foreach (uint id in poolIds)
|
||||
{
|
||||
if (id == donor)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
uint v = virt[id];
|
||||
uint need = items[id].Count;
|
||||
if (v < need && need - v > maxDeficit)
|
||||
{
|
||||
maxDeficit = need - v;
|
||||
receiver = id;
|
||||
}
|
||||
}
|
||||
|
||||
if (donor is null || receiver is null || maxSurplus is 0U || maxDeficit is 0U)
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
virt[donor.Value]--;
|
||||
virt[receiver.Value]++;
|
||||
}
|
||||
|
||||
foreach (uint id in poolIds)
|
||||
{
|
||||
StatisticsCultivateItem it = items[id];
|
||||
uint baseline = it.MergeAdjustedCurrent ?? it.Current;
|
||||
uint finalV = virt[id];
|
||||
if (finalV != baseline)
|
||||
{
|
||||
it.WeeklyBossInterchangeAdjustedCurrent = finalV;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,8 @@ internal interface ICultivationMetadataContext : IMetadataContext,
|
||||
IMetadataDictionaryIdMaterialSource,
|
||||
IMetadataDictionaryIdAvatarSource,
|
||||
IMetadataDictionaryIdWeaponSource,
|
||||
IMetadataDictionaryResultMaterialIdCombineSource
|
||||
IMetadataDictionaryResultMaterialIdCombineSource,
|
||||
IMetadataWeeklyBossMaterialInterchangeGroupsSource
|
||||
{
|
||||
Item GetAvatarItem(AvatarId avatarId);
|
||||
|
||||
|
||||
+138
@@ -0,0 +1,138 @@
|
||||
// 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.Model.Primitive;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Service.Cultivation;
|
||||
|
||||
/// <summary>
|
||||
/// 自 Combine 列表解析周本材料异梦转化互通组:配方为 Type=9、CONVERT、产物×1、材料为「异梦溶媒」+ 另一周本材料各×1。
|
||||
/// </summary>
|
||||
internal static class WeeklyBossMaterialInterchangeGroupsBuilder
|
||||
{
|
||||
/// <summary>异梦溶媒 Id(转化消耗,统计虚拟调配时不扣溶媒,仅利用池内 1:1 等价)。</summary>
|
||||
private const uint DreamSolventMaterialId = 113021U;
|
||||
|
||||
public static ImmutableArray<ImmutableArray<MaterialId>> Build(ImmutableArray<Combine> combines)
|
||||
{
|
||||
Dictionary<MaterialId, HashSet<MaterialId>> adjacency = [];
|
||||
|
||||
foreach (Combine combine in combines)
|
||||
{
|
||||
if (combine.Type is not 9U)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (combine.RecipeType is not RecipeType.RECIPE_TYPE_CONVERT)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (combine.Materials.Length is not 2 || combine.Result.Count is not 1)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
MaterialId resultId = combine.Result.Id;
|
||||
MaterialId? otherMaterial = null;
|
||||
foreach (ref readonly IdCount m in combine.Materials.AsSpan())
|
||||
{
|
||||
if (m.Id == DreamSolventMaterialId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
if (m.Count is not 1)
|
||||
{
|
||||
otherMaterial = null;
|
||||
break;
|
||||
}
|
||||
|
||||
otherMaterial = m.Id;
|
||||
}
|
||||
|
||||
if (otherMaterial is null || otherMaterial.Value == resultId)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
AddUndirectedEdge(adjacency, resultId, otherMaterial.Value);
|
||||
}
|
||||
|
||||
return ToComponents(adjacency);
|
||||
}
|
||||
|
||||
private static void AddUndirectedEdge(Dictionary<MaterialId, HashSet<MaterialId>> adjacency, MaterialId a, MaterialId b)
|
||||
{
|
||||
if (!adjacency.TryGetValue(a, out HashSet<MaterialId>? setA))
|
||||
{
|
||||
setA = [];
|
||||
adjacency[a] = setA;
|
||||
}
|
||||
|
||||
if (!adjacency.TryGetValue(b, out HashSet<MaterialId>? setB))
|
||||
{
|
||||
setB = [];
|
||||
adjacency[b] = setB;
|
||||
}
|
||||
|
||||
_ = setA.Add(b);
|
||||
_ = setB.Add(a);
|
||||
}
|
||||
|
||||
private static ImmutableArray<ImmutableArray<MaterialId>> ToComponents(Dictionary<MaterialId, HashSet<MaterialId>> adjacency)
|
||||
{
|
||||
if (adjacency.Count is 0)
|
||||
{
|
||||
return ImmutableArray<ImmutableArray<MaterialId>>.Empty;
|
||||
}
|
||||
|
||||
HashSet<MaterialId> visited = [];
|
||||
ImmutableArray<ImmutableArray<MaterialId>>.Builder groups = ImmutableArray.CreateBuilder<ImmutableArray<MaterialId>>();
|
||||
|
||||
foreach (MaterialId start in adjacency.Keys)
|
||||
{
|
||||
if (visited.Contains(start))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
List<MaterialId> component = [];
|
||||
Queue<MaterialId> queue = new();
|
||||
queue.Enqueue(start);
|
||||
visited.Add(start);
|
||||
|
||||
while (queue.Count > 0)
|
||||
{
|
||||
MaterialId id = queue.Dequeue();
|
||||
component.Add(id);
|
||||
|
||||
if (!adjacency.TryGetValue(id, out HashSet<MaterialId>? neighbors))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (MaterialId n in neighbors)
|
||||
{
|
||||
if (visited.Add(n))
|
||||
{
|
||||
queue.Enqueue(n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (component.Count >= 2)
|
||||
{
|
||||
component.Sort(MaterialIdComparer.Shared);
|
||||
groups.Add(component.ToImmutableArray());
|
||||
}
|
||||
}
|
||||
|
||||
return groups.ToImmutable();
|
||||
}
|
||||
}
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
|
||||
|
||||
/// <summary>
|
||||
/// 周本 Boss 掉落材料「异梦转化」互通组(由 Combine 元数据解析)。
|
||||
/// </summary>
|
||||
internal interface IMetadataWeeklyBossMaterialInterchangeGroupsSource : IMetadataContext
|
||||
{
|
||||
ImmutableArray<ImmutableArray<MaterialId>> WeeklyBossMaterialInterchangeGroups { get; set; }
|
||||
}
|
||||
+5
@@ -211,6 +211,11 @@ internal static class MetadataServiceContextExtension
|
||||
{
|
||||
dictionaryResultMaterialIdCombineSource.ResultMaterialIdCombineMap = await metadataService.GetResultMaterialIdToCombineMapAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (context is IMetadataWeeklyBossMaterialInterchangeGroupsSource weeklyBossInterchange)
|
||||
{
|
||||
weeklyBossInterchange.WeeklyBossMaterialInterchangeGroups = await metadataService.GetWeeklyBossMaterialInterchangeGroupsAsync(token).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
if (context is IMetadataSupportInitialization supportInitialization)
|
||||
|
||||
+13
@@ -13,6 +13,7 @@ using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Model.Metadata.Tower;
|
||||
using Snap.Hutao.Model.Metadata.Weapon;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.Service.Cultivation;
|
||||
using System.Collections.Immutable;
|
||||
|
||||
namespace Snap.Hutao.Service.Metadata;
|
||||
@@ -229,6 +230,18 @@ internal static class MetadataServiceImmutableDictionaryExtension
|
||||
return result;
|
||||
}
|
||||
|
||||
public async ValueTask<ImmutableArray<ImmutableArray<MaterialId>>> GetWeeklyBossMaterialInterchangeGroupsAsync(CancellationToken token = default)
|
||||
{
|
||||
string cacheKey = $"{nameof(MetadataService)}.Cache.{MetadataFileStrategies.Combine.Name}.WeeklyBossMaterialInterchangeGroups";
|
||||
ImmutableArray<ImmutableArray<MaterialId>>? result = await metadataService.MemoryCache.GetOrCreateAsync(cacheKey, async entry =>
|
||||
{
|
||||
ImmutableArray<Combine> array = await metadataService.FromCacheOrFileAsync<Combine>(MetadataFileStrategies.Combine, token).ConfigureAwait(false);
|
||||
return WeeklyBossMaterialInterchangeGroupsBuilder.Build(array);
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
return result ?? ImmutableArray<ImmutableArray<MaterialId>>.Empty;
|
||||
}
|
||||
|
||||
private ValueTask<ImmutableDictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue>(MetadataFileStrategy strategy, CancellationToken token)
|
||||
where TKey : notnull
|
||||
where TValue : class, IDefaultIdentity<TKey>
|
||||
|
||||
@@ -588,6 +588,12 @@
|
||||
IsEnabled="{Binding MergeUpgradeMaterials}"
|
||||
Label="{shuxm:ResourceString Name=ViewPageCultivationTalentSynthCritTenPercentLabel}"
|
||||
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
|
||||
<AppBarToggleButton
|
||||
Command="{Binding RefreshStatisticsItemsCommand}"
|
||||
Icon="{shuxm:FontIcon Glyph=}"
|
||||
IsChecked="{Binding WeeklyBossMaterialInterchange, Mode=TwoWay}"
|
||||
Label="{shuxm:ResourceString Name=ViewPageCultivationWeeklyBossMaterialInterchangeLabel}"
|
||||
Visibility="{Binding ElementName=MaterialListPivot, Path=SelectedItem.Tag, Converter={StaticResource MaterialListSelectedItemIsStatisticsViewConverter}}"/>
|
||||
<AppBarButton Icon="{shuxm:FontIcon Glyph=}" Label="{shuxm:ResourceString Name=ViewPageCultivationMaterialListResinStatisticsLabel}">
|
||||
<AppBarButton.Flyout>
|
||||
<Flyout FlyoutPresenterStyle="{ThemeResource FlyoutPresenterPadding0Style}" Placement="BottomEdgeAlignedRight">
|
||||
|
||||
@@ -77,6 +77,9 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
[ObservableProperty]
|
||||
public partial bool TalentSynthCritTenPercent { get; set; } = LocalSetting.Get(SettingKeys.CultivationStatisticsTalentSynthCritTenPercent, false);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial bool WeeklyBossMaterialInterchange { get; set; } = LocalSetting.Get(SettingKeys.CultivationStatisticsWeeklyBossMaterialInterchange, false);
|
||||
|
||||
[ObservableProperty]
|
||||
public partial ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get; set; }
|
||||
|
||||
@@ -365,8 +368,10 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
|
||||
bool merge = MergeUpgradeMaterials;
|
||||
bool talentCrit = merge && TalentSynthCritTenPercent;
|
||||
bool weeklyBoss = WeeklyBossMaterialInterchange;
|
||||
LocalSetting.Set(SettingKeys.CultivationStatisticsMergeUpgradeMaterials, merge);
|
||||
LocalSetting.Set(SettingKeys.CultivationStatisticsTalentSynthCritTenPercent, talentCrit);
|
||||
LocalSetting.Set(SettingKeys.CultivationStatisticsWeeklyBossMaterialInterchange, weeklyBoss);
|
||||
|
||||
CancellationToken token = exclusiveTokenProvider.GetNewToken();
|
||||
StatisticsCultivateItemCollection statistics;
|
||||
@@ -374,7 +379,7 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
|
||||
try
|
||||
{
|
||||
statistics = await cultivationService
|
||||
.GetStatisticsCultivateItemCollectionAsync(Projects.CurrentItem, metadataContext, new CultivationStatisticsMergeOptions(merge, talentCrit), token)
|
||||
.GetStatisticsCultivateItemCollectionAsync(Projects.CurrentItem, metadataContext, new CultivationStatisticsMergeOptions(merge, talentCrit, weeklyBoss), token)
|
||||
.ConfigureAwait(false);
|
||||
resinStatistics = await cultivationService.GetResinStatisticsAsync(statistics, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
@@ -35,26 +35,33 @@ internal sealed class StatisticsCultivateItem
|
||||
/// </summary>
|
||||
public uint? MergeAdjustedCurrent { get; set; }
|
||||
|
||||
public uint DisplayCurrent { get => MergeAdjustedCurrent ?? Current; }
|
||||
/// <summary>
|
||||
/// 周本材料异梦转化池内调配后的展示用持有量;未启用时为 <see langword="null"/>。在 <see cref="MergeAdjustedCurrent"/> 之后应用。
|
||||
/// </summary>
|
||||
public uint? WeeklyBossInterchangeAdjustedCurrent { get; set; }
|
||||
|
||||
public uint DisplayCurrent { get => WeeklyBossInterchangeAdjustedCurrent ?? MergeAdjustedCurrent ?? Current; }
|
||||
|
||||
public bool IsFinished { get => DisplayCurrent >= Count; }
|
||||
|
||||
public string FormattedCount { get => $"{DisplayCurrent}/{Count}"; }
|
||||
|
||||
private bool HasStatisticsAdjustedDisplay { get => MergeAdjustedCurrent.HasValue || WeeklyBossInterchangeAdjustedCurrent.HasValue; }
|
||||
|
||||
/// <summary>未启用合并展示链时,使用紧凑 <see cref="FormattedCount"/>。</summary>
|
||||
public bool ShowNonMergeCompactCount { get => !MergeAdjustedCurrent.HasValue; }
|
||||
public bool ShowNonMergeCompactCount { get => !HasStatisticsAdjustedDisplay; }
|
||||
|
||||
/// <summary>已合并但合成前后有效持有量与背包数一致,不显示括号,仅「合并后 / 需求」加空格。</summary>
|
||||
public bool ShowMergeSpacedWithoutParen { get => MergeAdjustedCurrent.HasValue && DisplayCurrent == Current; }
|
||||
public bool ShowMergeSpacedWithoutParen { get => HasStatisticsAdjustedDisplay && DisplayCurrent == Current; }
|
||||
|
||||
/// <summary>合并后有效持有与背包原数不同,显示「合并后 (背包原数)」。</summary>
|
||||
public bool ShowMergeInventoryParen { get => MergeAdjustedCurrent.HasValue && DisplayCurrent != Current; }
|
||||
public bool ShowMergeInventoryParen { get => HasStatisticsAdjustedDisplay && DisplayCurrent != Current; }
|
||||
|
||||
/// <summary>首位合并显示量 > 背包原数时着红色(相对原库存变多)。</summary>
|
||||
public bool MergeDisplayLeadUseRed { get => MergeAdjustedCurrent.HasValue && DisplayCurrent > Current; }
|
||||
public bool MergeDisplayLeadUseRed { get => HasStatisticsAdjustedDisplay && DisplayCurrent > Current; }
|
||||
|
||||
/// <summary>首位合并显示量 < 背包原数时着绿色(相对原库存变少,如低档被向上消耗)。</summary>
|
||||
public bool MergeDisplayLeadUseGreen { get => MergeAdjustedCurrent.HasValue && DisplayCurrent < Current; }
|
||||
public bool MergeDisplayLeadUseGreen { get => HasStatisticsAdjustedDisplay && DisplayCurrent < Current; }
|
||||
|
||||
/// <summary>背包原数,紧接在合并后数字后,如 <c>(44)</c>。</summary>
|
||||
public string RawInventoryParenthetical { get => $"({Current})"; }
|
||||
|
||||
Reference in New Issue
Block a user