achievement progress

This commit is contained in:
DismissedLight
2022-12-20 15:11:05 +08:00
parent 0073636676
commit 2c0b32ab8b
61 changed files with 1118 additions and 536 deletions

View File

@@ -4,7 +4,7 @@
using System.Runtime.InteropServices;
using Windows.UI;
namespace Snap.Hutao.Control.Image;
namespace Snap.Hutao.Control.Media;
/// <summary>
/// BGRA8 结构

View File

@@ -0,0 +1,175 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
// Some part of this file came from:
// https://github.com/xunkong/desktop/tree/main/src/Desktop/Desktop/Pages/CharacterInfoPage.xaml.cs
using CommunityToolkit.WinUI;
using System.Runtime.InteropServices;
using Windows.UI;
namespace Snap.Hutao.Control.Media;
/// <summary>
/// RGBA 颜色
/// </summary>
[StructLayout(LayoutKind.Explicit)]
public struct Rgba8
{
/// <summary>
/// R
/// </summary>
[FieldOffset(3)]
public byte R;
/// <summary>
/// G
/// </summary>
[FieldOffset(2)]
public byte G;
/// <summary>
/// B
/// </summary>
[FieldOffset(1)]
public byte B;
/// <summary>
/// A
/// </summary>
[FieldOffset(0)]
public byte A;
[FieldOffset(0)]
private readonly uint data;
/// <summary>
/// 构造一个新的 RGBA8 颜色
/// </summary>
/// <param name="hex">色值字符串</param>
public Rgba8(ReadOnlySpan<char> hex)
{
Must.Argument(hex.Length == 8, "色值长度不为8");
R = 0;
G = 0;
B = 0;
A = 0;
data = Convert.ToUInt32(hex.ToString(), 16);
}
private Rgba8(byte r, byte g, byte b, byte a)
{
data = 0;
R = r;
G = g;
B = b;
A = a;
}
public static implicit operator Color(Rgba8 hexColor)
{
return Color.FromArgb(hexColor.A, hexColor.R, hexColor.G, hexColor.B);
}
/// <summary>
/// 从 HSL 颜色转换
/// </summary>
/// <param name="hsl">HSL 颜色</param>
/// <returns>RGBA8颜色</returns>
public static Rgba8 FromHsl(HslColor hsl)
{
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
double h1 = hsl.H / 60;
double x = chroma * (1 - Math.Abs((h1 % 2) - 1));
double m = hsl.L - (0.5 * chroma);
double r1, g1, b1;
if (h1 < 1)
{
r1 = chroma;
g1 = x;
b1 = 0;
}
else if (h1 < 2)
{
r1 = x;
g1 = chroma;
b1 = 0;
}
else if (h1 < 3)
{
r1 = 0;
g1 = chroma;
b1 = x;
}
else if (h1 < 4)
{
r1 = 0;
g1 = x;
b1 = chroma;
}
else if (h1 < 5)
{
r1 = x;
g1 = 0;
b1 = chroma;
}
else
{
r1 = chroma;
g1 = 0;
b1 = x;
}
byte r = (byte)(255 * (r1 + m));
byte g = (byte)(255 * (g1 + m));
byte b = (byte)(255 * (b1 + m));
byte a = (byte)(255 * hsl.A);
return new(r, g, b, a);
}
/// <summary>
/// 转换到 HSL 颜色
/// </summary>
/// <returns>HSL 颜色</returns>
public HslColor ToHsl()
{
const double toDouble = 1.0 / 255;
double r = toDouble * R;
double g = toDouble * G;
double b = toDouble * B;
double max = Math.Max(Math.Max(r, g), b);
double min = Math.Min(Math.Min(r, g), b);
double chroma = max - min;
double h1;
if (chroma == 0)
{
h1 = 0;
}
else if (max == r)
{
// The % operator doesn't do proper modulo on negative
// numbers, so we'll add 6 before using it
h1 = (((g - b) / chroma) + 6) % 6;
}
else if (max == g)
{
h1 = 2 + ((b - r) / chroma);
}
else
{
h1 = 4 + ((r - g) / chroma);
}
double lightness = 0.5 * (max + min);
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
HslColor ret;
ret.H = 60 * h1;
ret.S = saturation;
ret.L = lightness;
ret.A = toDouble * A;
return ret;
}
}

View File

@@ -0,0 +1,42 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Win32;
using Windows.Win32.System.WinRT;
using WinRT;
namespace Snap.Hutao.Control.Media;
/// <summary>
/// 软件位图拓展
/// </summary>
public static class SoftwareBitmapExtension
{
/// <summary>
/// 混合模式 正常
/// </summary>
/// <param name="softwareBitmap">软件位图</param>
/// <param name="tint">底色</param>
public static unsafe void NormalBlend(this SoftwareBitmap softwareBitmap, Bgra8 tint)
{
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
{
using (IMemoryBufferReference reference = buffer.CreateReference())
{
reference.As<IMemoryBufferByteAccess>().GetBuffer(out byte* data, out uint length);
for (int i = 0; i < length; i += 4)
{
Bgra8* pixel = (Bgra8*)(data + i);
byte baseAlpha = pixel->A;
pixel->B = (byte)(((pixel->B * baseAlpha) + (tint.B * (0xFF - baseAlpha))) / 0xFF);
pixel->G = (byte)(((pixel->G * baseAlpha) + (tint.G * (0xFF - baseAlpha))) / 0xFF);
pixel->R = (byte)(((pixel->R * baseAlpha) + (tint.R * (0xFF - baseAlpha))) / 0xFF);
pixel->A = 0xFF;
}
}
}
}
}

View File

