mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
avatar info phase 1
This commit is contained in:
@@ -3,11 +3,11 @@
|
||||
|
||||

|
||||
|
||||
## 项目首页
|
||||
## 项目首页(文档)
|
||||
|
||||
[](https://github.com/DGP-Studio/Snap.Hutao.Docs/actions/workflows/deploy-docs.yml)
|
||||
|
||||
[https://hut.ao](https://hut.ao)
|
||||
[HUT.AO](https://hut.ao)
|
||||
|
||||
## 安装
|
||||
|
||||
|
||||
@@ -1,41 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.WinUI.UI.Behaviors;
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.UI.Xaml.Media;
|
||||
using Microsoft.UI.Xaml.Shapes;
|
||||
|
||||
namespace Snap.Hutao.Control.Behavior;
|
||||
|
||||
/// <summary>
|
||||
/// Make ContentDialog's SmokeLayerBackground dsiplay over custom titleBar
|
||||
/// </summary>
|
||||
public class ContentDialogBehavior : BehaviorBase<ContentDialog>
|
||||
{
|
||||
/// <inheritdoc/>
|
||||
protected override void OnAssociatedObjectLoaded()
|
||||
{
|
||||
DependencyObject parent = VisualTreeHelper.GetParent(AssociatedObject);
|
||||
|
||||
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
DependencyObject current = VisualTreeHelper.GetChild(parent, i);
|
||||
if (current is Rectangle { Name: "SmokeLayerBackground" } background)
|
||||
{
|
||||
background.ClearValue(FrameworkElement.MarginProperty);
|
||||
background.RegisterPropertyChangedCallback(FrameworkElement.MarginProperty, OnMarginChanged);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void OnMarginChanged(DependencyObject sender, DependencyProperty property)
|
||||
{
|
||||
if (property == FrameworkElement.MarginProperty)
|
||||
{
|
||||
sender.ClearValue(property);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,8 +3,6 @@
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Microsoft.Xaml.Interactivity;
|
||||
using Snap.Hutao.Control.Behavior;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
|
||||
namespace Snap.Hutao.Control.Extension;
|
||||
@@ -23,7 +21,6 @@ internal static class ContentDialogExtensions
|
||||
public static ContentDialog InitializeWithWindow(this ContentDialog contentDialog, Window window)
|
||||
{
|
||||
contentDialog.XamlRoot = window.Content.XamlRoot;
|
||||
Interaction.SetBehaviors(contentDialog, new() { new ContentDialogBehavior() });
|
||||
|
||||
return contentDialog;
|
||||
}
|
||||
|
||||
@@ -29,7 +29,6 @@ public class MonoChrome : CompositionImage
|
||||
{
|
||||
CompositionColorBrush blackLayerBursh = compositor.CreateColorBrush(Colors.Black);
|
||||
CompositionSurfaceBrush imageSurfaceBrush = compositor.CompositeSurfaceBrush(imageSurface, stretch: CompositionStretch.Uniform, vRatio: 0f);
|
||||
|
||||
CompositionEffectBrush overlayBrush = compositor.CompositeBlendEffectBrush(blackLayerBursh, imageSurfaceBrush, BlendEffectMode.Overlay);
|
||||
CompositionEffectBrush opacityBrush = compositor.CompositeLuminanceToAlphaEffectBrush(overlayBrush);
|
||||
|
||||
|
||||
@@ -24,8 +24,8 @@ internal class I18NExtension : MarkupExtension
|
||||
static I18NExtension()
|
||||
{
|
||||
string currentName = CultureInfo.CurrentUICulture.Name;
|
||||
Type languageType = TranslationMap.GetValueOrDefault(currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType)!;
|
||||
Type? languageType = ((IDictionary<string, Type>)TranslationMap).GetValueOrDefault(currentName, typeof(LanguagezhCN));
|
||||
Translation = (ITranslation)Activator.CreateInstance(languageType!)!;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -22,7 +22,7 @@ internal static class CoreEnvironment
|
||||
/// <summary>
|
||||
/// 动态密钥2的盐
|
||||
/// </summary>
|
||||
public const string DynamicSecret2Salt = "YVEIkzDFNHLeKXLxzqCA9TzxCpWwbIbk";
|
||||
public const string DynamicSecret2Salt = "xV8v4Qu54lUKrEYFZkJhB8cuOh9Asafs";
|
||||
|
||||
/// <summary>
|
||||
/// 米游社请求UA
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using System.Reflection;
|
||||
|
||||
namespace Snap.Hutao.Core.Json.Converter;
|
||||
|
||||
/// <summary>
|
||||
@@ -33,7 +31,7 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
Type valueType = type.GetGenericArguments()[1];
|
||||
|
||||
Type innerConverterType = typeof(StringEnumDictionaryConverterInner<,>).MakeGenericType(keyType, valueType);
|
||||
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType, BindingFlags.Instance | BindingFlags.Public, null, new object[] { options }, null)!;
|
||||
JsonConverter converter = (JsonConverter)Activator.CreateInstance(innerConverterType)!;
|
||||
return converter;
|
||||
}
|
||||
|
||||
@@ -41,13 +39,11 @@ public class StringEnumKeyDictionaryConverter : JsonConverterFactory
|
||||
where TKey : struct, Enum
|
||||
{
|
||||
private readonly Type keyType;
|
||||
private readonly Type valueType;
|
||||
|
||||
public StringEnumDictionaryConverterInner(JsonSerializerOptions options)
|
||||
public StringEnumDictionaryConverterInner()
|
||||
{
|
||||
// Cache the key and value types.
|
||||
keyType = typeof(TKey);
|
||||
valueType = typeof(TValue);
|
||||
}
|
||||
|
||||
public override IDictionary<TKey, TValue> Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options)
|
||||
|
||||
@@ -88,6 +88,11 @@ internal static class EventIds
|
||||
/// 成就
|
||||
/// </summary>
|
||||
public static readonly EventId Achievement = 100130;
|
||||
|
||||
/// <summary>
|
||||
/// 祈愿统计生成
|
||||
/// </summary>
|
||||
public static readonly EventId GachaStatisticGeneration = 100140;
|
||||
#endregion
|
||||
|
||||
#region 杂项
|
||||
|
||||
@@ -99,6 +99,26 @@ public static partial class EnumerableExtensions
|
||||
return source.FirstOrDefault(predicate) ?? source.FirstOrDefault();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取值或默认值
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <typeparam name="TValue">值类型</typeparam>
|
||||
/// <param name="dictionary">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="defaultValue">默认值</param>
|
||||
/// <returns>结果值</returns>
|
||||
public static TValue? GetValueOrDefault<TKey, TValue>(this IDictionary<TKey, TValue> dictionary, TKey key, TValue? defaultValue = default)
|
||||
where TKey : notnull
|
||||
{
|
||||
if (dictionary.TryGetValue(key, out TValue? value))
|
||||
{
|
||||
return value;
|
||||
}
|
||||
|
||||
return defaultValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
@@ -111,6 +131,21 @@ public static partial class EnumerableExtensions
|
||||
++CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键类型</typeparam>
|
||||
/// <param name="dict">字典</param>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value">增加的值</param>
|
||||
public static void Increase<TKey>(this Dictionary<TKey, int> dict, TKey key, int value)
|
||||
where TKey : notnull
|
||||
{
|
||||
// ref the value, so that we can manipulate it outside the dict.
|
||||
ref int current = ref CollectionsMarshal.GetValueRefOrAddDefault(dict, key, out _);
|
||||
current += value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 增加计数
|
||||
/// </summary>
|
||||
@@ -166,6 +201,20 @@ public static partial class EnumerableExtensions
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="Enumerable.ToDictionary{TSource, TKey, TElement}(IEnumerable{TSource}, Func{TSource, TKey}, Func{TSource, TElement})"/>
|
||||
public static Dictionary<TKey, TValue> ToDictionaryOverride<TKey, TValue, TSource>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, Func<TSource, TValue> valueSelector)
|
||||
where TKey : notnull
|
||||
{
|
||||
Dictionary<TKey, TValue> dictionary = new();
|
||||
|
||||
foreach (TSource value in source)
|
||||
{
|
||||
dictionary[keySelector(value)] = valueSelector(value);
|
||||
}
|
||||
|
||||
return dictionary;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 表示一个对 <see cref="TItem"/> 类型的计数器
|
||||
/// </summary>
|
||||
|
||||
@@ -10,7 +10,6 @@ namespace Snap.Hutao;
|
||||
/// 主窗体
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Singleton)]
|
||||
[Injection(InjectAs.Singleton, typeof(Window))] // This is for IEnumerable<Window>
|
||||
[SuppressMessage("", "CA1001")]
|
||||
public sealed partial class MainWindow : Window
|
||||
{
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息
|
||||
/// </summary>
|
||||
public class Avatar
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 侧面图标
|
||||
/// </summary>
|
||||
public Uri SideIcon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 星级
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public string Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 好感度等级
|
||||
/// </summary>
|
||||
public int FetterLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public Weapon Weapon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物列表
|
||||
/// </summary>
|
||||
public List<Reliquary> Reliquaries { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 命之座列表
|
||||
/// </summary>
|
||||
public List<Constellation> Constellations { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 技能列表
|
||||
/// </summary>
|
||||
public List<Skill> Skills { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 属性
|
||||
/// </summary>
|
||||
public List<Pair2<string, string, string?>> Properties { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 命座信息
|
||||
/// </summary>
|
||||
public class Constellation : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否激活
|
||||
/// </summary>
|
||||
public bool IsActiviated { get; set; }
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 装备基类
|
||||
/// </summary>
|
||||
public class EquipBase : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public string Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 品质
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 主属性
|
||||
/// </summary>
|
||||
public Pair<string, string> MainProperty { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 名称与描述抽象
|
||||
/// </summary>
|
||||
public abstract class NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public Uri Icon { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家信息
|
||||
/// </summary>
|
||||
public class Player
|
||||
{
|
||||
/// <summary>
|
||||
/// 昵称
|
||||
/// </summary>
|
||||
public string Nickname { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 签名
|
||||
/// </summary>
|
||||
public string Signature { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 完成成就数
|
||||
/// </summary>
|
||||
public int FinishAchievementNumber { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 深渊层间
|
||||
/// </summary>
|
||||
public string SipralAbyssFloorLevel { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 头像
|
||||
/// </summary>
|
||||
public Uri ProfilePicture { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物
|
||||
/// </summary>
|
||||
public class Reliquary : EquipBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 副属性列表
|
||||
/// </summary>
|
||||
public List<Pair<string, string>> SubProperties { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 天赋
|
||||
/// </summary>
|
||||
public class Skill : NameIconDescription
|
||||
{
|
||||
/// <summary>
|
||||
/// 技能属性
|
||||
/// </summary>
|
||||
public LevelParam<string, ParameterInfo> Info { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 玩家与角色列表的包装器
|
||||
/// </summary>
|
||||
public class Summary
|
||||
{
|
||||
/// <summary>
|
||||
/// 玩家信息
|
||||
/// </summary>
|
||||
public Player Player { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 角色列表
|
||||
/// </summary>
|
||||
public List<Avatar> Avatars { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
|
||||
/// <summary>
|
||||
/// 武器
|
||||
/// </summary>
|
||||
public class Weapon : EquipBase
|
||||
{
|
||||
/// <summary>
|
||||
/// 副属性
|
||||
/// </summary>
|
||||
public Pair<string, string> SubProperty { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼属性
|
||||
/// </summary>
|
||||
public int AffixLevel { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 精炼名称
|
||||
/// </summary>
|
||||
public string AffixName { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 精炼被动
|
||||
/// </summary>
|
||||
public string AffixDescription { get; set; } = default!;
|
||||
}
|
||||
@@ -28,4 +28,15 @@ public class AvatarInfo
|
||||
/// 角色的信息
|
||||
/// </summary>
|
||||
public Web.Enka.Model.AvatarInfo Info { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 创建一个新的实体角色信息
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="info">角色信息</param>
|
||||
/// <returns>实体角色信息</returns>
|
||||
public static AvatarInfo Create(string uid, Web.Enka.Model.AvatarInfo info)
|
||||
{
|
||||
return new() { Uid = uid, Info = info };
|
||||
}
|
||||
}
|
||||
@@ -24,8 +24,10 @@ public enum FightProperty
|
||||
FIGHT_PROP_BASE_HP = 1,
|
||||
|
||||
/// <summary>
|
||||
/// 生命值加成
|
||||
/// 小生命值加成
|
||||
/// </summary>
|
||||
[Description("生命值")]
|
||||
[Format(FormatMethod.Integer)]
|
||||
FIGHT_PROP_HP = 2,
|
||||
|
||||
/// <summary>
|
||||
@@ -45,6 +47,8 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 攻击力加成
|
||||
/// </summary>
|
||||
[Description("攻击力")]
|
||||
[Format(FormatMethod.Integer)]
|
||||
FIGHT_PROP_ATTACK = 5,
|
||||
|
||||
/// <summary>
|
||||
@@ -64,6 +68,8 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 防御力加成
|
||||
/// </summary>
|
||||
[Description("防御力")]
|
||||
[Format(FormatMethod.Integer)]
|
||||
FIGHT_PROP_DEFENSE = 8,
|
||||
|
||||
/// <summary>
|
||||
@@ -387,6 +393,8 @@ public enum FightProperty
|
||||
/// <summary>
|
||||
/// 最大生命值
|
||||
/// </summary>
|
||||
[Description("生命值")]
|
||||
[Format(FormatMethod.Integer)]
|
||||
FIGHT_PROP_MAX_HP = 2000,
|
||||
|
||||
/// <summary>
|
||||
|
||||
@@ -15,7 +15,7 @@ public enum ItemType
|
||||
ITEM_NONE = 0,
|
||||
|
||||
/// <summary>
|
||||
/// 贵重道具
|
||||
/// 虚拟道具
|
||||
/// </summary>
|
||||
ITEM_VIRTUAL = 1,
|
||||
|
||||
|
||||
@@ -27,4 +27,14 @@ public class Costume
|
||||
/// 是否为默认
|
||||
/// </summary>
|
||||
public bool IsDefault { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 侧面图标
|
||||
/// </summary>
|
||||
public string? SideIcon { get; set; }
|
||||
}
|
||||
@@ -24,7 +24,7 @@ public class SkillDepot
|
||||
public IList<ProudableSkill> Inherents { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 全部天赋
|
||||
/// 全部天赋,包括固有天赋
|
||||
/// </summary>
|
||||
public IList<ProudableSkill> CompositeSkills
|
||||
{
|
||||
@@ -36,6 +36,20 @@ public class SkillDepot
|
||||
/// </summary>
|
||||
public IList<SkillBase> Talents { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 获取无固有天赋的技能列表
|
||||
/// </summary>
|
||||
/// <returns>天赋列表</returns>
|
||||
public IEnumerable<ProudableSkill> GetCompositeSkillsNoInherents()
|
||||
{
|
||||
foreach (ProudableSkill skill in Skills)
|
||||
{
|
||||
yield return skill;
|
||||
}
|
||||
|
||||
yield return EnergySkill;
|
||||
}
|
||||
|
||||
private IEnumerable<ProudableSkill> GetCompositeSkills()
|
||||
{
|
||||
foreach (ProudableSkill skill in Skills)
|
||||
|
||||
@@ -12,22 +12,43 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// </summary>
|
||||
internal sealed class DescParamDescriptor : ValueConverterBase<DescParam, IList<LevelParam<string, ParameterInfo>>>
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取特定等级的解释
|
||||
/// </summary>
|
||||
/// <param name="from">源</param>
|
||||
/// <param name="level">等级</param>
|
||||
/// <returns>特定等级的解释</returns>
|
||||
public static LevelParam<string, ParameterInfo> Convert(DescParam from, int level)
|
||||
{
|
||||
// DO NOT INLINE!
|
||||
// Cache the formats
|
||||
IList<DescFormat> formats = from.Descriptions
|
||||
.Select(desc => new DescFormat(desc))
|
||||
.ToList();
|
||||
|
||||
LevelParam<int, double> param = from.Parameters.Single(param => param.Level == level);
|
||||
|
||||
return new LevelParam<string, ParameterInfo>(param.Level.ToString(), GetParameterInfos(formats, param.Parameters));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IList<LevelParam<string, ParameterInfo>> Convert(DescParam from)
|
||||
{
|
||||
// DO NOT INLINE!
|
||||
// Cache the formats
|
||||
IList<DescFormat> formats = from.Descriptions
|
||||
.Select(desc => new DescFormat(desc))
|
||||
.ToList();
|
||||
|
||||
IList<LevelParam<string, ParameterInfo>> parameters = from.Parameters
|
||||
.Select(param => new LevelParam<string, ParameterInfo>(param.Level.ToString(), GetParameterInfos(from, param.Parameters)))
|
||||
.Select(param => new LevelParam<string, ParameterInfo>(param.Level.ToString(), GetParameterInfos(formats, param.Parameters)))
|
||||
.ToList();
|
||||
|
||||
return parameters;
|
||||
}
|
||||
|
||||
private static IList<ParameterInfo> GetParameterInfos(DescParam rawDescParam, IList<double> param)
|
||||
private static IList<ParameterInfo> GetParameterInfos(IList<DescFormat> formats, IList<double> param)
|
||||
{
|
||||
IList<DescFormat> formats = rawDescParam.Descriptions
|
||||
.Select(desc => new DescFormat(desc))
|
||||
.ToList();
|
||||
|
||||
List<ParameterInfo> results = new();
|
||||
|
||||
for (int index = 0; index < formats.Count; index++)
|
||||
|
||||
@@ -27,4 +27,4 @@ internal class EquipIconConverter : ValueConverterBase<string, Uri>
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,25 @@ namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
/// </summary>
|
||||
internal class PropertyInfoDescriptor : ValueConverterBase<PropertyInfo, IList<LevelParam<string, ParameterInfo>>?>
|
||||
{
|
||||
/// <summary>
|
||||
/// 格式化战斗属性
|
||||
/// </summary>
|
||||
/// <param name="property">战斗属性</param>
|
||||
/// <param name="value">值</param>
|
||||
/// <returns>格式化的值</returns>
|
||||
public static string FormatProperty(FightProperty property, double value)
|
||||
{
|
||||
FormatMethod method = property.GetFormatMethod();
|
||||
|
||||
string valueFormatted = method switch
|
||||
{
|
||||
FormatMethod.Integer => Math.Round((double)value, MidpointRounding.AwayFromZero).ToString(),
|
||||
FormatMethod.Percent => string.Format("{0:P1}", value),
|
||||
_ => value.ToString(),
|
||||
};
|
||||
return valueFormatted;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override IList<LevelParam<string, ParameterInfo>> Convert(PropertyInfo from)
|
||||
{
|
||||
@@ -34,14 +53,7 @@ internal class PropertyInfoDescriptor : ValueConverterBase<PropertyInfo, IList<L
|
||||
for (int index = 0; index < parameters.Count; index++)
|
||||
{
|
||||
double param = parameters[index];
|
||||
FormatMethod method = properties[index].GetFormatMethod();
|
||||
|
||||
string valueFormatted = method switch
|
||||
{
|
||||
FormatMethod.Integer => Math.Round((double)param, MidpointRounding.AwayFromZero).ToString(),
|
||||
FormatMethod.Percent => string.Format("{0:P1}", param),
|
||||
_ => param.ToString(),
|
||||
};
|
||||
string valueFormatted = FormatProperty(properties[index], param);
|
||||
|
||||
results.Add(new ParameterInfo { Description = properties[index].GetDescription(), Parameter = valueFormatted });
|
||||
}
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Converter;
|
||||
|
||||
/// <summary>
|
||||
/// 武器图片转换器
|
||||
/// </summary>
|
||||
internal class RelicIconConverter : ValueConverterBase<string, Uri>
|
||||
{
|
||||
private const string BaseUrl = "https://static.snapgenshin.com/RelicIcon/{0}.png";
|
||||
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
return new Uri(string.Format(BaseUrl, name));
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
@@ -13,16 +13,26 @@ internal class SkillIconConverter : ValueConverterBase<string, Uri>
|
||||
private const string SkillUrl = "https://static.snapgenshin.com/Skill/{0}.png";
|
||||
private const string TalentUrl = "https://static.snapgenshin.com/Talent/{0}.png";
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
/// <summary>
|
||||
/// 名称转Uri
|
||||
/// </summary>
|
||||
/// <param name="name">名称</param>
|
||||
/// <returns>链接</returns>
|
||||
public static Uri IconNameToUri(string name)
|
||||
{
|
||||
if (from.StartsWith("UI_Talent_"))
|
||||
if (name.StartsWith("UI_Talent_"))
|
||||
{
|
||||
return new Uri(string.Format(TalentUrl, from));
|
||||
return new Uri(string.Format(TalentUrl, name));
|
||||
}
|
||||
else
|
||||
{
|
||||
return new Uri(string.Format(SkillUrl, from));
|
||||
return new Uri(string.Format(SkillUrl, name));
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public override Uri Convert(string from)
|
||||
{
|
||||
return IconNameToUri(from);
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,7 @@ public class Reliquary
|
||||
/// <summary>
|
||||
/// 允许出现的等级
|
||||
/// </summary>
|
||||
public IEnumerable<ItemQuality> RankLevels { get; set; } = default!;
|
||||
public ItemQuality RankLevel { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 套装Id
|
||||
|
||||
@@ -19,4 +19,4 @@ public class ReliquaryAffixBase
|
||||
/// 战斗属性
|
||||
/// </summary>
|
||||
public FightProperty Type { get; set; }
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Json.Converter;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Reliquary;
|
||||
|
||||
/// <summary>
|
||||
/// 圣遗物等级
|
||||
/// </summary>
|
||||
public class ReliquaryLevel
|
||||
{
|
||||
/// <summary>
|
||||
/// 品质
|
||||
/// </summary>
|
||||
public ItemQuality Quality { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 等级 1-21
|
||||
/// </summary>
|
||||
public int Level { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 属性
|
||||
/// </summary>
|
||||
[JsonConverter(typeof(StringEnumKeyDictionaryConverter))]
|
||||
public IDictionary<FightProperty, double> Properties { get; set; } = default!;
|
||||
}
|
||||
21
src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/AffixInfo.cs
Normal file
21
src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/AffixInfo.cs
Normal file
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// 武器被动信息
|
||||
/// </summary>
|
||||
public class AffixInfo
|
||||
{
|
||||
/// <summary>
|
||||
/// 被动的名称
|
||||
/// </summary>
|
||||
public string Name { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 各个等级的描述
|
||||
/// 0-4
|
||||
/// </summary>
|
||||
public List<LevelDescription<int>> Descriptions { get; set; } = default!;
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model.Metadata.Weapon;
|
||||
|
||||
/// <summary>
|
||||
/// 等级与描述
|
||||
/// </summary>
|
||||
/// <typeparam name="TLevel">等级的类型</typeparam>
|
||||
public class LevelDescription<TLevel>
|
||||
{
|
||||
/// <summary>
|
||||
/// 等级
|
||||
/// </summary>
|
||||
public TLevel Level { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 描述
|
||||
/// </summary>
|
||||
public string Description { get; set; } = default!;
|
||||
}
|
||||
@@ -54,6 +54,11 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
/// </summary>
|
||||
public PropertyInfo Property { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 被动信息, 无被动的武器为 <see langword="null"/>
|
||||
/// </summary>
|
||||
public AffixInfo? Affix { get; set; } = default!;
|
||||
|
||||
/// <inheritdoc/>
|
||||
[JsonIgnore]
|
||||
public ItemQuality Quality => RankLevel;
|
||||
@@ -110,4 +115,4 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
|
||||
IsUp = isUp,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
41
src/Snap.Hutao/Snap.Hutao/Model/Pair2.cs
Normal file
41
src/Snap.Hutao/Snap.Hutao/Model/Pair2.cs
Normal file
@@ -0,0 +1,41 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Model;
|
||||
|
||||
/// <summary>
|
||||
/// 对
|
||||
/// </summary>
|
||||
/// <typeparam name="TKey">键的类型</typeparam>
|
||||
/// <typeparam name="TValue1">值1的类型</typeparam>
|
||||
/// <typeparam name="TValue2">值2的类型</typeparam>
|
||||
public class Pair2<TKey, TValue1, TValue2>
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的对
|
||||
/// </summary>
|
||||
/// <param name="key">键</param>
|
||||
/// <param name="value1">值1</param>
|
||||
/// <param name="value2">值2</param>
|
||||
public Pair2(TKey key, TValue1 value1, TValue2 value2)
|
||||
{
|
||||
Key = key;
|
||||
Value1 = value1;
|
||||
Value2 = value2;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 键
|
||||
/// </summary>
|
||||
public TKey Key { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public TValue1 Value1 { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 值
|
||||
/// </summary>
|
||||
public TValue2 Value2 { get; set; }
|
||||
}
|
||||
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_Icon_BoostUp.png
Normal file
BIN
src/Snap.Hutao/Snap.Hutao/Resource/Icon/UI_Icon_BoostUp.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.7 KiB |
@@ -0,0 +1,118 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
using Snap.Hutao.Web.Enka;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息服务
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(IAvatarInfoService))]
|
||||
internal class AvatarInfoService : IAvatarInfoService
|
||||
{
|
||||
private readonly AppDbContext appDbContext;
|
||||
private readonly ISummaryFactory summaryFactory;
|
||||
private readonly EnkaClient enkaClient;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的角色信息服务
|
||||
/// </summary>
|
||||
/// <param name="appDbContext">数据库上下文</param>
|
||||
/// <param name="summaryFactory">简述工厂</param>
|
||||
/// <param name="enkaClient">Enka客户端</param>
|
||||
public AvatarInfoService(AppDbContext appDbContext, ISummaryFactory summaryFactory, EnkaClient enkaClient)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
this.summaryFactory = summaryFactory;
|
||||
this.enkaClient = enkaClient;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(PlayerUid uid, RefreshOption refreshOption, CancellationToken token = default)
|
||||
{
|
||||
if (HasOption(refreshOption, RefreshOption.RequestFromAPI))
|
||||
{
|
||||
EnkaResponse? resp = await GetEnkaResponseAsync(uid, token).ConfigureAwait(false);
|
||||
if (resp == null)
|
||||
{
|
||||
return new(RefreshResult.APIUnavailable, null);
|
||||
}
|
||||
|
||||
if (resp.IsValid)
|
||||
{
|
||||
IList<Web.Enka.Model.AvatarInfo> list = HasOption(refreshOption, RefreshOption.StoreInDatabase)
|
||||
? UpdateDbAvatarInfo(uid.Value, resp.AvatarInfoList)
|
||||
: resp.AvatarInfoList;
|
||||
|
||||
Summary summary = await summaryFactory.CreateAsync(resp.PlayerInfo, list).ConfigureAwait(false);
|
||||
return new(RefreshResult.Ok, summary);
|
||||
}
|
||||
else
|
||||
{
|
||||
return new(RefreshResult.ShowcaseNotOpen, null);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
PlayerInfo info = PlayerInfo.CreateEmpty(uid.Value);
|
||||
|
||||
Summary summary = await summaryFactory.CreateAsync(info, GetDbAvatarInfos(uid.Value)).ConfigureAwait(false);
|
||||
return new(RefreshResult.Ok, summary);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool HasOption(RefreshOption source, RefreshOption define)
|
||||
{
|
||||
return (source & define) == define;
|
||||
}
|
||||
|
||||
private async Task<EnkaResponse?> GetEnkaResponseAsync(PlayerUid uid, CancellationToken token = default)
|
||||
{
|
||||
return await enkaClient.GetForwardDataAsync(uid, token).ConfigureAwait(false)
|
||||
?? await enkaClient.GetDataAsync(uid, token).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private List<Web.Enka.Model.AvatarInfo> UpdateDbAvatarInfo(string uid, IEnumerable<Web.Enka.Model.AvatarInfo> webInfos)
|
||||
{
|
||||
List<Model.Entity.AvatarInfo> dbInfos = appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.ToList();
|
||||
|
||||
foreach (Web.Enka.Model.AvatarInfo webInfo in webInfos)
|
||||
{
|
||||
Model.Entity.AvatarInfo? entity = dbInfos.SingleOrDefault(i => i.Info.AvatarId == webInfo.AvatarId);
|
||||
|
||||
if (entity == null)
|
||||
{
|
||||
entity = Model.Entity.AvatarInfo.Create(uid, webInfo);
|
||||
appDbContext.Add(entity);
|
||||
}
|
||||
else
|
||||
{
|
||||
entity.Info = webInfo;
|
||||
appDbContext.Update(entity);
|
||||
}
|
||||
}
|
||||
|
||||
appDbContext.SaveChanges();
|
||||
|
||||
return GetDbAvatarInfos(uid);
|
||||
}
|
||||
|
||||
private List<Web.Enka.Model.AvatarInfo> GetDbAvatarInfos(string uid)
|
||||
{
|
||||
return appDbContext.AvatarInfos
|
||||
.Where(i => i.Uid == uid)
|
||||
.Select(i => i.Info)
|
||||
.AsEnumerable()
|
||||
.OrderByDescending(i => i.AvatarId)
|
||||
.ToList();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
/// <summary>
|
||||
/// 简述工厂
|
||||
/// </summary>
|
||||
internal interface ISummaryFactory
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步创建简述对象
|
||||
/// </summary>
|
||||
/// <param name="playerInfo">玩家信息</param>
|
||||
/// <param name="avatarInfos">角色列表</param>
|
||||
/// <returns>简述对象</returns>
|
||||
Task<Summary> CreateAsync(PlayerInfo playerInfo, IEnumerable<Web.Enka.Model.AvatarInfo> avatarInfos);
|
||||
}
|
||||
@@ -0,0 +1,197 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Annotation;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Model.Metadata.Reliquary;
|
||||
using Snap.Hutao.Service.Metadata;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
|
||||
using MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
|
||||
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
|
||||
using ModelPlayerInfo = Snap.Hutao.Web.Enka.Model.PlayerInfo;
|
||||
using PropertyAvatar = Snap.Hutao.Model.Binding.AvatarProperty.Avatar;
|
||||
using PropertyReliquary = Snap.Hutao.Model.Binding.AvatarProperty.Reliquary;
|
||||
using PropertyWeapon = Snap.Hutao.Model.Binding.AvatarProperty.Weapon;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
/// <summary>
|
||||
/// 简述工厂
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient, typeof(ISummaryFactory))]
|
||||
internal class SummaryFactory : ISummaryFactory
|
||||
{
|
||||
private readonly IMetadataService metadataService;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的简述工厂
|
||||
/// </summary>
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
public SummaryFactory(IMetadataService metadataService)
|
||||
{
|
||||
this.metadataService = metadataService;
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public async Task<Summary> CreateAsync(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos)
|
||||
{
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync().ConfigureAwait(false);
|
||||
|
||||
List<ReliquaryLevel> reliqueryLevels = await metadataService.GetReliquaryLevelsAsync().ConfigureAwait(false);
|
||||
List<MetadataReliquary> reliquaries = await metadataService.GetReliquariesAsync().ConfigureAwait(false);
|
||||
|
||||
SummaryFactoryInner inner = new(idAvatarMap, idRelicMainPropMap, idWeaponMap, idReliquaryAffixMap, reliqueryLevels, reliquaries);
|
||||
return inner.Create(playerInfo, avatarInfos);
|
||||
}
|
||||
|
||||
private class SummaryFactoryInner
|
||||
{
|
||||
private readonly Dictionary<int, MetadataAvatar> idAvatarMap;
|
||||
private readonly Dictionary<int, FightProperty> idRelicMainPropMap;
|
||||
private readonly Dictionary<int, MetadataWeapon> idWeaponMap;
|
||||
private readonly Dictionary<int, ReliquaryAffix> idReliquaryAffixMap;
|
||||
private readonly List<ReliquaryLevel> reliqueryLevels;
|
||||
private readonly List<MetadataReliquary> reliquaries;
|
||||
|
||||
public SummaryFactoryInner(
|
||||
Dictionary<int, MetadataAvatar> idAvatarMap,
|
||||
Dictionary<int, FightProperty> idRelicMainPropMap,
|
||||
Dictionary<int, MetadataWeapon> idWeaponMap,
|
||||
Dictionary<int, ReliquaryAffix> idReliquaryAffixMap,
|
||||
List<ReliquaryLevel> reliqueryLevels,
|
||||
List<MetadataReliquary> reliquaries)
|
||||
{
|
||||
this.idAvatarMap = idAvatarMap;
|
||||
this.idRelicMainPropMap = idRelicMainPropMap;
|
||||
this.idWeaponMap = idWeaponMap;
|
||||
this.reliqueryLevels = reliqueryLevels;
|
||||
this.reliquaries = reliquaries;
|
||||
this.idReliquaryAffixMap = idReliquaryAffixMap;
|
||||
}
|
||||
|
||||
public Summary Create(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos)
|
||||
{
|
||||
// 用作头像
|
||||
MetadataAvatar avatar = idAvatarMap[playerInfo.ProfilePicture.AvatarId];
|
||||
|
||||
return new()
|
||||
{
|
||||
Player = SummaryHelper.CreatePlayer(playerInfo, avatar),
|
||||
Avatars = avatarInfos.Select(a => CreateAvatar(a)).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private PropertyAvatar CreateAvatar(ModelAvatarInfo avatarInfo)
|
||||
{
|
||||
(List<PropertyReliquary> reliquaries, PropertyWeapon weapon) = ProcessEquip(avatarInfo.EquipList);
|
||||
MetadataAvatar avatar = idAvatarMap[avatarInfo.AvatarId];
|
||||
|
||||
return new()
|
||||
{
|
||||
Name = avatar.Name,
|
||||
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon),
|
||||
SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon),
|
||||
Quality = avatar.Quality,
|
||||
Level = avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value ?? string.Empty,
|
||||
FetterLevel = avatarInfo.FetterInfo.ExpLevel,
|
||||
Weapon = weapon,
|
||||
Reliquaries = reliquaries,
|
||||
Constellations = SummaryHelper.CreateConstellations(avatarInfo.TalentIdList, avatar.SkillDepot.Talents),
|
||||
Skills = SummaryHelper.CreateSkills(avatarInfo.SkillLevelMap, avatarInfo.ProudSkillExtraLevelMap, avatar.SkillDepot.GetCompositeSkillsNoInherents()),
|
||||
Properties = SummaryHelper.CreateAvatarProperties(avatarInfo.FightPropMap),
|
||||
};
|
||||
}
|
||||
|
||||
private (List<PropertyReliquary> Reliquaries, PropertyWeapon Weapon) ProcessEquip(IList<Equip> equipments)
|
||||
{
|
||||
List<PropertyReliquary> reliquaries = new();
|
||||
PropertyWeapon? weapon = null;
|
||||
|
||||
foreach (Equip equip in equipments)
|
||||
{
|
||||
switch (equip.Flat.ItemType)
|
||||
{
|
||||
case ItemType.ITEM_RELIQUARY:
|
||||
reliquaries.Add(CreateReliquary(equip));
|
||||
break;
|
||||
case ItemType.ITEM_WEAPON:
|
||||
weapon = CreateWeapon(equip);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return (reliquaries, weapon!);
|
||||
}
|
||||
|
||||
private PropertyReliquary CreateReliquary(Equip equip)
|
||||
{
|
||||
MetadataReliquary reliquary = reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
|
||||
|
||||
return new()
|
||||
{
|
||||
// NameIconDescription
|
||||
Name = reliquary.Name,
|
||||
Icon = RelicIconConverter.IconNameToUri(reliquary.Icon),
|
||||
Description = reliquary.Description,
|
||||
|
||||
// EquipBase
|
||||
Level = $"+ {equip.Reliquary!.Level - 1}",
|
||||
Quality = reliquary.RankLevel,
|
||||
MainProperty = CreateReliquaryMainProperty(equip.Reliquary.MainPropId, reliquary.RankLevel, equip.Reliquary.Level),
|
||||
|
||||
// Reliquary
|
||||
SubProperties = equip.Reliquary.AppendPropIdList.Select(id => CreateReliquarySubProperty(id)).ToList(),
|
||||
};
|
||||
}
|
||||
|
||||
private Pair<string, string> CreateReliquaryMainProperty(int propId, ItemQuality quality, int level)
|
||||
{
|
||||
ReliquaryLevel reliquaryLevel = reliqueryLevels.Single(r => r.Level == level && r.Quality == quality);
|
||||
FightProperty property = idRelicMainPropMap[propId];
|
||||
|
||||
return new(property.GetDescription(), PropertyInfoDescriptor.FormatProperty(property, reliquaryLevel.Properties[property]));
|
||||
}
|
||||
|
||||
private Pair<string, string> CreateReliquarySubProperty(int appendPropId)
|
||||
{
|
||||
ReliquaryAffix affix = idReliquaryAffixMap[appendPropId];
|
||||
FightProperty property = affix.Type;
|
||||
|
||||
return new(property.GetDescription(), PropertyInfoDescriptor.FormatProperty(property, affix.Value));
|
||||
}
|
||||
|
||||
private PropertyWeapon CreateWeapon(Equip equip)
|
||||
{
|
||||
MetadataWeapon weapon = idWeaponMap[equip.ItemId];
|
||||
(string id, int level) = equip.Weapon!.AffixMap.Single();
|
||||
|
||||
return new()
|
||||
{
|
||||
// NameIconDescription
|
||||
Name = weapon.Name,
|
||||
Icon = EquipIconConverter.IconNameToUri(weapon.Icon),
|
||||
Description = weapon.Description,
|
||||
|
||||
// EquipBase
|
||||
Level = $"{equip.Weapon!.Level}",
|
||||
Quality = weapon.Quality,
|
||||
MainProperty = new(string.Empty, string.Empty), // TODO
|
||||
|
||||
// Weapon
|
||||
SubProperty = new(string.Empty, string.Empty), // TODO
|
||||
AffixLevel = level + 1,
|
||||
AffixName = weapon.Affix?.Name ?? string.Empty,
|
||||
AffixDescription = weapon.Affix?.Descriptions.Single(a => a.Level == level).Description ?? string.Empty,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,227 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata.Annotation;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
using Snap.Hutao.Model.Metadata.Converter;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar;
|
||||
using ModelPlayerInfo = Snap.Hutao.Web.Enka.Model.PlayerInfo;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo.Factory;
|
||||
|
||||
/// <summary>
|
||||
/// 简述帮助类
|
||||
/// </summary>
|
||||
internal static class SummaryHelper
|
||||
{
|
||||
/// <summary>
|
||||
/// 创建玩家对象
|
||||
/// </summary>
|
||||
/// <param name="playerInfo">玩家信息</param>
|
||||
/// <param name="avatar">角色</param>
|
||||
/// <returns>玩家对象</returns>
|
||||
public static Player CreatePlayer(ModelPlayerInfo playerInfo, MetadataAvatar avatar)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Nickname = playerInfo.Nickname,
|
||||
Level = playerInfo.Level,
|
||||
Signature = playerInfo.Signature,
|
||||
FinishAchievementNumber = playerInfo.FinishAchievementNum,
|
||||
SipralAbyssFloorLevel = $"{playerInfo.TowerFloorIndex} - {playerInfo.TowerLevelIndex}",
|
||||
ProfilePicture = AvatarIconConverter.IconNameToUri(GetIconName(playerInfo.ProfilePicture, avatar)),
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建命之座
|
||||
/// </summary>
|
||||
/// <param name="talentIds">激活的命座列表</param>
|
||||
/// <param name="talents">全部命座</param>
|
||||
/// <returns>命之座</returns>
|
||||
public static List<Constellation> CreateConstellations(IList<int>? talentIds, IList<SkillBase> talents)
|
||||
{
|
||||
List<Constellation> constellations = new();
|
||||
|
||||
foreach (SkillBase talent in talents)
|
||||
{
|
||||
Constellation constellation = new()
|
||||
{
|
||||
Name = talent.Name,
|
||||
Icon = SkillIconConverter.IconNameToUri(talent.Icon),
|
||||
Description = talent.Description,
|
||||
IsActiviated = talentIds?.Contains(talent.Id) ?? false,
|
||||
};
|
||||
|
||||
constellations.Add(constellation);
|
||||
}
|
||||
|
||||
return constellations;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建技能组
|
||||
/// </summary>
|
||||
/// <param name="skillLevelMap">技能等级映射</param>
|
||||
/// <param name="proudSkillExtraLevelMap">额外提升等级映射</param>
|
||||
/// <param name="proudSkills">技能列表</param>
|
||||
/// <returns>技能</returns>
|
||||
public static List<Skill> CreateSkills(IDictionary<string, int> skillLevelMap, IDictionary<string, int>? proudSkillExtraLevelMap, IEnumerable<ProudableSkill> proudSkills)
|
||||
{
|
||||
Dictionary<string, int> skillLevelMapCopy = new(skillLevelMap);
|
||||
|
||||
if (proudSkillExtraLevelMap != null)
|
||||
{
|
||||
foreach ((string skillGroupId, int extraLevel) in proudSkillExtraLevelMap)
|
||||
{
|
||||
int skillGroupIdInt32 = int.Parse(skillGroupId);
|
||||
int skillId = proudSkills.Single(p => p.GroupId == skillGroupIdInt32).Id;
|
||||
|
||||
skillLevelMapCopy.Increase($"{skillId}", extraLevel);
|
||||
}
|
||||
}
|
||||
|
||||
List<Skill> skills = new();
|
||||
|
||||
foreach (ProudableSkill proudableSkill in proudSkills)
|
||||
{
|
||||
Skill skill = new()
|
||||
{
|
||||
Name = proudableSkill.Name,
|
||||
Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon),
|
||||
Description = proudableSkill.Description,
|
||||
Info = DescParamDescriptor.Convert(proudableSkill.Proud, skillLevelMapCopy[$"{proudableSkill.Id}"]),
|
||||
};
|
||||
|
||||
skills.Add(skill);
|
||||
}
|
||||
|
||||
return skills;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 创建角色属性
|
||||
/// </summary>
|
||||
/// <param name="fightPropMap">属性映射</param>
|
||||
/// <returns>列表</returns>
|
||||
public static List<Pair2<string, string, string?>> CreateAvatarProperties(IDictionary<FightProperty, double> fightPropMap)
|
||||
{
|
||||
List<Pair2<string, string, string?>> properties;
|
||||
|
||||
double baseHp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_HP); // 1
|
||||
double hp = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP); // 2
|
||||
double hpPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_HP_PERCENT); // 3
|
||||
double hpAdd = hp + (baseHp * hpPercent);
|
||||
double maxHp = baseHp + hpAdd;
|
||||
Pair2<string, string, string?> hpPair2 = new("生命值", FormatValue(FormatMethod.Integer, maxHp), $"[+{FormatValue(FormatMethod.Integer, hpAdd)}]");
|
||||
|
||||
double baseAtk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_ATTACK); // 4
|
||||
double atk = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK); // 5
|
||||
double atkPrecent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ATTACK_PERCENT); // 6
|
||||
double atkAdd = atk + (baseAtk * atkPrecent);
|
||||
double maxAtk = baseAtk + atkAdd;
|
||||
Pair2<string, string, string?> atkPair2 = new("攻击力", FormatValue(FormatMethod.Integer, maxAtk), $"[+{FormatValue(FormatMethod.Integer, atkAdd)}]");
|
||||
|
||||
double baseDef = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_BASE_DEFENSE); // 7
|
||||
double def = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE); // 8
|
||||
double defPercent = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_DEFENSE_PERCENT); // 9
|
||||
double defAdd = def + (baseDef * defPercent);
|
||||
double maxDef = baseDef + defPercent;
|
||||
Pair2<string, string, string?> defPair2 = new("防御力", FormatValue(FormatMethod.Integer, maxDef), $"[+{FormatValue(FormatMethod.Integer, defAdd)}]");
|
||||
|
||||
double em = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_ELEMENT_MASTERY); // 28
|
||||
Pair2<string, string, string?> emPair2 = new("元素精通", FormatValue(FormatMethod.Integer, em), null);
|
||||
|
||||
double critRate = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL); // 20
|
||||
Pair2<string, string, string?> critRatePair2 = new("暴击率", FormatValue(FormatMethod.Percent, critRate), null);
|
||||
|
||||
double critDMG = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CRITICAL_HURT); // 22
|
||||
Pair2<string, string, string?> critDMGPair2 = new("暴击伤害", FormatValue(FormatMethod.Percent, critDMG), null);
|
||||
|
||||
double chargeEff = fightPropMap.GetValueOrDefault(FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY); // 23
|
||||
Pair2<string, string, string?> chargeEffPair2 = new("元素充能效率", FormatValue(FormatMethod.Percent, chargeEff), null);
|
||||
|
||||
properties = new() { hpPair2, atkPair2, defPair2, emPair2, critRatePair2, critDMGPair2, chargeEffPair2 };
|
||||
|
||||
FightProperty bonusProperty = GetBonusFightProperty(fightPropMap);
|
||||
if (bonusProperty != FightProperty.FIGHT_PROP_NONE)
|
||||
{
|
||||
double value = fightPropMap[bonusProperty];
|
||||
Pair2<string, string, string?> bonusPair2 = new(bonusProperty.GetDescription(), FormatValue(FormatMethod.Percent, value), null);
|
||||
properties.Add(bonusPair2);
|
||||
}
|
||||
|
||||
return properties;
|
||||
}
|
||||
|
||||
private static string FormatValue(FormatMethod method, double value)
|
||||
{
|
||||
return method switch
|
||||
{
|
||||
FormatMethod.Integer => Math.Round((double)value, MidpointRounding.AwayFromZero).ToString(),
|
||||
FormatMethod.Percent => string.Format("{0:P1}", value),
|
||||
_ => value.ToString(),
|
||||
};
|
||||
}
|
||||
|
||||
private static FightProperty GetBonusFightProperty(IDictionary<FightProperty, double> fightPropMap)
|
||||
{
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_FIRE_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_FIRE_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_ELEC_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_ELEC_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_WATER_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_WATER_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_GRASS_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_GRASS_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_WIND_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_WIND_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_ICE_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_ICE_ADD_HURT;
|
||||
}
|
||||
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_MAX_ROCK_ENERGY))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_ROCK_ADD_HURT;
|
||||
}
|
||||
|
||||
// 物伤
|
||||
if (fightPropMap.ContainsKey(FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT))
|
||||
{
|
||||
return FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT;
|
||||
}
|
||||
|
||||
return FightProperty.FIGHT_PROP_NONE;
|
||||
}
|
||||
|
||||
private static string GetIconName(ProfilePicture profilePicture, MetadataAvatar avatar)
|
||||
{
|
||||
if (profilePicture.CostumeId != null)
|
||||
{
|
||||
return avatar.Costumes.Single(c => c.Id == profilePicture.CostumeId).Icon ?? string.Empty;
|
||||
}
|
||||
|
||||
return avatar.Icon;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息服务
|
||||
/// </summary>
|
||||
internal interface IAvatarInfoService
|
||||
{
|
||||
/// <summary>
|
||||
/// 异步获取总览数据
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="refreshOption">刷新选项</param>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>总览数据</returns>
|
||||
Task<ValueResult<RefreshResult, Summary?>> GetSummaryAsync(PlayerUid uid, RefreshOption refreshOption, CancellationToken token = default);
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新选项
|
||||
/// </summary>
|
||||
[Flags]
|
||||
public enum RefreshOption
|
||||
{
|
||||
/// <summary>
|
||||
/// 是否存入本地数据库
|
||||
/// </summary>
|
||||
StoreInDatabase = 0b00000001,
|
||||
|
||||
/// <summary>
|
||||
/// 从API获取
|
||||
/// </summary>
|
||||
RequestFromAPI = 0b00000010,
|
||||
|
||||
/// <summary>
|
||||
/// 仅数据库
|
||||
/// </summary>
|
||||
DatabaseOnly = 0b00000000,
|
||||
|
||||
/// <summary>
|
||||
/// 标准操作
|
||||
/// </summary>
|
||||
Standard = StoreInDatabase | RequestFromAPI,
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Service.AvatarInfo;
|
||||
|
||||
/// <summary>
|
||||
/// 刷新结果
|
||||
/// </summary>
|
||||
public enum RefreshResult
|
||||
{
|
||||
/// <summary>
|
||||
/// 正常
|
||||
/// </summary>
|
||||
Ok,
|
||||
|
||||
/// <summary>
|
||||
/// API 不可用
|
||||
/// </summary>
|
||||
APIUnavailable,
|
||||
|
||||
/// <summary>
|
||||
/// 角色橱窗未对外开放
|
||||
/// </summary>
|
||||
ShowcaseNotOpen,
|
||||
}
|
||||
@@ -5,6 +5,8 @@ using CommunityToolkit.Mvvm.Messaging;
|
||||
using Snap.Hutao.Context.Database;
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Binding.Gacha;
|
||||
@@ -45,6 +47,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
private readonly IMetadataService metadataService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private readonly IGachaStatisticsFactory gachaStatisticsFactory;
|
||||
private readonly ILogger<GachaLogService> logger;
|
||||
private readonly DbCurrent<GachaArchive, Message.GachaArchiveChangedMessage> dbCurrent;
|
||||
|
||||
private readonly Dictionary<string, ItemBase> itemBaseCache = new();
|
||||
@@ -65,6 +68,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
/// <param name="metadataService">元数据服务</param>
|
||||
/// <param name="infoBarService">信息条服务</param>
|
||||
/// <param name="gachaStatisticsFactory">祈愿统计工厂</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
/// <param name="messenger">消息器</param>
|
||||
public GachaLogService(
|
||||
AppDbContext appDbContext,
|
||||
@@ -73,6 +77,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
IMetadataService metadataService,
|
||||
IInfoBarService infoBarService,
|
||||
IGachaStatisticsFactory gachaStatisticsFactory,
|
||||
ILogger<GachaLogService> logger,
|
||||
IMessenger messenger)
|
||||
{
|
||||
this.appDbContext = appDbContext;
|
||||
@@ -80,6 +85,7 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
this.gachaInfoClient = gachaInfoClient;
|
||||
this.metadataService = metadataService;
|
||||
this.infoBarService = infoBarService;
|
||||
this.logger = logger;
|
||||
this.gachaStatisticsFactory = gachaStatisticsFactory;
|
||||
|
||||
dbCurrent = new(appDbContext, appDbContext.GachaArchives, messenger);
|
||||
@@ -143,21 +149,24 @@ internal class GachaLogService : IGachaLogService, ISupportAsyncInitialization
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public Task<GachaStatistics> GetStatisticsAsync(GachaArchive? archive = null)
|
||||
public async Task<GachaStatistics> GetStatisticsAsync(GachaArchive? archive = null)
|
||||
{
|
||||
archive ??= CurrentArchive;
|
||||
|
||||
// Return statistics
|
||||
if (archive != null)
|
||||
{
|
||||
ValueStopwatch stopwatch = ValueStopwatch.StartNew();
|
||||
IQueryable<GachaItem> items = appDbContext.GachaItems
|
||||
.Where(i => i.ArchiveId == archive.InnerId);
|
||||
|
||||
return gachaStatisticsFactory.CreateAsync(items);
|
||||
GachaStatistics statistics = await gachaStatisticsFactory.CreateAsync(items).ConfigureAwait(false);
|
||||
logger.LogInformation(EventIds.GachaStatisticGeneration, "GachaStatistic Generation toke {time} ms.", stopwatch.GetElapsedTime().TotalMilliseconds);
|
||||
return statistics;
|
||||
}
|
||||
else
|
||||
{
|
||||
return Must.Fault<GachaStatistics>("没有选中的存档");
|
||||
throw Must.NeverHappen();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
@@ -47,21 +48,35 @@ internal interface IMetadataService
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>卡池配置列表</returns>
|
||||
ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default(CancellationToken));
|
||||
ValueTask<List<GachaEvent>> GetGachaEventsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取Id到角色的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到角色的字典</returns>
|
||||
ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default(CancellationToken));
|
||||
ValueTask<Dictionary<int, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到圣遗物副词条的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物主词条Id与属性的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>字典</returns>
|
||||
ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取ID到武器的字典
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>Id到武器的字典</returns>
|
||||
ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default(CancellationToken));
|
||||
ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取名称到角色的字典
|
||||
@@ -91,6 +106,13 @@ internal interface IMetadataService
|
||||
/// <returns>圣遗物强化属性列表</returns>
|
||||
ValueTask<List<ReliquaryAffix>> GetReliquaryAffixesAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物等级数据
|
||||
/// </summary>
|
||||
/// <param name="token">取消令牌</param>
|
||||
/// <returns>圣遗物等级数据</returns>
|
||||
ValueTask<List<ReliquaryLevel>> GetReliquaryLevelsAsync(CancellationToken token = default);
|
||||
|
||||
/// <summary>
|
||||
/// 异步获取圣遗物主属性强化属性列表
|
||||
/// </summary>
|
||||
|
||||
@@ -8,6 +8,7 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Core.Diagnostics;
|
||||
using Snap.Hutao.Core.Logging;
|
||||
using Snap.Hutao.Extension;
|
||||
using Snap.Hutao.Model.Intrinsic;
|
||||
using Snap.Hutao.Model.Metadata;
|
||||
using Snap.Hutao.Model.Metadata.Achievement;
|
||||
using Snap.Hutao.Model.Metadata.Avatar;
|
||||
@@ -122,6 +123,18 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
return FromCacheAsDictionaryAsync<int, Avatar>("Avatar", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, ReliquaryAffix>("ReliquaryAffix", a => a.Id, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, FightProperty>> GetIdToReliquaryMainPropertyMapAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheAsDictionaryAsync<int, FightProperty, ReliquaryAffixBase>("ReliquaryMainAffix", r => r.Id, r => r.Type, token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<Dictionary<int, Weapon>> GetIdToWeaponMapAsync(CancellationToken token = default)
|
||||
{
|
||||
@@ -152,6 +165,12 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
return FromCacheOrFileAsync<List<ReliquaryAffix>>("ReliquaryAffix", token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquaryLevel>> GetReliquaryLevelsAsync(CancellationToken token = default)
|
||||
{
|
||||
return FromCacheOrFileAsync<List<ReliquaryLevel>>("ReliquaryMainAffixLevel", token);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public ValueTask<List<ReliquaryAffixBase>> GetReliquaryMainAffixesAsync(CancellationToken token = default)
|
||||
{
|
||||
@@ -295,4 +314,20 @@ internal class MetadataService : IMetadataService, IMetadataInitializer, ISuppor
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryOverride(keySelector);
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
|
||||
private async ValueTask<Dictionary<TKey, TValue>> FromCacheAsDictionaryAsync<TKey, TValue, TData>(string fileName, Func<TData, TKey> keySelector, Func<TData, TValue> valueSelector, CancellationToken token)
|
||||
where TKey : notnull
|
||||
{
|
||||
Verify.Operation(IsInitialized, "元数据服务尚未初始化,或初始化失败");
|
||||
string cacheKey = $"{nameof(MetadataService)}.Cache.{fileName}.Map.{typeof(TKey).Name}.{typeof(TValue).Name}";
|
||||
|
||||
if (memoryCache.TryGetValue(cacheKey, out object? value))
|
||||
{
|
||||
return Must.NotNull((Dictionary<TKey, TValue>)value!);
|
||||
}
|
||||
|
||||
List<TData> list = await FromCacheOrFileAsync<List<TData>>(fileName, token).ConfigureAwait(false);
|
||||
Dictionary<TKey, TValue> dict = list.ToDictionaryOverride(keySelector, valueSelector);
|
||||
return memoryCache.Set(cacheKey, dict);
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,7 @@
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<None Remove="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
<None Remove="Resource\Icon\UI_Icon_None.png" />
|
||||
<None Remove="Resource\Icon\UI_ItemIcon_201.png" />
|
||||
<None Remove="Resource\Segoe Fluent Icons.ttf" />
|
||||
@@ -48,6 +49,7 @@
|
||||
<None Remove="View\Control\StatisticsCard.xaml" />
|
||||
<None Remove="View\Dialog\AchievementArchiveCreateDialog.xaml" />
|
||||
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
|
||||
<None Remove="View\Dialog\AvatarInfoQueryDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogImportDialog.xaml" />
|
||||
<None Remove="View\Dialog\GachaLogRefreshProgressDialog.xaml" />
|
||||
<None Remove="View\Dialog\UserAutoCookieDialog.xaml" />
|
||||
@@ -56,6 +58,7 @@
|
||||
<None Remove="View\Page\AchievementPage.xaml" />
|
||||
<None Remove="View\Page\AnnouncementContentPage.xaml" />
|
||||
<None Remove="View\Page\AnnouncementPage.xaml" />
|
||||
<None Remove="View\Page\AvatarPropertyPage.xaml" />
|
||||
<None Remove="View\Page\GachaLogPage.xaml" />
|
||||
<None Remove="View\Page\SettingPage.xaml" />
|
||||
<None Remove="View\Page\WikiAvatarPage.xaml" />
|
||||
@@ -83,6 +86,7 @@
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_ActivityEntry.png" />
|
||||
<Content Include="Resource\Icon\UI_BtnIcon_Gacha.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_Achievement.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_BoostUp.png" />
|
||||
<Content Include="Resource\Icon\UI_Icon_None.png" />
|
||||
<Content Include="Resource\Icon\UI_ItemIcon_201.png" />
|
||||
</ItemGroup>
|
||||
@@ -128,6 +132,16 @@
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\AvatarInfoQueryDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Page\AvatarPropertyPage.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
</Page>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<Page Update="View\Dialog\UserAutoCookieDialog.xaml">
|
||||
<Generator>MSBuild:Compile</Generator>
|
||||
|
||||
@@ -1,9 +1,8 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.AchievementArchiveCreateDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Snap.Hutao.View.Dialog"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
Title="设置成就存档的名称"
|
||||
|
||||
@@ -5,8 +5,6 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
mc:Ignorable="d"
|
||||
Title="为当前存档导入成就"
|
||||
DefaultButton="Primary"
|
||||
@@ -14,10 +12,6 @@
|
||||
CloseButtonText="取消"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:ContentDialogBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
<ContentDialog
|
||||
x:Class="Snap.Hutao.View.Dialog.AvatarInfoQueryDialog"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:Snap.Hutao.View.Dialog"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
Title="查询UID对应的橱窗"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="请输入UID"
|
||||
CloseButtonText="取消"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
|
||||
<Grid>
|
||||
<TextBox
|
||||
x:Name="InputText"
|
||||
TextChanged="InputTextChanged"
|
||||
PlaceholderText="请输入UID"/>
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
@@ -0,0 +1,47 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.UI.Xaml;
|
||||
using Microsoft.UI.Xaml.Controls;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
namespace Snap.Hutao.View.Dialog;
|
||||
|
||||
/// <summary>
|
||||
/// 角色信息查询UID对话框
|
||||
/// </summary>
|
||||
public sealed partial class AvatarInfoQueryDialog : ContentDialog
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的角色信息查询UID对话框
|
||||
/// </summary>
|
||||
/// <param name="window">窗口</param>
|
||||
public AvatarInfoQueryDialog(Window window)
|
||||
{
|
||||
InitializeComponent();
|
||||
XamlRoot = window.Content.XamlRoot;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取玩家UID
|
||||
/// </summary>
|
||||
/// <returns>玩家UID</returns>
|
||||
public async Task<ValueResult<bool, PlayerUid>> GetPlayerUidAsync()
|
||||
{
|
||||
ContentDialogResult result = await ShowAsync();
|
||||
|
||||
return new(result == ContentDialogResult.Primary, result == ContentDialogResult.Primary ? new(InputText.Text) : default);
|
||||
}
|
||||
|
||||
private void InputTextChanged(object sender, TextChangedEventArgs e)
|
||||
{
|
||||
bool inputValid = string.IsNullOrEmpty(InputText.Text) && InputText.Text.Length == 9;
|
||||
|
||||
(PrimaryButtonText, IsPrimaryButtonEnabled) = inputValid switch
|
||||
{
|
||||
true => ("请输入正确的UID", false),
|
||||
false => ("确认", true),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -5,18 +5,12 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
mc:Ignorable="d"
|
||||
Title="导入祈愿记录"
|
||||
DefaultButton="Primary"
|
||||
PrimaryButtonText="确认"
|
||||
CloseButtonText="取消"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:ContentDialogBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -7,12 +7,10 @@
|
||||
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:cwuconv="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
mc:Ignorable="d"
|
||||
Style="{StaticResource DefaultContentDialogStyle}"
|
||||
Title="获取祈愿物品中">
|
||||
|
||||
|
||||
<ContentDialog.Resources>
|
||||
<cwuconv:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
|
||||
<DataTemplate x:Key="GachaItemDataTemplate">
|
||||
@@ -27,10 +25,6 @@
|
||||
</DataTemplate>
|
||||
</ContentDialog.Resources>
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:ContentDialogBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<StackPanel>
|
||||
<TextBlock
|
||||
Text="祈愿记录Url已失效,请重新获取"
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
|
||||
</Grid.RowDefinitions>
|
||||
<TextBlock
|
||||
Text="在下方登录"
|
||||
|
||||
@@ -4,9 +4,7 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:settings="using:SettingsUI.Controls"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
mc:Ignorable="d"
|
||||
IsPrimaryButtonEnabled="False"
|
||||
Title="设置米游社Cookie"
|
||||
@@ -14,10 +12,7 @@
|
||||
PrimaryButtonText="请输入Cookie"
|
||||
CloseButtonText="取消"
|
||||
Style="{StaticResource DefaultContentDialogStyle}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:ContentDialogBehavior/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
|
||||
<StackPanel>
|
||||
<TextBox
|
||||
Margin="0,0,0,8"
|
||||
@@ -33,7 +28,7 @@
|
||||
HorizontalAlignment="Stretch">
|
||||
<HyperlinkButton
|
||||
Margin="12,0,0,0"
|
||||
Padding="4"
|
||||
Padding="6"
|
||||
Content="立即前往"
|
||||
NavigateUri="https://www.snapgenshin.com/documents/features/mhy-account-switch.html#%E5%A6%82%E4%BD%95%E8%8E%B7%E5%8F%96-cookie"/>
|
||||
</settings:Setting>
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
IsBackEnabled="{Binding ElementName=ContentFrame,Path=CanGoBack}">
|
||||
<NavigationView.MenuItems>
|
||||
<NavigationViewItem
|
||||
Content="活动"
|
||||
Content="活动公告"
|
||||
shvh:NavHelper.NavigateTo="shvp:AnnouncementPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_BtnIcon_ActivityEntry.png}"/>
|
||||
|
||||
@@ -34,14 +34,19 @@
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_BtnIcon_Gacha.png}"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="成就"
|
||||
Content="成就管理"
|
||||
shvh:NavHelper.NavigateTo="shvp:AchievementPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_Icon_Achievement.png}"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="属性统计"
|
||||
shvh:NavHelper.NavigateTo="shvp:AvatarPropertyPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_Icon_BoostUp.png}"/>
|
||||
|
||||
<NavigationViewItemHeader Content="WIKI"/>
|
||||
|
||||
<NavigationViewItem
|
||||
Content="角色"
|
||||
Content="角色资料"
|
||||
shvh:NavHelper.NavigateTo="shvp:WikiAvatarPage"
|
||||
Icon="{shcm:BitmapIcon Source=ms-appx:///Resource/Icon/UI_BagTabIcon_Avatar.png}"/>
|
||||
|
||||
|
||||
@@ -4,14 +4,15 @@
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shmmc="using:Snap.Hutao.Model.Metadata.Converter"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
d:DataContext="{d:DesignInstance shv:AchievementViewModel}">
|
||||
@@ -42,12 +43,23 @@
|
||||
DefaultLabelPosition="Right">
|
||||
<CommandBar.Content>
|
||||
<AutoSuggestBox
|
||||
Text="{Binding SearchText,Mode=TwoWay}"
|
||||
Height="36"
|
||||
PlaceholderText="搜索成就名称,描述或编号"
|
||||
HorizontalAlignment="Stretch"
|
||||
Margin="12,6,12,0"
|
||||
Width="220"
|
||||
QueryIcon="Find"/>
|
||||
VerticalContentAlignment="Center"
|
||||
Style="{StaticResource DefaultAutoSuggestBoxStyle}"
|
||||
QueryIcon="{shcm:FontIcon Glyph=}">
|
||||
<mxi:Interaction.Behaviors>
|
||||
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
|
||||
<mxic:InvokeCommandAction
|
||||
Command="{Binding SearchAchievementCommand}"
|
||||
CommandParameter="{Binding SearchText}"/>
|
||||
</mxic:EventTriggerBehavior>
|
||||
</mxi:Interaction.Behaviors>
|
||||
</AutoSuggestBox>
|
||||
</CommandBar.Content>
|
||||
|
||||
<AppBarElementContainer>
|
||||
@@ -134,7 +146,7 @@
|
||||
<ItemsControl
|
||||
Margin="16,0,0,16"
|
||||
ItemsSource="{Binding Achievements}">
|
||||
<!--ContentThemeTransition here can make items blinking, cause we are using ItemsStackPanel-->
|
||||
<!--ContentThemeTransition here can make items blinking-->
|
||||
<!--<ItemsControl.Transitions>
|
||||
<ContentThemeTransition/>
|
||||
</ItemsControl.Transitions>-->
|
||||
|
||||
333
src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml
Normal file
333
src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml
Normal file
@@ -0,0 +1,333 @@
|
||||
<shc:ScopedPage
|
||||
x:Class="Snap.Hutao.View.Page.AvatarPropertyPage"
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations"
|
||||
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
|
||||
xmlns:cwuconv="using:CommunityToolkit.WinUI.UI.Converters"
|
||||
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
|
||||
xmlns:shc="using:Snap.Hutao.Control"
|
||||
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
|
||||
xmlns:shci="using:Snap.Hutao.Control.Image"
|
||||
xmlns:shcm="using:Snap.Hutao.Control.Markup"
|
||||
xmlns:shvc="using:Snap.Hutao.View.Control"
|
||||
xmlns:shv="using:Snap.Hutao.ViewModel"
|
||||
mc:Ignorable="d"
|
||||
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
|
||||
d:DataContext="{d:DesignInstance shv:AvatarPropertyViewModel}">
|
||||
|
||||
<mxi:Interaction.Behaviors>
|
||||
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
|
||||
</mxi:Interaction.Behaviors>
|
||||
|
||||
<Page.Resources>
|
||||
<cwuconv:BoolToObjectConverter x:Key="BoolToOpacityConverter">
|
||||
<cwuconv:BoolToObjectConverter.TrueValue>
|
||||
<x:Double>1</x:Double>
|
||||
</cwuconv:BoolToObjectConverter.TrueValue>
|
||||
<cwuconv:BoolToObjectConverter.FalseValue>
|
||||
<x:Double>0.3</x:Double>
|
||||
</cwuconv:BoolToObjectConverter.FalseValue>
|
||||
</cwuconv:BoolToObjectConverter>
|
||||
</Page.Resources>
|
||||
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="auto"/>
|
||||
<RowDefinition/>
|
||||
</Grid.RowDefinitions>
|
||||
<CommandBar
|
||||
DefaultLabelPosition="Right"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<CommandBar.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition Width="240"/>
|
||||
<ColumnDefinition Width="72"/>
|
||||
<ColumnDefinition Width="72"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<PersonPicture
|
||||
Grid.Column="0"
|
||||
Width="36"
|
||||
Margin="2,4,0,0"
|
||||
ProfilePicture="{Binding Summary.Player.ProfilePicture}"/>
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="6,6,0,0">
|
||||
<TextBlock
|
||||
Text="{Binding Summary.Player.Nickname}"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Summary.Player.Signature}"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="2"
|
||||
Margin="12,6,0,0">
|
||||
<TextBlock
|
||||
Text="成就总数"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Summary.Player.FinishAchievementNumber}"/>
|
||||
</StackPanel>
|
||||
<StackPanel
|
||||
Grid.Column="3"
|
||||
Margin="12,6,0,0">
|
||||
<TextBlock
|
||||
Text="深境螺旋"/>
|
||||
<TextBlock
|
||||
Opacity="0.6"
|
||||
TextWrapping="NoWrap"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Summary.Player.SipralAbyssFloorLevel}"/>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
</CommandBar.Content>
|
||||
<AppBarSeparator/>
|
||||
<AppBarButton
|
||||
Label="刷新"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByUserGameRoleCommand}"/>
|
||||
|
||||
<AppBarButton
|
||||
Label="按UID查询"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Command="{Binding RefreshByInputUidCommand}"/>
|
||||
</CommandBar>
|
||||
<SplitView
|
||||
Grid.Row="1"
|
||||
IsPaneOpen="True"
|
||||
DisplayMode="Inline"
|
||||
OpenPaneLength="200"
|
||||
PaneBackground="Transparent">
|
||||
<SplitView.Pane>
|
||||
<ListView
|
||||
SelectedItem="{Binding SelectedAvatar,Mode=TwoWay}"
|
||||
ItemsSource="{Binding Summary.Avatars}">
|
||||
<ListView.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:CachedImage
|
||||
Grid.Column="0"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Margin="0,0,12,12"
|
||||
Source="{Binding SideIcon,Mode=OneWay}"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Center"
|
||||
Grid.Column="1"
|
||||
Margin="12,0,0,0"
|
||||
Text="{Binding Name}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ListView.ItemTemplate>
|
||||
</ListView>
|
||||
</SplitView.Pane>
|
||||
<SplitView.Content>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="290"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel>
|
||||
<!--<Border
|
||||
Margin="12,12,12,0"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<TextBlock
|
||||
Margin="12,6"
|
||||
Style="{StaticResource SubtitleTextBlockStyle}"
|
||||
Text="{Binding SelectedAvatar.Name}"/>
|
||||
</Border>-->
|
||||
<Border
|
||||
Margin="12,12,12,0"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding SelectedAvatar.Constellations}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Padding="4"
|
||||
Background="Transparent"
|
||||
BorderBrush="{x:Null}"
|
||||
Margin="2,2,0,2">
|
||||
<shci:CachedImage
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Icon}"
|
||||
Opacity="{Binding IsActiviated,Converter={StaticResource BoolToOpacityConverter}}"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
<Border
|
||||
Margin="12,12,12,0"
|
||||
VerticalAlignment="Top"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding SelectedAvatar.Skills}">
|
||||
<ItemsControl.ItemsPanel>
|
||||
<ItemsPanelTemplate>
|
||||
<StackPanel Orientation="Horizontal"/>
|
||||
</ItemsPanelTemplate>
|
||||
</ItemsControl.ItemsPanel>
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Button
|
||||
Padding="4"
|
||||
Background="Transparent"
|
||||
BorderBrush="{x:Null}"
|
||||
Margin="2,2,0,2">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shci:CachedImage
|
||||
Width="32"
|
||||
Height="32"
|
||||
Source="{Binding Icon}"/>
|
||||
<TextBlock
|
||||
VerticalAlignment="Bottom"
|
||||
HorizontalAlignment="Right"
|
||||
Text="{Binding Info.Level}"/>
|
||||
</Grid>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
<Border
|
||||
Margin="12,12,12,0"
|
||||
Padding="6,6,0,6"
|
||||
VerticalAlignment="Top"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<ItemsControl
|
||||
ItemsSource="{Binding SelectedAvatar.Properties}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Margin="4">
|
||||
<TextBlock
|
||||
Text="{Binding Key}"
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition/>
|
||||
<ColumnDefinition Width="68"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<TextBlock
|
||||
Grid.Column="0"
|
||||
Text="{Binding Value1}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
HorizontalAlignment="Right"/>
|
||||
<TextBlock
|
||||
Margin="6,0,0,0"
|
||||
Grid.Column="1"
|
||||
Text="{Binding Value2}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
HorizontalAlignment="Left"/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</Border>
|
||||
</StackPanel>
|
||||
|
||||
<ScrollViewer Grid.Column="1" Padding="0,0,12,0">
|
||||
<cwucont:AdaptiveGridView
|
||||
DesiredWidth="224"
|
||||
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
|
||||
SelectionMode="None"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
ItemsSource="{Binding SelectedAvatar.Reliquaries}"
|
||||
Margin="0,12,0,0">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Border
|
||||
HorizontalAlignment="Stretch"
|
||||
CornerRadius="{StaticResource CompatCornerRadius}"
|
||||
Background="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<StackPanel Margin="6" HorizontalAlignment="Stretch">
|
||||
<Grid>
|
||||
<Grid.ColumnDefinitions>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
<ColumnDefinition/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<shvc:ItemIcon
|
||||
Grid.Column="0"
|
||||
HorizontalAlignment="Left"
|
||||
Width="48"
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<StackPanel Grid.Column="1" Margin="6,0,0,0">
|
||||
<Grid>
|
||||
<TextBlock
|
||||
Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Margin="12,0,0,0"
|
||||
Text="{Binding Level}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
<Grid Margin="0,10,0,0">
|
||||
<TextBlock
|
||||
Text="{Binding MainProperty.Key}"
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Text="{Binding MainProperty.Value}"
|
||||
FontWeight="Bold"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
HorizontalAlignment="Right"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
<MenuFlyoutSeparator Margin="-2,4,-2,0"/>
|
||||
<ItemsControl ItemsSource="{Binding SubProperties}">
|
||||
<ItemsControl.ItemTemplate>
|
||||
<DataTemplate>
|
||||
<Grid Padding="2">
|
||||
<TextBlock
|
||||
Text="{Binding Key}"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
<TextBlock
|
||||
Text="{Binding Value}"
|
||||
HorizontalAlignment="Right"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"/>
|
||||
</Grid>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</ItemsControl>
|
||||
</StackPanel>
|
||||
</Border>
|
||||
</DataTemplate>
|
||||
</ItemsControl.ItemTemplate>
|
||||
</cwucont:AdaptiveGridView>
|
||||
</ScrollViewer>
|
||||
</Grid>
|
||||
</SplitView.Content>
|
||||
</SplitView>
|
||||
</Grid>
|
||||
</shc:ScopedPage>
|
||||
@@ -0,0 +1,22 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.ViewModel;
|
||||
|
||||
namespace Snap.Hutao.View.Page;
|
||||
|
||||
/// <summary>
|
||||
/// 角色属性页
|
||||
/// </summary>
|
||||
public sealed partial class AvatarPropertyPage : ScopedPage
|
||||
{
|
||||
/// <summary>
|
||||
/// 初始化一个新的角色属性页
|
||||
/// </summary>
|
||||
public AvatarPropertyPage()
|
||||
{
|
||||
InitializeWith<AvatarPropertyViewModel>();
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -23,7 +23,7 @@
|
||||
<ColumnDefinition MaxWidth="1000"/>
|
||||
<ColumnDefinition Width="auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
<StackPanel Margin="32,0,24,0">
|
||||
<StackPanel Margin="32,0,24,24">
|
||||
<sc:SettingsGroup Header="关于 胡桃">
|
||||
<Grid Margin="0,4,0,16">
|
||||
<Grid.ColumnDefinitions>
|
||||
|
||||
@@ -73,10 +73,8 @@
|
||||
<SplitView
|
||||
IsPaneOpen="True"
|
||||
DisplayMode="Inline"
|
||||
OpenPaneLength="200">
|
||||
<SplitView.PaneBackground>
|
||||
<SolidColorBrush Color="{ThemeResource CardBackgroundFillColorSecondary}"/>
|
||||
</SplitView.PaneBackground>
|
||||
OpenPaneLength="200"
|
||||
PaneBackground="{StaticResource CardBackgroundFillColorSecondary}">
|
||||
<SplitView.Pane>
|
||||
<Grid>
|
||||
<Grid.RowDefinitions>
|
||||
|
||||
@@ -197,15 +197,15 @@
|
||||
<TextBlock
|
||||
Margin="10,6,0,6"
|
||||
Style="{StaticResource BaseTextBlockStyle}"
|
||||
Text="Cookie"/>
|
||||
Text="Cookie操作"/>
|
||||
<CommandBar DefaultLabelPosition="Right">
|
||||
<AppBarButton
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="升级Stoken"
|
||||
Label="登录米哈游通行证"
|
||||
Command="{Binding UpgradeToStokenCommand}"/>
|
||||
<AppBarButton
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="手动添加"
|
||||
Label="手动输入"
|
||||
Command="{Binding AddUserCommand}"/>
|
||||
</CommandBar>
|
||||
</StackPanel>
|
||||
|
||||
@@ -57,6 +57,7 @@ internal class AchievementViewModel
|
||||
private ObservableCollection<Model.Entity.AchievementArchive>? archives;
|
||||
private Model.Entity.AchievementArchive? selectedArchive;
|
||||
private bool isIncompletedItemsFirst = true;
|
||||
private string searchText = string.Empty;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的成就视图模型
|
||||
@@ -88,6 +89,7 @@ internal class AchievementViewModel
|
||||
ImportUIAFFromFileCommand = asyncRelayCommandFactory.Create(ImportUIAFFromFileAsync);
|
||||
AddArchiveCommand = asyncRelayCommandFactory.Create(AddArchiveAsync);
|
||||
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
|
||||
SearchAchievementCommand = new RelayCommand<string>(SearchAchievement);
|
||||
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
|
||||
|
||||
messenger.Register(this);
|
||||
@@ -147,10 +149,20 @@ internal class AchievementViewModel
|
||||
set
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, value);
|
||||
SearchText = string.Empty;
|
||||
UpdateAchievementFilter(value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 搜索文本
|
||||
/// </summary>
|
||||
public string SearchText
|
||||
{
|
||||
get => searchText;
|
||||
set => SetProperty(ref searchText, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 未完成优先
|
||||
/// </summary>
|
||||
@@ -175,6 +187,11 @@ internal class AchievementViewModel
|
||||
/// </summary>
|
||||
public ICommand RemoveArchiveCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 搜索成就命令
|
||||
/// </summary>
|
||||
public ICommand SearchAchievementCommand { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 从剪贴板导入UIAF命令
|
||||
/// </summary>
|
||||
@@ -345,6 +362,30 @@ internal class AchievementViewModel
|
||||
}
|
||||
}
|
||||
|
||||
private void SearchAchievement(string? search)
|
||||
{
|
||||
if (Achievements != null)
|
||||
{
|
||||
SetProperty(ref selectedAchievementGoal, null);
|
||||
|
||||
if (!string.IsNullOrEmpty(search))
|
||||
{
|
||||
if (search.Length == 5 && int.TryParse(search, out int achiId))
|
||||
{
|
||||
Achievements.Filter = (object o) => ((Model.Binding.Achievement)o).Inner.Id == achiId;
|
||||
}
|
||||
else
|
||||
{
|
||||
Achievements.Filter = (object o) =>
|
||||
{
|
||||
Model.Binding.Achievement achi = (Model.Binding.Achievement)o;
|
||||
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[ThreadAccess(ThreadAccessState.AnyThread)]
|
||||
private async Task ImportUIAFFromClipboardAsync()
|
||||
{
|
||||
@@ -469,7 +510,7 @@ internal class AchievementViewModel
|
||||
{
|
||||
Achievements.Filter = goal != null
|
||||
? ((object o) => o is Model.Binding.Achievement achi && achi.Inner.Goal == goal.Id)
|
||||
: ((object o) => true);
|
||||
: null;
|
||||
}
|
||||
}
|
||||
}
|
||||
148
src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs
Normal file
148
src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarPropertyViewModel.cs
Normal file
@@ -0,0 +1,148 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
using Snap.Hutao.Control;
|
||||
using Snap.Hutao.Core.Threading;
|
||||
using Snap.Hutao.Factory.Abstraction;
|
||||
using Snap.Hutao.Model.Binding.AvatarProperty;
|
||||
using Snap.Hutao.Service.Abstraction;
|
||||
using Snap.Hutao.Service.AvatarInfo;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
|
||||
|
||||
namespace Snap.Hutao.ViewModel;
|
||||
|
||||
/// <summary>
|
||||
/// 角色属性视图模型
|
||||
/// </summary>
|
||||
[Injection(InjectAs.Transient)]
|
||||
internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
|
||||
{
|
||||
private readonly IUserService userService;
|
||||
private readonly IAvatarInfoService avatarInfoService;
|
||||
private readonly IInfoBarService infoBarService;
|
||||
private Summary? summary;
|
||||
private Avatar? selectedAvatar;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的角色属性视图模型
|
||||
/// </summary>
|
||||
/// <param name="userService">用户服务</param>
|
||||
/// <param name="avatarInfoService">角色信息服务</param>
|
||||
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
|
||||
/// <param name="infoBarService">信息条服务</param>
|
||||
public AvatarPropertyViewModel(
|
||||
IUserService userService,
|
||||
IAvatarInfoService avatarInfoService,
|
||||
IAsyncRelayCommandFactory asyncRelayCommandFactory,
|
||||
IInfoBarService infoBarService)
|
||||
{
|
||||
this.userService = userService;
|
||||
this.avatarInfoService = avatarInfoService;
|
||||
this.infoBarService = infoBarService;
|
||||
|
||||
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
|
||||
RefreshByUserGameRoleCommand = asyncRelayCommandFactory.Create(RefreshByUserGameRoleAsync);
|
||||
RefreshByInputUidCommand = asyncRelayCommandFactory.Create(RefreshByInputUidAsync);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public CancellationToken CancellationToken { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 简述对象
|
||||
/// </summary>
|
||||
public Summary? Summary { get => summary; set => SetProperty(ref summary, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 选中的角色
|
||||
/// </summary>
|
||||
public Avatar? SelectedAvatar { get => selectedAvatar; set => SetProperty(ref selectedAvatar, value); }
|
||||
|
||||
/// <summary>
|
||||
/// 加载界面命令
|
||||
/// </summary>
|
||||
public ICommand OpenUICommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按当前角色刷新命令
|
||||
/// </summary>
|
||||
public ICommand RefreshByUserGameRoleCommand { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 按UID刷新命令
|
||||
/// </summary>
|
||||
public ICommand RefreshByInputUidCommand { get; set; }
|
||||
|
||||
private Task OpenUIAsync()
|
||||
{
|
||||
return RefreshCoreAsync(RefreshOption.DatabaseOnly);
|
||||
}
|
||||
|
||||
private Task RefreshByUserGameRoleAsync()
|
||||
{
|
||||
return RefreshCoreAsync(RefreshOption.Standard);
|
||||
}
|
||||
|
||||
private async Task RefreshByInputUidAsync()
|
||||
{
|
||||
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
|
||||
(bool isOk, PlayerUid uid) = await new AvatarInfoQueryDialog(mainWindow).GetPlayerUidAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
(RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync(uid, RefreshOption.RequestFromAPI).ConfigureAwait(false);
|
||||
|
||||
if (result == RefreshResult.Ok)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Summary = summary;
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case RefreshResult.APIUnavailable:
|
||||
infoBarService.Warning("面板服务当前不可用");
|
||||
break;
|
||||
case RefreshResult.ShowcaseNotOpen:
|
||||
infoBarService.Warning("角色橱窗尚未开启,请前往游戏操作后重试");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async Task RefreshCoreAsync(RefreshOption option)
|
||||
{
|
||||
if (userService.Current is Model.Binding.User user)
|
||||
{
|
||||
if (user.SelectedUserGameRole is UserGameRole role)
|
||||
{
|
||||
(RefreshResult result, Summary? summary) = await avatarInfoService.GetSummaryAsync((PlayerUid)role, option).ConfigureAwait(false);
|
||||
|
||||
if (result == RefreshResult.Ok)
|
||||
{
|
||||
await ThreadHelper.SwitchToMainThreadAsync();
|
||||
Summary = summary;
|
||||
SelectedAvatar = Summary?.Avatars.FirstOrDefault();
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (result)
|
||||
{
|
||||
case RefreshResult.APIUnavailable:
|
||||
infoBarService.Warning("面板服务当前不可用");
|
||||
break;
|
||||
case RefreshResult.ShowcaseNotOpen:
|
||||
infoBarService.Warning("角色橱窗尚未开启,请前往游戏操作后重试");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,6 @@ using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
|
||||
using Snap.Hutao.Web.Enka.Model;
|
||||
using Snap.Hutao.Web.Hoyolab;
|
||||
using System.Net.Http;
|
||||
using System.Net.Http.Json;
|
||||
|
||||
namespace Snap.Hutao.Web.Enka;
|
||||
|
||||
@@ -19,14 +18,20 @@ internal class EnkaClient
|
||||
private const string EnkaAPIHutaoForward = "https://enka-api.hut.ao/{0}";
|
||||
|
||||
private readonly HttpClient httpClient;
|
||||
private readonly JsonSerializerOptions options;
|
||||
private readonly ILogger<EnkaClient> logger;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的 Enka API 客户端
|
||||
/// </summary>
|
||||
/// <param name="httpClient">http客户端</param>
|
||||
public EnkaClient(HttpClient httpClient)
|
||||
/// <param name="options">序列化选项</param>
|
||||
/// <param name="logger">日志器</param>
|
||||
public EnkaClient(HttpClient httpClient, JsonSerializerOptions options, ILogger<EnkaClient> logger)
|
||||
{
|
||||
this.httpClient = httpClient;
|
||||
this.options = options;
|
||||
this.logger = logger;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -37,7 +42,7 @@ internal class EnkaClient
|
||||
/// <returns>Enka API 响应</returns>
|
||||
public Task<EnkaResponse?> GetForwardDataAsync(PlayerUid playerUid, CancellationToken token)
|
||||
{
|
||||
return httpClient.GetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPIHutaoForward, playerUid.Value), token);
|
||||
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPIHutaoForward, playerUid.Value), options, logger, token);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -48,6 +53,6 @@ internal class EnkaClient
|
||||
/// <returns>Enka API 响应</returns>
|
||||
public Task<EnkaResponse?> GetDataAsync(PlayerUid playerUid, CancellationToken token)
|
||||
{
|
||||
return httpClient.GetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPI, playerUid.Value), token);
|
||||
return httpClient.TryCatchGetFromJsonAsync<EnkaResponse>(string.Format(EnkaAPI, playerUid.Value), options, logger, token);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class AvatarInfo
|
||||
/// 命座 Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("talentIdList")]
|
||||
public IList<int> TalentIdList { get; set; } = default!;
|
||||
public IList<int>? TalentIdList { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 属性Map
|
||||
@@ -86,5 +86,5 @@ public class AvatarInfo
|
||||
/// 命座额外技能等级
|
||||
/// </summary>
|
||||
[JsonPropertyName("proudSkillExtraLevelMap")]
|
||||
public IDictionary<string, int> ProudSkillExtraLevelMap { get; set; } = default!;
|
||||
public IDictionary<string, int>? ProudSkillExtraLevelMap { get; set; } = default!;
|
||||
}
|
||||
@@ -32,14 +32,7 @@ public class EnkaResponse
|
||||
/// </summary>
|
||||
public bool IsValid
|
||||
{
|
||||
get => Ttl.HasValue;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否包含角色详细数据
|
||||
/// </summary>
|
||||
public bool HasDetail
|
||||
{
|
||||
get => AvatarInfoList != null;
|
||||
[MemberNotNullWhen(true, nameof(PlayerInfo), nameof(AvatarInfoList))]
|
||||
get => PlayerInfo != null && AvatarInfoList != null;
|
||||
}
|
||||
}
|
||||
@@ -83,4 +83,19 @@ public class PlayerInfo
|
||||
/// </summary>
|
||||
[JsonPropertyName("profilePicture")]
|
||||
public ProfilePicture ProfilePicture { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 创建空对象
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <returns>空的玩家信息</returns>
|
||||
public static PlayerInfo CreateEmpty(string uid)
|
||||
{
|
||||
return new()
|
||||
{
|
||||
Nickname = uid,
|
||||
Signature = string.Empty,
|
||||
ProfilePicture = new() { AvatarId = 10000046 }, // use Hutao as default.
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,4 +13,10 @@ public class ProfilePicture
|
||||
/// </summary>
|
||||
[JsonPropertyName("avatarId")]
|
||||
public int AvatarId { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 衣装Id
|
||||
/// </summary>
|
||||
[JsonPropertyName("costumeId")]
|
||||
public int? CostumeId { get; set; }
|
||||
}
|
||||
@@ -1,7 +1,6 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore.Metadata.Internal;
|
||||
using Microsoft.Web.WebView2.Core;
|
||||
using Snap.Hutao.Extension;
|
||||
|
||||
|
||||
@@ -29,7 +29,7 @@ internal abstract class DynamicSecretProvider2 : Md5Convert
|
||||
string b = postBody is null ? string.Empty : JsonSerializer.Serialize(postBody, options);
|
||||
|
||||
// query
|
||||
string q = string.Join("&", new UriBuilder(queryUrl).Query.Split('&').OrderBy(x => x));
|
||||
string q = string.Join('&', new UriBuilder(queryUrl).Query.Split('&').OrderBy(x => x));
|
||||
|
||||
// check
|
||||
string check = ToHexString($"salt={Core.CoreEnvironment.DynamicSecret2Salt}&t={t}&r={r}&b={b}&q={q}").ToLowerInvariant();
|
||||
|
||||
@@ -1,31 +0,0 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab;
|
||||
|
||||
/// <summary>
|
||||
/// Uid Token 对
|
||||
/// </summary>
|
||||
public struct UidToken
|
||||
{
|
||||
/// <summary>
|
||||
/// Uid
|
||||
/// </summary>
|
||||
public string Uid;
|
||||
|
||||
/// <summary>
|
||||
/// Token
|
||||
/// </summary>
|
||||
public string Token;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的 Uid Token 对
|
||||
/// </summary>
|
||||
/// <param name="uid">uid</param>
|
||||
/// <param name="token">token</param>
|
||||
public UidToken(string uid, string token)
|
||||
{
|
||||
Uid = uid;
|
||||
Token = token;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user