mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
achievement progress
This commit is contained in:
@@ -4,7 +4,7 @@
|
||||
using System.Runtime.InteropServices;
|
||||
using Windows.UI;
|
||||
|
||||
namespace Snap.Hutao.Control.Image;
|
||||
namespace Snap.Hutao.Control.Media;
|
||||
|
||||
/// <summary>
|
||||
/// BGRA8 结构
|
||||
175
src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba8.cs
Normal file
175
src/Snap.Hutao/Snap.Hutao/Control/Media/Rgba8.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core;
|
||||
/// <summary>
|
||||
/// 任务计划器服务
|
||||
/// </summary>
|
||||
internal static class TaskSchedulerHelper
|
||||
internal static class ScheduleTaskHelper
|
||||
{
|
||||
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
|
||||
|
||||
@@ -43,6 +43,9 @@ public readonly struct DispatherQueueSwitchOperation : IAwaitable<DispatherQueue
|
||||
/// <inheritdoc/>
|
||||
public void OnCompleted(Action continuation)
|
||||
{
|
||||
dispatherQueue.TryEnqueue(() => { continuation(); });
|
||||
dispatherQueue.TryEnqueue(() =>
|
||||
{
|
||||
continuation();
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -50,6 +50,10 @@ internal abstract class WebView2Helper
|
||||
/// </summary>
|
||||
public static string Version
|
||||
{
|
||||
get => version;
|
||||
get
|
||||
{
|
||||
_ = IsSupported;
|
||||
return version;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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"/> 的呈现矩形
|
||||
@@ -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();
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Snap.Hutao.Model.Binding;
|
||||
namespace Snap.Hutao.Model.Binding.Achievement;
|
||||
|
||||
/// <summary>
|
||||
/// 用于视图绑定的成就
|
||||
@@ -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}";
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
@@ -94,4 +94,4 @@ public enum WeaponType
|
||||
/// </summary>
|
||||
[Description("单手剑")]
|
||||
WEAPON_POLE = 13,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -31,5 +31,5 @@ public class AchievementGoal
|
||||
/// <summary>
|
||||
/// 图标
|
||||
/// </summary>
|
||||
public string? Icon { get; set; }
|
||||
public string Icon { get; set; } = default!;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
/// 检查该角色是否为主角
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -4,6 +4,8 @@ WM_GETMINMAXINFO
|
||||
WM_NCRBUTTONDOWN
|
||||
WM_NCRBUTTONUP
|
||||
|
||||
STDAPI
|
||||
|
||||
// Type definition
|
||||
CWMO_FLAGS
|
||||
HRESULT
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
}
|
||||
@@ -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,
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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(),
|
||||
};
|
||||
|
||||
@@ -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()]),
|
||||
};
|
||||
|
||||
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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>
|
||||
/// 获取物品列表
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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=}"
|
||||
Label="刷新成就进度"/>
|
||||
<AppBarButton Icon="{shcm:FontIcon Glyph=}" 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>
|
||||
|
||||
@@ -58,6 +58,11 @@
|
||||
CommandParameter="{Binding ElementName=ImageExportPanel}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="导出图片"/>
|
||||
<AppBarButton
|
||||
Command="{Binding CultivateCommand}"
|
||||
CommandParameter="{Binding SelectedAvatar}"
|
||||
Icon="{shcm:FontIcon Glyph=}"
|
||||
Label="养成计算"/>
|
||||
<AppBarSeparator/>
|
||||
<AppBarButton Icon="{shcm:FontIcon Glyph=}" 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>
|
||||
|
||||
@@ -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">
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -65,6 +65,10 @@
|
||||
Text="{Binding DeviceId}"/>
|
||||
</sc:Setting.Description>
|
||||
</sc:Setting>
|
||||
<sc:Setting
|
||||
Description="{Binding WebView2Version}"
|
||||
Header="Webview2 Runtime"
|
||||
Icon=""/>
|
||||
<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="">
|
||||
<sc:Setting.ActionContent>
|
||||
<Button Command="{Binding Experimental.DeleteAllScheduleTasksCommand}" Content="执行"/>
|
||||
</sc:Setting.ActionContent>
|
||||
</sc:Setting>
|
||||
|
||||
<sc:Setting
|
||||
Background="{StaticResource SystemFillColorCriticalBackgroundBrush}"
|
||||
Description="直接删除用户表的所有记录,用于修复特定的账号冲突问题"
|
||||
|
||||
@@ -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=}"
|
||||
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}"
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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())
|
||||
{
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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("清除任务计划失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
@@ -11,7 +11,6 @@ namespace Snap.Hutao.Web.Hoyolab.Bbs.User;
|
||||
/// <summary>
|
||||
/// 用户信息客户端
|
||||
/// </summary>
|
||||
[IgnoreSetCookie]
|
||||
[HttpClient(HttpClientConfigration.XRpc)]
|
||||
internal class UserClient
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user