@@ -8,8 +8,8 @@ using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core;
using System.Runtime.InteropServices;
using Windows.UI;
namespace Snap.Hutao.Control.Text;
@@ -19,8 +19,7 @@ namespace Snap.Hutao.Control.Text;
/// </summary>
public class DescriptionTextBlock : ContentControl
{
private static readonly DependencyProperty DescriptionProperty =
Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly DependencyProperty DescriptionProperty = Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".Length;
@@ -79,7 +78,7 @@ public class DescriptionTextBlock : ContentControl
else if (description[i] == '<' && description[i + 1] == 'c')
{
AppendText(text, description[last..i]);
HexColor color = new(description.Slice(i + 8, 8));
Rgba8 color = new(description.Slice(i + 8, 8));
int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(text, description.Slice(i + ColorTagLeftLength, length), color);
@@ -115,7 +114,7 @@ public class DescriptionTextBlock : ContentControl
text.Inlines.Add(new Run { Text = slice.ToString() });
}
private static void AppendColorText(TextBlock text, ReadOnlySpan<char> slice, HexColor color)
private static void AppendColorText(TextBlock text, ReadOnlySpan<char> slice, Rgba8 color)
{
Color targetColor;
if (ThemeHelper.IsDarkMode(text.ActualTheme))
@@ -126,7 +125,7 @@ public class DescriptionTextBlock : ContentControl
{
HslColor hsl = color.ToHsl();
hsl.L *= 0.3;
targetColor = HexColor.FromHsl(hsl);
targetColor = Rgba8.FromHsl(hsl);
}
text.Inlines.Add(new Run
@@ -154,138 +153,4 @@ public class DescriptionTextBlock : ContentControl
{
ApplyDescription((TextBlock)Content, Description);
}
[StructLayout(LayoutKind.Explicit)]
private struct HexColor
{
[FieldOffset(3)]
public byte R;
[FieldOffset(2)]
public byte G;
[FieldOffset(1)]
public byte B;
[FieldOffset(0)]
public byte A;
[FieldOffset(0)]
private readonly uint data;
public HexColor(ReadOnlySpan<char> hex)
{
Must.Argument(hex.Length == 8, "色值长度不为8");
R = 0;
G = 0;
B = 0;
A = 0;
data = Convert.ToUInt32(hex.ToString(), 16);
}
private HexColor(byte r, byte g, byte b, byte a)
{
data = 0;
R = r;
G = g;
B = b;
A = a;
}
public static implicit operator Color(HexColor hexColor)
{
return Color.FromArgb(hexColor.A, hexColor.R, hexColor.G, hexColor.B);
}
public static HexColor FromHsl(HslColor hsl)
{
double chroma = (1 - Math.Abs((2 * hsl.L) - 1)) * hsl.S;
double h1 = hsl.H / 60;
double x = chroma * (1 - Math.Abs((h1 % 2) - 1));
double m = hsl.L - (0.5 * chroma);
double r1, g1, b1;
if (h1 < 1)
{
r1 = chroma;
g1 = x;
b1 = 0;
}
else if (h1 < 2)
{
r1 = x;
g1 = chroma;
b1 = 0;
}
else if (h1 < 3)
{
r1 = 0;
g1 = chroma;
b1 = x;
}
else if (h1 < 4)
{
r1 = 0;
g1 = x;
b1 = chroma;
}
else if (h1 < 5)
{
r1 = x;
g1 = 0;
b1 = chroma;
}
else
{
r1 = chroma;
g1 = 0;
b1 = x;
}
byte r = (byte)(255 * (r1 + m));
byte g = (byte)(255 * (g1 + m));
byte b = (byte)(255 * (b1 + m));
byte a = (byte)(255 * hsl.A);
return new(r, g, b, a);
}
public HslColor ToHsl()
{
const double toDouble = 1.0 / 255;
double r = toDouble * R;
double g = toDouble * G;
double b = toDouble * B;
double max = Math.Max(Math.Max(r, g), b);
double min = Math.Min(Math.Min(r, g), b);
double chroma = max - min;
double h1;
if (chroma == 0)
{
h1 = 0;
}
else if (max == r)
{
// The % operator doesn't do proper modulo on negative
// numbers, so we'll add 6 before using it
h1 = (((g - b) / chroma) + 6) % 6;
}
else if (max == g)
{
h1 = 2 + ((b - r) / chroma);
}
else
{
h1 = 4 + ((r - g) / chroma);
}
double lightness = 0.5 * (max + min);
double saturation = chroma == 0 ? 0 : chroma / (1 - Math.Abs((2 * lightness) - 1));
HslColor ret;
ret.H = 60 * h1;
ret.S = saturation;
ret.L = lightness;
ret.A = toDouble * A;
return ret;
}
}
}
}

View File

@@ -5,8 +5,10 @@ using Microsoft.Win32;
using Snap.Hutao.Core.Convert;
using Snap.Hutao.Core.Json;
using Snap.Hutao.Extension;
using System.Runtime.InteropServices;
using System.Text.Json.Serialization.Metadata;
using Windows.ApplicationModel;
using Windows.Win32.Foundation;
namespace Snap.Hutao.Core;

View File

@@ -44,11 +44,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static Task<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
public static async ValueTask<int> AddAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.Add(entity);
return dbSet.Context().SaveChangesAsync();
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
/// <summary>
@@ -72,11 +72,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param>
/// <param name="entities">实体</param>
/// <returns>影响条数</returns>
public static Task<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
public static async ValueTask<int> AddRangeAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, IEnumerable<TEntity> entities)
where TEntity : class
{
dbSet.AddRange(entities);
return dbSet.Context().SaveChangesAsync();
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
/// <summary>
@@ -100,11 +100,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static Task<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
public static async ValueTask<int> RemoveAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.Remove(entity);
return dbSet.Context().SaveChangesAsync();
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
/// <summary>
@@ -128,10 +128,10 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param>
/// <returns>影响条数</returns>
public static Task<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
public static async ValueTask<int> UpdateAndSaveAsync<TEntity>(this DbSet<TEntity> dbSet, TEntity entity)
where TEntity : class
{
dbSet.Update(entity);
return dbSet.Context().SaveChangesAsync();
return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core;
/// <summary>
/// 任务计划器服务
/// </summary>
internal static class TaskSchedulerHelper
internal static class ScheduleTaskHelper
{
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";

View File

@@ -43,6 +43,9 @@ public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueue
/// <inheritdoc/>
public void OnCompleted(Action continuation)
{
dispatherQueue.TryEnqueue(() => { continuation(); });
dispatherQueue.TryEnqueue(() =>
{
continuation();
});
}
}

View File

@@ -50,6 +50,10 @@ internal abstract class WebView2Helper
/// </summary>
public static string Version
{
get => version;
get
{
_ = IsSupported;
return version;
}
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core.Windowing;
/// <summary>
/// <see cref="AppWindow"/> 扩展
/// </summary>
public static class AppWindowExtensions
public static class AppWindowExtension
{
/// <summary>
/// 获取当前 <see cref="AppWindow"/> 的呈现矩形

View File

@@ -134,7 +134,8 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMe
(string pos, string size) = GetPostionAndSize(appWindow);
logger.LogInformation(EventIds.WindowState, "Postion: [{pos}], Size: [{size}]", pos, size);
appWindow.Show(true);
// appWindow.Show(true);
window.Activate();
systemBackdrop = new(window);
bool micaApplied = systemBackdrop.TryApply();

View File

@@ -3,7 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model.Binding;
namespace Snap.Hutao.Model.Binding.Achievement;
/// <summary>
/// 用于视图绑定的成就

View File

@@ -0,0 +1,68 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model.Binding.Achievement;
/// <summary>
/// 绑定成就分类
/// </summary>
public class AchievementGoal : ObservableObject
{
private double finishPercent;
private string? finishDescription;
/// <summary>
/// 构造一个新的成就分类
/// </summary>
/// <param name="goal">分类</param>
public AchievementGoal(Metadata.Achievement.AchievementGoal goal)
{
Id = goal.Id;
Order = goal.Order;
Name = goal.Name;
Icon = Metadata.Converter.AchievementIconConverter.IconNameToUri(goal.Icon);
}
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 排序顺序
/// </summary>
public int Order { get; set; }
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public Uri? Icon { get; set; }
/// <summary>
/// 完成百分比
/// </summary>
public double FinishPercent { get => finishPercent; set => SetProperty(ref finishPercent, value); }
/// <summary>
/// 完成百分比
/// </summary>
public string? FinishDescription { get => finishDescription; set => SetProperty(ref finishDescription, value); }
/// <summary>
/// 更新进度
/// </summary>
/// <param name="finished">完成项</param>
/// <param name="count">总项</param>
public void UpdateFinishPercent(int finished, int count)
{
FinishPercent = (double)finished / count;
FinishDescription = $"{finished}/{count} - {FinishPercent:P2}";
}
}

View File

@@ -1,14 +1,17 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary>
/// 角色信息
/// </summary>
public class Avatar
public class Avatar : ICalculableSource<ICalculableAvatar>
{
/// <summary>
/// 名称
@@ -45,11 +48,6 @@ public class Avatar
/// </summary>
public string Level { get; set; } = default!;
/// <summary>
/// 好感度等级
/// </summary>
public int FetterLevel { get; set; }
/// <summary>
/// 武器
/// </summary>
@@ -84,4 +82,25 @@ public class Avatar
/// 双爆评分
/// </summary>
public string CritScore { get; set; } = default!;
/// <summary>
/// 好感度等级
/// </summary>
public int FetterLevel { get; set; }
/// <summary>
/// Id
/// </summary>
internal AvatarId Id { get; set; }
/// <summary>
/// 等级数字
/// </summary>
internal int LevelNumber { get; set; }
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()
{
return new CalculableAvatar(this);
}
}

View File

@@ -1,22 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary>
/// 天赋
/// </summary>
public class Skill : NameIconDescription
public class Skill : NameIconDescription, ICalculableSource<ICalculableSkill>
{
/// <summary>
/// 技能属性
/// </summary>
public LevelParam<string, ParameterInfo> Info { get; set; } = default!;
/// <summary>
/// 技能组Id
/// </summary>
internal int GroupId { get; set; }
/// <summary>
/// 技能等级,仅用于养成计算
/// </summary>
internal int Level { get; set; }
internal int LevelNumber { get; set; }
/// <inheritdoc/>
public ICalculableSkill ToCalculable()
{
return new CalculableSkill(this);
}
}

View File

@@ -1,12 +1,16 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary>
/// 武器
/// </summary>
public class Weapon : EquipBase
public class Weapon : EquipBase, ICalculableSource<ICalculableWeapon>
{
/// <summary>
/// 副属性
@@ -27,4 +31,20 @@ public class Weapon : EquipBase
/// 精炼被动
/// </summary>
public string AffixDescription { get; set; } = default!;
/// <summary>
/// Id
/// </summary>
internal WeaponId Id { get; set; }
/// <summary>
/// 等级数字
/// </summary>
internal int LevelNumber { get; set; }
/// <inheritdoc/>
public ICalculableWeapon ToCalculable()
{
return new CalculableWeapon(this);
}
}

View File

@@ -7,7 +7,7 @@ using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
namespace Snap.Hutao.Model.Calculable;
/// <summary>
/// 可计算角色
@@ -21,7 +21,7 @@ internal class CalculableAvatar : ObservableObject, ICalculableAvatar
/// 构造一个新的可计算角色
/// </summary>
/// <param name="avatar">角色</param>
public CalculableAvatar(Avatar avatar)
public CalculableAvatar(Metadata.Avatar.Avatar avatar)
{
AvatarId = avatar.Id;
LevelMin = 1;
@@ -30,6 +30,25 @@ internal class CalculableAvatar : ObservableObject, ICalculableAvatar
Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算角色
/// </summary>
/// <param name="avatar">角色</param>
public CalculableAvatar(Binding.AvatarProperty.Avatar avatar)
{
AvatarId = avatar.Id;
LevelMin = avatar.LevelNumber;
LevelMax = 90; // hard coded 90
Skills = avatar.Skills.Select(s => s.ToCalculable()).ToList();
Name = avatar.Name;
Icon = avatar.Icon;
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}

View File

@@ -3,10 +3,11 @@
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
namespace Snap.Hutao.Model.Calculable;
/// <summary>
/// 可计算的技能
@@ -28,6 +29,24 @@ internal class CalculableSkill : ObservableObject, ICalculableSkill
Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = ItemQuality.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
public CalculableSkill(Binding.AvatarProperty.Skill skill)
{
GruopId = skill.GroupId;
LevelMin = skill.LevelNumber;
LevelMax = 10; // hard coded 10 here
Name = skill.Name;
Icon = skill.Icon;
Quality = ItemQuality.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}

View File

@@ -7,7 +7,7 @@ using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Weapon;
namespace Snap.Hutao.Model.Calculable;
/// <summary>
/// 可计算武器
@@ -21,7 +21,7 @@ public class CalculableWeapon : ObservableObject, ICalculableWeapon
/// 构造一个新的可计算武器
/// </summary>
/// <param name="weapon">武器</param>
public CalculableWeapon(Weapon weapon)
public CalculableWeapon(Metadata.Weapon.Weapon weapon)
{
WeaponId = weapon.Id;
LevelMin = 1;
@@ -29,6 +29,24 @@ public class CalculableWeapon : ObservableObject, ICalculableWeapon
Name = weapon.Name;
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
Quality = weapon.RankLevel;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算武器
/// </summary>
/// <param name="weapon">武器</param>
public CalculableWeapon(Binding.AvatarProperty.Weapon weapon)
{
WeaponId = weapon.Id;
LevelMin = weapon.LevelNumber;
LevelMax = (int)weapon.Quality >= 3 ? 90 : 70;
Name = weapon.Name;
Icon = weapon.Icon;
Quality = weapon.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}

View File

@@ -0,0 +1,38 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Extension;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 不可变的原生枚举
/// </summary>
public static class ImmutableIntrinsics
{
/// <summary>
/// 所属地区
/// </summary>
public static readonly ImmutableList<string> AssociationTypes = Enum.GetValues<AssociationType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
/// <summary>
/// 武器类型
/// </summary>
public static readonly ImmutableList<string> WeaponTypes = Enum.GetValues<WeaponType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
/// <summary>
/// 物品类型
/// </summary>
public static readonly ImmutableList<string> ItemQualities = Enum.GetValues<ItemQuality>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
/// <summary>
/// 身材类型
/// </summary>
public static readonly ImmutableList<string> BodyTypes = Enum.GetValues<BodyType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
/// <summary>
/// 战斗属性
/// </summary>
public static readonly ImmutableList<string> FightProperties = Enum.GetValues<FightProperty>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
}

View File

@@ -94,4 +94,4 @@ public enum WeaponType
/// </summary>
[Description("单手剑")]
WEAPON_POLE = 13,
}
}

View File

@@ -31,5 +31,5 @@ public class AchievementGoal
/// <summary>
/// 图标
/// </summary>
public string? Icon { get; set; }
public string Icon { get; set; } = default!;
}

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;

View File

@@ -80,6 +80,8 @@ public static class AvatarIds
public static readonly AvatarId Layla = 10000074;
public static readonly AvatarId Wanderer = 10000075;
public static readonly AvatarId Faruzan = 10000076;
public static readonly AvatarId Yaoyao = 10000077;
public static readonly AvatarId Alhaitham = 10000078;
/// <summary>
/// 检查该角色是否为主角

View File

@@ -12,9 +12,19 @@ internal class AchievementIconConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/AchievementIcon/{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 new Uri(string.Format(BaseUrl, from));
return IconNameToUri(from);
}
}

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Control;
using Snap.Hutao.Control.Image;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Converter;

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;

View File

@@ -4,6 +4,8 @@ WM_GETMINMAXINFO
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
STDAPI
// Type definition
CWMO_FLAGS
HRESULT

View File

@@ -12,7 +12,7 @@
<Identity
Name="7f0db578-026f-4e0b-a75b-d5d06bb0a74d"
Publisher="CN=DGP Studio"
Version="1.2.13.0" />
Version="1.2.14.0" />
<Properties>
<DisplayName>胡桃</DisplayName>

View File

@@ -66,9 +66,7 @@ public static partial class Program
ServiceProvider services = new ServiceCollection()
// Microsoft extension
.AddLogging(builder => builder
.AddDebug()
.AddDatabase())
.AddLogging(builder => builder.AddDebug().AddDatabase())
.AddMemoryCache()
// Hutao extensions

View File

@@ -9,7 +9,7 @@ using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;

View File

@@ -4,7 +4,7 @@
using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;

View File

@@ -15,28 +15,38 @@ internal class AffixWeight : Dictionary<FightProperty, double>
/// 构造一个新的词条权重
/// </summary>
/// <param name="avatarId">角色Id</param>
/// <param name="hp">大生命</param>
/// <param name="atk">大攻击</param>
/// <param name="def">大防御</param>
/// <param name="cr">暴击率</param>
/// <param name="ch">暴击伤害</param>
/// <param name="em">元素精通</param>
/// <param name="ce">充能效率</param>
/// <param name="ha">治疗加成</param>
/// <param name="hpPercent">大生命</param>
/// <param name="attackPercenr">大攻击</param>
/// <param name="defensePercent">大防御</param>
/// <param name="critical">暴击率</param>
/// <param name="criticalHurt">暴击伤害</param>
/// <param name="elementMastery">元素精通</param>
/// <param name="chargeEfficiency">充能效率</param>
/// <param name="healAdd">治疗加成</param>
/// <param name="name">名称</param>
public AffixWeight(int avatarId, double hp, double atk, double def, double cr, double ch, double em, double ce, double ha, string name = "通用")
public AffixWeight(
int avatarId,
double hpPercent,
double attackPercenr,
double defensePercent,
double critical,
double criticalHurt,
double elementMastery,
double chargeEfficiency,
double healAdd,
string name = "通用")
{
AvatarId = avatarId;
Name = name;
this[FightProperty.FIGHT_PROP_HP_PERCENT] = hp;
this[FightProperty.FIGHT_PROP_ATTACK_PERCENT] = atk;
this[FightProperty.FIGHT_PROP_DEFENSE_PERCENT] = def;
this[FightProperty.FIGHT_PROP_CRITICAL] = cr;
this[FightProperty.FIGHT_PROP_CRITICAL_HURT] = ch;
this[FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = em;
this[FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = ce;
this[FightProperty.FIGHT_PROP_HEAL_ADD] = ha;
this[FightProperty.FIGHT_PROP_HP_PERCENT] = hpPercent;
this[FightProperty.FIGHT_PROP_ATTACK_PERCENT] = attackPercenr;
this[FightProperty.FIGHT_PROP_DEFENSE_PERCENT] = defensePercent;
this[FightProperty.FIGHT_PROP_CRITICAL] = critical;
this[FightProperty.FIGHT_PROP_CRITICAL_HURT] = criticalHurt;
this[FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = elementMastery;
this[FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = chargeEfficiency;
this[FightProperty.FIGHT_PROP_HEAL_ADD] = healAdd;
}
/// <summary>
@@ -48,4 +58,92 @@ internal class AffixWeight : Dictionary<FightProperty, double>
/// 名称
/// </summary>
public string Name { get; }
/// <summary>
/// 风元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Anemo(double value = 100)
{
this[FightProperty.FIGHT_PROP_WIND_ADD_HURT] = value;
return this;
}
/// <summary>
/// 冰元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Cryo(double value = 100)
{
this[FightProperty.FIGHT_PROP_ICE_ADD_HURT] = value;
return this;
}
/// <summary>
/// 草元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Dendro(double value = 100)
{
this[FightProperty.FIGHT_PROP_GRASS_ADD_HURT] = value;
return this;
}
/// <summary>
/// 雷元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Electro(double value = 100)
{
this[FightProperty.FIGHT_PROP_ELEC_ADD_HURT] = value;
return this;
}
/// <summary>
/// 岩元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Geo(double value = 100)
{
this[FightProperty.FIGHT_PROP_ROCK_ADD_HURT] = value;
return this;
}
/// <summary>
/// 水元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Hydro(double value = 100)
{
this[FightProperty.FIGHT_PROP_WATER_ADD_HURT] = value;
return this;
}
/// <summary>
/// 火元素伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Pyro(double value = 100)
{
this[FightProperty.FIGHT_PROP_FIRE_ADD_HURT] = value;
return this;
}
/// <summary>
/// 物理伤害伤害加成
/// </summary>
/// <param name="value">值</param>
/// <returns>链式调用对象</returns>
public AffixWeight Phyiscal(double value = 100)
{
this[FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT] = value;
return this;
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -14,74 +13,74 @@ internal static partial class ReliquaryWeightConfiguration
/// <summary>
/// 默认
/// </summary>
public static readonly AffixWeight Default = new(0, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } };
public static readonly AffixWeight Default = new(0, 100, 75, 0, 100, 100, 0, 55, 0);
/// <summary>
/// 词条权重
/// </summary>
public static readonly List<AffixWeight> AffixWeights = new()
{
new(AvatarIds.Ayaka, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Qin, 0, 75, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Lisa, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Barbara, 100, 50, 0, 50, 50, 0, 55, 100) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 80 } },
new(AvatarIds.Barbara, 50, 75, 0, 100, 100, 0, 55, 100, "暴力奶妈") { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Kaeya, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Diluc, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Razor, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 50 }, { FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, 100 } },
new(AvatarIds.Ambor, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Venti, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Xiangling, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Beidou, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Xingqiu, 0, 75, 0, 100, 100, 0, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Xiao, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Ningguang, 0, 75, 0, 100, 100, 0, 30, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } },
new(AvatarIds.Klee, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Zhongli, 80, 75, 0, 100, 100, 0, 55, 0, "武神钟离") { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 }, { FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, 50 } },
new(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 55, 0, "血牛钟离") { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 75 } },
new(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 75, 0, "血牛钟离2命+") { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 75 } },
new(AvatarIds.Fischl, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Bennett, 100, 50, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 70 } },
new(AvatarIds.Tartaglia, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Noel, 0, 50, 90, 100, 100, 0, 70, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } },
new(AvatarIds.Qiqi, 0, 100, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 60 } },
new(AvatarIds.Chongyun, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 75, 0, 0, "融化流") { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 0, 55, 0, "永冻流") { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Albedo, 0, 0, 100, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } },
new(AvatarIds.Diona, 100, 50, 0, 50, 50, 0, 90, 100) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Mona, 0, 75, 0, 100, 100, 75, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Keqing, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 }, { FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, 100 } },
new(AvatarIds.Sucrose, 0, 75, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 40 } },
new(AvatarIds.Xinyan, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 50 } },
new(AvatarIds.Rosaria, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 70 }, { FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, 80 } },
new(AvatarIds.Hutao, 80, 50, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Kazuha, 0, 75, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Feiyan, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Yoimiya, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } },
new(AvatarIds.Tohma, 100, 50, 0, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 75 } },
new(AvatarIds.Eula, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 40 }, { FightProperty.FIGHT_PROP_PHYSICAL_ADD_HURT, 100 } },
new(AvatarIds.Shougun, 0, 75, 0, 100, 100, 0, 90, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 75 } },
new(AvatarIds.Sayu, 0, 50, 0, 50, 50, 100, 55, 100) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 80 } },
new(AvatarIds.Kokomi, 100, 50, 0, 0, 0, 0, 55, 100) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Gorou, 0, 50, 100, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 25 } },
new(AvatarIds.Sara, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Itto, 0, 50, 100, 100, 100, 0, 30, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } },
new(AvatarIds.Yae, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Heizou, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } },
new(AvatarIds.Yelan, 80, 0, 0, 100, 100, 0, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Aloy, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Shenhe, 0, 100, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } },
new(AvatarIds.Yunjin, 0, 0, 100, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 25 } },
new(AvatarIds.Shinobu, 100, 50, 0, 100, 100, 75, 55, 100) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Ayato, 50, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Collei, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } },
new(AvatarIds.Dori, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Tighnari, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } },
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "直伤流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "反应流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Cyno, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } },
new(AvatarIds.Candace, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } },
new(AvatarIds.Nahida, 0, 55, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } },
new AffixWeight(AvatarIds.Ayaka, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new AffixWeight(AvatarIds.Qin, 0, 75, 0, 100, 100, 0, 55, 100).Anemo(),
new AffixWeight(AvatarIds.Lisa, 0, 75, 0, 100, 100, 75, 0, 0).Electro(),
new AffixWeight(AvatarIds.Barbara, 100, 50, 0, 50, 50, 0, 55, 100).Hydro(80),
new AffixWeight(AvatarIds.Barbara, 50, 75, 0, 100, 100, 0, 55, 100, "暴力奶妈").Hydro(),
new AffixWeight(AvatarIds.Kaeya, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new AffixWeight(AvatarIds.Diluc, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Razor, 0, 75, 0, 100, 100, 0, 0, 0).Electro(50).Phyiscal(),
new AffixWeight(AvatarIds.Ambor, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Venti, 0, 75, 0, 100, 100, 75, 55, 0).Anemo(),
new AffixWeight(AvatarIds.Xiangling, 0, 75, 0, 100, 100, 75, 55, 0).Pyro(),
new AffixWeight(AvatarIds.Beidou, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new AffixWeight(AvatarIds.Xingqiu, 0, 75, 0, 100, 100, 0, 75, 0).Hydro(),
new AffixWeight(AvatarIds.Xiao, 0, 75, 0, 100, 100, 0, 55, 0).Anemo(),
new AffixWeight(AvatarIds.Ningguang, 0, 75, 0, 100, 100, 0, 30, 0).Geo(),
new AffixWeight(AvatarIds.Klee, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Zhongli, 80, 75, 0, 100, 100, 0, 55, 0, "武神钟离").Geo().Phyiscal(50),
new AffixWeight(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 55, 0, "血牛钟离").Geo(75),
new AffixWeight(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 75, 0, "血牛钟离2命+").Geo(75),
new AffixWeight(AvatarIds.Fischl, 0, 75, 0, 100, 100, 0, 0, 0).Electro(),
new AffixWeight(AvatarIds.Bennett, 100, 50, 0, 100, 100, 0, 55, 100).Pyro(70),
new AffixWeight(AvatarIds.Tartaglia, 0, 75, 0, 100, 100, 75, 0, 0).Hydro(),
new AffixWeight(AvatarIds.Noel, 0, 50, 90, 100, 100, 0, 70, 0).Geo(),
new AffixWeight(AvatarIds.Qiqi, 0, 100, 0, 100, 100, 0, 55, 100).Cryo(60),
new AffixWeight(AvatarIds.Chongyun, 0, 75, 0, 100, 100, 75, 55, 0).Cryo(),
new AffixWeight(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 75, 0, 0, "融化流").Cryo(),
new AffixWeight(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 0, 55, 0, "永冻流").Cryo(),
new AffixWeight(AvatarIds.Albedo, 0, 0, 100, 100, 100, 0, 0, 0).Geo(),
new AffixWeight(AvatarIds.Diona, 100, 50, 0, 50, 50, 0, 90, 100).Cryo(),
new AffixWeight(AvatarIds.Mona, 0, 75, 0, 100, 100, 75, 75, 0).Hydro(),
new AffixWeight(AvatarIds.Keqing, 0, 75, 0, 100, 100, 0, 0, 0).Electro().Phyiscal(),
new AffixWeight(AvatarIds.Sucrose, 0, 75, 0, 100, 100, 100, 55, 0).Anemo(40),
new AffixWeight(AvatarIds.Xinyan, 0, 75, 0, 100, 100, 0, 0, 0).Pyro(50),
new AffixWeight(AvatarIds.Rosaria, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(70).Phyiscal(80),
new AffixWeight(AvatarIds.Hutao, 80, 50, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Kazuha, 0, 75, 0, 100, 100, 100, 55, 0).Anemo(),
new AffixWeight(AvatarIds.Feiyan, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Yoimiya, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new AffixWeight(AvatarIds.Tohma, 100, 50, 0, 50, 50, 0, 90, 0).Pyro(75),
new AffixWeight(AvatarIds.Eula, 0, 75, 0, 100, 100, 0, 55, 0).Cryo(40).Phyiscal(100),
new AffixWeight(AvatarIds.Shougun, 0, 75, 0, 100, 100, 0, 90, 0).Electro(75),
new AffixWeight(AvatarIds.Sayu, 0, 50, 0, 50, 50, 100, 55, 100).Anemo(80),
new AffixWeight(AvatarIds.Kokomi, 100, 50, 0, 0, 0, 0, 55, 100).Hydro(),
new AffixWeight(AvatarIds.Gorou, 0, 50, 100, 50, 50, 0, 90, 0).Geo(25),
new AffixWeight(AvatarIds.Sara, 0, 75, 0, 100, 100, 0, 55, 0).Electro(),
new AffixWeight(AvatarIds.Itto, 0, 50, 100, 100, 100, 0, 30, 0).Geo(),
new AffixWeight(AvatarIds.Yae, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new AffixWeight(AvatarIds.Heizou, 0, 75, 0, 100, 100, 75, 0, 0).Anemo(),
new AffixWeight(AvatarIds.Yelan, 80, 0, 0, 100, 100, 0, 75, 0).Hydro(),
new AffixWeight(AvatarIds.Aloy, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new AffixWeight(AvatarIds.Shenhe, 0, 100, 0, 100, 100, 0, 55, 0).Cryo(),
new AffixWeight(AvatarIds.Yunjin, 0, 0, 100, 50, 50, 0, 90, 0).Geo(25),
new AffixWeight(AvatarIds.Shinobu, 100, 50, 0, 100, 100, 75, 55, 100).Electro(),
new AffixWeight(AvatarIds.Ayato, 50, 75, 0, 100, 100, 0, 0, 0).Hydro(),
new AffixWeight(AvatarIds.Collei, 0, 75, 0, 100, 100, 0, 55, 0).Dendro(),
new AffixWeight(AvatarIds.Dori, 100, 75, 0, 100, 100, 0, 55, 0).Electro(),
new AffixWeight(AvatarIds.Tighnari, 0, 75, 0, 100, 100, 75, 55, 0).Dendro(),
new AffixWeight(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "直伤流").Hydro(),
new AffixWeight(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "反应流").Hydro(),
new AffixWeight(AvatarIds.Cyno, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new AffixWeight(AvatarIds.Candace, 100, 75, 0, 100, 100, 0, 55, 0).Hydro(),
new AffixWeight(AvatarIds.Nahida, 0, 55, 0, 100, 100, 100, 55, 0).Dendro(),
};
}

View File

@@ -5,11 +5,8 @@ using Snap.Hutao.Extension;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
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 PropertyAvatar = Snap.Hutao.Model.Binding.AvatarProperty.Avatar;
@@ -23,40 +20,17 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// </summary>
internal class SummaryAvatarFactory
{
private readonly Dictionary<AvatarId, MetadataAvatar> idAvatarMap;
private readonly Dictionary<WeaponId, MetadataWeapon> idWeaponMap;
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
private readonly Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap;
private readonly List<ReliquaryLevel> reliqueryLevels;
private readonly List<MetadataReliquary> reliquaries;
private readonly ModelAvatarInfo avatarInfo;
private readonly SummaryMetadataContext metadataContext;
/// <summary>
/// 构造一个新的角色工厂
/// </summary>
/// <param name="idAvatarMap">角色映射</param>
/// <param name="idWeaponMap">武器映射</param>
/// <param name="idRelicMainPropMap">圣遗物主属性映射</param>
/// <param name="idReliquaryAffixMap">圣遗物副词条映射</param>
/// <param name="reliqueryLevels">圣遗物主属性等级</param>
/// <param name="reliquaries">圣遗物</param>
/// <param name="metadataContext">元数据上下文</param>
/// <param name="avatarInfo">角色信息</param>
public SummaryAvatarFactory(
Dictionary<AvatarId, MetadataAvatar> idAvatarMap,
Dictionary<WeaponId, MetadataWeapon> idWeaponMap,
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap,
List<ReliquaryLevel> reliqueryLevels,
List<MetadataReliquary> reliquaries,
ModelAvatarInfo avatarInfo)
public SummaryAvatarFactory(SummaryMetadataContext metadataContext, ModelAvatarInfo avatarInfo)
{
this.idAvatarMap = idAvatarMap;
this.idWeaponMap = idWeaponMap;
this.idRelicMainPropMap = idRelicMainPropMap;
this.idReliquaryAffixMap = idReliquaryAffixMap;
this.reliqueryLevels = reliqueryLevels;
this.reliquaries = reliquaries;
this.metadataContext = metadataContext;
this.avatarInfo = avatarInfo;
}
@@ -67,10 +41,11 @@ internal class SummaryAvatarFactory
public PropertyAvatar CreateAvatar()
{
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList);
MetadataAvatar avatar = idAvatarMap[avatarInfo.AvatarId];
MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId];
return new()
{
Id = avatar.Id,
Name = avatar.Name,
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon),
SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon),
@@ -78,6 +53,7 @@ internal class SummaryAvatarFactory
Quality = avatar.Quality,
Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore),
Level = $"Lv.{avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value}",
LevelNumber = int.Parse(avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
FetterLevel = avatarInfo.FetterInfo.ExpLevel,
Weapon = reliquaryAndWeapon.Weapon,
Reliquaries = reliquaryAndWeapon.Reliquaries,
@@ -99,7 +75,7 @@ internal class SummaryAvatarFactory
switch (equip.Flat.ItemType)
{
case ItemType.ITEM_RELIQUARY:
SummaryReliquaryFactory summaryReliquaryFactory = new(idReliquaryAffixMap, idRelicMainPropMap, reliqueryLevels, reliquaries, avatarInfo, equip);
SummaryReliquaryFactory summaryReliquaryFactory = new(metadataContext, avatarInfo, equip);
reliquaryList.Add(summaryReliquaryFactory.CreateReliquary());
break;
case ItemType.ITEM_WEAPON:
@@ -113,7 +89,7 @@ internal class SummaryAvatarFactory
private PropertyWeapon CreateWeapon(Equip equip)
{
MetadataWeapon weapon = idWeaponMap[equip.ItemId];
MetadataWeapon weapon = metadataContext.IdWeaponMap[equip.ItemId];
// AffixMap can be empty when it's a white weapon.
KeyValuePair<string, int>? idLevel = equip.Weapon!.AffixMap?.Single();
@@ -146,6 +122,8 @@ internal class SummaryAvatarFactory
MainProperty = mainStat == null ? default! : new(mainStat.AppendPropId.GetDescription(), mainStat.StatValue.ToString()),
// Weapon
Id = weapon.Id,
LevelNumber = equip.Weapon!.Level,
SubProperty = subProperty,
AffixLevel = $"精炼{affixLevel + 1}",
AffixName = weapon.Affix?.Name ?? string.Empty,

View File

@@ -2,13 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
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;
@@ -34,15 +28,17 @@ internal class SummaryFactory : ISummaryFactory
/// <inheritdoc/>
public async Task<Summary> CreateAsync(ModelPlayerInfo playerInfo, IEnumerable<ModelAvatarInfo> avatarInfos, CancellationToken token)
{
Dictionary<AvatarId, MetadataAvatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<WeaponId, MetadataWeapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false);
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false);
SummaryMetadataContext metadataContext = new()
{
IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false),
IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false),
IdRelicMainPropMap = await metadataService.GetIdToReliquaryMainPropertyMapAsync(token).ConfigureAwait(false),
IdReliquaryAffixMap = await metadataService.GetIdReliquaryAffixMapAsync(token).ConfigureAwait(false),
ReliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false),
Reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false),
};
List<ReliquaryLevel> reliqueryLevels = await metadataService.GetReliquaryLevelsAsync(token).ConfigureAwait(false);
List<MetadataReliquary> reliquaries = await metadataService.GetReliquariesAsync(token).ConfigureAwait(false);
SummaryFactoryImplementation inner = new(idAvatarMap, idWeaponMap, idRelicMainPropMap, idReliquaryAffixMap, reliqueryLevels, reliquaries);
SummaryFactoryImplementation inner = new(metadataContext);
return inner.Create(playerInfo, avatarInfos);
}
}

