Compare commits

..

15 Commits

Author SHA1 Message Date
DismissedLight
423188c16a code style 2022-12-29 15:43:17 +08:00
DismissedLight
5fb935635b Merge branch 'main' of https://github.com/DGP-Studio/Snap.Hutao 2022-12-29 15:03:33 +08:00
DismissedLight
761049ec17 cultivation improvement 2022-12-29 15:03:21 +08:00
Masterain
3f8c8874f3 Update azure-pipelines.yml for Azure Pipelines 2022-12-28 20:39:16 -08:00
Masterain
b8f354bbc7 Merge pull request #329 from Masterain98/main
devops improvements
2022-12-28 20:35:18 -08:00
Masterain
d45c40d4d7 Update bug-report.yml
add missing dropdown option
2022-12-28 20:19:48 -08:00
Masterain
6b309c4886 Update azure-pipelines.yml
add CI exception
2022-12-28 20:18:29 -08:00
DismissedLight
26e6d2008e add required field for API 2022-12-24 19:28:38 +08:00
Masterain
fb77bd2f6b Set up CI with Azure Pipelines
[skip ci]
2022-12-24 02:08:14 -08:00
DismissedLight
50459923f9 apply window-mode 2022-12-23 16:29:28 +08:00
DismissedLight
a97bab8a1c cultivation optimization 2022-12-22 14:20:19 +08:00
DismissedLight
bbc8324f5d fix feature request template 2022-12-20 15:29:06 +08:00
DismissedLight
2c0b32ab8b achievement progress 2022-12-20 15:11:05 +08:00
DismissedLight
0073636676 fix ci build 2022-12-18 15:23:43 +08:00
DismissedLight
7457d72e1b code style 2022-12-17 17:19:29 +08:00
131 changed files with 2766 additions and 1152 deletions

View File

@@ -68,6 +68,7 @@ body:
- 游戏启动器
- 实时便笺
- 养成计算器
- 用户面板
- 文件缓存
- 祈愿记录
- 玩家查询

View File

@@ -8,12 +8,20 @@ body:
- type: markdown
attributes:
value: |
请按下方的要求填写完整的问题表单,以便我们更快的定位问题
请按下方的要求填写完整的问题表单。
- type: textarea
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由
validations:
required: true
- type: textarea
id: req
attributes:
label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能
label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
validations:
required: true

View File

