From 7cf106ec503b2d3215294c5698c0f2b84d963391 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Wed, 24 Apr 2024 17:12:34 +0800 Subject: [PATCH] avatar info optimization --- .../Core/Diagnostics/MeasureExecutionToken.cs | 4 +- .../Snap.Hutao/Core/IO/StreamReaderWriter.cs | 40 ++++++++ .../Snap.Hutao/Core/Logging/LogArgument.cs | 5 + src/Snap.Hutao/Snap.Hutao/Program.cs | 1 + .../Builder/AvatarViewBuilderExtension.cs | 2 +- .../Factory/Builder/IWeaponViewBuilder.cs | 11 +++ .../Factory/Builder/WeaponViewBuilder.cs | 9 ++ .../Builder/WeaponViewBuilderExtension.cs | 98 +++++++++++++++++++ .../Factory/SummaryAvatarFactory.cs | 61 +++++------- .../Factory/SummaryAvatarProperties.cs | 38 +++---- .../AvatarInfo/Factory/SummaryFactory.cs | 9 +- .../Factory/SummaryFactoryMetadataContext.cs | 3 +- .../AvatarInfo/Factory/SummaryHelper.cs | 15 +-- .../Factory/SummaryReliquaryFactory.cs | 9 +- .../IMetadataDictionaryIdReliquarySource.cs | 12 +++ .../MetadataServiceContextExtension.cs | 5 + .../Service/Metadata/MetadataService.cs | 44 +++++---- .../MetadataServiceDictionaryExtension.cs | 25 ++--- .../AvatarProperty/{Equip.cs => EquipView.cs} | 2 +- .../ViewModel/AvatarProperty/ReliquaryView.cs | 2 +- .../ViewModel/AvatarProperty/WeaponView.cs | 5 +- 21 files changed, 282 insertions(+), 118 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IWeaponViewBuilder.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilder.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilderExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdReliquarySource.cs rename src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/{Equip.cs => EquipView.cs} (91%) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/MeasureExecutionToken.cs b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/MeasureExecutionToken.cs index 4cc4692b..e5fe4cd2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/MeasureExecutionToken.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Diagnostics/MeasureExecutionToken.cs @@ -1,5 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.Logging; + namespace Snap.Hutao.Core.Diagnostics; internal readonly struct MeasureExecutionToken : IDisposable @@ -17,6 +19,6 @@ internal readonly struct MeasureExecutionToken : IDisposable public void Dispose() { - logger.LogDebug("{Caller} toke {Time} ms", callerName, stopwatch.GetElapsedTime().TotalMilliseconds); + logger.LogColorizedDebug(("{Caller} toke {Time} ms", ConsoleColor.Gray), (callerName, ConsoleColor.Yellow), (stopwatch.GetElapsedTime().TotalMilliseconds, ConsoleColor.DarkGreen)); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs new file mode 100644 index 00000000..70690a23 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs @@ -0,0 +1,40 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using System.IO; + +namespace Snap.Hutao.Core.IO; + +internal sealed class StreamReaderWriter : IDisposable +{ + private readonly StreamReader reader; + private readonly StreamWriter writer; + + public StreamReaderWriter(StreamReader reader, StreamWriter writer) + { + this.reader = reader; + this.writer = writer; + } + + public StreamReader Reader { get => reader; } + + public StreamWriter Writer { get => writer; } + + /// + public ValueTask ReadLineAsync(CancellationToken token) + { + return reader.ReadLineAsync(token); + } + + /// + public Task WriteAsync(string value) + { + return writer.WriteAsync(value); + } + + public void Dispose() + { + writer.Dispose(); + reader.Dispose(); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs index 9ab67810..ee2e21d1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Logging/LogArgument.cs @@ -21,6 +21,11 @@ internal readonly struct LogArgument return new(argument); } + public static implicit operator LogArgument(double argument) + { + return new(argument); + } + public static implicit operator LogArgument((object? Argument, ConsoleColor Foreground) tuple) { return new(tuple.Argument, tuple.Foreground); diff --git a/src/Snap.Hutao/Snap.Hutao/Program.cs b/src/Snap.Hutao/Snap.Hutao/Program.cs index 110500f0..8240cc83 100644 --- a/src/Snap.Hutao/Snap.Hutao/Program.cs +++ b/src/Snap.Hutao/Snap.Hutao/Program.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; using WinRT; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs index 03ab9f7e..30c5806d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; internal static class AvatarViewBuilderExtension { - public static TBuilder ApplyCostumeIconOrDefault(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar) + public static TBuilder SetCostumeIconOrDefault(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar) where TBuilder : IAvatarViewBuilder { if (avatarInfo.CostumeId.TryGetValue(out CostumeId id)) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IWeaponViewBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IWeaponViewBuilder.cs new file mode 100644 index 00000000..d3eaf724 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IWeaponViewBuilder.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; + +namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; + +internal interface IWeaponViewBuilder : IBuilder +{ + ViewModel.AvatarProperty.WeaponView WeaponView { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilder.cs new file mode 100644 index 00000000..b1e099e6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilder.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; + +internal sealed class WeaponViewBuilder : IWeaponViewBuilder +{ + public ViewModel.AvatarProperty.WeaponView WeaponView { get; } = new(); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilderExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilderExtension.cs new file mode 100644 index 00000000..152829f8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/WeaponViewBuilderExtension.cs @@ -0,0 +1,98 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction.Extension; +using Snap.Hutao.Model; +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Converter; +using Snap.Hutao.Model.Primitive; +using Snap.Hutao.Web.Enka.Model; + +namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder; + +internal static class WeaponViewBuilderExtension +{ + public static TBuilder SetAffixLevelNumber(this TBuilder builder, uint affixLevelNumber) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.AffixLevelNumber = affixLevelNumber); + } + + public static TBuilder SetAffixDescription(this TBuilder builder, string? affixDescription) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.AffixDescription = affixDescription ?? string.Empty); + } + + public static TBuilder SetAffixName(this TBuilder builder, string affixName) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.AffixName = affixName); + } + + public static TBuilder SetDescription(this TBuilder builder, string description) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Description = description); + } + + public static TBuilder SetIcon(this TBuilder builder, Uri icon) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Icon = icon); + } + + public static TBuilder SetId(this TBuilder builder, WeaponId id) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Id = id); + } + + public static TBuilder SetLevel(this TBuilder builder, string level) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Level = level); + } + + public static TBuilder SetLevelNumber(this TBuilder builder, uint levelNumber) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.LevelNumber = levelNumber); + } + + public static TBuilder SetMainProperty(this TBuilder builder, WeaponStat? mainStat) + where TBuilder : IWeaponViewBuilder + { + return builder.SetMainProperty(mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : NameValueDefaults.String); + } + + public static TBuilder SetMainProperty(this TBuilder builder, NameValue mainProperty) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.MainProperty = mainProperty); + } + + public static TBuilder SetName(this TBuilder builder, string name) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Name = name); + } + + public static TBuilder SetQuality(this TBuilder builder, QualityType quality) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.Quality = quality); + } + + public static TBuilder SetSubProperty(this TBuilder builder, NameDescription subProperty) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.SubProperty = subProperty); + } + + public static TBuilder SetWeaponType(this TBuilder builder, WeaponType weaponType) + where TBuilder : IWeaponViewBuilder + { + return builder.Configure(b => b.WeaponView.WeaponType = weaponType); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs index 1be0eaee..e67cce99 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -6,15 +6,13 @@ using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic.Format; using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Service.AvatarInfo.Factory.Builder; +using Snap.Hutao.ViewModel.AvatarProperty; using Snap.Hutao.Web.Enka.Model; using System.Runtime.InteropServices; using EntityAvatarInfo = Snap.Hutao.Model.Entity.AvatarInfo; using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon; using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; -using PropertyAvatar = Snap.Hutao.ViewModel.AvatarProperty.AvatarView; -using PropertyReliquary = Snap.Hutao.ViewModel.AvatarProperty.ReliquaryView; -using PropertyWeapon = Snap.Hutao.ViewModel.AvatarProperty.WeaponView; namespace Snap.Hutao.Service.AvatarInfo.Factory; @@ -37,17 +35,17 @@ internal sealed class SummaryAvatarFactory calculatorRefreshTime = avatarInfo.CalculatorRefreshTime; } - public static PropertyAvatar Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo) + public static AvatarView Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo) { return new SummaryAvatarFactory(context, avatarInfo).Create(); } - public PropertyAvatar Create() + public AvatarView Create() { ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull()); MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId]; - PropertyAvatar propertyAvatar = new AvatarViewBuilder() + AvatarView propertyAvatar = new AvatarViewBuilder() .SetId(avatar.Id) .SetName(avatar.Name) .SetQuality(avatar.Quality) @@ -65,7 +63,7 @@ internal sealed class SummaryAvatarFactory .SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed) .SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed) .SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed) - .ApplyCostumeIconOrDefault(avatarInfo, avatar) + .SetCostumeIconOrDefault(avatarInfo, avatar) .AvatarView; return propertyAvatar; @@ -73,8 +71,8 @@ internal sealed class SummaryAvatarFactory private ReliquaryAndWeapon ProcessEquip(List equipments) { - List reliquaryList = []; - PropertyWeapon? weapon = null; + List reliquaryList = []; + WeaponView? weapon = null; foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments)) { @@ -92,7 +90,7 @@ internal sealed class SummaryAvatarFactory return new(reliquaryList, weapon); } - private PropertyWeapon CreateWeapon(Equip equip) + private WeaponView CreateWeapon(Equip equip) { MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId]; @@ -118,36 +116,29 @@ internal sealed class SummaryAvatarFactory ArgumentNullException.ThrowIfNull(equip.Weapon); - return new() - { - // NameIconDescription - Name = weapon.Name, - Icon = EquipIconConverter.IconNameToUri(weapon.Icon), - Description = weapon.Description, - - // EquipBase - Level = $"Lv.{equip.Weapon.Level.Value}", - Quality = weapon.Quality, - MainProperty = mainStat is not null - ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) - : NameValueDefaults.String, - - // Weapon - Id = weapon.Id, - LevelNumber = equip.Weapon.Level, - SubProperty = subProperty, - AffixLevelNumber = affixLevel + 1, - AffixName = weapon.Affix?.Name ?? string.Empty, - AffixDescription = weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description ?? string.Empty, - }; + return new WeaponViewBuilder() + .SetName(weapon.Name) + .SetIcon(EquipIconConverter.IconNameToUri(weapon.Icon)) + .SetDescription(weapon.Description) + .SetLevel($"Lv.{equip.Weapon.Level.Value}") + .SetQuality(weapon.Quality) + .SetMainProperty(mainStat) + .SetId(weapon.Id) + .SetLevelNumber(equip.Weapon.Level) + .SetSubProperty(subProperty) + .SetAffixLevelNumber(affixLevel + 1) + .SetAffixName(weapon.Affix?.Name ?? string.Empty) + .SetAffixDescription(weapon.Affix?.Descriptions.Single(a => a.Level == affixLevel).Description) + .SetWeaponType(weapon.WeaponType) + .WeaponView; } private readonly struct ReliquaryAndWeapon { - public readonly List Reliquaries; - public readonly PropertyWeapon? Weapon; + public readonly List Reliquaries; + public readonly WeaponView? Weapon; - public ReliquaryAndWeapon(List reliquaries, PropertyWeapon? weapon) + public ReliquaryAndWeapon(List reliquaries, WeaponView? weapon) { Reliquaries = reliquaries; Weapon = weapon; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs index 0df0cc30..5b2e54dd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarProperties.cs @@ -13,11 +13,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; [HighQuality] internal static class SummaryAvatarProperties { - /// - /// 创建角色属性 - /// - /// 属性映射 - /// 列表 public static List Create(Dictionary? fightPropMap) { if (fightPropMap is null) @@ -25,32 +20,27 @@ internal static class SummaryAvatarProperties return []; } - AvatarProperty hpProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap); - AvatarProperty atkProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap); - AvatarProperty defProp = ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap); - AvatarProperty emProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap); - AvatarProperty critRateProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap); - AvatarProperty critDMGProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap); - AvatarProperty chargeEffProp = FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap); - - List properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp }; + List properties = + [ + ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_HP, fightPropMap), + ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_ATTACK, fightPropMap), + ToAvatarProperty(FightProperty.FIGHT_PROP_BASE_DEFENSE, fightPropMap), + FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_ELEMENT_MASTERY, fightPropMap), + FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL, fightPropMap), + FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CRITICAL_HURT, fightPropMap), + FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY, fightPropMap) + ]; // 元素伤害 - if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value)) + if (TryGetBonusFightProperty(fightPropMap, out FightProperty bonusProperty, out float value) && value > 0) { - if (value > 0) - { - properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value)); - } + properties.Add(FightPropertyFormat.ToAvatarProperty(bonusProperty, value)); } // 物伤 可以和其他元素伤害并存,所以分别判断 - if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue)) + if (fightPropMap.TryGetValue(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, out float addValue) && addValue > 0) { - if (addValue > 0) - { - properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue)); - } + properties.Add(FightPropertyFormat.ToAvatarProperty(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, addValue)); } return properties; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs index a0c5ce88..7ff774a5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -28,11 +28,12 @@ internal sealed partial class SummaryFactory : ISummaryFactory IOrderedEnumerable avatars = avatarInfos .Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId)) .Select(a => SummaryAvatarFactory.Create(context, a)) - .OrderByDescending(a => a.LevelNumber) - .ThenByDescending(a => a.FetterLevel) - .ThenBy(a => a.Element); + .OrderByDescending(a => a.Quality) + .ThenByDescending(a => a.LevelNumber) + .ThenBy(a => a.Element) + .ThenBy(a => a.Weapon?.WeaponType) + .ThenByDescending(a => a.FetterLevel); - // TODO: thenby weapon type return new() { Avatars = [.. avatars], diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs index df3040e7..6434f797 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs @@ -18,6 +18,7 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext, IMetadataDictionaryIdReliquaryAffixWeightSource, IMetadataDictionaryIdReliquaryMainPropertySource, IMetadataDictionaryIdReliquarySubAffixSource, + IMetadataDictionaryIdReliquarySource, IMetadataListReliquaryMainAffixLevelSource { public Dictionary IdAvatarMap { get; set; } = default!; @@ -32,5 +33,5 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext, public List ReliquaryMainAffixLevels { get; set; } = default!; - public List Reliquaries { get; set; } = default!; + public Dictionary IdReliquaryMap { get; set; } = default!; } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs index 6039d8fd..98e5eeca 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs @@ -1,6 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model.Primitive; namespace Snap.Hutao.Service.AvatarInfo.Factory; @@ -11,11 +12,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; [HighQuality] internal static class SummaryHelper { - /// - /// 获取副属性对应的最大属性的Id - /// - /// 属性Id - /// 最大属性Id public static ReliquarySubAffixId GetAffixMaxId(in ReliquarySubAffixId appendId) { // axxxxx -> a @@ -27,18 +23,13 @@ internal static class SummaryHelper 1 => 2, 2 => 3, 3 or 4 or 5 => 4, - _ => throw Must.NeverHappen(), + _ => throw HutaoException.Throw($"不支持的 ReliquarySubAffixId: {appendId}"), }; // axxxxb -> axxxx -> axxxx0 -> axxxxm return ((appendId / 10) * 10) + max; } - /// - /// 获取百分比属性副词条分数 - /// - /// id - /// 分数 public static float GetPercentSubAffixScore(in ReliquarySubAffixId appendId) { // 圣遗物相同类型副词条强化档位一共为 4/3/2 档 @@ -65,7 +56,7 @@ internal static class SummaryHelper (1, 0) => 100F, (1, 1) => 80F, - _ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"), + _ => throw HutaoException.Throw($"Unexpected AppendId: {appendId} Delta: {delta}"), }; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs index ebb4129a..3998abd5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs @@ -13,9 +13,6 @@ using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; namespace Snap.Hutao.Service.AvatarInfo.Factory; -/// -/// 圣遗物工厂 -/// [HighQuality] internal sealed class SummaryReliquaryFactory { @@ -37,7 +34,7 @@ internal sealed class SummaryReliquaryFactory public ReliquaryView Create() { - MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId)); + MetadataReliquary reliquary = metadataContext.IdReliquaryMap[equip.ItemId]; ArgumentNullException.ThrowIfNull(equip.Reliquary); List subProperty = equip.Reliquary.AppendPropIdList.EmptyIfNull().SelectList(CreateSubProperty); @@ -74,10 +71,10 @@ internal sealed class SummaryReliquaryFactory return result; } - private static int GetSecondaryAffixCount(MetadataReliquary reliquary, Web.Enka.Model.Reliquary enkaReliquary) + private static int GetSecondaryAffixCount(MetadataReliquary metaReliquary, Web.Enka.Model.Reliquary enkaReliquary) { // 强化词条个数 - return (reliquary.RankLevel, enkaReliquary.Level.Value) switch + return (metaReliquary.RankLevel, enkaReliquary.Level.Value) switch { (QualityType.QUALITY_ORANGE, > 20U) => 5, (QualityType.QUALITY_ORANGE, > 16U) => 4, diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdReliquarySource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdReliquarySource.cs new file mode 100644 index 00000000..fb504da9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdReliquarySource.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Reliquary; +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdReliquarySource +{ + public Dictionary IdReliquaryMap { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs index d50f85ac..8ec47d27 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -62,6 +62,11 @@ internal static class MetadataServiceContextExtension dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); } + if (context is IMetadataDictionaryIdReliquarySource dictionaryIdReliquarySource) + { + dictionaryIdReliquarySource.IdReliquaryMap = await metadataService.GetIdToReliquaryMapAsync(token).ConfigureAwait(false); + } + if (context is IMetadataDictionaryIdReliquaryAffixWeightSource dictionaryIdReliquaryAffixWeightSource) { dictionaryIdReliquaryAffixWeightSource.IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs index 43f6e26c..75454d80 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.cs @@ -6,6 +6,7 @@ using Snap.Hutao.Core; using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient; using Snap.Hutao.Core.Diagnostics; using Snap.Hutao.Core.ExceptionService; +using Snap.Hutao.Core.IO; using Snap.Hutao.Core.IO.Hashing; using Snap.Hutao.Core.Setting; using Snap.Hutao.Service.Notification; @@ -29,7 +30,7 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi private readonly TaskCompletionSource initializeCompletionSource = new(); - private readonly IHttpClientFactory httpClientFactory; + private readonly IServiceScopeFactory serviceScopeFactory; private readonly ILogger logger; private readonly MetadataOptions metadataOptions; private readonly IInfoBarService infoBarService; @@ -119,12 +120,16 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi Dictionary? metadataFileHashs; try { - // Download meta check file - using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService))) + using (IServiceScope scope = serviceScopeFactory.CreateScope()) { - metadataFileHashs = await httpClient - .GetFromJsonAsync>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token) - .ConfigureAwait(false); + IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService(); + using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService))) + { + // Download meta check file + metadataFileHashs = await httpClient + .GetFromJsonAsync>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token) + .ConfigureAwait(false); + } } if (metadataFileHashs is null) @@ -180,26 +185,27 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi private async ValueTask DownloadMetadataSourceFilesAsync(string fileFullName, CancellationToken token) { Stream sourceStream; - using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService))) + using (IServiceScope scope = serviceScopeFactory.CreateScope()) { - sourceStream = await httpClient - .GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token) - .ConfigureAwait(false); + IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService(); + using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService))) + { + sourceStream = await httpClient + .GetStreamAsync(metadataOptions.GetLocalizedRemoteFile(fileFullName), token) + .ConfigureAwait(false); + } } // Write stream while convert LF to CRLF - using (StreamReader streamReader = new(sourceStream)) + using (StreamReaderWriter readerWriter = new(new(sourceStream), File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName)))) { - using (StreamWriter streamWriter = File.CreateText(metadataOptions.GetLocalizedLocalFile(fileFullName))) + while (await readerWriter.ReadLineAsync(token).ConfigureAwait(false) is { } line) { - while (await streamReader.ReadLineAsync(token).ConfigureAwait(false) is { } line) - { - await streamWriter.WriteAsync(line).ConfigureAwait(false); + await readerWriter.WriteAsync(line).ConfigureAwait(false); - if (!streamReader.EndOfStream) - { - await streamWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false); - } + if (!readerWriter.Reader.EndOfStream) + { + await readerWriter.WriteAsync(StringLiterals.CRLF).ConfigureAwait(false); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs index b7720d1e..364808a5 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceDictionaryExtension.cs @@ -25,22 +25,12 @@ internal static class MetadataServiceDictionaryExtension public static ValueTask> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default) { - return metadataService.FromCacheAsDictionaryAsync( - FileNameReliquarySet, - list => list.SelectMany(set => set.EquipAffixIds.Select(id => (Id: id, Set: set))), - tuple => tuple.Id, - tuple => tuple.Set, - token); + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquarySet, (List list) => list.SelectMany(set => set.EquipAffixIds, (set, id) => (Id: id, Set: set)), token); } public static ValueTask>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default) { - return metadataService.FromCacheAsDictionaryAsync, TowerLevelGroupId, List>( - FileNameTowerLevel, - list => list.GroupBy(l => l.GroupId), - g => g.Key, - g => g.ToList(), - token); + return metadataService.FromCacheAsDictionaryAsync(FileNameTowerLevel, (List list) => list.GroupBy(l => l.GroupId), g => g.Key, g => g.ToList(), token); } public static ValueTask> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default) @@ -81,6 +71,11 @@ internal static class MetadataServiceDictionaryExtension return metadataService.FromCacheAsDictionaryAsync(FileNameMaterial, a => a.Id, token); } + public static ValueTask> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default) + { + return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token); + } + public static ValueTask> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default) { return metadataService.FromCacheAsDictionaryAsync(FileNameReliquaryAffixWeight, r => r.AvatarId, token); @@ -177,6 +172,12 @@ internal static class MetadataServiceDictionaryExtension return metadataService.MemoryCache.Set(cacheKey, dict); } + private static ValueTask> FromCacheAsDictionaryAsync(this IMetadataService metadataService, string fileName, Func, IEnumerable<(TKey Key, TValue Value)>> listSelector, CancellationToken token) + where TKey : notnull + { + return FromCacheAsDictionaryAsync(metadataService, fileName, listSelector, kvp => kvp.Key, kvp => kvp.Value, token); + } + private static async ValueTask> FromCacheAsDictionaryAsync(this IMetadataService metadataService, string fileName, Func, IEnumerable> listSelector, Func keySelector, Func valueSelector, CancellationToken token) where TKey : notnull { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/Equip.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/EquipView.cs similarity index 91% rename from src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/Equip.cs rename to src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/EquipView.cs index e797f7db..708045d0 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/Equip.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/EquipView.cs @@ -10,7 +10,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty; /// 装备基类 /// [HighQuality] -internal abstract class Equip : NameIconDescription +internal abstract class EquipView : NameIconDescription { /// /// 等级 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/ReliquaryView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/ReliquaryView.cs index 3d14a34a..c8a2dc24 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/ReliquaryView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/ReliquaryView.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty; /// 圣遗物 /// [HighQuality] -internal sealed class ReliquaryView : Equip +internal sealed class ReliquaryView : EquipView { /// /// 初始词条 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs index 495b0b9d..08a59244 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/WeaponView.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Model; using Snap.Hutao.Model.Calculable; +using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Primitive; namespace Snap.Hutao.ViewModel.AvatarProperty; @@ -11,7 +12,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty; /// 武器 /// [HighQuality] -internal sealed class WeaponView : Equip, ICalculableSource +internal sealed class WeaponView : EquipView, ICalculableSource { /// /// 副属性 @@ -53,6 +54,8 @@ internal sealed class WeaponView : Equip, ICalculableSource /// internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); } + internal WeaponType WeaponType { get; set; } + /// public ICalculableWeapon ToCalculable() {