View File

@@ -2,13 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
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;
@@ -19,36 +13,15 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// </summary>
internal class SummaryFactoryImplementation
{
private readonly Dictionary<AvatarId, MetadataAvatar> idAvatarMap;
private readonly Dictionary<WeaponId, MetadataWeapon> idWeaponMap;
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
private readonly Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap;
private readonly List<ReliquaryLevel> reliqueryLevels;
private readonly List<MetadataReliquary> reliquaries;
private readonly SummaryMetadataContext metadataContext;
/// <summary>
/// 装配一个工厂实现
/// </summary>
/// <param name="idAvatarMap">角色映射</param>
/// <param name="idWeaponMap">武器映射</param>
/// <param name="idRelicMainPropMap">圣遗物主属性映射</param>
/// <param name="idReliquaryAffixMap">圣遗物副词条映射</param>
/// <param name="reliqueryLevels">圣遗物主属性等级</param>
/// <param name="reliquaries">圣遗物</param>
public SummaryFactoryImplementation(
Dictionary<AvatarId, MetadataAvatar> idAvatarMap,
Dictionary<WeaponId, MetadataWeapon> idWeaponMap,
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap,
List<ReliquaryLevel> reliqueryLevels,
List<MetadataReliquary> reliquaries)
/// <param name="metadataContext">元数据上下文</param>
public SummaryFactoryImplementation(SummaryMetadataContext metadataContext)
{
this.idAvatarMap = idAvatarMap;
this.idRelicMainPropMap = idRelicMainPropMap;
this.idWeaponMap = idWeaponMap;
this.reliqueryLevels = reliqueryLevels;
this.reliquaries = reliquaries;
this.idReliquaryAffixMap = idReliquaryAffixMap;
this.metadataContext = metadataContext;
}
/// <summary>
@@ -64,14 +37,7 @@ internal class SummaryFactoryImplementation
Player = SummaryHelper.CreatePlayer(playerInfo),
Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a =>
{
SummaryAvatarFactory summaryAvatarFactory = new(
idAvatarMap,
idWeaponMap,
idRelicMainPropMap,
idReliquaryAffixMap,
reliqueryLevels,
reliquaries,
a);
SummaryAvatarFactory summaryAvatarFactory = new(metadataContext, a);
return summaryAvatarFactory.CreateAvatar();
}).ToList(),
};

