From e390ad28392254d3fd67a30209e4e06f88498488 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Fri, 19 Apr 2024 15:21:59 +0800 Subject: [PATCH] AvatarViewBuilder --- .../Converter/AvatarNameCardPicConverter.cs | 5 - .../Factory/Builder/AvatarViewBuilder.cs | 9 + .../Builder/AvatarViewBuilderExtension.cs | 260 ++++++++++++++++++ .../Factory/Builder/IAvatarViewBuilder.cs | 11 + .../Factory/SummaryAvatarFactory.cs | 102 +++---- .../AvatarInfo/Factory/SummaryFactory.cs | 20 +- .../Factory/SummaryFactoryMetadataContext.cs | 5 +- .../AvatarInfo/Factory/SummaryHelper.cs | 82 ------ .../Factory/SummaryReliquaryFactory.cs | 4 +- ...tadataListReliquaryMainAffixLevelSource.cs | 11 + .../IMetadataListReliquarySource.cs | 11 + .../MetadataServiceContextExtension.cs | 10 + .../Metadata/MetadataServiceListExtension.cs | 2 +- 13 files changed, 363 insertions(+), 169 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilder.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IAvatarViewBuilder.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquaryMainAffixLevelSource.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquarySource.cs diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarNameCardPicConverter.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarNameCardPicConverter.cs index 1925b8e0..99a5da2f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarNameCardPicConverter.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Converter/AvatarNameCardPicConverter.cs @@ -11,11 +11,6 @@ namespace Snap.Hutao.Model.Metadata.Converter; [HighQuality] internal sealed class AvatarNameCardPicConverter : ValueConverter { - /// - /// 从角色转换到名片 - /// - /// 角色 - /// 名片 public static Uri AvatarToUri(Avatar.Avatar? avatar) { if (avatar is null) diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilder.cs new file mode 100644 index 00000000..bb9a2004 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilder.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 AvatarViewBuilder : IAvatarViewBuilder +{ + public ViewModel.AvatarProperty.AvatarView AvatarView { get; } = new(); +} \ No newline at end of file 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 new file mode 100644 index 00000000..03ab9f7e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/AvatarViewBuilderExtension.cs @@ -0,0 +1,260 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction.Extension; +using Snap.Hutao.Model.Intrinsic; +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Converter; +using Snap.Hutao.Model.Primitive; +using Snap.Hutao.ViewModel.AvatarProperty; + +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) + where TBuilder : IAvatarViewBuilder + { + if (avatarInfo.CostumeId.TryGetValue(out CostumeId id)) + { + Costume costume = avatar.Costumes.Single(c => c.Id == id); + + // Set to costume icon + builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon); + builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon); + } + else + { + builder.AvatarView.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon); + builder.AvatarView.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon); + } + + return builder; + } + + public static TBuilder SetCalculatorRefreshTimeFormat(this TBuilder builder, DateTimeOffset refreshTime, Func format, string defaultValue) + where TBuilder : IAvatarViewBuilder + { + return builder.SetCalculatorRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime())); + } + + public static TBuilder SetCalculatorRefreshTimeFormat(this TBuilder builder, string calculatorRefreshTimeFormat) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.CalculatorRefreshTimeFormat = calculatorRefreshTimeFormat); + } + + public static TBuilder SetConstellations(this TBuilder builder, List talents, List? talentIds) + where TBuilder : IAvatarViewBuilder + { + return builder.SetConstellations(CreateConstellations(talents, talentIds.EmptyIfNull())); + + static List CreateConstellations(List talents, List talentIds) + { + // TODO: use builder here + return talents.SelectList(talent => new ViewModel.AvatarProperty.ConstellationView() + { + Name = talent.Name, + Icon = SkillIconConverter.IconNameToUri(talent.Icon), + Description = talent.Description, + IsActivated = talentIds.Contains(talent.Id), + }); + } + } + + public static TBuilder SetConstellations(this TBuilder builder, List constellations) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Constellations = constellations); + } + + public static TBuilder SetCritScore(this TBuilder builder, Dictionary? fightPropMap) + where TBuilder : IAvatarViewBuilder + { + return builder.SetCritScore(ScoreCrit(fightPropMap)); + + static float ScoreCrit(Dictionary? fightPropMap) + { + if (fightPropMap.IsNullOrEmpty()) + { + return 0F; + } + + float cr = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL]; + float cd = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL_HURT]; + + return 100 * ((cr * 2) + cd); + } + } + + public static TBuilder SetCritScore(this TBuilder builder, float critScore) + where TBuilder : IAvatarViewBuilder + { + return builder.SetCritScore($"{critScore:F2}"); + } + + public static TBuilder SetCritScore(this TBuilder builder, string critScore) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.CritScore = critScore); + } + + public static TBuilder SetElement(this TBuilder builder, ElementType element) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Element = element); + } + + public static TBuilder SetFetterLevel(this TBuilder builder, FetterLevel? level) + where TBuilder : IAvatarViewBuilder + { + if (level.TryGetValue(out FetterLevel value)) + { + return builder.Configure(b => b.AvatarView.FetterLevel = value); + } + + return builder; + } + + public static TBuilder SetFetterLevel(this TBuilder builder, uint level) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.FetterLevel = level); + } + + public static TBuilder SetGameRecordRefreshTimeFormat(this TBuilder builder, DateTimeOffset refreshTime, Func format, string defaultValue) + where TBuilder : IAvatarViewBuilder + { + return builder.SetGameRecordRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime())); + } + + public static TBuilder SetGameRecordRefreshTimeFormat(this TBuilder builder, string gameRecordRefreshTimeFormat) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.GameRecordRefreshTimeFormat = gameRecordRefreshTimeFormat); + } + + public static TBuilder SetId(this TBuilder builder, AvatarId id) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Id = id); + } + + public static TBuilder SetLevelNumber(this TBuilder builder, uint? levelNumber) + where TBuilder : IAvatarViewBuilder + { + if (levelNumber.TryGetValue(out uint value)) + { + return builder.Configure(b => b.AvatarView.LevelNumber = value); + } + + return builder; + } + + public static TBuilder SetName(this TBuilder builder, string name) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Name = name); + } + + public static TBuilder SetNameCard(this TBuilder builder, Uri nameCard) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.NameCard = nameCard); + } + + public static TBuilder SetProperties(this TBuilder builder, List properties) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Properties = properties); + } + + public static TBuilder SetQuality(this TBuilder builder, QualityType quality) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Quality = quality); + } + + public static TBuilder SetReliquaries(this TBuilder builder, List reliquaries) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Reliquaries = reliquaries); + } + + public static TBuilder SetScore(this TBuilder builder, float score) + where TBuilder : IAvatarViewBuilder + { + return builder.SetScore($"{score:F2}"); + } + + public static TBuilder SetScore(this TBuilder builder, string score) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Score = score); + } + + public static TBuilder SetShowcaseRefreshTimeFormat(this TBuilder builder, DateTimeOffset refreshTime, Func format, string defaultValue) + where TBuilder : IAvatarViewBuilder + { + return builder.SetShowcaseRefreshTimeFormat(refreshTime == DateTimeOffsetExtension.DatebaseDefaultTime ? defaultValue : format(refreshTime.ToLocalTime())); + } + + public static TBuilder SetShowcaseRefreshTimeFormat(this TBuilder builder, string showcaseRefreshTimeFormat) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.ShowcaseRefreshTimeFormat = showcaseRefreshTimeFormat); + } + + public static TBuilder SetSkills(this TBuilder builder, Dictionary? skillLevelMap, Dictionary? proudSkillExtraLevelMap, List proudSkills) + where TBuilder : IAvatarViewBuilder + { + return builder.SetSkills(CreateSkills(skillLevelMap, proudSkillExtraLevelMap, proudSkills)); + + static List CreateSkills(Dictionary? skillLevelMap, Dictionary? proudSkillExtraLevelMap, List proudSkills) + { + if (skillLevelMap.IsNullOrEmpty()) + { + return []; + } + + Dictionary skillExtraLeveledMap = new(skillLevelMap); + + if (proudSkillExtraLevelMap is not null) + { + foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap) + { + skillExtraLeveledMap.IncreaseValue(proudSkills.Single(p => p.GroupId == groupId).Id, extraLevel); + } + } + + return proudSkills.SelectList(proudableSkill => + { + SkillId skillId = proudableSkill.Id; + + // TODO: use builder here + return new SkillView() + { + Name = proudableSkill.Name, + Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon), + Description = proudableSkill.Description, + + GroupId = proudableSkill.GroupId, + LevelNumber = skillLevelMap[skillId], + Info = DescriptionsParametersDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[skillId]), + }; + }); + } + } + + public static TBuilder SetSkills(this TBuilder builder, List skills) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Skills = skills); + } + + public static TBuilder SetWeapon(this TBuilder builder, WeaponView? weapon) + where TBuilder : IAvatarViewBuilder + { + return builder.Configure(b => b.AvatarView.Weapon = weapon); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IAvatarViewBuilder.cs b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IAvatarViewBuilder.cs new file mode 100644 index 00000000..f7c70c5b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/Builder/IAvatarViewBuilder.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 IAvatarViewBuilder : IBuilder +{ + ViewModel.AvatarProperty.AvatarView AvatarView { get; } +} \ 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 accfb2c2..1f261894 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryAvatarFactory.cs @@ -6,7 +6,9 @@ using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic.Format; using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Primitive; +using Snap.Hutao.Service.AvatarInfo.Factory.Builder; 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; @@ -24,11 +26,11 @@ internal sealed class SummaryAvatarFactory private readonly DateTimeOffset showcaseRefreshTime; private readonly DateTimeOffset gameRecordRefreshTime; private readonly DateTimeOffset calculatorRefreshTime; - private readonly SummaryFactoryMetadataContext metadataContext; + private readonly SummaryFactoryMetadataContext context; - public SummaryAvatarFactory(SummaryFactoryMetadataContext metadataContext, EntityAvatarInfo avatarInfo) + public SummaryAvatarFactory(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo) { - this.metadataContext = metadataContext; + this.context = context; this.avatarInfo = avatarInfo.Info; showcaseRefreshTime = avatarInfo.ShowcaseRefreshTime; @@ -36,83 +38,51 @@ internal sealed class SummaryAvatarFactory calculatorRefreshTime = avatarInfo.CalculatorRefreshTime; } - /// - /// 创建角色 - /// - /// 角色 + public static PropertyAvatar Create(SummaryFactoryMetadataContext context, EntityAvatarInfo avatarInfo) + { + return new SummaryAvatarFactory(context, avatarInfo).Create(); + } + public PropertyAvatar Create() { ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList.EmptyIfNull()); - MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId]; + MetadataAvatar avatar = context.IdAvatarMap[avatarInfo.AvatarId]; - PropertyAvatar propertyAvatar = new() - { - // metadata part - Id = avatar.Id, - Name = avatar.Name, - Quality = avatar.Quality, - NameCard = AvatarNameCardPicConverter.AvatarToUri(avatar), - Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore), + PropertyAvatar propertyAvatar = new AvatarViewBuilder() + .SetId(avatar.Id) + .SetName(avatar.Name) + .SetQuality(avatar.Quality) + .SetNameCard(AvatarNameCardPicConverter.AvatarToUri(avatar)) + .SetElement(ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore)) + .SetConstellations(avatar.SkillDepot.Talents, avatarInfo.TalentIdList) + .SetSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.CompositeSkillsNoInherents()) + .SetFetterLevel(avatarInfo.FetterInfo?.ExpLevel) + .SetProperties(SummaryAvatarProperties.Create(avatarInfo.FightPropMap)) + .SetCritScore(avatarInfo.FightPropMap) + .SetLevelNumber(avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value) + .SetWeapon(reliquaryAndWeapon.Weapon) + .SetReliquaries(reliquaryAndWeapon.Reliquaries) + .SetScore(reliquaryAndWeapon.Reliquaries.Sum(r => r.Score)) + .SetShowcaseRefreshTimeFormat(showcaseRefreshTime, SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat, SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed) + .SetGameRecordRefreshTimeFormat(gameRecordRefreshTime, SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat, SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed) + .SetCalculatorRefreshTimeFormat(calculatorRefreshTime, SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat, SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed) + .ApplyCostumeIconOrDefault(avatarInfo, avatar) + .AvatarView; - // webinfo & metadata mixed part - Constellations = SummaryHelper.CreateConstellations(avatar.SkillDepot.Talents, avatarInfo.TalentIdList), - Skills = SummaryHelper.CreateSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.CompositeSkillsNoInherents()), - - // webinfo part - FetterLevel = avatarInfo.FetterInfo?.ExpLevel ?? 0U, - Properties = SummaryAvatarProperties.Create(avatarInfo.FightPropMap), - CritScore = $"{SummaryHelper.ScoreCrit(avatarInfo.FightPropMap):F2}", - LevelNumber = avatarInfo.PropMap?[PlayerProperty.PROP_LEVEL].Value ?? 0U, - - // processed webinfo part - Weapon = reliquaryAndWeapon.Weapon, - Reliquaries = reliquaryAndWeapon.Reliquaries, - Score = $"{reliquaryAndWeapon.Reliquaries.Sum(r => r.Score):F2}", - - // times - ShowcaseRefreshTimeFormat = showcaseRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime - ? SH.ServiceAvatarInfoSummaryShowcaseNotRefreshed - : SH.FormatServiceAvatarInfoSummaryShowcaseRefreshTimeFormat(showcaseRefreshTime.ToLocalTime()), - GameRecordRefreshTimeFormat = gameRecordRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime - ? SH.ServiceAvatarInfoSummaryGameRecordNotRefreshed - : SH.FormatServiceAvatarInfoSummaryGameRecordRefreshTimeFormat(gameRecordRefreshTime.ToLocalTime()), - CalculatorRefreshTimeFormat = calculatorRefreshTime == DateTimeOffsetExtension.DatebaseDefaultTime - ? SH.ServiceAvatarInfoSummaryCalculatorNotRefreshed - : SH.FormatServiceAvatarInfoSummaryCalculatorRefreshTimeFormat(calculatorRefreshTime.ToLocalTime()), - }; - - ApplyCostumeIconOrDefault(ref propertyAvatar, avatar); return propertyAvatar; } - private void ApplyCostumeIconOrDefault(ref PropertyAvatar propertyAvatar, MetadataAvatar avatar) - { - if (avatarInfo.CostumeId.TryGetValue(out CostumeId id)) - { - Model.Metadata.Avatar.Costume costume = avatar.Costumes.Single(c => c.Id == id); - - // Set to costume icon - propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(costume.FrontIcon); - propertyAvatar.SideIcon = AvatarIconConverter.IconNameToUri(costume.SideIcon); - } - else - { - propertyAvatar.Icon = AvatarIconConverter.IconNameToUri(avatar.Icon); - propertyAvatar.SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon); - } - } - private ReliquaryAndWeapon ProcessEquip(List equipments) { List reliquaryList = []; PropertyWeapon? weapon = null; - foreach (Equip equip in equipments) + foreach (ref readonly Equip equip in CollectionsMarshal.AsSpan(equipments)) { switch (equip.Flat.ItemType) { case ItemType.ITEM_RELIQUARY: - SummaryReliquaryFactory summaryReliquaryFactory = new(metadataContext, avatarInfo, equip); + SummaryReliquaryFactory summaryReliquaryFactory = new(context, avatarInfo, equip); reliquaryList.Add(summaryReliquaryFactory.CreateReliquary()); break; case ItemType.ITEM_WEAPON: @@ -126,7 +96,7 @@ internal sealed class SummaryAvatarFactory private PropertyWeapon CreateWeapon(Equip equip) { - MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId]; + MetadataWeapon weapon = context.IdWeaponMap[equip.ItemId]; // AffixMap can be null when it's a white weapon. ArgumentNullException.ThrowIfNull(equip.Weapon); @@ -160,7 +130,9 @@ internal sealed class SummaryAvatarFactory // EquipBase Level = $"Lv.{equip.Weapon.Level.Value}", Quality = weapon.Quality, - MainProperty = mainStat is not null ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) : NameValueDefaults.String, + MainProperty = mainStat is not null + ? FightPropertyFormat.ToNameValue(mainStat.AppendPropId, mainStat.StatValue) + : NameValueDefaults.String, // Weapon Id = weapon.Id, 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 e4bd7462..a0c5ce88 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactory.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Model.Metadata; using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.Service.AvatarInfo.Factory; @@ -20,23 +21,18 @@ internal sealed partial class SummaryFactory : ISummaryFactory /// public async ValueTask CreateAsync(IEnumerable avatarInfos, CancellationToken token) { - SummaryFactoryMetadataContext metadataContext = new() - { - IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false), - IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false), - IdReliquaryAffixWeightMap = await metadataService.GetIdToReliquaryAffixWeightMapAsync(token).ConfigureAwait(false), - IdReliquaryMainPropertyMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false), - IdReliquarySubAffixMap = await metadataService.GetIdToReliquarySubAffixMapAsync(token).ConfigureAwait(false), - ReliquaryLevels = await metadataService.GetReliquaryLevelListAsync(token).ConfigureAwait(false), - Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false), - }; + SummaryFactoryMetadataContext context = await metadataService + .GetContextAsync(token) + .ConfigureAwait(false); IOrderedEnumerable avatars = avatarInfos .Where(a => !AvatarIds.IsPlayer(a.Info.AvatarId)) - .Select(a => new SummaryAvatarFactory(metadataContext, a).Create()) + .Select(a => SummaryAvatarFactory.Create(context, a)) .OrderByDescending(a => a.LevelNumber) - .ThenBy(a => a.Name); + .ThenByDescending(a => a.FetterLevel) + .ThenBy(a => a.Element); + // 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 c3505403..df3040e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryFactoryMetadataContext.cs @@ -17,7 +17,8 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext, IMetadataDictionaryIdWeaponSource, IMetadataDictionaryIdReliquaryAffixWeightSource, IMetadataDictionaryIdReliquaryMainPropertySource, - IMetadataDictionaryIdReliquarySubAffixSource + IMetadataDictionaryIdReliquarySubAffixSource, + IMetadataListReliquaryMainAffixLevelSource { public Dictionary IdAvatarMap { get; set; } = default!; @@ -29,7 +30,7 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext, public Dictionary IdReliquarySubAffixMap { get; set; } = default!; - public List ReliquaryLevels { get; set; } = default!; + public List ReliquaryMainAffixLevels { get; set; } = default!; public List Reliquaries { 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 dfd73b85..6039d8fd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryHelper.cs @@ -1,11 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Snap.Hutao.Model.Intrinsic; -using Snap.Hutao.Model.Metadata.Avatar; -using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Primitive; -using Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.Service.AvatarInfo.Factory; @@ -15,66 +11,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory; [HighQuality] internal static class SummaryHelper { - /// - /// 创建命之座 - /// - /// 全部命座 - /// 激活的命座列表 - /// 命之座 - public static List CreateConstellations(List talents, List? talentIds) - { - talentIds ??= []; - - return talents.SelectList(talent => new ConstellationView() - { - Name = talent.Name, - Icon = SkillIconConverter.IconNameToUri(talent.Icon), - Description = talent.Description, - IsActivated = talentIds.Contains(talent.Id), - }); - } - - /// - /// 创建技能组 - /// - /// 技能等级映射 - /// 额外提升等级映射 - /// 技能列表 - /// 技能 - public static List CreateSkills(Dictionary? skillLevelMap, Dictionary? proudSkillExtraLevelMap, List proudSkills) - { - if (skillLevelMap.IsNullOrEmpty()) - { - return []; - } - - Dictionary skillExtraLeveledMap = new(skillLevelMap); - - if (proudSkillExtraLevelMap is not null) - { - foreach ((SkillGroupId groupId, SkillLevel extraLevel) in proudSkillExtraLevelMap) - { - skillExtraLeveledMap.IncreaseValue(proudSkills.Single(p => p.GroupId == groupId).Id, extraLevel); - } - } - - return proudSkills.SelectList(proudableSkill => - { - SkillId skillId = proudableSkill.Id; - - return new SkillView() - { - Name = proudableSkill.Name, - Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon), - Description = proudableSkill.Description, - - GroupId = proudableSkill.GroupId, - LevelNumber = skillLevelMap[skillId], - Info = DescriptionsParametersDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[skillId]), - }; - }); - } - /// /// 获取副属性对应的最大属性的Id /// @@ -132,22 +68,4 @@ internal static class SummaryHelper _ => throw Must.NeverHappen($"Unexpected AppendId: {appendId.Value} Delta: {delta}"), }; } - - /// - /// 获取双爆评分 - /// - /// 属性 - /// 评分 - public static float ScoreCrit(Dictionary? fightPropMap) - { - if (fightPropMap.IsNullOrEmpty()) - { - return 0F; - } - - float cr = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL]; - float cd = fightPropMap[FightProperty.FIGHT_PROP_CRITICAL_HURT]; - - return 100 * ((cr * 2) + cd); - } } \ 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 d0d44769..e9b84e8d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/AvatarInfo/Factory/SummaryReliquaryFactory.cs @@ -69,7 +69,7 @@ internal sealed class SummaryReliquaryFactory ArgumentNullException.ThrowIfNull(equip.Flat.ReliquarySubstats); result.ComposedSubProperties = CreateComposedSubProperties(equip.Reliquary.AppendPropIdList); - ReliquaryMainAffixLevel relicLevel = metadataContext.ReliquaryLevels.Single(r => r.Level == equip.Reliquary.Level && r.Rank == reliquary.RankLevel); + ReliquaryMainAffixLevel relicLevel = metadataContext.ReliquaryMainAffixLevels.Single(r => r.Level == equip.Reliquary.Level && r.Rank == reliquary.RankLevel); FightProperty property = metadataContext.IdReliquaryMainPropertyMap[equip.Reliquary.MainPropId]; result.MainProperty = FightPropertyFormat.ToNameValue(property, relicLevel.PropertyMap[property]); @@ -146,7 +146,7 @@ internal sealed class SummaryReliquaryFactory // 从喵插件抓取的圣遗物评分权重 // 部分复杂的角色暂时使用了默认值 ReliquaryAffixWeight affixWeight = metadataContext.IdReliquaryAffixWeightMap.GetValueOrDefault(avatarInfo.AvatarId, ReliquaryAffixWeight.Default); - ReliquaryMainAffixLevel? maxRelicLevel = metadataContext.ReliquaryLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level); + ReliquaryMainAffixLevel? maxRelicLevel = metadataContext.ReliquaryMainAffixLevels.Where(r => r.Rank == reliquary.RankLevel).MaxBy(r => r.Level); ArgumentNullException.ThrowIfNull(maxRelicLevel); float percent = relicLevel.PropertyMap[property] / maxRelicLevel.PropertyMap[property]; diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquaryMainAffixLevelSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquaryMainAffixLevelSource.cs new file mode 100644 index 00000000..16b77f38 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquaryMainAffixLevelSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Reliquary; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataListReliquaryMainAffixLevelSource +{ + public List ReliquaryMainAffixLevels { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquarySource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquarySource.cs new file mode 100644 index 00000000..7b288794 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListReliquarySource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Reliquary; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataListReliquarySource +{ + public List Reliquaries { 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 6ddd8d78..d50f85ac 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -33,6 +33,16 @@ internal static class MetadataServiceContextExtension { listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); } + + if (context is IMetadataListReliquaryMainAffixLevelSource listReliquaryMainAffixLevelSource) + { + listReliquaryMainAffixLevelSource.ReliquaryMainAffixLevels = await metadataService.GetReliquaryMainAffixLevelListAsync(token).ConfigureAwait(false); + } + + if (context is IMetadataListReliquarySource listReliquarySource) + { + listReliquarySource.Reliquaries = await metadataService.GetReliquaryListAsync(token).ConfigureAwait(false); + } } // Dictionary diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs index f6387425..4377b0e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataServiceListExtension.cs @@ -80,7 +80,7 @@ internal static class MetadataServiceListExtension return metadataService.FromCacheOrFileAsync>(FileNameReliquaryMainAffix, token); } - public static ValueTask> GetReliquaryLevelListAsync(this IMetadataService metadataService, CancellationToken token = default) + public static ValueTask> GetReliquaryMainAffixLevelListAsync(this IMetadataService metadataService, CancellationToken token = default) { return metadataService.FromCacheOrFileAsync>(FileNameReliquaryMainAffixLevel, token); }