diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/CombineResultMaterialIdMapFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/CombineResultMaterialIdMapFactory.cs new file mode 100644 index 0000000..120b7e9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/CombineResultMaterialIdMapFactory.cs @@ -0,0 +1,64 @@ +// 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.Immutable; + +namespace Snap.Hutao.Service.Metadata; + +/// +/// Combine 元数据中多条配方可共享同一产物 Id(例如元素断片既有 3×碎屑合成,也有用其他元素石与尘的转化)。 +/// 构建 MaterialId→Combine 映射时必须优先保留「单材料×3→产物×1」的合成台主链,否则后项覆盖前项会导致养成统计无法向下展开原料。 +/// +internal static class CombineResultMaterialIdMapFactory +{ + public static ImmutableDictionary ToImmutableDictionary(ImmutableArray combines) + { + Dictionary map = []; + + foreach (Combine current in combines) + { + MaterialId key = current.Result.Id; + if (!map.TryGetValue(key, out Combine? existing)) + { + map[key] = current; + continue; + } + + if (ComparePreference(current, existing) > 0) + { + map[key] = current; + } + } + + return map.ToImmutableDictionary(); + } + + /// 正数表示 应取代已有项。 + private static int ComparePreference(Combine incoming, Combine existing) + { + return Score(incoming).CompareTo(Score(existing)); + } + + private static int Score(Combine c) + { + if (c.Materials.Length is 1 && c.Materials[0].Count is 3 && c.Result.Count is 1) + { + return c.RecipeType switch + { + RecipeType.RECIPE_TYPE_COMBINE => 300, + RecipeType.RECIPE_TYPE_CONVERT => 200, + _ => 150, + }; + } + + if (c.RecipeType is RecipeType.RECIPE_TYPE_COMBINE) + { + return 100; + } + + return 0; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceImmutableDictionaryExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceImmutableDictionaryExtension.cs index 0b93ecf..f7ced14 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceImmutableDictionaryExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceImmutableDictionaryExtension.cs @@ -216,12 +216,17 @@ internal static class MetadataServiceImmutableDictionaryExtension token); } - public ValueTask> GetResultMaterialIdToCombineMapAsync(CancellationToken token = default) + public async ValueTask> GetResultMaterialIdToCombineMapAsync(CancellationToken token = default) { - return metadataService.FromCacheAsDictionaryAsync( - MetadataFileStrategies.Combine, - c => c.Result.Id, - token); + string cacheKey = $"{nameof(MetadataService)}.Cache.{MetadataFileStrategies.Combine.Name}.Map.{nameof(MaterialId)}.{nameof(Combine)}.PreferThreeToOne"; + ImmutableDictionary? result = await metadataService.MemoryCache.GetOrCreateAsync(cacheKey, async entry => + { + ImmutableArray array = await metadataService.FromCacheOrFileAsync(MetadataFileStrategies.Combine, token).ConfigureAwait(false); + return CombineResultMaterialIdMapFactory.ToImmutableDictionary(array); + }).ConfigureAwait(false); + + ArgumentNullException.ThrowIfNull(result); + return result; } private ValueTask> FromCacheAsDictionaryAsync(MetadataFileStrategy strategy, CancellationToken token)