View File

@@ -88,7 +88,8 @@ internal static class SummaryHelper
Icon = SkillIconConverter.IconNameToUri(proudableSkill.Icon),
Description = proudableSkill.Description,
Level = skillLevelMap[proudableSkill.Id.ToString()],
GroupId = proudableSkill.GroupId,
LevelNumber = skillLevelMap[proudableSkill.Id.ToString()],
Info = DescParamDescriptor.Convert(proudableSkill.Proud, skillExtraLeveledMap[proudableSkill.Id.ToString()]),
};

View File

@@ -0,0 +1,31 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata;
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;
namespace Snap.Hutao.Service.AvatarInfo.Factory;
[SuppressMessage("", "SA1600")]
internal class SummaryMetadataContext
{
public Dictionary<AvatarId, MetadataAvatar> IdAvatarMap { get; set; } = default!;
public Dictionary<WeaponId, MetadataWeapon> IdWeaponMap { get; set; } = default!;
public Dictionary<ReliquaryMainAffixId, FightProperty> IdRelicMainPropMap { get; set; } = default!;
public Dictionary<ReliquaryAffixId, ReliquaryAffix> IdReliquaryAffixMap { get; set; } = default!;
public List<ReliquaryLevel> ReliqueryLevels { get; set; } = default!;
public List<MetadataReliquary> Reliquaries { get; set; } = default!;
}

