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 - type: markdown
attributes: attributes:
value: | value: |
请按下方的要求填写完整的问题表单,以便我们更快的定位问题 请按下方的要求填写完整的问题表单。
- type: textarea
id: back
attributes:
label: 背景与动机
description: 添加此功能的理由
validations:
required: true
- type: textarea - type: textarea
id: req id: req
attributes: attributes:
label: 想要实现或优化的功能 label: 想要实现或优化的功能
description: 详细的描述一下你想要的功能 description: 详细的描述一下你想要的功能,描述的越具体,采纳的可能性越高
validations: validations:
required: true 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) ![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) * [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) * [xunkong/xunkong](https://github.com/xunkong/xunkong)
* [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat) * [YuehaiTeam/cocogoat](https://github.com/YuehaiTeam/cocogoat)
### 技术栈 ### 使用的技术栈
* [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet) * [CommunityToolkit/dotnet](https://github.com/CommunityToolkit/dotnet)
* [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit) * [CommunityToolkit/WindowsCommunityToolkit](https://github.com/CommunityToolkit/WindowsCommunityToolkit)
* [dahall/taskscheduler](https://github.com/dahall/taskscheduler)
* [dotnet/efcore](https://github.com/dotnet/efcore) * [dotnet/efcore](https://github.com/dotnet/efcore)
* [dotnet/runtime](https://github.com/dotnet/runtime) * [dotnet/runtime](https://github.com/dotnet/runtime)
* [DotNetAnalyzers/StyleCopAnalyzers](https://github.com/DotNetAnalyzers/StyleCopAnalyzers) * [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> <Platforms>x64</Platforms>
<RuntimeIdentifiers>win10-x64</RuntimeIdentifiers> <RuntimeIdentifiers>win10-x64</RuntimeIdentifiers>
<UseWinUI>true</UseWinUI> <UseWinUI>true</UseWinUI>
<LangVersion>latest</LangVersion> <LangVersion>latest</LangVersion>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<EnablePreviewMsixTooling>true</EnablePreviewMsixTooling>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>

View File

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

View File

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

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using System.IO; using System.IO;
using Windows.ApplicationModel;
namespace Snap.Hutao.Context.FileSystem.Location; namespace Snap.Hutao.Context.FileSystem.Location;
@@ -19,7 +20,10 @@ internal class HutaoLocation : IFileSystemLocation
if (string.IsNullOrEmpty(path)) if (string.IsNullOrEmpty(path))
{ {
string myDocument = Environment.GetFolderPath(Environment.SpecialFolder.MyDocuments); 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; return path;

View File

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

View File

@@ -4,7 +4,7 @@
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using Windows.UI; using Windows.UI;
namespace Snap.Hutao.Control.Image; namespace Snap.Hutao.Control.Media;
/// <summary> /// <summary>
/// BGRA8 结构 /// 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.Controls;
using Microsoft.UI.Xaml.Documents; using Microsoft.UI.Xaml.Documents;
using Microsoft.UI.Xaml.Media; using Microsoft.UI.Xaml.Media;
using Snap.Hutao.Control.Media;
using Snap.Hutao.Core; using Snap.Hutao.Core;
using System.Runtime.InteropServices;
using Windows.UI; using Windows.UI;
namespace Snap.Hutao.Control.Text; namespace Snap.Hutao.Control.Text;
@@ -19,8 +19,7 @@ namespace Snap.Hutao.Control.Text;
/// </summary> /// </summary>
public class DescriptionTextBlock : ContentControl public class DescriptionTextBlock : ContentControl
{ {
private static readonly DependencyProperty DescriptionProperty = private static readonly DependencyProperty DescriptionProperty = Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
Property<DescriptionTextBlock>.Depend(nameof(Description), string.Empty, OnDescriptionChanged);
private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length; private static readonly int ColorTagFullLength = "<color=#FFFFFFFF></color>".Length;
private static readonly int ColorTagLeftLength = "<color=#FFFFFFFF>".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') else if (description[i] == '<' && description[i + 1] == 'c')
{ {
AppendText(text, description[last..i]); 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('<'); int length = description[(i + ColorTagLeftLength)..].IndexOf('<');
AppendColorText(text, description.Slice(i + ColorTagLeftLength, length), color); AppendColorText(text, description.Slice(i + ColorTagLeftLength, length), color);
@@ -115,7 +114,7 @@ public class DescriptionTextBlock : ContentControl
text.Inlines.Add(new Run { Text = slice.ToString() }); 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; Color targetColor;
if (ThemeHelper.IsDarkMode(text.ActualTheme)) if (ThemeHelper.IsDarkMode(text.ActualTheme))
@@ -126,7 +125,7 @@ public class DescriptionTextBlock : ContentControl
{ {
HslColor hsl = color.ToHsl(); HslColor hsl = color.ToHsl();
hsl.L *= 0.3; hsl.L *= 0.3;
targetColor = HexColor.FromHsl(hsl); targetColor = Rgba8.FromHsl(hsl);
} }
text.Inlines.Add(new Run text.Inlines.Add(new Run
@@ -154,138 +153,4 @@ public class DescriptionTextBlock : ContentControl
{ {
ApplyDescription((TextBlock)Content, Description); 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; 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>
/// 添加参数 /// 添加参数
/// </summary> /// </summary>
@@ -37,12 +48,6 @@ public class CommandLineBuilder
return this; return this;
} }
/// <inheritdoc cref="ToString"/>
public string Build()
{
return ToString();
}
/// <inheritdoc/> /// <inheritdoc/>
public override string ToString() public override string ToString()
{ {

View File

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

View File

@@ -44,11 +44,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param> /// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param> /// <param name="entity">实体</param>
/// <returns>影响条数</returns> /// <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 where TEntity : class
{ {
dbSet.Add(entity); dbSet.Add(entity);
return dbSet.Context().SaveChangesAsync(); return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -72,11 +72,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param> /// <param name="dbSet">数据库集</param>
/// <param name="entities">实体</param> /// <param name="entities">实体</param>
/// <returns>影响条数</returns> /// <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 where TEntity : class
{ {
dbSet.AddRange(entities); dbSet.AddRange(entities);
return dbSet.Context().SaveChangesAsync(); return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -100,11 +100,11 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param> /// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param> /// <param name="entity">实体</param>
/// <returns>影响条数</returns> /// <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 where TEntity : class
{ {
dbSet.Remove(entity); dbSet.Remove(entity);
return dbSet.Context().SaveChangesAsync(); return await dbSet.Context().SaveChangesAsync().ConfigureAwait(false);
} }
/// <summary> /// <summary>
@@ -128,10 +128,10 @@ public static class DbSetExtension
/// <param name="dbSet">数据库集</param> /// <param name="dbSet">数据库集</param>
/// <param name="entity">实体</param> /// <param name="entity">实体</param>
/// <returns>影响条数</returns> /// <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 where TEntity : class
{ {
dbSet.Update(entity); 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. // Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation; using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Core.Json.Converter;
using System.Reflection;
using System.Text.Json.Serialization.Metadata; using System.Text.Json.Serialization.Metadata;
namespace Snap.Hutao.Core.Json; namespace Snap.Hutao.Core.Json;

View File

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

View File

@@ -19,20 +19,22 @@ internal static class AppInstanceExtension
/// </summary> /// </summary>
/// <param name="appInstance">app实例</param> /// <param name="appInstance">app实例</param>
/// <param name="args">参数</param> /// <param name="args">参数</param>
[SuppressMessage("", "VSTHRD002")]
[SuppressMessage("", "VSTHRD110")] [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); HANDLE redirectEventHandle = UnsafeCreateEvent();
Task.Run(() => Task.Run(async () =>
{ {
appInstance.RedirectActivationToAsync(args).AsTask().Wait(); await appInstance.RedirectActivationToAsync(args);
SetEvent(redirectEventHandle); SetEvent(redirectEventHandle);
}); });
ReadOnlySpan<HANDLE> handles = new(in redirectEventHandle); ReadOnlySpan<HANDLE> handles = new(in redirectEventHandle);
// non-blocking
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _); 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>
/// 任务计划器服务 /// 任务计划器服务
/// </summary> /// </summary>
internal static class TaskSchedulerHelper internal static class ScheduleTaskHelper
{ {
private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask"; private const string DailyNoteRefreshTaskName = "SnapHutaoDailyNoteRefreshTask";
@@ -45,4 +45,30 @@ internal static class TaskSchedulerHelper
return false; 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/> /// <inheritdoc/>
public void OnCompleted(Action continuation) public void OnCompleted(Action continuation)
{ {
dispatherQueue.TryEnqueue(() => { continuation(); }); dispatherQueue.TryEnqueue(() =>
{
continuation();
});
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.Model.Binding; namespace Snap.Hutao.Model.Binding.Achievement;
/// <summary> /// <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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Binding.AvatarProperty; namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary> /// <summary>
/// 角色信息 /// 角色信息
/// </summary> /// </summary>
public class Avatar public class Avatar : ICalculableSource<ICalculableAvatar>
{ {
/// <summary> /// <summary>
/// 名称 /// 名称
@@ -45,11 +48,6 @@ public class Avatar
/// </summary> /// </summary>
public string Level { get; set; } = default!; public string Level { get; set; } = default!;
/// <summary>
/// 好感度等级
/// </summary>
public int FetterLevel { get; set; }
/// <summary> /// <summary>
/// 武器 /// 武器
/// </summary> /// </summary>
@@ -84,4 +82,25 @@ public class Avatar
/// 双爆评分 /// 双爆评分
/// </summary> /// </summary>
public string CritScore { get; set; } = default!; 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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Binding.AvatarProperty; namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary> /// <summary>
/// 天赋 /// 天赋
/// </summary> /// </summary>
public class Skill : NameIconDescription public class Skill : NameIconDescription, ICalculableSource<ICalculableSkill>
{ {
/// <summary> /// <summary>
/// 技能属性 /// 技能属性
/// </summary> /// </summary>
public LevelParam<string, ParameterInfo> Info { get; set; } = default!; public LevelParam<string, ParameterInfo> Info { get; set; } = default!;
/// <summary>
/// 技能组Id
/// </summary>
internal int GroupId { get; set; }
/// <summary> /// <summary>
/// 技能等级,仅用于养成计算 /// 技能等级,仅用于养成计算
/// </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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // 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; namespace Snap.Hutao.Model.Binding.AvatarProperty;
/// <summary> /// <summary>
/// 武器 /// 武器
/// </summary> /// </summary>
public class Weapon : EquipBase public class Weapon : EquipBase, ICalculableSource<ICalculableWeapon>
{ {
/// <summary> /// <summary>
/// 副属性 /// 副属性
@@ -27,4 +31,20 @@ public class Weapon : EquipBase
/// 精炼被动 /// 精炼被动
/// </summary> /// </summary>
public string AffixDescription { get; set; } = default!; 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; Inner = inner;
Entity = entity; Entity = entity;
isFinished = Entity.IsFinished; isFinished = Entity.IsFinished;
IsToday = CultivateItemHelper.IsTodaysMaterial(inner.Id, DateTimeOffset.Now);
FinishStateCommand = new RelayCommand(FlipIsFinished); 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() private void FlipIsFinished()
{ {
IsFinished = !IsFinished; 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) public Team(ItemRate<string, int> team, Dictionary<AvatarId, Avatar> idAvatarMap)
: base(4) : 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) foreach (int id in ids)
{ {
@@ -28,8 +28,14 @@ internal class Team : List<ComplexAvatar>
} }
Rate = $"上场 {team.Rate} 次"; Rate = $"上场 {team.Rate} 次";
Name = TeamPopularNameParser.GetName(ids.ToHashSet());
} }
/// <summary>
/// 队伍俗名
/// </summary>
public string Name { get; set; }
/// <summary> /// <summary>
/// 上场次数 /// 上场次数
/// </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. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; 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.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar; namespace Snap.Hutao.Model.Calculable;
/// <summary> /// <summary>
/// 可计算角色 /// 可计算角色
@@ -25,7 +21,7 @@ internal class CalculableAvatar : ObservableObject, ICalculableAvatar
/// 构造一个新的可计算角色 /// 构造一个新的可计算角色
/// </summary> /// </summary>
/// <param name="avatar">角色</param> /// <param name="avatar">角色</param>
public CalculableAvatar(Avatar avatar) public CalculableAvatar(Metadata.Avatar.Avatar avatar)
{ {
AvatarId = avatar.Id; AvatarId = avatar.Id;
LevelMin = 1; LevelMin = 1;
@@ -34,6 +30,25 @@ internal class CalculableAvatar : ObservableObject, ICalculableAvatar
Name = avatar.Name; Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon); Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality; 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; LevelCurrent = LevelMin;
LevelTarget = LevelMax; LevelTarget = LevelMax;
} }

View File

@@ -3,10 +3,11 @@
using CommunityToolkit.Mvvm.ComponentModel; using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar; namespace Snap.Hutao.Model.Calculable;
/// <summary> /// <summary>
/// 可计算的技能 /// 可计算的技能
@@ -28,6 +29,24 @@ internal class CalculableSkill : ObservableObject, ICalculableSkill
Name = skill.Name; Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon); Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = ItemQuality.QUALITY_NONE; 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; LevelCurrent = LevelMin;
LevelTarget = LevelMax; LevelTarget = LevelMax;
} }

View File

@@ -2,16 +2,12 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel; 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.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Weapon; namespace Snap.Hutao.Model.Calculable;
/// <summary> /// <summary>
/// 可计算武器 /// 可计算武器
@@ -25,7 +21,7 @@ public class CalculableWeapon : ObservableObject, ICalculableWeapon
/// 构造一个新的可计算武器 /// 构造一个新的可计算武器
/// </summary> /// </summary>
/// <param name="weapon">武器</param> /// <param name="weapon">武器</param>
public CalculableWeapon(Weapon weapon) public CalculableWeapon(Metadata.Weapon.Weapon weapon)
{ {
WeaponId = weapon.Id; WeaponId = weapon.Id;
LevelMin = 1; LevelMin = 1;
@@ -33,6 +29,24 @@ public class CalculableWeapon : ObservableObject, ICalculableWeapon
Name = weapon.Name; Name = weapon.Name;
Icon = EquipIconConverter.IconNameToUri(weapon.Icon); Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
Quality = weapon.RankLevel; 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; LevelCurrent = LevelMin;
LevelTarget = LevelMax; LevelTarget = LevelMax;
} }

View File

@@ -31,60 +31,4 @@ internal class UIIF
/// </summary> /// </summary>
[JsonPropertyName("list")] [JsonPropertyName("list")]
public List<UIIFItem> List { get; set; } = default!; 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.Core;
using Snap.Hutao.Extension; 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; 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>
/// 法器 /// 法器
/// </summary> /// </summary>
[Description("单手剑")] [Description("法器")]
WEAPON_CATALYST = 10, WEAPON_CATALYST = 10,
/// <summary> /// <summary>
/// 双手剑 /// 双手剑
/// </summary> /// </summary>
[Description("手剑")] [Description("手剑")]
WEAPON_CLAYMORE = 11, WEAPON_CLAYMORE = 11,
/// <summary> /// <summary>
/// 弓 /// 弓
/// </summary> /// </summary>
[Description("单手剑")] [Description("")]
WEAPON_BOW = 12, WEAPON_BOW = 12,
/// <summary> /// <summary>
/// 长柄武器 /// 长柄武器
/// </summary> /// </summary>
[Description("单手剑")] [Description("长柄武器")]
WEAPON_POLE = 13, WEAPON_POLE = 13,
} }

View File

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

View File

@@ -81,5 +81,10 @@ public partial class Avatar
/// <summary> /// <summary>
/// 皮肤 /// 皮肤
/// </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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Avatar; namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -13,45 +13,15 @@ public class CookBonus
/// <summary> /// <summary>
/// 原型名称 /// 原型名称
/// </summary> /// </summary>
public string OriginName { get; set; } = default!; public MaterialId OriginItemId { get; set; } = default!;
/// <summary>
/// 原型描述
/// </summary>
public string OriginDescription { get; set; } = default!;
/// <summary>
/// 原型图标
/// </summary>
public string OriginIcon { get; set; } = default!;
/// <summary> /// <summary>
/// 名称 /// 名称
/// </summary> /// </summary>
public string Name { get; set; } = default!; public MaterialId ItemId { 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; }
/// <summary> /// <summary>
/// 材料列表 /// 材料列表
/// </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>
/// 料理 /// 料理
/// </summary> /// </summary>
public CookBonus? CookBonus { get; set; } public CookBonus? CookBonus2 { get; set; }
/// <summary> /// <summary>
/// 好感语音 /// 好感语音

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar; 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 Layla = 10000074;
public static readonly AvatarId Wanderer = 10000075; public static readonly AvatarId Wanderer = 10000075;
public static readonly AvatarId Faruzan = 10000076; public static readonly AvatarId Faruzan = 10000076;
public static readonly AvatarId Yaoyao = 10000077;
public static readonly AvatarId Alhaitham = 10000078;
/// <summary> /// <summary>
/// 检查该角色是否为主角 /// 检查该角色是否为主角

View File

@@ -12,9 +12,19 @@ internal class AchievementIconConverter : ValueConverterBase<string, Uri>
{ {
private const string BaseUrl = "https://static.snapgenshin.com/AchievementIcon/{0}.png"; 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/> /// <inheritdoc/>
public override Uri Convert(string from) 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. // Licensed under the MIT license.
using Snap.Hutao.Control; using Snap.Hutao.Control;
using Snap.Hutao.Control.Image; using Snap.Hutao.Control.Media;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Model.Metadata.Converter; 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 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> /// <summary>
/// 名称转Uri /// 名称转Uri
/// </summary> /// </summary>

View File

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

View File

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

View File

@@ -2,7 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata; namespace Snap.Hutao.Model.Metadata;
@@ -14,7 +14,7 @@ public class Material
/// <summary> /// <summary>
/// 物品Id /// 物品Id
/// </summary> /// </summary>
public int Id { get; set; } public MaterialId Id { get; set; }
/// <summary> /// <summary>
/// 等级 /// 等级
@@ -26,6 +26,11 @@ public class Material
/// </summary> /// </summary>
public ItemType ItemType { get; set; } public ItemType ItemType { get; set; }
/// <summary>
/// 材料类型
/// </summary>
public MaterialType MaterialType { get; set; }
/// <summary> /// <summary>
/// 图标 /// 图标
/// </summary> /// </summary>
@@ -45,4 +50,9 @@ public class Material
/// 类型描述 /// 类型描述
/// </summary> /// </summary>
public string TypeDescription { get; set; } = default!; 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;
using Snap.Hutao.Model.Binding.Gacha.Abstraction; using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao; using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Calculable;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction; using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter; 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(); return Value.GetHashCode();
} }
} }

View File

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

View File

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

View File

@@ -9,7 +9,7 @@ using Snap.Hutao.Core.Diagnostics;
using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Logging;
using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel; 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 EntityAchievement = Snap.Hutao.Model.Entity.Achievement;
using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive; using EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement; using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
@@ -22,6 +22,7 @@ namespace Snap.Hutao.Service.Achievement;
[Injection(InjectAs.Scoped, typeof(IAchievementService))] [Injection(InjectAs.Scoped, typeof(IAchievementService))]
internal class AchievementService : IAchievementService internal class AchievementService : IAchievementService
{ {
private readonly object saveAchievementLocker = new();
private readonly AppDbContext appDbContext; private readonly AppDbContext appDbContext;
private readonly ILogger<AchievementService> logger; private readonly ILogger<AchievementService> logger;
private readonly DbCurrent<EntityArchive, Message.AchievementArchiveChangedMessage> dbCurrent; 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, "{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); 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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Core.Threading.CodeAnalysis;
using Snap.Hutao.Model.InterChange.Achievement; using Snap.Hutao.Model.InterChange.Achievement;
using System.Collections.ObjectModel; 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 EntityArchive = Snap.Hutao.Model.Entity.AchievementArchive;
using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement; using MetadataAchievement = Snap.Hutao.Model.Metadata.Achievement.Achievement;
@@ -57,6 +56,12 @@ internal interface IAchievementService
/// <returns>任务</returns> /// <returns>任务</returns>
Task RemoveArchiveAsync(EntityArchive archive); Task RemoveArchiveAsync(EntityArchive archive);
/// <summary>
/// 保存单个成就
/// </summary>
/// <param name="achievement">成就</param>
void SaveAchievement(BindingAchievement achievement);
/// <summary> /// <summary>
/// 保存成就 /// 保存成就
/// </summary> /// </summary>
@@ -69,6 +74,5 @@ internal interface IAchievementService
/// </summary> /// </summary>
/// <param name="newArchive">新存档</param> /// <param name="newArchive">新存档</param>
/// <returns>存档添加结果</returns> /// <returns>存档添加结果</returns>
[ThreadAccess(ThreadAccessState.AnyThread)]
Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive); Task<ArchiveAddResult> TryAddArchiveAsync(EntityArchive newArchive);
} }

View File

@@ -15,28 +15,38 @@ internal class AffixWeight : Dictionary<FightProperty, double>
/// 构造一个新的词条权重 /// 构造一个新的词条权重
/// </summary> /// </summary>
/// <param name="avatarId">角色Id</param> /// <param name="avatarId">角色Id</param>
/// <param name="hp">大生命</param> /// <param name="hpPercent">大生命</param>
/// <param name="atk">大攻击</param> /// <param name="attackPercenr">大攻击</param>
/// <param name="def">大防御</param> /// <param name="defensePercent">大防御</param>
/// <param name="cr">暴击率</param> /// <param name="critical">暴击率</param>
/// <param name="ch">暴击伤害</param> /// <param name="criticalHurt">暴击伤害</param>
/// <param name="em">元素精通</param> /// <param name="elementMastery">元素精通</param>
/// <param name="ce">充能效率</param> /// <param name="chargeEfficiency">充能效率</param>
/// <param name="ha">治疗加成</param> /// <param name="healAdd">治疗加成</param>
/// <param name="name">名称</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; AvatarId = avatarId;
Name = name; Name = name;
this[FightProperty.FIGHT_PROP_HP_PERCENT] = hp; this[FightProperty.FIGHT_PROP_HP_PERCENT] = hpPercent;
this[FightProperty.FIGHT_PROP_ATTACK_PERCENT] = atk; this[FightProperty.FIGHT_PROP_ATTACK_PERCENT] = attackPercenr;
this[FightProperty.FIGHT_PROP_DEFENSE_PERCENT] = def; this[FightProperty.FIGHT_PROP_DEFENSE_PERCENT] = defensePercent;
this[FightProperty.FIGHT_PROP_CRITICAL] = cr; this[FightProperty.FIGHT_PROP_CRITICAL] = critical;
this[FightProperty.FIGHT_PROP_CRITICAL_HURT] = ch; this[FightProperty.FIGHT_PROP_CRITICAL_HURT] = criticalHurt;
this[FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = em; this[FightProperty.FIGHT_PROP_ELEMENT_MASTERY] = elementMastery;
this[FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = ce; this[FightProperty.FIGHT_PROP_CHARGE_EFFICIENCY] = chargeEfficiency;
this[FightProperty.FIGHT_PROP_HEAL_ADD] = ha; this[FightProperty.FIGHT_PROP_HEAL_ADD] = healAdd;
} }
/// <summary> /// <summary>
@@ -48,4 +58,92 @@ internal class AffixWeight : Dictionary<FightProperty, double>
/// 名称 /// 名称
/// </summary> /// </summary>
public string Name { get; } 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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Service.AvatarInfo.Factory; namespace Snap.Hutao.Service.AvatarInfo.Factory;
@@ -14,74 +13,74 @@ internal static partial class ReliquaryWeightConfiguration
/// <summary> /// <summary>
/// 默认 /// 默认
/// </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>
/// 词条权重 /// 词条权重
/// </summary> /// </summary>
public static readonly List<AffixWeight> AffixWeights = new() 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 AffixWeight(AvatarIds.Ayaka, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new(AvatarIds.Qin, 0, 75, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Qin, 0, 75, 0, 100, 100, 0, 55, 100).Anemo(),
new(AvatarIds.Lisa, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Lisa, 0, 75, 0, 100, 100, 75, 0, 0).Electro(),
new(AvatarIds.Barbara, 100, 50, 0, 50, 50, 0, 55, 100) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 80 } }, new AffixWeight(AvatarIds.Barbara, 100, 50, 0, 50, 50, 0, 55, 100).Hydro(80),
new(AvatarIds.Barbara, 50, 75, 0, 100, 100, 0, 55, 100, "暴力奶妈") { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Barbara, 50, 75, 0, 100, 100, 0, 55, 100, "暴力奶妈").Hydro(),
new(AvatarIds.Kaeya, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Kaeya, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new(AvatarIds.Diluc, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Diluc, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
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 AffixWeight(AvatarIds.Razor, 0, 75, 0, 100, 100, 0, 0, 0).Electro(50).Phyiscal(),
new(AvatarIds.Ambor, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Ambor, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new(AvatarIds.Venti, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Venti, 0, 75, 0, 100, 100, 75, 55, 0).Anemo(),
new(AvatarIds.Xiangling, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Xiangling, 0, 75, 0, 100, 100, 75, 55, 0).Pyro(),
new(AvatarIds.Beidou, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Beidou, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new(AvatarIds.Xingqiu, 0, 75, 0, 100, 100, 0, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Xingqiu, 0, 75, 0, 100, 100, 0, 75, 0).Hydro(),
new(AvatarIds.Xiao, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Xiao, 0, 75, 0, 100, 100, 0, 55, 0).Anemo(),
new(AvatarIds.Ningguang, 0, 75, 0, 100, 100, 0, 30, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Ningguang, 0, 75, 0, 100, 100, 0, 30, 0).Geo(),
new(AvatarIds.Klee, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Klee, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
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 AffixWeight(AvatarIds.Zhongli, 80, 75, 0, 100, 100, 0, 55, 0, "武神钟离").Geo().Phyiscal(50),
new(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 55, 0, "血牛钟离") { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 75 } }, new AffixWeight(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 55, 0, "血牛钟离").Geo(75),
new(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 75, 0, "血牛钟离2命+") { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 75 } }, new AffixWeight(AvatarIds.Zhongli, 100, 55, 0, 100, 100, 0, 75, 0, "血牛钟离2命+").Geo(75),
new(AvatarIds.Fischl, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Fischl, 0, 75, 0, 100, 100, 0, 0, 0).Electro(),
new(AvatarIds.Bennett, 100, 50, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 70 } }, new AffixWeight(AvatarIds.Bennett, 100, 50, 0, 100, 100, 0, 55, 100).Pyro(70),
new(AvatarIds.Tartaglia, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Tartaglia, 0, 75, 0, 100, 100, 75, 0, 0).Hydro(),
new(AvatarIds.Noel, 0, 50, 90, 100, 100, 0, 70, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Noel, 0, 50, 90, 100, 100, 0, 70, 0).Geo(),
new(AvatarIds.Qiqi, 0, 100, 0, 100, 100, 0, 55, 100) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 60 } }, new AffixWeight(AvatarIds.Qiqi, 0, 100, 0, 100, 100, 0, 55, 100).Cryo(60),
new(AvatarIds.Chongyun, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Chongyun, 0, 75, 0, 100, 100, 75, 55, 0).Cryo(),
new(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 75, 0, 0, "融化流") { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 75, 0, 0, "融化流").Cryo(),
new(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 0, 55, 0, "永冻流") { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Ganyu, 0, 75, 0, 100, 100, 0, 55, 0, "永冻流").Cryo(),
new(AvatarIds.Albedo, 0, 0, 100, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Albedo, 0, 0, 100, 100, 100, 0, 0, 0).Geo(),
new(AvatarIds.Diona, 100, 50, 0, 50, 50, 0, 90, 100) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Diona, 100, 50, 0, 50, 50, 0, 90, 100).Cryo(),
new(AvatarIds.Mona, 0, 75, 0, 100, 100, 75, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Mona, 0, 75, 0, 100, 100, 75, 75, 0).Hydro(),
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 AffixWeight(AvatarIds.Keqing, 0, 75, 0, 100, 100, 0, 0, 0).Electro().Phyiscal(),
new(AvatarIds.Sucrose, 0, 75, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 40 } }, new AffixWeight(AvatarIds.Sucrose, 0, 75, 0, 100, 100, 100, 55, 0).Anemo(40),
new(AvatarIds.Xinyan, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 50 } }, new AffixWeight(AvatarIds.Xinyan, 0, 75, 0, 100, 100, 0, 0, 0).Pyro(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 AffixWeight(AvatarIds.Rosaria, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(70).Phyiscal(80),
new(AvatarIds.Hutao, 80, 50, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Hutao, 80, 50, 0, 100, 100, 75, 0, 0).Pyro(),
new(AvatarIds.Kazuha, 0, 75, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Kazuha, 0, 75, 0, 100, 100, 100, 55, 0).Anemo(),
new(AvatarIds.Feiyan, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Feiyan, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new(AvatarIds.Yoimiya, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Yoimiya, 0, 75, 0, 100, 100, 75, 0, 0).Pyro(),
new(AvatarIds.Tohma, 100, 50, 0, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_FIRE_ADD_HURT, 75 } }, new AffixWeight(AvatarIds.Tohma, 100, 50, 0, 50, 50, 0, 90, 0).Pyro(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 AffixWeight(AvatarIds.Eula, 0, 75, 0, 100, 100, 0, 55, 0).Cryo(40).Phyiscal(100),
new(AvatarIds.Shougun, 0, 75, 0, 100, 100, 0, 90, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 75 } }, new AffixWeight(AvatarIds.Shougun, 0, 75, 0, 100, 100, 0, 90, 0).Electro(75),
new(AvatarIds.Sayu, 0, 50, 0, 50, 50, 100, 55, 100) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 80 } }, new AffixWeight(AvatarIds.Sayu, 0, 50, 0, 50, 50, 100, 55, 100).Anemo(80),
new(AvatarIds.Kokomi, 100, 50, 0, 0, 0, 0, 55, 100) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Kokomi, 100, 50, 0, 0, 0, 0, 55, 100).Hydro(),
new(AvatarIds.Gorou, 0, 50, 100, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 25 } }, new AffixWeight(AvatarIds.Gorou, 0, 50, 100, 50, 50, 0, 90, 0).Geo(25),
new(AvatarIds.Sara, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Sara, 0, 75, 0, 100, 100, 0, 55, 0).Electro(),
new(AvatarIds.Itto, 0, 50, 100, 100, 100, 0, 30, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Itto, 0, 50, 100, 100, 100, 0, 30, 0).Geo(),
new(AvatarIds.Yae, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Yae, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new(AvatarIds.Heizou, 0, 75, 0, 100, 100, 75, 0, 0) { { FightProperty.FIGHT_PROP_WIND_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Heizou, 0, 75, 0, 100, 100, 75, 0, 0).Anemo(),
new(AvatarIds.Yelan, 80, 0, 0, 100, 100, 0, 75, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Yelan, 80, 0, 0, 100, 100, 0, 75, 0).Hydro(),
new(AvatarIds.Aloy, 0, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Aloy, 0, 75, 0, 100, 100, 0, 0, 0).Cryo(),
new(AvatarIds.Shenhe, 0, 100, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ICE_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Shenhe, 0, 100, 0, 100, 100, 0, 55, 0).Cryo(),
new(AvatarIds.Yunjin, 0, 0, 100, 50, 50, 0, 90, 0) { { FightProperty.FIGHT_PROP_ROCK_ADD_HURT, 25 } }, new AffixWeight(AvatarIds.Yunjin, 0, 0, 100, 50, 50, 0, 90, 0).Geo(25),
new(AvatarIds.Shinobu, 100, 50, 0, 100, 100, 75, 55, 100) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Shinobu, 100, 50, 0, 100, 100, 75, 55, 100).Electro(),
new(AvatarIds.Ayato, 50, 75, 0, 100, 100, 0, 0, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Ayato, 50, 75, 0, 100, 100, 0, 0, 0).Hydro(),
new(AvatarIds.Collei, 0, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Collei, 0, 75, 0, 100, 100, 0, 55, 0).Dendro(),
new(AvatarIds.Dori, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Dori, 100, 75, 0, 100, 100, 0, 55, 0).Electro(),
new(AvatarIds.Tighnari, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Tighnari, 0, 75, 0, 100, 100, 75, 55, 0).Dendro(),
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "直伤流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "直伤流").Hydro(),
new(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "反应流") { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Nilou, 100, 75, 0, 100, 100, 0, 55, 0, "反应流").Hydro(),
new(AvatarIds.Cyno, 0, 75, 0, 100, 100, 75, 55, 0) { { FightProperty.FIGHT_PROP_ELEC_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Cyno, 0, 75, 0, 100, 100, 75, 55, 0).Electro(),
new(AvatarIds.Candace, 100, 75, 0, 100, 100, 0, 55, 0) { { FightProperty.FIGHT_PROP_WATER_ADD_HURT, 100 } }, new AffixWeight(AvatarIds.Candace, 100, 75, 0, 100, 100, 0, 55, 0).Hydro(),
new(AvatarIds.Nahida, 0, 55, 0, 100, 100, 100, 55, 0) { { FightProperty.FIGHT_PROP_GRASS_ADD_HURT, 100 } }, 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;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter; using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Metadata.Reliquary;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Enka.Model; using Snap.Hutao.Web.Enka.Model;
using MetadataAvatar = Snap.Hutao.Model.Metadata.Avatar.Avatar; 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 MetadataWeapon = Snap.Hutao.Model.Metadata.Weapon.Weapon;
using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo; using ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using PropertyAvatar = Snap.Hutao.Model.Binding.AvatarProperty.Avatar; using PropertyAvatar = Snap.Hutao.Model.Binding.AvatarProperty.Avatar;
@@ -23,40 +20,17 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// </summary> /// </summary>
internal class SummaryAvatarFactory 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 ModelAvatarInfo avatarInfo;
private readonly SummaryMetadataContext metadataContext;
/// <summary> /// <summary>
/// 构造一个新的角色工厂 /// 构造一个新的角色工厂
/// </summary> /// </summary>
/// <param name="idAvatarMap">角色映射</param> /// <param name="metadataContext">元数据上下文</param>
/// <param name="idWeaponMap">武器映射</param>
/// <param name="idRelicMainPropMap">圣遗物主属性映射</param>
/// <param name="idReliquaryAffixMap">圣遗物副词条映射</param>
/// <param name="reliqueryLevels">圣遗物主属性等级</param>
/// <param name="reliquaries">圣遗物</param>
/// <param name="avatarInfo">角色信息</param> /// <param name="avatarInfo">角色信息</param>
public SummaryAvatarFactory( public SummaryAvatarFactory(SummaryMetadataContext metadataContext, ModelAvatarInfo avatarInfo)
Dictionary<AvatarId, MetadataAvatar> idAvatarMap,
Dictionary<WeaponId, MetadataWeapon> idWeaponMap,
Dictionary<ReliquaryMainAffixId, FightProperty> idRelicMainPropMap,
Dictionary<ReliquaryAffixId, ReliquaryAffix> idReliquaryAffixMap,
List<ReliquaryLevel> reliqueryLevels,
List<MetadataReliquary> reliquaries,
ModelAvatarInfo avatarInfo)
{ {
this.idAvatarMap = idAvatarMap; this.metadataContext = metadataContext;
this.idWeaponMap = idWeaponMap;
this.idRelicMainPropMap = idRelicMainPropMap;
this.idReliquaryAffixMap = idReliquaryAffixMap;
this.reliqueryLevels = reliqueryLevels;
this.reliquaries = reliquaries;
this.avatarInfo = avatarInfo; this.avatarInfo = avatarInfo;
} }
@@ -67,10 +41,11 @@ internal class SummaryAvatarFactory
public PropertyAvatar CreateAvatar() public PropertyAvatar CreateAvatar()
{ {
ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList); ReliquaryAndWeapon reliquaryAndWeapon = ProcessEquip(avatarInfo.EquipList);
MetadataAvatar avatar = idAvatarMap[avatarInfo.AvatarId]; MetadataAvatar avatar = metadataContext.IdAvatarMap[avatarInfo.AvatarId];
return new() return new()
{ {
Id = avatar.Id,
Name = avatar.Name, Name = avatar.Name,
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon), Icon = AvatarIconConverter.IconNameToUri(avatar.Icon),
SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon), SideIcon = AvatarIconConverter.IconNameToUri(avatar.SideIcon),
@@ -78,6 +53,7 @@ internal class SummaryAvatarFactory
Quality = avatar.Quality, Quality = avatar.Quality,
Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore), Element = ElementNameIconConverter.ElementNameToElementType(avatar.FetterInfo.VisionBefore),
Level = $"Lv.{avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value}", Level = $"Lv.{avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value}",
LevelNumber = int.Parse(avatarInfo.PropMap[PlayerProperty.PROP_LEVEL].Value ?? string.Empty),
FetterLevel = avatarInfo.FetterInfo.ExpLevel, FetterLevel = avatarInfo.FetterInfo.ExpLevel,
Weapon = reliquaryAndWeapon.Weapon, Weapon = reliquaryAndWeapon.Weapon,
Reliquaries = reliquaryAndWeapon.Reliquaries, Reliquaries = reliquaryAndWeapon.Reliquaries,
@@ -99,7 +75,7 @@ internal class SummaryAvatarFactory
switch (equip.Flat.ItemType) switch (equip.Flat.ItemType)
{ {
case ItemType.ITEM_RELIQUARY: case ItemType.ITEM_RELIQUARY:
SummaryReliquaryFactory summaryReliquaryFactory = new(idReliquaryAffixMap, idRelicMainPropMap, reliqueryLevels, reliquaries, avatarInfo, equip); SummaryReliquaryFactory summaryReliquaryFactory = new(metadataContext, avatarInfo, equip);
reliquaryList.Add(summaryReliquaryFactory.CreateReliquary()); reliquaryList.Add(summaryReliquaryFactory.CreateReliquary());
break; break;
case ItemType.ITEM_WEAPON: case ItemType.ITEM_WEAPON:
@@ -113,7 +89,7 @@ internal class SummaryAvatarFactory
private PropertyWeapon CreateWeapon(Equip equip) 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. // AffixMap can be empty when it's a white weapon.
KeyValuePair<string, int>? idLevel = equip.Weapon!.AffixMap?.Single(); 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()), MainProperty = mainStat == null ? default! : new(mainStat.AppendPropId.GetDescription(), mainStat.StatValue.ToString()),
// Weapon // Weapon
Id = weapon.Id,
LevelNumber = equip.Weapon!.Level,
SubProperty = subProperty, SubProperty = subProperty,
AffixLevel = $"精炼{affixLevel + 1}", AffixLevel = $"精炼{affixLevel + 1}",
AffixName = weapon.Affix?.Name ?? string.Empty, AffixName = weapon.Affix?.Name ?? string.Empty,

View File

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

View File

@@ -2,13 +2,7 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model.Binding.AvatarProperty; using Snap.Hutao.Model.Binding.AvatarProperty;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata; 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 ModelAvatarInfo = Snap.Hutao.Web.Enka.Model.AvatarInfo;
using ModelPlayerInfo = Snap.Hutao.Web.Enka.Model.PlayerInfo; using ModelPlayerInfo = Snap.Hutao.Web.Enka.Model.PlayerInfo;
@@ -19,36 +13,15 @@ namespace Snap.Hutao.Service.AvatarInfo.Factory;
/// </summary> /// </summary>
internal class SummaryFactoryImplementation internal class SummaryFactoryImplementation
{ {
private readonly Dictionary<AvatarId, MetadataAvatar> idAvatarMap; private readonly SummaryMetadataContext metadataContext;
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;
/// <summary> /// <summary>
/// 装配一个工厂实现 /// 装配一个工厂实现
/// </summary> /// </summary>
/// <param name="idAvatarMap">角色映射</param> /// <param name="metadataContext">元数据上下文</param>
/// <param name="idWeaponMap">武器映射</param> public SummaryFactoryImplementation(SummaryMetadataContext metadataContext)
/// <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)
{ {
this.idAvatarMap = idAvatarMap; this.metadataContext = metadataContext;
this.idRelicMainPropMap = idRelicMainPropMap;
this.idWeaponMap = idWeaponMap;
this.reliqueryLevels = reliqueryLevels;
this.reliquaries = reliquaries;
this.idReliquaryAffixMap = idReliquaryAffixMap;
} }
/// <summary> /// <summary>
@@ -64,14 +37,7 @@ internal class SummaryFactoryImplementation
Player = SummaryHelper.CreatePlayer(playerInfo), Player = SummaryHelper.CreatePlayer(playerInfo),
Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a => Avatars = avatarInfos.Where(a => !AvatarIds.IsPlayer(a.AvatarId)).Select(a =>
{ {
SummaryAvatarFactory summaryAvatarFactory = new( SummaryAvatarFactory summaryAvatarFactory = new(metadataContext, a);
idAvatarMap,
idWeaponMap,
idRelicMainPropMap,
idReliquaryAffixMap,
reliqueryLevels,
reliquaries,
a);
return summaryAvatarFactory.CreateAvatar(); return summaryAvatarFactory.CreateAvatar();
}).ToList(), }).ToList(),
}; };

View File

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

View File

@@ -131,7 +131,7 @@ internal class CultivationService : ICultivationService
/// <inheritdoc/> /// <inheritdoc/>
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync( public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(
CultivateProject cultivateProject, CultivateProject cultivateProject,
List<Model.Metadata.Material> metadata, List<Model.Metadata.Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap) Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
{ {
@@ -148,19 +148,20 @@ internal class CultivationService : ICultivationService
.ToListAsync() .ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
foreach (CultivateEntry? entry in entries) foreach (CultivateEntry entry in entries)
{ {
Guid entryId = entry.InnerId; Guid entryId = entry.InnerId;
List<BindingCultivateItem> resultItems = new(); List<BindingCultivateItem> resultItems = new();
List<CultivateItem> items = await appDbContext.CultivateItems List<CultivateItem> items = await appDbContext.CultivateItems
.Where(i => i.EntryId == entryId) .Where(i => i.EntryId == entryId)
.OrderBy(i => i.ItemId).ToListAsync() .OrderBy(i => i.ItemId)
.ToListAsync()
.ConfigureAwait(false); .ConfigureAwait(false);
foreach (CultivateItem item in items) 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 Model.Binding.Gacha.Abstraction.ItemBase itemBase = entry.Type switch
@@ -173,7 +174,71 @@ internal class CultivationService : ICultivationService
results.Add(new(entry, itemBase, resultItems)); 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/> /// <inheritdoc/>
public async Task<bool> SaveConsumptionAsync(Model.Binding.Cultivation.CultivateType type, int itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items) 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()) using (IServiceScope scope = scopeFactory.CreateScope())
{ {
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();

View File

@@ -24,11 +24,11 @@ internal interface ICultivationService
/// 获取绑定用的养成列表 /// 获取绑定用的养成列表
/// </summary> /// </summary>
/// <param name="cultivateProject">养成计划</param> /// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">材料</param> /// <param name="materials">材料</param>
/// <param name="idAvatarMap">Id角色映射</param> /// <param name="idAvatarMap">Id角色映射</param>
/// <param name="idWeaponMap">Id武器映射</param> /// <param name="idWeaponMap">Id武器映射</param>
/// <returns>绑定用的养成列表</returns> /// <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> /// <summary>
/// 获取物品列表 /// 获取物品列表
@@ -44,6 +44,14 @@ internal interface ICultivationService
/// <returns>项目集合</returns> /// <returns>项目集合</returns>
ObservableCollection<CultivateProject> GetProjectCollection(); 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>
/// 删除养成清单 /// 删除养成清单
/// </summary> /// </summary>

View File

@@ -1,22 +1,14 @@
// Copyright (c) DGP Studio. All rights reserved. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications; using CommunityToolkit.WinUI.Notifications;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database; using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.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.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.Binding;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote; 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; namespace Snap.Hutao.Service.DailyNote;
@@ -26,19 +18,16 @@ namespace Snap.Hutao.Service.DailyNote;
internal class DailyNoteNotifier internal class DailyNoteNotifier
{ {
private readonly IServiceScopeFactory scopeFactory; private readonly IServiceScopeFactory scopeFactory;
private readonly BindingClient bindingClient;
private readonly DailyNoteEntry entry; private readonly DailyNoteEntry entry;
/// <summary> /// <summary>
/// 构造一个新的实时便笺通知器 /// 构造一个新的实时便笺通知器
/// </summary> /// </summary>
/// <param name="scopeFactory">范围工厂</param> /// <param name="scopeFactory">范围工厂</param>
/// <param name="bindingClient">绑定客户端</param>
/// <param name="entry">实时便笺入口</param> /// <param name="entry">实时便笺入口</param>
public DailyNoteNotifier(IServiceScopeFactory scopeFactory, BindingClient bindingClient, DailyNoteEntry entry) public DailyNoteNotifier(IServiceScopeFactory scopeFactory, DailyNoteEntry entry)
{ {
this.scopeFactory = scopeFactory; this.scopeFactory = scopeFactory;
this.bindingClient = bindingClient;
this.entry = entry; this.entry = entry;
} }
@@ -128,37 +117,48 @@ internal class DailyNoteNotifier
return; 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()) using (IServiceScope scope = scopeFactory.CreateScope())
{ {
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); 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()) if (appDbContext.Settings.SingleOrAdd(SettingEntry.DailyNoteReminderNotify, SettingEntryHelper.FalseString).GetBoolean())
{ {
builder.SetToastScenario(ToastScenario.Reminder); builder.SetToastScenario(ToastScenario.Reminder);
} }
}
if (hints.Count > 2) if (hints.Count > 2)
{
builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{ {
builder.AddText(hint); builder.AddText("多个提醒项达到设定值");
}
else
{
foreach (string hint in hints)
{
builder.AddText(hint);
}
} }
}
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();
builder.Show(); builder.Show();
}
} }
} }

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging; using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.Notifications;
using Microsoft.EntityFrameworkCore; using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database; using Snap.Hutao.Context.Database;
@@ -12,9 +11,7 @@ using Snap.Hutao.Message;
using Snap.Hutao.Model.Binding.User; using Snap.Hutao.Model.Binding.User;
using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User; 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;
using Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using WebDailyNote = Snap.Hutao.Web.Hoyolab.Takumi.GameRecord.DailyNote.DailyNote; 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); DailyNoteEntry newEntry = DailyNoteEntry.Create(role);
newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false); newEntry.DailyNote = await gameRecordClient.GetDailyNoteAsync(role.User, newEntry.Uid).ConfigureAwait(false);
appDbContext.DailyNotes.AddAndSave(newEntry);
newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid); newEntry.UserGameRole = userService.GetUserGameRoleByUid(roleUid);
await appDbContext.DailyNotes.AddAndSaveAsync(newEntry).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync(); await ThreadHelper.SwitchToMainThreadAsync();
entries?.Add(newEntry); entries?.Add(newEntry);
} }
@@ -84,7 +81,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>(); AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<DailyNoteEntry> entryList = appDbContext.DailyNotes.AsNoTracking().ToList(); List<DailyNoteEntry> entryList = appDbContext.DailyNotes.AsNoTracking().ToList();
entryList.ForEach(entry => { entry.UserGameRole = userService.GetUserGameRoleByUid(entry.Uid); }); 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>(); AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
GameRecordClient gameRecordClient = scope.ServiceProvider.GetRequiredService<GameRecordClient>(); GameRecordClient gameRecordClient = scope.ServiceProvider.GetRequiredService<GameRecordClient>();
BindingClient bindingClient = scope.ServiceProvider.GetRequiredService<BindingClient>();
foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User)) foreach (DailyNoteEntry entry in appDbContext.DailyNotes.Include(n => n.User))
{ {
@@ -113,7 +109,7 @@ internal class DailyNoteService : IDailyNoteService, IRecipient<UserRemovedMessa
if (notify) 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. // Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license. // Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database; using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database; using Snap.Hutao.Core.Database;
using Snap.Hutao.Extension; using Snap.Hutao.Extension;

View File

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

View File

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

View File

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

View File

@@ -150,9 +150,12 @@ internal class GameFpsUnlocker : IGameFpsUnlocker
Must.Range(adr >= 0, "未匹配到FPS字节"); Must.Range(adr >= 0, "未匹配到FPS字节");
int rip = adr + 2; fixed (byte* pSpan = image)
int rel = image.Fixed<int>(rip + 2); // Unsafe.ReadUnaligned<int>(ref image[rip + 2]); {
int ofs = rip + rel + 6; int rip = adr + 2;
fpsAddress = (nuint)((long)unityPlayer.modBaseAddr + ofs); 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> /// <returns>Id到角色的字典</returns>
ValueTask<Dictionary<AvatarId, Avatar>> GetIdToAvatarMapAsync(CancellationToken token = default); 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> /// <summary>
/// 异步获取ID到圣遗物副词条的字典 /// 异步获取ID到圣遗物副词条的字典
/// </summary> /// </summary>

View File

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

View File

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

View File

@@ -41,8 +41,8 @@ public sealed partial class DescParamComboBox : UserControl
/// </summary> /// </summary>
public int PreferredSelectedIndex public int PreferredSelectedIndex
{ {
get { return (int)GetValue(PreferredSelectedIndexProperty); } get => (int)GetValue(PreferredSelectedIndexProperty);
set { SetValue(PreferredSelectedIndexProperty, value); } set => SetValue(PreferredSelectedIndexProperty, value);
} }
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) 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) if (args.NewValue != args.OldValue && args.NewValue is IList<LevelParam<string, ParameterInfo>> list)
{ {
descParamComboBox.ItemHost.ItemsSource = 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;
using Microsoft.UI.Xaml.Controls; 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.Model.Entity;
using Snap.Hutao.Service.User; 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; namespace Snap.Hutao.View.Dialog;

View File

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

View File

@@ -50,9 +50,6 @@ public sealed partial class DailyNoteVerificationDialog : ContentDialog
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent(); coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider); dailyNoteJsInterface = new(coreWebView2, scope.ServiceProvider);
#if DEBUG
coreWebView2.OpenDevToolsWindow();
#endif
string query = $"?role_id={uid.Value}&server={uid.Region}"; 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}"); 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(); await WebView.EnsureCoreWebView2Async();
CoreWebView2 coreWebView2 = WebView.CoreWebView2; CoreWebView2 coreWebView2 = WebView.CoreWebView2;
IUserService userService = scope.ServiceProvider.GetRequiredService<IUserService>(); User? user = scope.ServiceProvider.GetRequiredService<IUserService>().Current;
User? user = userService.Current;
if (user == null) if (user == null)
{ {
@@ -49,10 +48,6 @@ public sealed partial class SignInWebViewDialog : ContentDialog
coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent(); coreWebView2.SetCookie(user.CookieToken, user.Ltoken, null).SetMobileUserAgent();
signInJsInterface = new(coreWebView2, scope.ServiceProvider); 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"); 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; signInJsInterface = null;
scope.Dispose(); scope.Dispose();
} }
} }

View File

@@ -15,6 +15,10 @@
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}" Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d"> mc:Ignorable="d">
<Page.Resources>
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
</Page.Resources>
<mxi:Interaction.Behaviors> <mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/> <shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors> </mxi:Interaction.Behaviors>
@@ -30,6 +34,12 @@
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<TextBlock
Margin="16,0,0,2"
VerticalAlignment="Center"
Style="{StaticResource SubtitleTextBlockStyle}"
Text="{Binding FinishDescription}"/>
<CommandBar Grid.Column="1" DefaultLabelPosition="Right"> <CommandBar Grid.Column="1" DefaultLabelPosition="Right">
<CommandBar.Content> <CommandBar.Content>
@@ -71,7 +81,6 @@
Command="{Binding RemoveArchiveCommand}" Command="{Binding RemoveArchiveCommand}"
Icon="{shcm:FontIcon Glyph=&#xE74D;}" Icon="{shcm:FontIcon Glyph=&#xE74D;}"
Label="删除当前存档"/> Label="删除当前存档"/>
<AppBarSeparator/> <AppBarSeparator/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入"> <AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8B5;}" Label="导入">
@@ -93,6 +102,7 @@
Icon="{shcm:FontIcon Glyph=&#xEDE1;}" Icon="{shcm:FontIcon Glyph=&#xEDE1;}"
Label="导出"/> Label="导出"/>
<AppBarSeparator/> <AppBarSeparator/>
<AppBarToggleButton <AppBarToggleButton
Command="{Binding SortIncompletedSwitchCommand}" Command="{Binding SortIncompletedSwitchCommand}"
Icon="{shcm:FontIcon Glyph=&#xE8CB;}" Icon="{shcm:FontIcon Glyph=&#xE8CB;}"
@@ -114,21 +124,31 @@
SelectionMode="Single"> SelectionMode="Single">
<ListView.ItemTemplate> <ListView.ItemTemplate>
<DataTemplate> <DataTemplate>
<Grid> <Grid Margin="0,6">
<Grid.ColumnDefinitions> <Grid.ColumnDefinitions>
<ColumnDefinition Width="24"/> <ColumnDefinition Width="36"/>
<ColumnDefinition/> <ColumnDefinition/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<shci:CachedImage <shci:CachedImage
Grid.Column="0" Grid.Column="0"
Width="24" Width="36"
Height="24" Height="36"
Source="{Binding Icon, Converter={StaticResource AchievementIconConverter}}"/> Source="{Binding Icon}"/>
<TextBlock <StackPanel Grid.Column="1" Margin="12,0,0,2">
Grid.Column="1" <TextBlock VerticalAlignment="Center" Text="{Binding Name}"/>
Margin="12,0,0,2" <TextBlock
VerticalAlignment="Center" Margin="0,2,0,0"
Text="{Binding Name}"/> 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> </Grid>
</DataTemplate> </DataTemplate>
</ListView.ItemTemplate> </ListView.ItemTemplate>
@@ -163,6 +183,8 @@
Grid.Column="1" Grid.Column="1"
Margin="6,0,12,0" Margin="6,0,12,0"
Padding="16,0,0,0" Padding="16,0,0,0"
Command="{Binding Path=DataContext.SaveAchievementCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
IsChecked="{Binding IsChecked, Mode=TwoWay}" IsChecked="{Binding IsChecked, Mode=TwoWay}"
Style="{StaticResource DefaultCheckBoxStyle}"> Style="{StaticResource DefaultCheckBoxStyle}">
<CheckBox.Content> <CheckBox.Content>

View File

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

View File

@@ -58,6 +58,11 @@
CommandParameter="{Binding ElementName=ImageExportPanel}" CommandParameter="{Binding ElementName=ImageExportPanel}"
Icon="{shcm:FontIcon Glyph=&#xE91B;}" Icon="{shcm:FontIcon Glyph=&#xE91B;}"
Label="导出图片"/> Label="导出图片"/>
<AppBarButton
Command="{Binding CultivateCommand}"
CommandParameter="{Binding SelectedAvatar}"
Icon="{shcm:FontIcon Glyph=&#xE91B;}"
Label="养成计算"/>
<AppBarSeparator/> <AppBarSeparator/>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE72C;}" Label="同步角色信息"> <AppBarButton Icon="{shcm:FontIcon Glyph=&#xE72C;}" Label="同步角色信息">
<AppBarButton.Flyout> <AppBarButton.Flyout>
@@ -129,7 +134,7 @@
</cwucont:SwitchPresenter> </cwucont:SwitchPresenter>
</SplitView.Pane> </SplitView.Pane>
<SplitView.Content> <SplitView.Content>
<ScrollViewer Padding="0,0,0,-88"> <ScrollViewer Padding="0,0,0,0">
<StackPanel <StackPanel
Name="ImageExportPanel" Name="ImageExportPanel"
MaxWidth="800" MaxWidth="800"
@@ -551,30 +556,6 @@
</DataTemplate> </DataTemplate>
</ItemsControl.ItemTemplate> </ItemsControl.ItemTemplate>
</ItemsControl> </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> </StackPanel>
</ScrollViewer> </ScrollViewer>
</SplitView.Content> </SplitView.Content>

View File

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

View File

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

View File

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

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