@@ -1,35 +1,26 @@
# Snap.Hutao
# [Snap.Hutao](https://hut.ao)
> 唷,找本堂主有何贵干呀?
![Snap.Hutao](https://repobeats.axiom.co/api/embed/f029553fbe0c60689b1710476ec8512452163fc9.svg)
## 项目首页(文档)
# 特别感谢
[![Deploy Docs](https://github.com/DGP-Studio/Snap.Hutao.Docs/actions/workflows/deploy-docs.yml/badge.svg)](https://github.com/DGP-Studio/Snap.Hutao.Docs/actions/workflows/deploy-docs.yml)
### 原神组织与个人
[HUT.AO](https://hut.ao)
* [HolographicHat](https://github.com/HolographicHat)
* [UIGF organization](https://uigf.org)
## 安装
* 前往 [下载页面](https://go.hut.ao/down) 下载最新版本的 `胡桃` 安装包
* (曾启用的可以跳过此步骤)在系统设置中打开 **开发者选项** 界面,勾选 `开发人员模式``允许 PowerShell 脚本`
* 完全解压后,右键使用 powershell 运行 `install.ps1` 文件
* 安装完成后可以关闭 `允许 PowerShell 脚本`
## 特别感谢
### 原神项目
### 特定的原神项目
* [biuuu/genshin-wish-export](https://github.com/biuuu/genshin-wish-export)
* [HolographicHat/YaeAchievement](https://github.com/HolographicHat/YaeAchievement)
* [HolographicHat/MiHoYoWebBridge](https://github.com/HolographicHat/MiHoYoWebBridge)
* [xunkong/xunkong](https://github.com/xunkong/xunkong)
* [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat)
### 技术栈
### 使用的技术栈
* [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet)
* [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)
* [dahall/taskscheduler](https://github.com/dahall/taskscheduler)
* [dotnet/efcore](https://github.com/dotnet/efcore)
* [dotnet/runtime](https://github.com/dotnet/runtime)
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers)

156
azure-pipelines.yml Normal file
View File

@@ -0,0 +1,156 @@
# CI process script for Snap.Hutao
# Usage:
# 1. Append the script in Pipelines
# 2. Upload the pfx and cer certificates to Pipelines Library secrets
# 3. Permit the pfx usage
# 4. Add a `pw` variable in the script variables, which is pfx password
# 5. Connect the GitHub in project settings
# 6. Run
trigger:
branches:
include:
- main
paths:
exclude:
- README.md
- azure-pipelines.yml
- .github/ISSUE_TEMPLATE/*.yml
- .github/workflows/*.yml
pool:
vmImage: 'windows-2022'
variables:
DOTNET_SKIP_FIRST_TIME_EXPERIENCE: true
solution: '$(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao.sln'
project: $(Build.SourcesDirectory)/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj'
buildPlatform: 'x64'
buildConfiguration: 'Release'
build_date: $[ format('{0:yyyy}.{0:MM}.{0:dd}', pipeline.startTime) ]
steps:
- task: GetRevision@1
displayName: get Pipelines revision number
inputs:
VariableName: 'rev_number'
- task: UseDotNet@2
displayName: Install dotNet
inputs:
packageType: 'sdk'
version: '7.x'
includePreviewVersions: true
- task: NuGetToolInstaller@1
name: 'NuGetToolInstaller'
displayName: 'NuGet Installer'
- task: NuGetCommand@2
displayName: NuGet restore
inputs:
command: 'restore'
restoreSolution: '$(solution)'
feedsToUse: 'select'
- task: MsixPackaging@1
displayName: Build binary package
inputs:
outputPath: '$(Build.ArtifactStagingDirectory)/'
solution: '$(solution)'
clean: false
generateBundle: false
buildConfiguration: 'Release'
buildPlatform: 'x64'
updateAppVersion: false
appPackageDistributionMode: 'SideloadOnly'
msbuildLocationMethod: 'location'
msbuildLocation: 'C:\Program Files\Microsoft Visual Studio\2022\Enterprise\Msbuild\Current\Bin\MSBuild.exe'
- task: MagicChunks@2
inputs:
sourcePath: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\AppxManifest.xml'
fileType: 'Xml'
targetPathType: 'source'
transformationType: 'json'
transformations: |
{
"Package/Identity/@Name": "7f0db578-026f-4e0b-a75b-d5d06bb0a74c",
"Package/Identity/@Publisher": "CN=DGP Studio CI",
"Package/Identity/@Version": "$(build_date).$(rev_number)",
"Package/Properties/DisplayName": "胡桃 Alpha",
"Package/Properties/PublisherDisplayName":"DGP Studio CI"
}
- task: CmdLine@2
displayName: Create resources folder
inputs:
script: |
mkdir Assets
mkdir Resource
workingDirectory: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64'
- task: CopyFiles@2
displayName: Copy Assets Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Assets'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Assets'
- task: CopyFiles@2
displayName: Copy Resource Folder
inputs:
SourceFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\Resource'
Contents: '**'
TargetFolder: '$(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64\Resource'
- task: CmdLine@2
displayName: Build MSIX
inputs:
script: '"C:\Program Files (x86)\Windows Kits\10\bin\10.0.22000.0\x64\makeappx.exe" pack /d $(Build.SourcesDirectory)\src\Snap.Hutao\Snap.Hutao\bin\x64\Release\net7.0-windows10.0.18362.0\win10-x64 /p $(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
- task: MsixSigning@1
displayName: Sign MSIX package
inputs:
package: '$(Build.ArtifactStagingDirectory)/Snap.Hutao.Alpha-$(build_date).$(rev_number).msix'
certificate: 'DGP_Studio_CI.pfx'
passwordVariable: 'pw'
- task: PublishPipelineArtifact@1
displayName: 'Upload Output'
inputs:
targetPath: '$(Build.ArtifactStagingDirectory)/'
artifact: 'Output'
publishLocation: 'pipeline'
- task: DownloadSecureFile@1
name: cerFile
displayName: Download Root CA
inputs:
secureFile: 'Snap.Hutao.CI.cer'
- task: GitHubRelease@1
inputs:
gitHubConnection: 'github.com_Masterain'
repositoryName: 'DGP-Studio/Snap.Hutao'
action: 'create'
target: '$(Build.SourceVersion)'
tagSource: 'userSpecifiedTag'
tag: '$(build_date).$(rev_number)'
title: '$(build_date).$(rev_number)'
releaseNotesSource: 'inline'
releaseNotesInline: |
## 提示 (Hint)
该发布版本由 CI 程序自动打包生成,属于 `Alpha` 测试版,仅用于开发调试和内部测试用途。使用该版本可能存在意料之外的风险,请仅在有明确用途的情况下使用该版本。
This release is a Alpha Testing version generated by CI program automatically in a purpose of debugging and interal testing. Using this release may have unexpected risk, please only use it when you know what you are doing.
assets: |
$(Build.ArtifactStagingDirectory)/*
$(cerFile.secureFilePath)
isPreRelease: true
changeLogCompareToRelease: 'lastFullRelease'
changeLogType: 'commitBased'

View File

@@ -6,8 +6,9 @@
<Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI>
<LangVersion>latest</LangVersion>
<LangVersion>latest</LangVersion>
<Nullable>enable</Nullable>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
</PropertyGroup>
<ItemGroup>

View File

@@ -43,8 +43,10 @@
<CornerRadius x:Key="CompatCornerRadiusBottom">0,0,6,6</CornerRadius>
<CornerRadius x:Key="CompatCornerRadiusSmall">2</CornerRadius>
<!-- Converters -->
<cwuc:BoolNegationConverter x:Key="BoolNegationConverter"/>
<cwuc:BoolToVisibilityConverter x:Key="BoolToVisibilityConverter"/>
<shmmc:AchievementIconConverter x:Key="AchievementIconConverter"/>
<shmmc:AvatarCardConverter x:Key="AvatarCardConverter"/>
<shmmc:AvatarIconConverter x:Key="AvatarIconConverter"/>
<shmmc:AvatarNameCardPicConverter x:Key="AvatarNameCardPicConverter"/>
<shmmc:AvatarSideIconConverter x:Key="AvatarSideIconConverter"/>

View File

@@ -46,7 +46,7 @@ public partial class App : Application
if (firstInstance.IsCurrent)
{
// manually invoke
Activation.Activate(firstInstance, activatedEventArgs);
Activation.NonRedirectToActivate(firstInstance, activatedEventArgs);
firstInstance.Activated += Activation.Activate;
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using System.IO;
using Windows.ApplicationModel;
namespace Snap.Hutao.Context.FileSystem.Location;
@@ -19,7 +20,10 @@ internal class HutaoLocation : IFileSystemLocation
if (string.IsNullOrEmpty(path))
{
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments);
path = Path.GetFullPath(Path.Combine(myDocument, "Hutao"));
// 将测试版与正式版的文件目录分离
string folderName = Package.Current.PublisherDisplayName == "DGP Studio CI" ? "HutaoAlpha" : "Hutao";
path = Path.GetFullPath(Path.Combine(myDocument, folderName));
}
return path;

View File

@@ -3,7 +3,6 @@
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Behavior;

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,17 @@ public class CommandLineBuilder
return condition ? Append(name, value) : this;
}
/// <summary>
/// 当参数不为 null 时添加参数
/// </summary>
/// <param name="name">参数名称</param>
/// <param name="value">值</param>
/// <returns>命令行建造器</returns>
public CommandLineBuilder AppendIfNotNull(string name, object? value = null)
{
return AppendIf(name, value != null, value);
}
/// <summary>
/// 添加参数
/// </summary>
@@ -37,12 +48,6 @@ public class CommandLineBuilder
return this;
}
/// <inheritdoc cref="ToString"/>
public string Build()
{
return ToString();
}
/// <inheritdoc/>
public override string ToString()
{

View File

@@ -28,7 +28,7 @@ internal static class CoreEnvironment
/// <summary>
/// 米游社 Rpc 版本
/// </summary>
public const string HoyolabXrpcVersion = "2.41.0";
public const string HoyolabXrpcVersion = "2.42.1";
/// <summary>
/// 标准UA

View File

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

View File

@@ -2,8 +2,6 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Core.Json.Converter;
using System.Reflection;
using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json;

View File

@@ -23,6 +23,11 @@ internal static class Activation
/// </summary>
public const string LaunchGame = "LaunchGame";
/// <summary>
/// 从剪贴板导入成就
/// </summary>
public const string ImportUIAFFromClipBoard = "ImportUIAFFromClipBoard";
private static readonly SemaphoreSlim ActivateSemaphore = new(1);
/// <summary>
@@ -49,7 +54,21 @@ internal static class Activation
_ = sender;
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
{
HandleActivationAsync(args).SafeForget();
HandleActivationAsync(args, true).SafeForget();
}
}
/// <summary>
/// 触发激活事件
/// </summary>
/// <param name="sender">发送方</param>
/// <param name="args">激活参数</param>
public static void NonRedirectToActivate(object? sender, AppActivationArguments args)
{
_ = sender;
if (!ToastNotificationManagerCompat.WasCurrentProcessToastActivated())
{
HandleActivationAsync(args, false).SafeForget();
}
}
@@ -76,24 +95,24 @@ internal static class Activation
/// 异步响应激活事件
/// </summary>
/// <returns>任务</returns>
private static async Task HandleActivationAsync(AppActivationArguments args)
private static async Task HandleActivationAsync(AppActivationArguments args, bool isRedirected)
{
if (ActivateSemaphore.CurrentCount > 0)
{
using (await ActivateSemaphore.EnterAsync().ConfigureAwait(false))
{
await HandleActivationCoreAsync(args).ConfigureAwait(false);
await HandleActivationCoreAsync(args, isRedirected).ConfigureAwait(false);
}
}
}
private static async Task HandleActivationCoreAsync(AppActivationArguments args)
private static async Task HandleActivationCoreAsync(AppActivationArguments args, bool isRedirected)
{
if (args.Kind == ExtendedActivationKind.Protocol)
{
if (args.TryGetProtocolActivatedUri(out Uri? uri))
{
await HandleUrlActivationAsync(uri).ConfigureAwait(false);
await HandleUrlActivationAsync(uri, isRedirected).ConfigureAwait(false);
}
}
else if (args.Kind == ExtendedActivationKind.Launch)
@@ -131,7 +150,7 @@ internal static class Activation
.SafeForget();
}
private static async Task HandleUrlActivationAsync(Uri uri)
private static async Task HandleUrlActivationAsync(Uri uri, bool isRedirected)
{
UriBuilder builder = new(uri);
@@ -144,28 +163,29 @@ internal static class Activation
case "achievement":
{
await WaitMainWindowAsync().ConfigureAwait(false);
await HandleAchievementActionAsync(action, parameter).ConfigureAwait(false);
await HandleAchievementActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
break;
}
case "dailynote":
{
await HandleDailyNoteActionAsync(action, parameter).ConfigureAwait(false);
await HandleDailyNoteActionAsync(action, parameter, isRedirected).ConfigureAwait(false);
break;
}
}
}
private static async Task HandleAchievementActionAsync(string action, string parameter)
private static async Task HandleAchievementActionAsync(string action, string parameter, bool isRedirected)
{
_ = parameter;
_ = isRedirected;
switch (action)
{
case "/import":
{
await ThreadHelper.SwitchToMainThreadAsync();
INavigationAwaiter navigationAwaiter = new NavigationExtra("InvokeByUri");
INavigationAwaiter navigationAwaiter = new NavigationExtra(ImportUIAFFromClipBoard);
await Ioc.Default
.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.AchievementPage>(navigationAwaiter, true)
@@ -175,7 +195,7 @@ internal static class Activation
}
}
private static async Task HandleDailyNoteActionAsync(string action, string parameter)
private static async Task HandleDailyNoteActionAsync(string action, string parameter, bool isRedirected)
{
_ = parameter;
switch (action)
@@ -186,6 +206,14 @@ internal static class Activation
.GetRequiredService<IDailyNoteService>()
.RefreshDailyNotesAsync(true)
.ConfigureAwait(false);
// Check if it's redirected.
if (!isRedirected)
{
// It's a direct open process, should exit immediately.
Environment.Exit(0);
}
break;
}
}

View File

@@ -19,20 +19,22 @@ internal static class AppInstanceExtension
/// </summary>
/// <param name="appInstance">app实例</param>
/// <param name="args">参数</param>
[SuppressMessage("", "VSTHRD002")]
[SuppressMessage("", "VSTHRD110")]
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
public static void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
{
HANDLE redirectEventHandle = CreateEvent((SECURITY_ATTRIBUTES*)null, true, false, null);
Task.Run(() =>
HANDLE redirectEventHandle = UnsafeCreateEvent();
Task.Run(async () =>
{
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
await appInstance.RedirectActivationToAsync(args);
SetEvent(redirectEventHandle);
});
ReadOnlySpan<HANDLE> handles = new(in redirectEventHandle);
// non-blocking
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
}
private static unsafe HANDLE UnsafeCreateEvent()
{
return CreateEvent(default(SECURITY_ATTRIBUTES*), true, false, null);
}
}

View File

@@ -10,7 +10,7 @@ namespace Snap.Hutao.Core;
/// <summary>
/// 任务计划器服务
/// </summary>
internal static class TaskSchedulerHelper
internal static class ScheduleTaskHelper
{
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
@@ -45,4 +45,30 @@ internal static class TaskSchedulerHelper
return false;
}
}
/// <summary>
/// 卸载全部注册的任务
/// </summary>
/// <returns>是否卸载成功</returns>
public static bool UnregisterAllTasks()
{
try
{
SchedulerTask? targetTask = TaskService.Instance.GetTask(DailyNoteRefreshTaskName);
if (targetTask != null)
{
TaskService.Instance.RootFolder.DeleteTask(DailyNoteRefreshTaskName);
}
return true;
}
catch (UnauthorizedAccessException)
{
return false;
}
catch (COMException)
{
return false;
}
}
}

View File

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

View File

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

View File

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

View File

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

View File

@@ -37,6 +37,7 @@ internal class PickerFactory : IPickerFactory
picker.FileTypeFilter.Add(type);
}
// https://github.com/microsoft/WindowsAppSDK/issues/2931
picker.FileTypeFilter.Add(AnyType);
return picker;

View File

@@ -1,8 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Windowing;
namespace Snap.Hutao.Message;
/// <summary>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,6 +25,7 @@ public class CultivateItem : ObservableObject
Inner = inner;
Entity = entity;
isFinished = Entity.IsFinished;
IsToday = CultivateItemHelper.IsTodaysMaterial(inner.Id, DateTimeOffset.Now);
FinishStateCommand = new RelayCommand(FlipIsFinished);
}
@@ -59,6 +60,16 @@ public class CultivateItem : ObservableObject
}
}
/// <summary>
/// 是否为今日物品
/// </summary>
public bool IsToday { get; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public uint InventoryItemCount { get; set; }
private void FlipIsFinished()
{
IsFinished = !IsFinished;

View File

@@ -0,0 +1,64 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 养成物品帮助类
/// </summary>
public static class CultivateItemHelper
{
/// <summary>
/// 判断是否为当日物品
/// </summary>
/// <param name="itemId">材料Id</param>
/// <param name="now">时间</param>
/// <returns>是否为当日物品</returns>
public static bool IsTodaysMaterial(int itemId, DateTimeOffset now)
{
DateTimeOffset utcNow = now.ToUniversalTime();
utcNow = utcNow.AddHours(4);
DayOfWeek dayOfWeek = utcNow.DayOfWeek;
return dayOfWeek switch
{
DayOfWeek.Monday or DayOfWeek.Thursday => itemId switch
{
104301 or 104302 or 104303 => true, // 「自由」
104310 or 104311 or 104312 => true, // 「繁荣」
104320 or 104321 or 104322 => true, // 「浮世」
104329 or 104330 or 104331 => true, // 「诤言」
114001 or 114002 or 114003 or 114004 => true, // 高塔孤王
114013 or 114014 or 114015 or 114016 => true, // 孤云寒林
114025 or 114026 or 114027 or 114028 => true, // 远海夷地
114037 or 114038 or 114039 or 114040 => true, // 谧林涓露
_ => false,
},
DayOfWeek.Tuesday or DayOfWeek.Friday => itemId switch
{
104304 or 104305 or 104306 => true, // 「抗争」
104313 or 104314 or 104315 => true, // 「勤劳」
104323 or 104324 or 104325 => true, // 「风雅」
104332 or 104333 or 104334 => true, // 「巧思」
114005 or 114006 or 114007 or 114008 => true, // 凛风奔狼
114017 or 114018 or 114019 or 114020 => true, // 雾海云间
114029 or 114030 or 114031 or 114032 => true, // 鸣神御灵
114041 or 114042 or 114043 or 114044 => true, // 绿洲花园
_ => false,
},
DayOfWeek.Wednesday or DayOfWeek.Saturday => itemId switch
{
104307 or 104308 or 104309 => true, // 「诗文」
104316 or 104317 or 104318 => true, // 「黄金」
104326 or 104327 or 104328 => true, // 「天光」
104335 or 104336 or 104337 => true, // 「笃行」
114009 or 114010 or 114011 or 114012 => true, // 狮牙斗士
114021 or 114022 or 114023 or 114024 => true, // 漆黑陨铁
114033 or 114034 or 114035 or 114036 => true, // 今昔剧画
114045 or 114046 or 114047 or 114048 => true, // 谧林涓露
_ => false,
},
_ => false,
};
}
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 仅用于统计总数的养成物品
/// </summary>
public class StatisticsCultivateItem
{
/// <summary>
/// 构造一个新的统计用养成物品
/// </summary>
/// <param name="inner">材料</param>
/// <param name="entity">实体</param>
public StatisticsCultivateItem(Material inner, Entity.CultivateItem entity)
{
Inner = inner;
Count = entity.Count;
}
/// <summary>
/// 元数据
/// </summary>
public Material Inner { get; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public int Count { get; set; }
/// <summary>
/// 对应背包物品的个数
/// </summary>
public uint TotalCount { get; set; }
/// <summary>
/// 是否完成
/// </summary>
public bool IsFinished { get => Count >= TotalCount; }
/// <summary>
/// 格式化总数
/// </summary>
public string CountFormatted { get => $"{Count}/{TotalCount}"; }
}

View File

@@ -0,0 +1,46 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 料理奖励视图
/// </summary>
public class CookBonusView
{
/// <summary>
/// 原型
/// </summary>
public Material OriginItem { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public Material Item { get; set; } = default!;
/// <summary>
/// 创建一个新的料理奖励视图
/// </summary>
/// <param name="cookBonus">料理奖励</param>
/// <param name="idMaterialMap">材料映射</param>
/// <returns>新的料理奖励视图</returns>
public static CookBonusView? Create(CookBonus? cookBonus, Dictionary<MaterialId, Material> idMaterialMap)
{
if (cookBonus == null)
{
return null;
}
CookBonusView view = new()
{
OriginItem = idMaterialMap[cookBonus.OriginItemId],
Item = idMaterialMap[cookBonus.ItemId],
};
return view;
}
}

View File

@@ -20,7 +20,7 @@ internal class Team : List<ComplexAvatar>
public Team(ItemRate<string, int> team, Dictionary<AvatarId, Avatar> idAvatarMap)
: base(4)
{
IEnumerable<int> ids = team.Item.Split(',').Select(int.Parse);
IOrderedEnumerable<int> ids = team.Item.Split(',').Select(int.Parse).OrderByDescending(x => x);
foreach (int id in ids)
{
@@ -28,8 +28,14 @@ internal class Team : List<ComplexAvatar>
}
Rate = $"上场 {team.Rate} 次";
Name = TeamPopularNameParser.GetName(ids.ToHashSet());
}
/// <summary>
/// 队伍俗名
/// </summary>
public string Name { get; set; }
/// <summary>
/// 上场次数
/// </summary>

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Metadata;
using System.Collections.Immutable;
namespace Snap.Hutao.Model.Binding.Hutao;
/// <summary>
/// 队伍俗名解析器
/// </summary>
internal static class TeamPopularNameParser
{
/// <summary>
/// 已知的队伍名称
/// </summary>
private static readonly ImmutableDictionary<string, ImmutableHashSet<int>> KnownTeamNames = new Dictionary<string, ImmutableHashSet<int>>()
{
["雷国|雷行香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Xingqiu, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷国|雷夜香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Yelan, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷国|雷万香班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Kazuha, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["雷九|雷九万班"] = new HashSet<int>() { AvatarIds.Shougun, AvatarIds.Sara, AvatarIds.Kazuha, AvatarIds.Bennett, }.ToImmutableHashSet(),
["万达国际"] = new HashSet<int>() { AvatarIds.Kazuha, AvatarIds.Tartaglia, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["胡行钟夜"] = new HashSet<int>() { AvatarIds.Hutao, AvatarIds.Xingqiu, AvatarIds.Zhongli, AvatarIds.Yelan, }.ToImmutableHashSet(),
["激晴|刻皇万妲"] = new HashSet<int>() { AvatarIds.Keqing, AvatarIds.Fischl, AvatarIds.Kazuha, AvatarIds.Nahida, }.ToImmutableHashSet(),
["永冻|神鹤万心"] = new HashSet<int>() { AvatarIds.Ayaka, AvatarIds.Shenhe, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
["永冻|神钟万心"] = new HashSet<int>() { AvatarIds.Ayaka, AvatarIds.Zhongli, AvatarIds.Kazuha, AvatarIds.Kokomi, }.ToImmutableHashSet(),
["融甘|融化甘雨"] = new HashSet<int>() { AvatarIds.Ganyu, AvatarIds.Zhongli, AvatarIds.Xiangling, AvatarIds.Bennett, }.ToImmutableHashSet(),
["妮绽放|女主"] = new HashSet<int>() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerGirl, }.ToImmutableHashSet(),
["妮绽放|男主"] = new HashSet<int>() { AvatarIds.Nilou, AvatarIds.Kokomi, AvatarIds.Nahida, AvatarIds.PlayerBoy, }.ToImmutableHashSet(),
["一斗岩队"] = new HashSet<int>() { AvatarIds.Itto, AvatarIds.Albedo, AvatarIds.Zhongli, AvatarIds.Gorou, }.ToImmutableHashSet(),
}.ToImmutableDictionary();
/// <summary>
/// 获取队伍名称
/// </summary>
/// <param name="ids">队伍集</param>
/// <returns>队伍名称</returns>
public static string GetName(HashSet<int> ids)
{
foreach (KeyValuePair<string, ImmutableHashSet<int>> entry in KnownTeamNames)
{
if (entry.Value.SetEquals(ids))
{
return entry.Key;
}
}
return "尚未命名";
}
}

View File

@@ -2,16 +2,12 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
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>
/// 可计算角色
@@ -25,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;
@@ -34,6 +30,25 @@ internal class CalculableAvatar : ObservableObject, ICalculableAvatar
Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算角色
/// </summary>
/// <param name="avatar">角色</param>
public CalculableAvatar(Binding.AvatarProperty.Avatar avatar)
{
AvatarId = avatar.Id;
LevelMin = avatar.LevelNumber;
LevelMax = 90; // hard coded 90
Skills = avatar.Skills.Select(s => s.ToCalculable()).ToList();
Name = avatar.Name;
Icon = avatar.Icon;
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}

View File

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

View File

@@ -2,16 +2,12 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
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>
/// 可计算武器
@@ -25,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;
@@ -33,6 +29,24 @@ public class CalculableWeapon : ObservableObject, ICalculableWeapon
Name = weapon.Name;
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
Quality = weapon.RankLevel;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <summary>
/// 构造一个新的可计算武器
/// </summary>
/// <param name="weapon">武器</param>
public CalculableWeapon(Binding.AvatarProperty.Weapon weapon)
{
WeaponId = weapon.Id;
LevelMin = weapon.LevelNumber;
LevelMax = (int)weapon.Quality >= 3 ? 90 : 70;
Name = weapon.Name;
Icon = weapon.Icon;
Quality = weapon.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}

View File

@@ -31,60 +31,4 @@ internal class UIIF
/// </summary>
[JsonPropertyName("list")]
public List<UIIFItem> List { get; set; } = default!;
}
/// <summary>
/// UIIF物品
/// </summary>
[JsonDerivedType(typeof(UIIFReliquary))]
[JsonDerivedType(typeof(UIIFWeapon))]
internal class UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("itemId")]
public int ItemId { get; set; }
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("count")]
public int Count { get; set; }
}
/// <summary>
/// UIIF圣遗物
/// </summary>
internal class UIIFReliquary : UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 副属性列表
/// </summary>
[JsonPropertyName("appendPropIdList")]
public List<int> AppendPropIdList { get; set; } = default!;
}
/// <summary>
/// UIIF武器
/// </summary>
internal class UIIFWeapon : UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 精炼等级 0-4
/// </summary>
[JsonPropertyName("promoteLevel")]
public int PromoteLevel { get; set; }
}

View File

@@ -3,12 +3,6 @@
using Snap.Hutao.Core;
using Snap.Hutao.Extension;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Snap.Hutao.Model.InterChange.Inventory;

View File

@@ -0,0 +1,43 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.InterChange.Inventory;
/// <summary>
/// UIIF物品
/// </summary>
internal class UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("itemId")]
public int ItemId { get; set; }
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("count")]
public int Count { get; set; }
/// <summary>
/// 等级
/// Reliquary/Weapon
/// </summary>
[JsonPropertyName("level")]
public int? Level { get; set; }
/// <summary>
/// 副属性列表
/// Reliquary
/// </summary>
[JsonPropertyName("appendPropIdList")]
public List<int>? AppendPropIdList { get; set; } = default!;
/// <summary>
/// 精炼等级 0-4
/// Weapon
/// </summary>
[JsonPropertyName("promoteLevel")]
public int? PromoteLevel { get; set; }
}

View File

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

View File

@@ -0,0 +1,59 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Model.Intrinsic;
/// <summary>
/// 材料类型
/// </summary>
[SuppressMessage("", "SA1602")]
public enum MaterialType
{
MATERIAL_NONE = 0,
MATERIAL_FOOD = 1,
MATERIAL_QUEST = 2,
MATERIAL_EXCHANGE = 4,
MATERIAL_CONSUME,
MATERIAL_EXP_FRUIT,
MATERIAL_AVATAR,
MATERIAL_ADSORBATE,
MATERIAL_CRICKET,
MATERIAL_ELEM_CRYSTAL,
MATERIAL_WEAPON_EXP_STONE,
MATERIAL_CHEST,
MATERIAL_RELIQUARY_MATERIAL,
MATERIAL_AVATAR_MATERIAL,
MATERIAL_NOTICE_ADD_HP,
MATERIAL_SEA_LAMP,
MATERIAL_SELECTABLE_CHEST,
MATERIAL_FLYCLOAK,
MATERIAL_NAMECARD,
MATERIAL_TALENT,
MATERIAL_WIDGET,
MATERIAL_CHEST_BATCH_USE,
MATERIAL_FAKE_ABSORBATE,
MATERIAL_CONSUME_BATCH_USE,
MATERIAL_WOOD,
MATERIAL_FURNITURE_FORMULA = 27,
MATERIAL_CHANNELLER_SLAB_BUFF,
MATERIAL_FURNITURE_SUITE_FORMULA,
MATERIAL_COSTUME,
MATERIAL_HOME_SEED,
MATERIAL_FISH_BAIT,
MATERIAL_FISH_ROD,
MATERIAL_SUMO_BUFF, // never appear
MATERIAL_FIREWORKS,
MATERIAL_BGM,
MATERIAL_SPICE_FOOD,
MATERIAL_ACTIVITY_ROBOT,
MATERIAL_ACTIVITY_GEAR,
MATERIAL_ACTIVITY_JIGSAW,
MATERIAL_ARANARA,
MATERIAL_GCG_CARD,
MATERIAL_GCG_CARD_FACE, // 影幻卡面
MATERIAL_GCG_CARD_BACK,
MATERIAL_GCG_FIELD,
MATERIAL_DESHRET_MANUAL,
MATERIAL_RENAME_ITEM,
MATERIAL_GCG_EXCHANGE_ITEM,
}

View File

@@ -74,24 +74,24 @@ public enum WeaponType
/// <summary>
/// 法器
/// </summary>
[Description("单手剑")]
[Description("法器")]
WEAPON_CATALYST = 10,
/// <summary>
/// 双手剑
/// </summary>
[Description("手剑")]
[Description("手剑")]
WEAPON_CLAYMORE = 11,
/// <summary>
/// 弓
/// </summary>
[Description("单手剑")]
[Description("")]
WEAPON_BOW = 12,
/// <summary>
/// 长柄武器
/// </summary>
[Description("单手剑")]
[Description("长柄武器")]
WEAPON_POLE = 13,
}
}

View File

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

View File

@@ -4,6 +4,7 @@
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
@@ -21,6 +22,17 @@ public partial class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQu
[JsonIgnore]
public ComplexAvatarCollocation? Collocation { get; set; }
/// <summary>
/// [非元数据] 烹饪奖励
/// </summary>
[JsonIgnore]
public CookBonusView? CookBonusView { get; set; }
/// <summary>
/// 养成物品视图
/// </summary>
public List<Material>? CultivationItemsView { get; set; }
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()
{

View File

@@ -81,5 +81,10 @@ public partial class Avatar
/// <summary>
/// 皮肤
/// </summary>
public IEnumerable<Costume> Costumes { get; set; } = default!;
public List<Costume> Costumes { get; set; } = default!;
/// <summary>
/// 养成物品
/// </summary>
public List<MaterialId> CultivationItems { get; set; } = default!;
}

View File

@@ -1,7 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -13,45 +13,15 @@ public class CookBonus
/// <summary>
/// 原型名称
/// </summary>
public string OriginName { get; set; } = default!;
/// <summary>
/// 原型描述
/// </summary>
public string OriginDescription { get; set; } = default!;
/// <summary>
/// 原型图标
/// </summary>
public string OriginIcon { get; set; } = default!;
public MaterialId OriginItemId { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 效果描述
/// </summary>
public string EffectDescription { get; set; } = default!;
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 物品等级
/// </summary>
public ItemQuality RankLevel { get; set; }
public MaterialId ItemId { get; set; } = default!;
/// <summary>
/// 材料列表
/// </summary>
public List<ItemWithCount> InputList { get; set; } = default!;
public List<MaterialId> InputList { get; set; } = default!;
}

View File

@@ -83,7 +83,7 @@ public class FetterInfo
/// <summary>
/// 料理
/// </summary>
public CookBonus? CookBonus { get; set; }
public CookBonus? CookBonus2 { get; set; }
/// <summary>
/// 好感语音

View File

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

View File

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

View File

@@ -12,9 +12,19 @@ internal class AchievementIconConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/AchievementIcon/{0}.png";
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
return new Uri(string.Format(BaseUrl, name));
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return new Uri(string.Format(BaseUrl, from));
return IconNameToUri(from);
}
}

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Control;
namespace Snap.Hutao.Model.Metadata.Converter;
/// <summary>
/// 角色卡片转换器
/// </summary>
internal class AvatarCardConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/AvatarCard/{0}_Card.png";
private static readonly Uri UIAvatarIconCostumeCard = new("https://static.snapgenshin.com/AvatarCard/UI_AvatarIcon_Costume_Card.png");
/// <summary>
/// 名称转Uri
/// </summary>
/// <param name="name">名称</param>
/// <returns>链接</returns>
public static Uri IconNameToUri(string name)
{
if (string.IsNullOrEmpty(name))
{
return UIAvatarIconCostumeCard;
}
return new Uri(string.Format(BaseUrl, name));
}
/// <inheritdoc/>
public override Uri Convert(string from)
{
return IconNameToUri(from);
}
}

View File

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

View File

@@ -12,6 +12,8 @@ internal class ItemIconConverter : ValueConverterBase<string, Uri>
{
private const string BaseUrl = "https://static.snapgenshin.com/ItemIcon/{0}.png";
private static readonly Uri UIItemIconNone = new("https://static.snapgenshin.com/Bg/UI_ItemIcon_None.png");
/// <summary>
/// 名称转Uri
/// </summary>

View File

@@ -105,4 +105,4 @@ internal class PropertyInfoDescriptor : ValueConverterBase<PropertyInfo, IList<L
return results;
}
}
}

View File

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

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata;
@@ -14,7 +14,7 @@ public class Material
/// <summary>
/// 物品Id
/// </summary>
public int Id { get; set; }
public MaterialId Id { get; set; }
/// <summary>
/// 等级
@@ -26,6 +26,11 @@ public class Material
/// </summary>
public ItemType ItemType { get; set; }
/// <summary>
/// 材料类型
/// </summary>
public MaterialType MaterialType { get; set; }
/// <summary>
/// 图标
/// </summary>
@@ -45,4 +50,9 @@ public class Material
/// 类型描述
/// </summary>
public string TypeDescription { get; set; } = default!;
/// <summary>
/// 效果描述
/// </summary>
public string? EffectDescription { get; set; }
}

View File

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

View File

@@ -0,0 +1,71 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive.Converter;
namespace Snap.Hutao.Model.Primitive;
/// <summary>
/// 3-6位 材料Id
/// </summary>
[JsonConverter(typeof(IdentityConverter<MaterialId>))]
public readonly struct MaterialId : IEquatable<MaterialId>, IComparable<MaterialId>
{
/// <summary>
/// 值
/// </summary>
public readonly int Value;
/// <summary>
/// Initializes a new instance of the <see cref="MaterialId"/> struct.
/// </summary>
/// <param name="value">value</param>
public MaterialId(int value)
{
Value = value;
}
public static implicit operator int(MaterialId value)
{
return value.Value;
}
public static implicit operator MaterialId(int value)
{
return new(value);
}
public static bool operator ==(MaterialId left, MaterialId right)
{
return left.Value == right.Value;
}
public static bool operator !=(MaterialId left, MaterialId right)
{
return !(left == right);
}
/// <inheritdoc/>
public int CompareTo(MaterialId other)
{
return Value.CompareTo(other.Value);
}
/// <inheritdoc/>
public bool Equals(MaterialId other)
{
return Value == other.Value;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
return obj is MaterialId other && Equals(other);
}
/// <inheritdoc/>
public override int GetHashCode()
{
return Value.GetHashCode();
}
}

View File

@@ -62,4 +62,4 @@ public readonly struct WeaponId : IEquatable<WeaponId>
{
return Value.GetHashCode();
}
}
}

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement;
using BindingAchievement = Snap.Hutao.Model.Binding.Achievement.Achievement;
using EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
@@ -22,6 +22,7 @@ namespace Snap.Hutao.Service.Achievement;
[Injection(InjectAs.Scoped, typeof(IAchievementService))]
internal class AchievementService : IAchievementService
{
private readonly object saveAchievementLocker = new();
private readonly AppDbContext appDbContext;
private readonly ILogger<AchievementService> logger;
private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent;
@@ -187,4 +188,19 @@ internal class AchievementService : IAchievementService
logger.LogInformation(EventIds.Achievement, "{add} added, {update} updated, {remove} removed", result.Add, result.Update, result.Remove);
logger.LogInformation(EventIds.Achievement, "Save achievements for [{name}] completed in {time}ms", name, time);
}
/// <inheritdoc/>
public void SaveAchievement(BindingAchievement achievement)
{
if (achievement.IsChecked)
{
// set to default allow multiple time add
achievement.Entity.InnerId = default;
appDbContext.Achievements.UpdateAndSave(achievement.Entity);
}
else
{
appDbContext.Achievements.RemoveAndSave(achievement.Entity);
}
}
}

View File

@@ -1,10 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
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;
@@ -57,6 +56,12 @@ internal interface IAchievementService
/// <returns>任务</returns>
Task RemoveArchiveAsync(EntityArchive archive);
/// <summary>
/// 保存单个成就
/// </summary>
/// <param name="achievement">成就</param>
void SaveAchievement(BindingAchievement achievement);
/// <summary>
/// 保存成就
/// </summary>
@@ -69,6 +74,5 @@ internal interface IAchievementService
/// </summary>
/// <param name="newArchive">新存档</param>
/// <returns>存档添加结果</returns>
[ThreadAccess(ThreadAccessState.AnyThread)]
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -131,7 +131,7 @@ internal class CultivationService : ICultivationService
/// <inheritdoc/>
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Model.Metadata.Material> metadata,
List<Model.Metadata.Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
{
@@ -148,19 +148,20 @@ internal class CultivationService : ICultivationService
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateEntry? entry in entries)
foreach (CultivateEntry entry in entries)
{
Guid entryId = entry.InnerId;
List<BindingCultivateItem> resultItems = new();
List<CultivateItem> items = await appDbContext.CultivateItems
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId).ToListAsync()
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
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
@@ -173,7 +174,71 @@ internal class CultivationService : ICultivationService
results.Add(new(entry, itemBase, resultItems));
}
return new(results);
return new(results.OrderByDescending(e => e.Items.Any(i => i.IsToday)));
}
}
/// <inheritdoc/>
public async Task<List<Model.Binding.Cultivation.StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Model.Metadata.Material> materials)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Guid projectId = cultivateProject.InnerId;
List<Model.Binding.Cultivation.StatisticsCultivateItem> resultItems = new();
List<CultivateEntry> entries = await appDbContext.CultivateEntries
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateEntry entry in entries)
{
Guid entryId = entry.InnerId;
List<CultivateItem> items = await appDbContext.CultivateItems
.AsNoTracking()
.Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false);
foreach (CultivateItem item in items)
{
if (item.IsFinished)
{
continue;
}
if (resultItems.SingleOrDefault(i => i.Inner.Id == item.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
{
inPlaceItem.Count += item.Count;
}
else
{
resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item));
}
}
}
List<InventoryItem> inventoryItems = await appDbContext.InventoryItems
.AsNoTracking()
.Where(e => e.ProjectId == projectId)
.ToListAsync()
.ConfigureAwait(false);
foreach (InventoryItem inventoryItem in inventoryItems)
{
if (resultItems.SingleOrDefault(i => i.Inner.Id == inventoryItem.ItemId) is Model.Binding.Cultivation.StatisticsCultivateItem inPlaceItem)
{
inPlaceItem.TotalCount += inventoryItem.Count;
}
}
return resultItems.OrderByDescending(i => i.Count).ToList();
}
}
@@ -209,6 +274,11 @@ internal class CultivationService : ICultivationService
/// <inheritdoc/>
public async Task<bool> SaveConsumptionAsync(Model.Binding.Cultivation.CultivateType type, int itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
{
if (items.Count == 0)
{
return true;
}
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

View File

@@ -24,11 +24,11 @@ internal interface ICultivationService
/// 获取绑定用的养成列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">材料</param>
/// <param name="materials">材料</param>
/// <param name="idAvatarMap">Id角色映射</param>
/// <param name="idWeaponMap">Id武器映射</param>
/// <returns>绑定用的养成列表</returns>
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> metadata, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> materials, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
/// <summary>
/// 获取物品列表
@@ -44,6 +44,14 @@ internal interface ICultivationService
/// <returns>项目集合</returns>
ObservableCollection<CultivateProject> GetProjectCollection();
/// <summary>
/// 异步获取统计物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">元数据</param>
/// <returns>统计物品列表</returns>
Task<List<StatisticsCultivateItem>> GetStatisticsCultivateItemsAsync(CultivateProject cultivateProject, List<Material> materials);
/// <summary>
/// 删除养成清单
/// </summary>

View File

@@ -1,22 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Auth;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
using System.Collections.ObjectModel;
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
namespace Snap.Hutao.Service.DailyNote;
@@ -26,19 +18,16 @@ namespace Snap.Hutao.Service.DailyNote;
internal class DailyNoteNotifier
{
private readonly IServiceScopeFactory scopeFactory;
private readonly BindingClient bindingClient;
private readonly DailyNoteEntry entry;
/// <summary>
/// 构造一个新的实时便笺通知器
/// </summary>
/// <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;
}
@@ -128,37 +117,48 @@ 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>();
AuthClient authClient = scope.ServiceProvider.GetRequiredService<AuthClient>();
string? actionTicket = await authClient
.GetActionTicketByStokenAsync("game_role", entry.User)
.ConfigureAwait(false);
List<UserGameRole> roles = await scope.ServiceProvider
.GetRequiredService<BindingClient>()
.GetUserGameRolesByActionTicketAsync(actionTicket!, entry.User)
.ConfigureAwait(false);
string attribution = roles.SingleOrDefault(r => r.GameUid == entry.Uid)?.ToString() ?? "未知角色";
ToastContentBuilder builder = new ToastContentBuilder()
.AddHeader("DAILYNOTE", "实时便笺提醒", "DAILYNOTE")
.AddAttributionText(attribution)
.AddButton(new ToastButton().SetContent("开始游戏").AddArgument("Action", "LaunchGame").AddArgument("Uid", entry.Uid))
.AddButton(new ToastButtonDismiss("我知道了"));
if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean())
{
builder.SetToastScenario(ToastScenario.Reminder);
}
}
if (hints.Count > 2)
{
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
if (hints.Count > 2)
{
builder.AddText(hint);
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{
builder.AddText(hint);
}
}
}
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
await ThreadHelper.SwitchToMainThreadAsync();
builder.Show();
}
}
}

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
@@ -12,9 +11,7 @@ using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Hoyolab.Takumi.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
using System.Collections.ObjectModel;
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote;
@@ -63,9 +60,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);
}
@@ -84,7 +81,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);
}
}
@@ -98,7 +95,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))
{
@@ -113,7 +109,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
if (notify)
{
await new DailyNoteNotifier(scopeFactory, bindingClient, entry).NotifyAsync().ConfigureAwait(false);
await new DailyNoteNotifier(scopeFactory, entry).NotifyAsync().ConfigureAwait(false);
}
}

View File

@@ -1,7 +1,6 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension;

View File

@@ -244,9 +244,10 @@ internal class GameService : IGameService, IDisposable
string commandLine = new CommandLineBuilder()
.AppendIf("-popupwindow", configuration.IsBorderless)
.Append("-screen-fullscreen", configuration.IsFullScreen ? 1 : 0)
.AppendIf("-window-mode", configuration.IsExclusive, "exclusive")
.Append("-screen-width", configuration.ScreenWidth)
.Append("-screen-height", configuration.ScreenHeight)
.Build();
.ToString();
Process game = new()
{
@@ -310,10 +311,6 @@ internal class GameService : IGameService, IDisposable
{
account = GameAccount.Create(name, registrySdk);
// sync cache
await ThreadHelper.SwitchToMainThreadAsync();
gameAccounts.Add(GameAccount.Create(name, registrySdk));
// sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
@@ -324,6 +321,10 @@ internal class GameService : IGameService, IDisposable
.AddAndSaveAsync(account)
.ConfigureAwait(false);
}
// sync cache
await ThreadHelper.SwitchToMainThreadAsync();
gameAccounts.Add(account);
}
}
}

View File

@@ -8,6 +8,11 @@ namespace Snap.Hutao.Service.Game;
/// </summary>
internal readonly struct LaunchConfiguration
{
/// <summary>
/// 是否为独占全屏
/// </summary>
public readonly bool IsExclusive;
/// <summary>
/// 是否全屏,全屏时无边框设置将被覆盖
/// </summary>
@@ -41,14 +46,16 @@ internal readonly struct LaunchConfiguration
/// <summary>
/// 构造一个新的启动配置
/// </summary>
/// <param name="isExclusive">独占全屏</param>
/// <param name="isFullScreen">全屏</param>
/// <param name="isBorderless">无边框</param>
/// <param name="screenWidth">宽度</param>
/// <param name="screenHeight">高度</param>
/// <param name="unlockFps">解锁帧率</param>
/// <param name="targetFps">目标帧率</param>
public LaunchConfiguration(bool isFullScreen, bool isBorderless, int screenWidth, int screenHeight, bool unlockFps, int targetFps)
public LaunchConfiguration(bool isExclusive, bool isFullScreen, bool isBorderless, int screenWidth, int screenHeight, bool unlockFps, int targetFps)
{
IsExclusive = isExclusive;
IsFullScreen = isFullScreen;
IsBorderless = isBorderless;
ScreenHeight = screenHeight;

View File

@@ -23,7 +23,7 @@ internal partial class UnityLogGameLocator : IGameLocator
string appDataPath = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);
string logFilePath = Path.Combine(appDataPath, @"..\LocalLow\miHoYo\原神\output_log.txt");
using (var tempFile = TemporaryFile.CreateFromFileCopy(logFilePath))
using (TemporaryFile? tempFile = TemporaryFile.CreateFromFileCopy(logFilePath))
{
if (tempFile == null)
{
@@ -32,7 +32,7 @@ internal partial class UnityLogGameLocator : IGameLocator
string content = File.ReadAllText(tempFile.Path);
var matchResult = WarmupFileLine().Match(content);
Match matchResult = WarmupFileLine().Match(content);
if (!matchResult.Success)
{
return new(false, $"在 Unity 日志文件中找不到游戏路径");

View File

@@ -150,9 +150,12 @@ internal class GameFpsUnlocker : IGameFpsUnlocker
Must.Range(adr >= 0, "未匹配到FPS字节");
int rip = adr + 2;
int rel = image.Fixed<int>(rip + 2); // Unsafe.ReadUnaligned<int>(ref image[rip + 2]);
int ofs = rip + rel + 6;
fpsAddress = (nuint)((long)unityPlayer.modBaseAddr + ofs);
fixed (byte* pSpan = image)
{
int rip = adr + 2;
int rel = *(int*)(pSpan + rip + 2); // Unsafe.ReadUnaligned<int>(ref image[rip + 2]);
int ofs = rip + rel + 6;
fpsAddress = (nuint)((long)unityPlayer.modBaseAddr + ofs);
}
}
}

View File

@@ -64,6 +64,13 @@ internal interface IMetadataService
/// <returns>Id到角色的字典</returns>
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default);
/// <summary>
/// 异步获取Id到材料的字典
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>Id到材料的字典</returns>
ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default(CancellationToken));
/// <summary>
/// 异步获取ID到圣遗物副词条的字典
/// </summary>

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Metadata.Weapon;
@@ -26,6 +27,12 @@ internal partial class MetadataService
return FromCacheAsDictionaryAsync<AvatarId, Avatar>("Avatar", a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<MaterialId, Material>> GetIdToMaterialMapAsync(CancellationToken token = default)
{
return FromCacheAsDictionaryAsync<MaterialId, Material>("Material", a => a.Id, token);
}
/// <inheritdoc/>
public ValueTask<Dictionary<ReliquaryAffixId, ReliquaryAffix>> GetIdReliquaryAffixMapAsync(CancellationToken token = default)
{

View File

@@ -5,7 +5,9 @@
<TargetPlatformMinVersion>10.0.18362.0</TargetPlatformMinVersion>
<RootNamespace>Snap.Hutao</RootNamespace>
<ApplicationManifest>app.manifest</ApplicationManifest>
<Platform>x64</Platform>
<Platforms>x64</Platforms>
<RuntimeIdentifier>win10-x64</RuntimeIdentifier>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<PublishProfile>win10-$(Platform).pubxml</PublishProfile>
<UseWinUI>true</UseWinUI>
@@ -69,6 +71,7 @@
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
<None Remove="View\Dialog\AdoptCalculatorDialog.xaml" />
<None Remove="View\Dialog\AvatarInfoQueryDialog.xaml" />
<None Remove="View\Dialog\CommunityGameRecordDialog.xaml" />
<None Remove="View\Dialog\CultivateProjectDialog.xaml" />
<None Remove="View\Dialog\CultivatePromotionDeltaDialog.xaml" />
<None Remove="View\Dialog\DailyNoteNotificationDialog.xaml" />
@@ -136,7 +139,7 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview2" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview3" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
@@ -153,7 +156,7 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.164-beta">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.138-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -181,6 +184,11 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\CommunityGameRecordDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\CultivatePromotionDeltaDialog.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -41,8 +41,8 @@ public sealed partial class DescParamComboBox : UserControl
/// </summary>
public int PreferredSelectedIndex
{
get { return (int)GetValue(PreferredSelectedIndexProperty); }
set { SetValue(PreferredSelectedIndexProperty, value); }
get => (int)GetValue(PreferredSelectedIndexProperty);
set => SetValue(PreferredSelectedIndexProperty, value);
}
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
@@ -54,7 +54,7 @@ public sealed partial class DescParamComboBox : UserControl
if (args.NewValue != args.OldValue && args.NewValue is IList<LevelParam<string, ParameterInfo>> list)
{
descParamComboBox.ItemHost.ItemsSource = list;
descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count);
descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count - 1);
}
}
}

View File

@@ -0,0 +1,22 @@
<!-- Copyright (c) Microsoft Corporation and Contributors. -->
<!-- Licensed under the MIT License. -->
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.CommunityGameRecordDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Closed="OnContentDialogClosed"
DefaultButton="Primary"
PrimaryButtonText="完成"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<Grid Loaded="OnGridLoaded">
<WebView2
Name="WebView"
Width="360"
Height="580"/>
</Grid>
</ContentDialog>

View File

@@ -0,0 +1,60 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.
using Microsoft.Extensions.DependencyInjection;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.Web.WebView2.Core;
using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Service.User;
using Snap.Hutao.Web.Bridge;
namespace Snap.Hutao.View.Dialog;
/// <summary>
/// <20><><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7>¼<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
public sealed partial class CommunityGameRecordDialog : ContentDialog
{
private readonly IServiceScope scope;
[SuppressMessage("", "IDE0052")]
private MiHoYoJSInterface? jsInterface;
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>Ϸ<EFBFBD><CFB7>¼<EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
public CommunityGameRecordDialog(MainWindow window)
{
InitializeComponent();
XamlRoot = window.Content.XamlRoot;
scope = Ioc.Default.CreateScope();
}
private void OnGridLoaded(object sender, RoutedEventArgs e)
{
InitializeAsync().SafeForget();
}
private async Task InitializeAsync()
{
await WebView.EnsureCoreWebView2Async();
CoreWebView2 coreWebView2 = WebView.CoreWebView2;
User? user = scope.ServiceProvider.GetRequiredService<IUserService>().Current;
if (user == null)
{
return;
}
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
jsInterface = new(coreWebView2, scope.ServiceProvider);
coreWebView2.Navigate("https://webstatic.mihoyo.com/app/community-game-records/index.html");
}
private void OnContentDialogClosed(ContentDialog sender, ContentDialogClosedEventArgs args)
{
jsInterface = null;
scope.Dispose();
}
}

View File

@@ -3,20 +3,8 @@
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace Snap.Hutao.View.Dialog;

View File

@@ -36,8 +36,8 @@ public sealed partial class CultivatePromotionDeltaDialog : ContentDialog
/// </summary>
public ICalculableAvatar? Avatar
{
get { return (ICalculableAvatar?)GetValue(AvatarProperty); }
set { SetValue(AvatarProperty, value); }
get => (ICalculableAvatar?)GetValue(AvatarProperty);
set => SetValue(AvatarProperty, value);
}
/// <summary>
@@ -45,8 +45,8 @@ public sealed partial class CultivatePromotionDeltaDialog : ContentDialog
/// </summary>
public ICalculableWeapon? Weapon
{
get { return (ICalculableWeapon?)GetValue(WeaponProperty); }
set { SetValue(WeaponProperty, value); }
get => (ICalculableWeapon?)GetValue(WeaponProperty);
set => SetValue(WeaponProperty, value);
}
/// <summary>

View File

@@ -50,9 +50,6 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider);
#if DEBUG
coreWebView2.OpenDevToolsWindow();
#endif
string query = $"?role_id={uid.Value}&server={uid.Region}";
coreWebView2.Navigate($"https://webstatic.mihoyo.com/app/community-game-records/index.html?bbs_presentation_style=fullscreen#/ys/daily/{query}");
}

View File

@@ -39,8 +39,7 @@ public sealed partial class SignInWebViewDialog : ContentDialog
{
await WebView.EnsureCoreWebView2Async();
CoreWebView2 coreWebView2 = WebView.CoreWebView2;
IUserService userService = scope.ServiceProvider.GetRequiredService<IUserService>();
User? user = userService.Current;
User? user = scope.ServiceProvider.GetRequiredService<IUserService>().Current;
if (user == null)
{
@@ -49,10 +48,6 @@ public sealed partial class SignInWebViewDialog : ContentDialog
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
signInJsInterface = new(coreWebView2, scope.ServiceProvider);
#if DEBUG
coreWebView2.OpenDevToolsWindow();
#endif
coreWebView2.Navigate("https://webstatic.mihoyo.com/bbs/event/signin-ys/index.html?act_id=e202009291139501");
}
@@ -61,4 +56,4 @@ public sealed partial class SignInWebViewDialog : ContentDialog
signInJsInterface = null;
scope.Dispose();
}
}
}

View File

@@ -15,6 +15,10 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Page.Resources>
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
</Page.Resources>
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
@@ -30,6 +34,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>
@@ -71,7 +81,6 @@
Command="{Binding RemoveArchiveCommand}"
Icon="{shcm:FontIcon Glyph=&#xE74D;}"
Label="删除当前存档"/>
<AppBarSeparator/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入">
@@ -93,6 +102,7 @@
Icon="{shcm:FontIcon Glyph=&#xEDE1;}"
Label="导出"/>
<AppBarSeparator/>
<AppBarToggleButton
Command="{Binding SortIncompletedSwitchCommand}"
Icon="{shcm:FontIcon Glyph=&#xE8CB;}"
@@ -114,21 +124,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>
@@ -163,6 +183,8 @@
Grid.Column="1"
Margin="6,0,12,0"
Padding="16,0,0,0"
Command="{Binding Path=DataContext.SaveAchievementCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
IsChecked="{Binding IsChecked, Mode=TwoWay}"
Style="{StaticResource DefaultCheckBoxStyle}">
<CheckBox.Content>

View File

@@ -142,6 +142,13 @@
<Grid>
<ScrollViewer Padding="0,0,4,0">
<StackPanel>
<ItemsControl>
<ItemsControl.ItemTemplate>
<DataTemplate>
<InfoBar/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Pivot>
<PivotItem
Content="{Binding Announcement.List[0]}"

View File

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

View File

@@ -2,6 +2,7 @@
x:Class="Snap.Hutao.View.Page.CultivationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwua="using:CommunityToolkit.WinUI.UI.Animations"
xmlns:cwucont="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:cwuconv="using:CommunityToolkit.WinUI.UI.Converters"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
@@ -29,6 +30,11 @@
<x:Double>1</x:Double>
</cwuconv:BoolToObjectConverter.FalseValue>
</cwuconv:BoolToObjectConverter>
<cwuconv:BoolToObjectConverter
x:Key="BoolToStyleSelector"
FalseValue="{StaticResource BodyTextBlockStyle}"
TrueValue="{StaticResource BaseTextBlockStyle}"/>
</Page.Resources>
<mxi:Interaction.Behaviors>
@@ -43,6 +49,54 @@
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<AppBarButton
Command="{Binding UpdateStatisticsItemsCommand}"
Icon="{shcm:FontIcon Glyph=&#xEB05;}"
Label="材料统计">
<AppBarButton.Flyout>
<Flyout Placement="Bottom">
<Flyout.FlyoutPresenterStyle>
<Style BasedOn="{StaticResource DefaultFlyoutPresenterStyle}" TargetType="FlyoutPresenter">
<Setter Property="MaxHeight" Value="480"/>
<Setter Property="Padding" Value="0,2,0,2"/>
<Setter Property="Background" Value="{ThemeResource FlyoutPresenterBackground}"/>
</Style>
</Flyout.FlyoutPresenterStyle>
<ItemsControl Margin="16,0,16,16" ItemsSource="{Binding StatisticsItems}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,16,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="2"
Margin="16,0,4,0"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding CountFormatted}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarSeparator/>
<AppBarElementContainer>
<ComboBox
Height="36"
@@ -71,7 +125,8 @@
<PivotItem Header="材料清单">
<cwucont:AdaptiveGridView
Padding="16,16,4,4"
DesiredWidth="360"
cwua:ItemsReorderAnimation.Duration="0:0:0.1"
DesiredWidth="320"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding CultivateEntries}"
SelectionMode="None">
@@ -81,7 +136,7 @@
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<Grid Margin="8">
<Grid.ColumnDefinitions>
@@ -115,64 +170,65 @@
ToolTipService.ToolTip="删除清单"/>
</StackPanel>
</Grid>
<ItemsControl
Grid.Row="1"
Margin="8,0,8,8"
ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<shvc:ItemIcon
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
FontSize="24"
Glyph="&#xE73E;"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
<Button
Grid.Column="1"
Height="32"
Margin="6,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding FinishStateCommand}">
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
<ScrollViewer Grid.Row="1" Height="240">
<ItemsControl Margin="8,0,8,8" ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<Grid>
<shvc:ItemIcon
Width="32"
Height="32"
Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}"
Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}"
Quality="{Binding Inner.RankLevel}"/>
<FontIcon
HorizontalAlignment="Center"
VerticalAlignment="Center"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding Entity.Count}"/>
FontSize="24"
Glyph="&#xE73E;"
Visibility="{Binding IsFinished, Converter={StaticResource BoolToVisibilityConverter}}"/>
</Grid>
</Button>
<Button
Grid.Column="1"
Height="32"
Margin="6,0,0,0"
HorizontalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
Command="{Binding FinishStateCommand}">
<Grid Opacity="{Binding IsFinished, Converter={StaticResource BoolToOpacityConverter}}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<TextBlock
Grid.Column="0"
Margin="0,0,0,0"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Inner.Name}"
TextTrimming="CharacterEllipsis"/>
<TextBlock
Grid.Column="1"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Style="{Binding IsToday, Converter={StaticResource BoolToStyleSelector}}"
Text="{Binding Entity.Count}"/>
</Grid>
</Button>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</ScrollViewer>
<Grid.Resources>
<Storyboard x:Name="ButtonPanelVisibleStoryboard">

View File

@@ -87,6 +87,10 @@
</Flyout>
</AppBarButton.Flyout>
</AppBarButton>
<AppBarButton
Command="{Binding DailyNoteVerificationCommand}"
Icon="{shcm:FontIcon Glyph=&#xE9D5;}"
Label="验证当前账号角色"/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE713;}" Label="通知设置">
<AppBarButton.Flyout>
<Flyout Placement="BottomEdgeAlignedRight">

View File

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

Some files were not shown because too many files have changed in this diff Show More