View File

@@ -7,7 +7,6 @@ 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.Model.Primitive;
using Snap.Hutao.Web.Enka.Model;
using System.Runtime.InteropServices;
using MetadataReliquary = Snap.Hutao.Model.Metadata.Reliquary.Reliquary;
@@ -22,36 +21,19 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// </summary>
internal class SummaryReliquaryFactory
{
private readonly Dictionary<ReliquaryAffixId, MetadataReliquaryAffix> idReliquaryAffixMap;
private readonly Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap;
private readonly List<ReliquaryLevel> reliqueryLevels;
private readonly List<MetadataReliquary> reliquaries;
private readonly SummaryMetadataContext metadataContext;
private readonly ModelAvatarInfo avatarInfo;
private readonly Equip equip;
/// <summary>
/// 构造一个新的圣遗物工厂
/// </summary>
/// <param name="idReliquaryAffixMap">圣遗物副词条映射</param>
/// <param name="idRelicMainPropMap">圣遗物主属性映射</param>
/// <param name="reliqueryLevels">圣遗物主属性等级</param>
/// <param name="reliquaries">圣遗物列表</param>
/// <param name="metadataContext">元数据上下文</param>
/// <param name="avatarInfo">角色信息</param>
/// <param name="equip">圣遗物</param>
public SummaryReliquaryFactory(
Dictionary<ReliquaryAffixId, MetadataReliquaryAffix> idReliquaryAffixMap,
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
List<ReliquaryLevel> reliqueryLevels,
List<MetadataReliquary> reliquaries,
ModelAvatarInfo avatarInfo,
Equip equip)
public SummaryReliquaryFactory(SummaryMetadataContext metadataContext, ModelAvatarInfo avatarInfo, Equip equip)
{
this.idReliquaryAffixMap = idReliquaryAffixMap;
this.idRelicMainPropMap = idRelicMainPropMap;
this.reliqueryLevels = reliqueryLevels;
this.reliquaries = reliquaries;
this.metadataContext = metadataContext;
this.avatarInfo = avatarInfo;
this.equip = equip;
}
@@ -62,7 +44,7 @@ internal class SummaryReliquaryFactory
/// <returns>圣遗物</returns>
public PropertyReliquary CreateReliquary()
{
MetadataReliquary reliquary = reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
MetadataReliquary reliquary = metadataContext.Reliquaries.Single(r => r.Ids.Contains(equip.ItemId));
List<ReliquarySubProperty> subProperty = equip.Reliquary!.AppendPropIdList.EmptyIfNull().Select(CreateSubProperty).ToList();
int affixCount = GetAffixCount(reliquary);
@@ -87,8 +69,8 @@ internal class SummaryReliquaryFactory
List<ReliquarySubProperty> composed = equip.Flat.ReliquarySubstats!.Select(CreateComposedSubProperty).ToList();
ReliquaryLevel relicLevel = reliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel);
FightProperty property = idRelicMainPropMap[equip.Reliquary.MainPropId];
ReliquaryLevel relicLevel = metadataContext.ReliqueryLevels.Single(r => r.Level == equip.Reliquary!.Level && r.Quality == reliquary.RankLevel);
FightProperty property = metadataContext.IdRelicMainPropMap[equip.Reliquary.MainPropId];
return new()
{
@@ -148,7 +130,7 @@ internal class SummaryReliquaryFactory
if (equip.Flat.EquipType is EquipType.EQUIP_SHOES or EquipType.EQUIP_RING or EquipType.EQUIP_DRESS)
{
AffixWeight weightConfig = GetAffixWeightForAvatarId();
ReliquaryLevel maxRelicLevel = reliqueryLevels.Where(r => r.Quality == reliquary.RankLevel).MaxBy(r => r.Level)!;
ReliquaryLevel maxRelicLevel = metadataContext.ReliqueryLevels.Where(r => r.Quality == reliquary.RankLevel).MaxBy(r => r.Level)!;
double percent = relicLevel.Properties[property] / maxRelicLevel.Properties[property];
double baseScore = 8 * percent * weightConfig.GetValueOrDefault(property, 0);
@@ -183,7 +165,7 @@ internal class SummaryReliquaryFactory
private ReliquarySubProperty CreateSubProperty(int appendPropId)
{
MetadataReliquaryAffix affix = idReliquaryAffixMap[appendPropId];
MetadataReliquaryAffix affix = metadataContext.IdReliquaryAffixMap[appendPropId];
FightProperty property = affix.Type;
double score = ScoreSubAffix(appendPropId);
@@ -192,7 +174,7 @@ internal class SummaryReliquaryFactory
private double ScoreSubAffix(int appendId)
{
MetadataReliquaryAffix affix = idReliquaryAffixMap[appendId];
MetadataReliquaryAffix affix = metadataContext.IdReliquaryAffixMap[appendId];
AffixWeight weightConfig = GetAffixWeightForAvatarId();
double weight = weightConfig.GetValueOrDefault(affix.Type, 0) / 100D;
@@ -200,14 +182,14 @@ internal class SummaryReliquaryFactory
// 小字词条,转换到等效百分比计算
if (affix.Type is FightProperty.FIGHT_PROP_HP or FightProperty.FIGHT_PROP_ATTACK or FightProperty.FIGHT_PROP_DEFENSE)
{
// 等效百分比 [ 当前小字词条 / 角色基本属性 ]
// 等效百分比 [ 当前小字词条 / 角色基本属性 ]
double equalPercent = affix.Value / avatarInfo.FightPropMap[affix.Type - 1];
// 获取对应百分比词条权重
weight = weightConfig.GetValueOrDefault(affix.Type + 1, 0) / 100D;
// 最大同属性百分比数值 最大同属性百分比Id 第四五位是战斗属性位
MetadataReliquaryAffix maxPercentAffix = idReliquaryAffixMap[SummaryHelper.GetAffixMaxId(appendId + 10)];
MetadataReliquaryAffix maxPercentAffix = metadataContext.IdReliquaryAffixMap[SummaryHelper.GetAffixMaxId(appendId + 10)];
double equalScore = equalPercent / maxPercentAffix.Value;
return weight * equalScore * 100;

View File

@@ -131,7 +131,7 @@ internal class CultivationService : ICultivationService
/// <inheritdoc/>
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Model.Metadata.Material> metadata,
List<Model.Metadata.Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
{
@@ -160,7 +160,7 @@ internal class CultivationService : ICultivationService
foreach (CultivateItem item in items)
{
resultItems.Add(new(metadata.Single(m => m.Id == item.ItemId), item));
resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item));
}
Model.Binding.Gacha.Abstraction.ItemBase itemBase = entry.Type switch
@@ -209,6 +209,11 @@ internal class CultivationService : ICultivationService
/// <inheritdoc/>
public async Task<bool> SaveConsumptionAsync(Model.Binding.Cultivation.CultivateType type, int itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
{
if (items.Count == 0)
{
return true;
}
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

View File

@@ -24,11 +24,11 @@ internal interface ICultivationService
/// 获取绑定用的养成列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">材料</param>
/// <param name="materials">材料</param>
/// <param name="idAvatarMap">Id角色映射</param>
/// <param name="idWeaponMap">Id武器映射</param>
/// <returns>绑定用的养成列表</returns>
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> metadata, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> materials, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
/// <summary>
/// 获取物品列表

View File

@@ -17,7 +17,6 @@ namespace Snap.Hutao.Service.DailyNote;
internal class DailyNoteNotifier
{
private readonly IServiceScopeFactory scopeFactory;
private readonly BindingClient bindingClient;
private readonly DailyNoteEntry entry;
/// <summary>
@@ -26,10 +25,9 @@ internal class DailyNoteNotifier
/// <param name="scopeFactory">范围工厂</param>
/// <param name="bindingClient">绑定客户端</param>
/// <param name="entry">实时便笺入口</param>
public DailyNoteNotifier(IServiceScopeFactory scopeFactory, BindingClient bindingClient, DailyNoteEntry entry)
public DailyNoteNotifier(IServiceScopeFactory scopeFactory, DailyNoteEntry entry)
{
this.scopeFactory = scopeFactory;
this.bindingClient = bindingClient;
this.entry = entry;
}
@@ -119,37 +117,39 @@ internal class DailyNoteNotifier
return;
}
List<UserGameRole> roles = await bindingClient.GetUserGameRolesByCookieAsync(entry.User).ConfigureAwait(false);
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
.AddAttributionText(attribution)
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
.AddButton(new ToastButtonDismiss("我知道了"));
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
List<UserGameRole> roles = await bindingClient.GetUserGameRolesByCookieAsync(entry.User).ConfigureAwait(false);
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
.AddAttributionText(attribution)
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
.AddButton(new ToastButtonDismiss("我知道了"));
if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean())
{
builder.SetToastScenario(ToastScenario.Reminder);
}
}
if (hints.Count > 2)
{
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
if (hints.Count > 2)
{
builder.AddText(hint);
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{
builder.AddText(hint);
}
}
}
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
}
}
}

View File

@@ -61,9 +61,9 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
{
DailyNoteEntry newEntry = DailyNoteEntry.Create(role);
newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false);
appDbContext.DailyNotes.AddAndSave(newEntry);
newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
await appDbContext.DailyNotes.AddAndSaveAsync(newEntry).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
entries?.Add(newEntry);
}
@@ -82,7 +82,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<DailyNoteEntry> entryList = appDbContext.DailyNotes.AsNoTracking().ToList();
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); });
entries = new(appDbContext.DailyNotes);
entries = new(entryList);
}
}
@@ -96,7 +96,6 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
GameRecordClient gameRecordClient = scope.ServiceProvider.GetRequiredService<GameRecordClient>();
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
{
@@ -111,7 +110,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
if (notify)
{
await new DailyNoteNotifier(scopeFactory, bindingClient, entry).NotifyAsync().ConfigureAwait(false);
await new DailyNoteNotifier(scopeFactory, entry).NotifyAsync().ConfigureAwait(false);
}
}

