mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
avatar info optimization
This commit is contained in:
@@ -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));
|
||||
}
|
||||
}
|
||||
40
src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs
Normal file
40
src/Snap.Hutao/Snap.Hutao/Core/IO/StreamReaderWriter.cs
Normal file
@@ -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; }
|
||||
|
||||
/// <inheritdoc cref="StreamReader.ReadLineAsync(CancellationToken)"/>
|
||||
public ValueTask<string?> ReadLineAsync(CancellationToken token)
|
||||
{
|
||||
return reader.ReadLineAsync(token);
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="StreamWriter.WriteAsync(string?)"/>
|
||||
public Task WriteAsync(string value)
|
||||
{
|
||||
return writer.WriteAsync(value);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
writer.Dispose();
|
||||
reader.Dispose();
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -12,7 +12,7 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory.Builder;
|
||||
|
||||
internal static class AvatarViewBuilderExtension
|
||||
{
|
||||
public static TBuilder ApplyCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
|
||||
public static TBuilder SetCostumeIconOrDefault<TBuilder>(this TBuilder builder, Web.Enka.Model.AvatarInfo avatarInfo, Avatar avatar)
|
||||
where TBuilder : IAvatarViewBuilder
|
||||
{
|
||||
if (avatarInfo.CostumeId.TryGetValue(out CostumeId id))
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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<TBuilder>(this TBuilder builder, uint affixLevelNumber)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.AffixLevelNumber = affixLevelNumber);
|
||||
}
|
||||
|
||||
public static TBuilder SetAffixDescription<TBuilder>(this TBuilder builder, string? affixDescription)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.AffixDescription = affixDescription ?? string.Empty);
|
||||
}
|
||||
|
||||
public static TBuilder SetAffixName<TBuilder>(this TBuilder builder, string affixName)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.AffixName = affixName);
|
||||
}
|
||||
|
||||
public static TBuilder SetDescription<TBuilder>(this TBuilder builder, string description)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Description = description);
|
||||
}
|
||||
|
||||
public static TBuilder SetIcon<TBuilder>(this TBuilder builder, Uri icon)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Icon = icon);
|
||||
}
|
||||
|
||||
public static TBuilder SetId<TBuilder>(this TBuilder builder, WeaponId id)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Id = id);
|
||||
}
|
||||
|
||||
public static TBuilder SetLevel<TBuilder>(this TBuilder builder, string level)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Level = level);
|
||||
}
|
||||
|
||||
public static TBuilder SetLevelNumber<TBuilder>(this TBuilder builder, uint levelNumber)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.LevelNumber = levelNumber);
|
||||
}
|
||||
|
||||
public static TBuilder SetMainProperty<TBuilder>(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<TBuilder>(this TBuilder builder, NameValue<string> mainProperty)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.MainProperty = mainProperty);
|
||||
}
|
||||
|
||||
public static TBuilder SetName<TBuilder>(this TBuilder builder, string name)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Name = name);
|
||||
}
|
||||
|
||||
public static TBuilder SetQuality<TBuilder>(this TBuilder builder, QualityType quality)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.Quality = quality);
|
||||
}
|
||||
|
||||
public static TBuilder SetSubProperty<TBuilder>(this TBuilder builder, NameDescription subProperty)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.SubProperty = subProperty);
|
||||
}
|
||||
|
||||
public static TBuilder SetWeaponType<TBuilder>(this TBuilder builder, WeaponType weaponType)
|
||||
where TBuilder : IWeaponViewBuilder
|
||||
{
|
||||
return builder.Configure(b => b.WeaponView.WeaponType = weaponType);
|
||||
}
|
||||
}
|
||||
@@ -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<Equip> equipments)
|
||||
{
|
||||
List<PropertyReliquary> reliquaryList = [];
|
||||
PropertyWeapon? weapon = null;
|
||||
List<ReliquaryView> 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<PropertyReliquary> Reliquaries;
|
||||
public readonly PropertyWeapon? Weapon;
|
||||
public readonly List<ReliquaryView> Reliquaries;
|
||||
public readonly WeaponView? Weapon;
|
||||
|
||||
public ReliquaryAndWeapon(List<PropertyReliquary> reliquaries, PropertyWeapon? weapon)
|
||||
public ReliquaryAndWeapon(List<ReliquaryView> reliquaries, WeaponView? weapon)
|
||||
{
|
||||
Reliquaries = reliquaries;
|
||||
Weapon = weapon;
|
||||
|
||||
@@ -13,11 +13,6 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
[HighQuality]
|
||||
internal static class SummaryAvatarProperties
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建角色属性
|
||||
/// </summary>
|
||||
/// <param name="fightPropMap">属性映射</param>
|
||||
/// <returns>列表</returns>
|
||||
public static List<AvatarProperty> Create(Dictionary<FightProperty, float>? 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<AvatarProperty> properties = new(9) { hpProp, atkProp, defProp, emProp, critRateProp, critDMGProp, chargeEffProp };
|
||||
List<AvatarProperty> 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;
|
||||
|
||||
@@ -28,11 +28,12 @@ internal sealed partial class SummaryFactory : ISummaryFactory
|
||||
IOrderedEnumerable<AvatarView> 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],
|
||||
|
||||
@@ -18,6 +18,7 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
|
||||
IMetadataDictionaryIdReliquaryAffixWeightSource,
|
||||
IMetadataDictionaryIdReliquaryMainPropertySource,
|
||||
IMetadataDictionaryIdReliquarySubAffixSource,
|
||||
IMetadataDictionaryIdReliquarySource,
|
||||
IMetadataListReliquaryMainAffixLevelSource
|
||||
{
|
||||
public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!;
|
||||
@@ -32,5 +33,5 @@ internal sealed class SummaryFactoryMetadataContext : IMetadataContext,
|
||||
|
||||
public List<ReliquaryMainAffixLevel> ReliquaryMainAffixLevels { get; set; } = default!;
|
||||
|
||||
public List<MetadataReliquary> Reliquaries { get; set; } = default!;
|
||||
public Dictionary<ReliquaryId, MetadataReliquary> IdReliquaryMap { get; set; } = default!;
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取副属性对应的最大属性的Id
|
||||
/// </summary>
|
||||
/// <param name="appendId">属性Id</param>
|
||||
/// <returns>最大属性Id</returns>
|
||||
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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取百分比属性副词条分数
|
||||
/// </summary>
|
||||
/// <param name="appendId">id</param>
|
||||
/// <returns>分数</returns>
|
||||
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}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -13,9 +13,6 @@ using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物工厂
|
||||
/// </summary>
|
||||
[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<ReliquarySubProperty> 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,
|
||||
|
||||
@@ -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<ReliquaryId, Reliquary> IdReliquaryMap { get; set; }
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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<MetadataService> logger;
|
||||
private readonly MetadataOptions metadataOptions;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
@@ -119,12 +120,16 @@ internal sealed partial class MetadataService : IMetadataService, IMetadataServi
|
||||
Dictionary<string, string>? metadataFileHashs;
|
||||
try
|
||||
{
|
||||
// Download meta check file
|
||||
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
|
||||
using (IServiceScope scope = serviceScopeFactory.CreateScope())
|
||||
{
|
||||
metadataFileHashs = await httpClient
|
||||
.GetFromJsonAsync<Dictionary<string, string>>(metadataOptions.GetLocalizedRemoteFile(MetaFileName), options, token)
|
||||
.ConfigureAwait(false);
|
||||
IHttpClientFactory httpClientFactory = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>();
|
||||
using (HttpClient httpClient = httpClientFactory.CreateClient(nameof(MetadataService)))
|
||||
{
|
||||
// Download meta check file
|
||||
metadataFileHashs = await httpClient
|
||||
.GetFromJsonAsync<Dictionary<string, string>>(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<IHttpClientFactory>();
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,22 +25,12 @@ internal static class MetadataServiceDictionaryExtension
|
||||
|
||||
public static ValueTask<Dictionary<ExtendedEquipAffixId, ReliquarySet>> GetExtendedEquipAffixIdToReliquarySetMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<ReliquarySet, (ExtendedEquipAffixId Id, ReliquarySet Set), ExtendedEquipAffixId, ReliquarySet>(
|
||||
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<ReliquarySet> list) => list.SelectMany(set => set.EquipAffixIds, (set, id) => (Id: id, Set: set)), token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<TowerLevelGroupId, List<TowerLevel>>> GetGroupIdToTowerLevelGroupMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<TowerLevel, IGrouping<TowerLevelGroupId, TowerLevel>, TowerLevelGroupId, List<TowerLevel>>(
|
||||
FileNameTowerLevel,
|
||||
list => list.GroupBy(l => l.GroupId),
|
||||
g => g.Key,
|
||||
g => g.ToList(),
|
||||
token);
|
||||
return metadataService.FromCacheAsDictionaryAsync(FileNameTowerLevel, (List<TowerLevel> list) => list.GroupBy(l => l.GroupId), g => g.Key, g => g.ToList(), token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AchievementId, Model.Metadata.Achievement.Achievement>> GetIdToAchievementMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
@@ -81,6 +71,11 @@ internal static class MetadataServiceDictionaryExtension
|
||||
return metadataService.FromCacheAsDictionaryAsync<MaterialId, Material>(FileNameMaterial, a => a.Id, token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<ReliquaryId, Reliquary>> GetIdToReliquaryMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync(FileNameReliquary, (List<Reliquary> list) => list.SelectMany(r => r.Ids, (r, i) => (Index: i, Reliquary: r)), token);
|
||||
}
|
||||
|
||||
public static ValueTask<Dictionary<AvatarId, ReliquaryAffixWeight>> GetIdToReliquaryAffixWeightMapAsync(this IMetadataService metadataService, CancellationToken token = default)
|
||||
{
|
||||
return metadataService.FromCacheAsDictionaryAsync<AvatarId, ReliquaryAffixWeight>(FileNameReliquaryAffixWeight, r => r.AvatarId, token);
|
||||
@@ -177,6 +172,12 @@ internal static class MetadataServiceDictionaryExtension
|
||||
return metadataService.MemoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
private static ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, 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<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TData, TMiddle, TKey, TValue>(this IMetadataService metadataService, string fileName, Func<List<TData>, IEnumerable<TMiddle>> listSelector, Func<TMiddle, TKey> keySelector, Func<TMiddle, TValue> valueSelector, CancellationToken token)
|
||||
where TKey : notnull
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
|
||||
/// 装备基类
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal abstract class Equip : NameIconDescription
|
||||
internal abstract class EquipView : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
@@ -7,7 +7,7 @@ namespace Snap.Hutao.ViewModel.AvatarProperty;
|
||||
/// 圣遗物
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class ReliquaryView : Equip
|
||||
internal sealed class ReliquaryView : EquipView
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始词条
|
||||
|
||||
@@ -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;
|
||||
/// 武器
|
||||
/// </summary>
|
||||
[HighQuality]
|
||||
internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
|
||||
internal sealed class WeaponView : EquipView, ICalculableSource<ICalculableWeapon>
|
||||
{
|
||||
/// <summary>
|
||||
/// 副属性
|
||||
@@ -53,6 +54,8 @@ internal sealed class WeaponView : Equip, ICalculableSource<ICalculableWeapon>
|
||||
/// </summary>
|
||||
internal uint MaxLevel { get => Model.Metadata.Weapon.Weapon.GetMaxLevelByQuality(Quality); }
|
||||
|
||||
internal WeaponType WeaponType { get; set; }
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ICalculableWeapon ToCalculable()
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user