View File

@@ -54,7 +54,7 @@ public sealed partial class DescParamComboBox : UserControl
if (args.NewValue != args.OldValue && args.NewValue is IList<LevelParam<string, ParameterInfo>> list)
{
descParamComboBox.ItemHost.ItemsSource = list;
descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count);
descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count - 1);
}
}
}

View File

@@ -30,6 +30,12 @@
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
Margin="16,0,0,2"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
<CommandBar Grid.Column="1" DefaultLabelPosition="Right">
<CommandBar.Content>
@@ -73,7 +79,10 @@
Label="删除当前存档"/>
<AppBarSeparator/>
<AppBarButton
Command="{Binding RefreshFinishPercentCommand}"
Icon="{shcm:FontIcon Glyph=&#xE72C;}"
Label="刷新成就进度"/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入">
<AppBarButton.Flyout>
<MenuFlyout Placement="BottomEdgeAlignedRight">
@@ -114,21 +123,31 @@
SelectionMode="Single">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid Margin="0,6">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/>
<ColumnDefinition Width="36"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shci:CachedImage
Grid.Column="0"
Width="24"
Height="24"
Source="{Binding Icon, Converter={StaticResource AchievementIconConverter}}"/>
<TextBlock
Grid.Column="1"
Margin="12,0,0,2"
VerticalAlignment="Center"
Text="{Binding Name}"/>
Width="36"
Height="36"
Source="{Binding Icon}"/>
<StackPanel Grid.Column="1" Margin="12,0,0,2">
<TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
<TextBlock
Margin="0,2,0,0"
VerticalAlignment="Center"
Opacity="0.7"
Style="{StaticResource CaptionTextBlockStyle}"
Text="{Binding FinishDescription}"/>
<ProgressBar
Height="1"
MinHeight="1"
Margin="0,4,0,0"
Maximum="1"
Value="{Binding FinishPercent}"/>
</StackPanel>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>

View File

@@ -58,6 +58,11 @@
CommandParameter="{Binding ElementName=ImageExportPanel}"
Icon="{shcm:FontIcon Glyph=&#xE91B;}"
Label="导出图片"/>
<AppBarButton
Command="{Binding CultivateCommand}"
CommandParameter="{Binding SelectedAvatar}"
Icon="{shcm:FontIcon Glyph=&#xE91B;}"
Label="养成计算"/>
<AppBarSeparator/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE72C;}" Label="同步角色信息">
<AppBarButton.Flyout>
@@ -129,7 +134,7 @@
</cwucont:SwitchPresenter>
</SplitView.Pane>
<SplitView.Content>
<ScrollViewer Padding="0,0,0,-88">
<ScrollViewer Padding="0,0,0,0">
<StackPanel
Name="ImageExportPanel"
MaxWidth="800"
@@ -551,30 +556,6 @@
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Border
Margin="16,0,16,16"
Background="{StaticResource CardBackgroundFillColorDefault}"
BorderBrush="{StaticResource CardStrokeColorDefault}"
BorderThickness="1"
CornerRadius="{StaticResource CompatCornerRadius}">
<Grid Margin="16">
<Grid.ColumnDefinitions>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<TextBlock Style="{StaticResource TitleTextBlockStyle}" Text="{Binding Summary.Player.Nickname}"/>
<StackPanel Grid.Column="1">
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource BaseTextBlockStyle}"
Text="Created by Snap Hutao"/>
<TextBlock
HorizontalAlignment="Right"
Style="{StaticResource CaptionTextBlockStyle}"
Text="官网 hut.ao"/>
</StackPanel>
</Grid>
</Border>
</StackPanel>
</ScrollViewer>
</SplitView.Content>

View File

@@ -71,7 +71,7 @@
<PivotItem Header="材料清单">
<cwucont:AdaptiveGridView
Padding="16,16,4,4"
DesiredWidth="360"
DesiredWidth="320"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding CultivateEntries}"
SelectionMode="None">

View File

@@ -249,7 +249,7 @@
Margin="0,16,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="五星"/>
<GridView ItemsSource="{Binding SelectedHistoryWish.OrangeList}">
<GridView ItemsSource="{Binding SelectedHistoryWish.OrangeList}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
@@ -276,7 +276,7 @@
Margin="0,0,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="四星"/>
<GridView ItemsSource="{Binding SelectedHistoryWish.PurpleList}">
<GridView ItemsSource="{Binding SelectedHistoryWish.PurpleList}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>
@@ -303,7 +303,7 @@
Margin="0,0,0,8"
Style="{StaticResource BaseTextBlockStyle}"
Text="三星"/>
<GridView ItemsSource="{Binding SelectedHistoryWish.BlueList}">
<GridView ItemsSource="{Binding SelectedHistoryWish.BlueList}" SelectionMode="None">
<GridView.ItemTemplate>
<DataTemplate>
<Grid>

View File

@@ -65,6 +65,10 @@
Text="{Binding DeviceId}"/>
</sc:Setting.Description>
</sc:Setting>
<sc:Setting
Description="{Binding WebView2Version}"
Header="Webview2 Runtime"
Icon="&#xECAA;"/>
<sc:Setting
Description="Github 上反馈的问题会优先处理"
Header="反馈"
@@ -186,6 +190,16 @@
</sc:SettingsGroup>
<sc:SettingsGroup Foreground="{StaticResource SystemFillColorCriticalBrush}" Header="危险功能">
<sc:Setting
Background="{StaticResource SystemFillColorCriticalBackgroundBrush}"
Description="删除注册的计划任务,卸载前务必点击此项"
Header="删除所有计划任务"
Icon="&#xE7C4;">
<sc:Setting.ActionContent>
<Button Command="{Binding Experimental.DeleteAllScheduleTasksCommand}" Content="执行"/>
</sc:Setting.ActionContent>
</sc:Setting>
<sc:Setting
Background="{StaticResource SystemFillColorCriticalBackgroundBrush}"
Description="直接删除用户表的所有记录,用于修复特定的账号冲突问题"

View File

@@ -6,6 +6,7 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shci="using:Snap.Hutao.Control.Image"
@@ -44,6 +45,23 @@
<CommandBar.Content>
<shcp:PanelSelector x:Name="ItemsPanelSelector" Margin="6,8,0,0"/>
</CommandBar.Content>
<AppBarElementContainer>
<AutoSuggestBox
Width="240"
Height="36"
Margin="16,6,6,0"
HorizontalAlignment="Stretch"
VerticalContentAlignment="Center"
PlaceholderText="筛选武器"
QueryIcon="{shcm:FontIcon Glyph=&#xE721;}"
Text="{Binding FilterText, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="QuerySubmitted">
<mxic:InvokeCommandAction Command="{Binding FilterCommand}" CommandParameter="{Binding FilterText}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</AutoSuggestBox>
</AppBarElementContainer>
<AppBarButton
Command="{Binding CultivateCommand}"
CommandParameter="{Binding Selected}"

View File

@@ -14,6 +14,7 @@ using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.Achievement;
using Snap.Hutao.Model.InterChange.Achievement;
using Snap.Hutao.Model.Metadata.Achievement;
using Snap.Hutao.Service.Abstraction;
@@ -22,6 +23,7 @@ using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Navigation;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
using System.Runtime.InteropServices;
using Windows.Storage;
using Windows.Storage.Pickers;
@@ -38,8 +40,8 @@ internal class AchievementViewModel
IDisposable,
IRecipient<AchievementArchiveChangedMessage>
{
private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.IsChecked), SortDirection.Ascending);
private static readonly SortDescription CompletionTimeSortDescription = new(nameof(Model.Binding.Achievement.Time), SortDirection.Descending);
private static readonly SortDescription IncompletedItemsFirstSortDescription = new(nameof(Model.Binding.Achievement.Achievement.IsChecked), SortDirection.Ascending);
private static readonly SortDescription CompletionTimeSortDescription = new(nameof(Model.Binding.Achievement.Achievement.Time), SortDirection.Descending);
private readonly IMetadataService metadataService;
private readonly IInfoBarService infoBarService;
@@ -52,13 +54,14 @@ internal class AchievementViewModel
private bool disposed;
private AdvancedCollectionView? achievements;
private IList<AchievementGoal>? achievementGoals;
private AchievementGoal? selectedAchievementGoal;
private List<Model.Binding.Achievement.AchievementGoal>? achievementGoals;
private Model.Binding.Achievement.AchievementGoal? selectedAchievementGoal;
private ObservableCollection<Model.Entity.AchievementArchive>? archives;
private Model.Entity.AchievementArchive? selectedArchive;
private bool isIncompletedItemsFirst = true;
private string searchText = string.Empty;
private bool isInitialized;
private string? finishDescription;
/// <summary>
/// 构造一个新的成就视图模型
@@ -91,6 +94,7 @@ internal class AchievementViewModel
RemoveArchiveCommand = asyncRelayCommandFactory.Create(RemoveArchiveAsync);
SearchAchievementCommand = new RelayCommand<string>(SearchAchievement);
SortIncompletedSwitchCommand = new RelayCommand(UpdateAchievementsSort);
RefreshFinishPercentCommand = new RelayCommand(UpdateAchievementFinishPercent);
messenger.Register(this);
}
@@ -139,7 +143,7 @@ internal class AchievementViewModel
/// <summary>
/// 成就分类
/// </summary>
public IList<AchievementGoal>? AchievementGoals
public List<Model.Binding.Achievement.AchievementGoal>? AchievementGoals
{
get => achievementGoals;
set => SetProperty(ref achievementGoals, value);
@@ -148,7 +152,7 @@ internal class AchievementViewModel
/// <summary>
/// 选中的成就分类
/// </summary>
public AchievementGoal? SelectedAchievementGoal
public Model.Binding.Achievement.AchievementGoal? SelectedAchievementGoal
{
get => selectedAchievementGoal;
set
@@ -177,6 +181,11 @@ internal class AchievementViewModel
set => SetProperty(ref isIncompletedItemsFirst, value);
}
/// <summary>
/// 完成进度描述
/// </summary>
public string? FinishDescription { get => finishDescription; set => SetProperty(ref finishDescription, value); }
/// <summary>
/// 打开页面命令
/// </summary>
@@ -217,6 +226,11 @@ internal class AchievementViewModel
/// </summary>
public ICommand SortIncompletedSwitchCommand { get; }
/// <summary>
/// 刷新完成百分比命令
/// </summary>
public ICommand RefreshFinishPercentCommand { get; }
/// <inheritdoc/>
public void Receive(AchievementArchiveChangedMessage message)
{
@@ -230,7 +244,7 @@ internal class AchievementViewModel
{
if (Achievements != null && SelectedArchive != null)
{
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
}
disposed = true;
@@ -252,7 +266,6 @@ internal class AchievementViewModel
return false;
}
[ThreadAccess(ThreadAccessState.MainThread)]
private static Task<ContentDialogResult> ShowImportResultDialogAsync(string title, string message)
{
ContentDialog dialog = new()
@@ -267,7 +280,6 @@ internal class AchievementViewModel
return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
}
[ThreadAccess(ThreadAccessState.MainThread)]
private static Task<ContentDialogResult> ShowImportFailDialogAsync(string message)
{
ContentDialog dialog = new()
@@ -282,12 +294,11 @@ internal class AchievementViewModel
return dialog.InitializeWithWindow(mainWindow).ShowAsync().AsTask();
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task HandleArchiveChangeAsync(Model.Entity.AchievementArchive? oldArchieve, Model.Entity.AchievementArchive? newArchieve)
{
if (oldArchieve != null && Achievements != null)
{
achievementService.SaveAchievements(oldArchieve, (Achievements.Source as IList<Model.Binding.Achievement>)!);
achievementService.SaveAchievements(oldArchieve, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
}
if (newArchieve != null)
@@ -296,7 +307,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task OpenUIAsync()
{
bool metaInitialized = await metadataService.InitializeAsync().ConfigureAwait(false);
@@ -305,10 +315,10 @@ internal class AchievementViewModel
{
try
{
List<AchievementGoal> goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Metadata.Achievement.AchievementGoal> goals = await metadataService.GetAchievementGoalsAsync(CancellationToken).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
AchievementGoals = goals.OrderBy(goal => goal.Order).ToList();
AchievementGoals = goals.OrderBy(goal => goal.Order).Select(goal => new Model.Binding.Achievement.AchievementGoal(goal)).ToList();
Archives = achievementService.GetArchiveCollection();
SelectedArchive = Archives.SingleOrDefault(a => a.IsSelected == true);
@@ -329,21 +339,20 @@ internal class AchievementViewModel
IsInitialized = true;
}
[ThreadAccess(ThreadAccessState.AnyThread)]
private async Task UpdateAchievementsAsync(Model.Entity.AchievementArchive archive)
{
List<Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Binding.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements);
List<Model.Metadata.Achievement.Achievement> rawAchievements = await metadataService.GetAchievementsAsync(CancellationToken).ConfigureAwait(false);
List<Model.Binding.Achievement.Achievement> combined = achievementService.GetAchievements(archive, rawAchievements);
// Assemble achievements on the UI thread.
await ThreadHelper.SwitchToMainThreadAsync();
Achievements = new(combined, true);
UpdateAchievementFinishPercent();
UpdateAchievementFilter(SelectedAchievementGoal);
UpdateAchievementsSort();
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task AddArchiveAsync()
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
@@ -370,7 +379,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task RemoveArchiveAsync()
{
if (Archives != null && SelectedArchive != null)
@@ -397,7 +405,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task ExportAsUIAFToFileAsync()
{
if (SelectedArchive == null || Achievements == null)
@@ -406,7 +413,7 @@ internal class AchievementViewModel
return;
}
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement>)!);
achievementService.SaveAchievements(SelectedArchive, (Achievements.Source as IList<Model.Binding.Achievement.Achievement>)!);
await ThreadHelper.SwitchToMainThreadAsync();
IPickerFactory pickerFactory = Ioc.Default.GetRequiredService<IPickerFactory>();
@@ -443,13 +450,13 @@ internal class AchievementViewModel
{
if (search.Length == 5 && int.TryParse(search, out int achiId))
{
Achievements.Filter = (object o) => ((Model.Binding.Achievement)o).Inner.Id == achiId;
Achievements.Filter = (object o) => ((Model.Binding.Achievement.Achievement)o).Inner.Id == achiId;
}
else
{
Achievements.Filter = (object o) =>
{
Model.Binding.Achievement achi = (Model.Binding.Achievement)o;
Model.Binding.Achievement.Achievement achi = (Model.Binding.Achievement.Achievement)o;
return achi.Inner.Title.Contains(search) || achi.Inner.Description.Contains(search);
};
}
@@ -457,19 +464,17 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.AnyThread)]
private async Task ImportUIAFFromClipboardAsync()
{
if (achievementService.CurrentArchive == null)
{
// TODO: automatically create a archive.
infoBarService.Information("必须创建一个用户才能导入成就");
infoBarService.Information("必须选择一个存档才能导入成就");
return;
}
if (await GetUIAFFromClipboardAsync().ConfigureAwait(false) is UIAF uiaf)
{
await TryImportUIAFInternalAsync(achievementService.CurrentArchive, uiaf).ConfigureAwait(false);
await TryImportUIAFInternalAsync(achievementService.CurrentArchive!, uiaf).ConfigureAwait(false);
}
else
{
@@ -478,7 +483,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.MainThread)]
private async Task ImportUIAFFromFileAsync()
{
if (achievementService.CurrentArchive == null)
@@ -507,7 +511,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.AnyThread)]
private async Task<UIAF?> GetUIAFFromClipboardAsync()
{
try
@@ -521,7 +524,6 @@ internal class AchievementViewModel
}
}
[ThreadAccess(ThreadAccessState.AnyThread)]
private async Task<bool> TryImportUIAFInternalAsync(Model.Entity.AchievementArchive archive, UIAF uiaf)
{
if (uiaf.IsCurrentVersionSupported())
@@ -574,13 +576,53 @@ internal class AchievementViewModel
}
}
private void UpdateAchievementFilter(AchievementGoal? goal)
private void UpdateAchievementFilter(Model.Binding.Achievement.AchievementGoal? goal)
{
if (Achievements != null)
{
Achievements.Filter = goal != null
? ((object o) => o is Model.Binding.Achievement achi && achi.Inner.Goal == goal.Id)
? ((object o) => o is Snap.Hutao.Model.Binding.Achievement.Achievement achi && achi.Inner.Goal == goal.Id)
: null;
}
}
private void UpdateAchievementFinishPercent()
{
int finished = 0;
int count = 0;
if (Achievements != null && AchievementGoals != null)
{
Dictionary<int, AchievementGoalAggregation> counter = AchievementGoals.ToDictionary(x => x.Id, x => new AchievementGoalAggregation(x));
foreach (Model.Binding.Achievement.Achievement achievement in Achievements.OfType<Model.Binding.Achievement.Achievement>())
{
ref AchievementGoalAggregation aggregation = ref CollectionsMarshal.GetValueRefOrNullRef(counter, achievement.Inner.Goal);
aggregation.Count += 1;
count += 1;
if (achievement.IsChecked)
{
aggregation.Finished += 1;
finished += 1;
}
}
foreach (AchievementGoalAggregation aggregation1 in counter.Values)
{
aggregation1.AchievementGoal.UpdateFinishPercent(aggregation1.Finished, aggregation1.Count);
}
FinishDescription = $"{finished}/{count} - {(double)finished / count:P2}";
}
}
private struct AchievementGoalAggregation
{
public readonly Model.Binding.Achievement.AchievementGoal AchievementGoal;
public int Finished;
public int Count;
public AchievementGoalAggregation(Model.Binding.Achievement.AchievementGoal goal)
{
AchievementGoal = goal;
}
}
}

View File

@@ -5,22 +5,27 @@ using CommunityToolkit.Mvvm.ComponentModel;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Imaging;
using Snap.Hutao.Control;
using Snap.Hutao.Control.Image;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core.IO.DataTransfer;
using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.AvatarInfo;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Windows.Foundation;
using Windows.Graphics.Imaging;
using Windows.Storage.Streams;
using Windows.UI;
using Windows.Win32;
using Windows.Win32.System.WinRT;
using WinRT;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
namespace Snap.Hutao.ViewModel;
@@ -58,6 +63,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
RefreshFromHoyolabGameRecordCommand = asyncRelayCommandFactory.Create(RefreshByHoyolabGameRecordAsync);
RefreshFromHoyolabCalculateCommand = asyncRelayCommandFactory.Create(RefreshByHoyolabCalculateAsync);
ExportAsImageCommand = asyncRelayCommandFactory.Create<UIElement>(ExportAsImageAsync);
CultivateCommand = asyncRelayCommandFactory.Create<Avatar>(CultivateAsync);
}
/// <inheritdoc/>
@@ -98,26 +104,10 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
/// </summary>
public ICommand ExportAsImageCommand { get; }
private static unsafe void NormalBlend(SoftwareBitmap softwareBitmap, Bgra8 tint)
{
using (BitmapBuffer buffer = softwareBitmap.LockBuffer(BitmapBufferAccessMode.ReadWrite))
{
using (IMemoryBufferReference reference = buffer.CreateReference())
{
reference.As<IMemoryBufferByteAccess>().GetBuffer(out byte* data, out uint length);
for (int i = 0; i < length; i += 4)
{
Bgra8* pixel = (Bgra8*)(data + i);
byte baseAlpha = pixel->A;
pixel->B = (byte)(((pixel->B * baseAlpha) + (tint.B * (0xFF - baseAlpha))) / 0xFF);
pixel->G = (byte)(((pixel->G * baseAlpha) + (tint.G * (0xFF - baseAlpha))) / 0xFF);
pixel->R = (byte)(((pixel->R * baseAlpha) + (tint.R * (0xFF - baseAlpha))) / 0xFF);
pixel->A = 0xFF;
}
}
}
}
/// <summary>
/// 养成命令
/// </summary>
public ICommand CultivateCommand { get; }
private Task OpenUIAsync()
{
@@ -205,6 +195,58 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
}
}
private async Task CultivateAsync(Avatar? avatar)
{
if (avatar != null)
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
if (userService.Current != null)
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
(bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, avatar.ToCalculable(), avatar.Weapon.ToCalculable())
.GetPromotionDeltaAsync()
.ConfigureAwait(false);
if (isOk)
{
CalcConsumption? consumption = await Ioc.Default
.GetRequiredService<CalcClient>()
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (consumption != null)
{
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
bool avatarSaved = await Ioc.Default
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
bool weaponSaved = await Ioc.Default
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull())
.ConfigureAwait(false);
if (avatarSaved && weaponSaved)
{
infoBarService.Success("已成功添加至当前养成计划");
}
else
{
infoBarService.Warning("请先前往养成计划页面创建计划并选中");
}
}
}
}
else
{
infoBarService.Warning("必须先选择一个用户与角色");
}
}
}
private async Task ExportAsImageAsync(UIElement? element)
{
if (element == null)
@@ -219,7 +261,7 @@ internal class AvatarPropertyViewModel : ObservableObject, ISupportCancellation
SoftwareBitmap softwareBitmap = SoftwareBitmap.CreateCopyFromBuffer(buffer, BitmapPixelFormat.Bgra8, bitmap.PixelWidth, bitmap.PixelHeight, BitmapAlphaMode.Ignore);
Color tintColor = (Color)Ioc.Default.GetRequiredService<App>().Resources["CompatBackgroundColor"];
Bgra8 tint = Bgra8.FromColor(tintColor);
NormalBlend(softwareBitmap, tint);
softwareBitmap.NormalBlend(tint);
using (InMemoryRandomAccessStream memory = new())
{

View File

@@ -93,7 +93,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
{
refreshSecondsEntry!.SetInt32(value.Value);
appDbContext.Settings.UpdateAndSave(refreshSecondsEntry!);
TaskSchedulerHelper.RegisterForDailyNoteRefresh(value.Value);
ScheduleTaskHelper.RegisterForDailyNoteRefresh(value.Value);
}
}
}
@@ -161,7 +161,7 @@ internal class DailyNoteViewModel : ObservableObject, ISupportCancellation
refreshSecondsEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteRefreshSeconds, "480");
selectedRefreshTime = refreshTimes.Single(t => t.Value == refreshSecondsEntry.GetInt32());
TaskSchedulerHelper.RegisterForDailyNoteRefresh(480);
ScheduleTaskHelper.RegisterForDailyNoteRefresh(480);
OnPropertyChanged(nameof(SelectedRefreshTime));
reminderNotifyEntry = appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, false.ToString());

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
@@ -38,7 +39,8 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
OpenCacheFolderCommand = asyncRelayCommandFactory.Create(OpenCacheFolderAsync);
OpenDataFolderCommand = asyncRelayCommandFactory.Create(OpenDataFolderAsync);
UploadSpiralAbyssRecordCommand = asyncRelayCommandFactory.Create(UploadSpiralAbyssRecordAsync);
DeleteUsersCommand = asyncRelayCommandFactory.Create(DeleteUsersAsync);
DeleteUsersCommand = asyncRelayCommandFactory.Create(DangerousDeleteUsersAsync);
DeleteAllScheduleTasksCommand = new RelayCommand(DangerousDeleteAllScheduleTasks);
}
/// <summary>
@@ -61,6 +63,11 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
/// </summary>
public ICommand DeleteUsersCommand { get; }
/// <summary>
/// 删除所有计划任务命令
/// </summary>
public ICommand DeleteAllScheduleTasksCommand { get; }
private Task OpenCacheFolderAsync()
{
return Launcher.LaunchFolderAsync(ApplicationData.Current.TemporaryFolder).AsTask();
@@ -94,7 +101,7 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
}
}
private async Task DeleteUsersAsync()
private async Task DangerousDeleteUsersAsync()
{
using (IServiceScope scope = Ioc.Default.CreateScope())
{
@@ -105,4 +112,17 @@ internal class ExperimentalFeaturesViewModel : ObservableObject
infoBarService.Success("清除用户数据成功,请重启胡桃");
}
}
private void DangerousDeleteAllScheduleTasks()
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
if (Core.ScheduleTaskHelper.UnregisterAllTasks())
{
infoBarService.Success("清除任务计划成功");
}
else
{
infoBarService.Warning("清除任务计划失败");
}
}
}

View File

@@ -93,6 +93,15 @@ internal class SettingViewModel : ObservableObject
get => Core.CoreEnvironment.Version.ToString();
}
/// <summary>
/// Webview2 版本
/// </summary>
[SuppressMessage("", "CA1822")]
public string WebView2Version
{
get => Core.WebView2Helper.Version;
}
/// <summary>
/// 设备Id
/// </summary>

View File

@@ -185,11 +185,6 @@ internal class WikiAvatarViewModel : ObservableObject
private static class AvatarFilter
{
private static readonly ImmutableList<string> AssociationTypes = Enum.GetValues<AssociationType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
private static readonly ImmutableList<string> WeaponTypes = Enum.GetValues<WeaponType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
private static readonly ImmutableList<string> ItemQualities = Enum.GetValues<ItemQuality>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
private static readonly ImmutableList<string> BodyTypes = Enum.GetValues<BodyType>().Select(e => e.GetDescriptionOrNull()).OfType<string>().ToImmutableList();
public static Predicate<object> Compile(string input)
{
return (object o) => o is Avatar avatar && DoFilter(input, avatar);
@@ -209,25 +204,25 @@ internal class WikiAvatarViewModel : ObservableObject
continue;
}
if (AssociationTypes.Contains(value))
if (ImmutableIntrinsics.AssociationTypes.Contains(value))
{
keep = keep || avatar.FetterInfo.Association.GetDescriptionOrNull() == value;
continue;
}
if (WeaponTypes.Contains(value))
if (ImmutableIntrinsics.WeaponTypes.Contains(value))
{
keep = keep || avatar.Weapon.GetDescriptionOrNull() == value;
continue;
}
if (ItemQualities.Contains(value))
if (ImmutableIntrinsics.ItemQualities.Contains(value))
{
keep = keep || avatar.Quality.GetDescriptionOrNull() == value;
continue;
}
if (BodyTypes.Contains(value))
if (ImmutableIntrinsics.BodyTypes.Contains(value))
{
keep = keep || avatar.Body.GetDescriptionOrNull() == value;
continue;

View File

@@ -2,12 +2,15 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.WinUI.UI;
using Microsoft.Extensions.Primitives;
using Snap.Hutao.Control;
using Snap.Hutao.Extension;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
@@ -16,6 +19,7 @@ using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using System.Collections.Immutable;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
@@ -39,6 +43,7 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
private AdvancedCollectionView? weapons;
private Weapon? selected;
private string? filterText;
/// <summary>
/// 构造一个新的武器资料视图模型
@@ -53,6 +58,7 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
CultivateCommand = asyncRelayCommandFactory.Create<Weapon>(CultivateAsync);
FilterCommand = new RelayCommand<string>(ApplyFilter);
}
/// <inheritdoc/>
@@ -68,6 +74,11 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
/// </summary>
public Weapon? Selected { get => selected; set => SetProperty(ref selected, value); }
/// <summary>
/// 筛选文本
/// </summary>
public string? FilterText { get => filterText; set => SetProperty(ref filterText, value); }
/// <summary>
/// 打开界面命令
/// </summary>
@@ -78,6 +89,11 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
/// </summary>
public ICommand CultivateCommand { get; }
/// <summary>
/// 筛选命令
/// </summary>
public ICommand FilterCommand { get; }
private async Task OpenUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
@@ -152,4 +168,67 @@ internal class WikiWeaponViewModel : ObservableObject, ISupportCancellation
}
}
}
private void ApplyFilter(string? input)
{
if (Weapons != null)
{
if (!string.IsNullOrWhiteSpace(input))
{
Weapons.Filter = WeaponFilter.Compile(input);
if (!Weapons.Contains(Selected))
{
Weapons.MoveCurrentToFirst();
}
}
else
{
Weapons.Filter = null!;
}
}
}
private static class WeaponFilter
{
public static Predicate<object> Compile(string input)
{
return (object o) => o is Weapon weapon && DoFilter(input, weapon);
}
private static bool DoFilter(string input, Weapon weapon)
{
bool keep = false;
foreach (StringSegment segment in new StringTokenizer(input, ' '.Enumerate().ToArray()))
{
string value = segment.ToString();
if (ImmutableIntrinsics.WeaponTypes.Contains(value))
{
keep = keep || weapon.WeaponType.GetDescriptionOrNull() == value;
continue;
}
if (ImmutableIntrinsics.ItemQualities.Contains(value))
{
keep = keep || weapon.Quality.GetDescriptionOrNull() == value;
continue;
}
if (ImmutableIntrinsics.FightProperties.Contains(value))
{
keep = keep || weapon.Property.Properties.ElementAtOrDefault(1).GetDescriptionOrNull() == value;
continue;
}
if (weapon.Name == value)
{
keep = true;
}
}
return keep;
}
}
}

View File

@@ -1,12 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Annotation;
/// <summary>
/// 指示相关的类忽略Http请求的Set-Cookie头
/// </summary>
[AttributeUsage(AttributeTargets.Class, AllowMultiple = false, Inherited = false)]
internal class IgnoreSetCookieAttribute : Attribute
{
}

View File

@@ -11,7 +11,6 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
/// <summary>
/// 用户信息客户端
/// </summary>
[IgnoreSetCookie]
[HttpClient(HttpClientConfigration.XRpc)]
internal class UserClient
{