cultivation

This commit is contained in:
DismissedLight
2022-12-15 16:14:16 +08:00
parent 419f8b8882
commit ff146b4a2f
85 changed files with 3489 additions and 368 deletions

View File

@@ -59,6 +59,7 @@
<shmmc:QualityColorConverter x:Key="QualityColorConverter"/>
<shmmc:WeaponTypeIconConverter x:Key="WeaponTypeIconConverter"/>
<shvc:BoolToVisibilityRevertConverter x:Key="BoolToVisibilityRevertConverter"/>
<shvc:EmptyObjectToVisibilityConverter x:Key="EmptyObjectToVisibilityConverter"/>
<!-- Styles -->
<Style
@@ -67,6 +68,13 @@
TargetType="GridViewItem">
<Setter Property="Margin" Value="0,0,12,12"/>
</Style>
<Style x:Key="BorderCardStyle" TargetType="Border">
<Setter Property="Background" Value="{ThemeResource CardBackgroundFillColorDefaultBrush}"/>
<Setter Property="BorderBrush" Value="{ThemeResource CardStrokeColorDefaultBrush}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="CornerRadius" Value="{StaticResource CompatCornerRadius}"/>
</Style>
<!-- ItemsPanelTemplate -->
<ItemsPanelTemplate x:Key="ItemsStackPanelTemplate">
<ItemsStackPanel/>

View File

@@ -36,28 +36,35 @@ public partial class App : Application
}
/// <inheritdoc/>
[SuppressMessage("", "VSTHRD100")]
protected override async void OnLaunched(LaunchActivatedEventArgs args)
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main");
if (firstInstance.IsCurrent)
try
{
// manually invoke
Activation.Activate(firstInstance, activatedEventArgs);
firstInstance.Activated += Activation.Activate;
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs();
AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main");
logger.LogInformation(EventIds.CommonLog, "Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
if (firstInstance.IsCurrent)
{
// manually invoke
Activation.Activate(firstInstance, activatedEventArgs);
firstInstance.Activated += Activation.Activate;
ToastNotificationManagerCompat.OnActivated += Activation.NotificationActivate;
JumpListHelper.ConfigAsync().SafeForget(logger);
logger.LogInformation(EventIds.CommonLog, "Snap Hutao | {name} : {version}", CoreEnvironment.FamilyName, CoreEnvironment.Version);
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", ApplicationData.Current.TemporaryFolder.Path);
JumpListHelper.ConfigureAsync().SafeForget(logger);
}
else
{
// Redirect the activation (and args) to the "main" instance, and exit.
firstInstance.RedirectActivationTo(activatedEventArgs);
Process.GetCurrentProcess().Kill();
}
}
else
catch (Exception)
{
// Redirect the activation (and args) to the "main" instance, and exit.
await firstInstance.RedirectActivationToAsync(activatedEventArgs);
// AppInstance.GetCurrent() calls failed
Process.GetCurrentProcess().Kill();
}
}

View File

@@ -102,6 +102,21 @@ public sealed class AppDbContext : DbContext
/// </summary>
public DbSet<CultivateItem> CultivateItems { get; set; } = default!;
/// <summary>
/// 背包内物品
/// </summary>
public DbSet<InventoryItem> InventoryItems { get; set; } = default!;
/// <summary>
/// 背包内武器
/// </summary>
public DbSet<InventoryWeapon> InventoryWeapons { get; set; } = default!;
/// <summary>
/// 背包内圣遗物
/// </summary>
public DbSet<InventoryReliquary> InventoryReliquaries { get; set; } = default!;
/// <summary>
/// 构造一个临时的应用程序数据库上下文
/// </summary>
@@ -125,6 +140,7 @@ public sealed class AppDbContext : DbContext
modelBuilder
.ApplyConfiguration(new AvatarInfoConfiguration())
.ApplyConfiguration(new UserConfiguration())
.ApplyConfiguration(new DailyNoteEntryConfiguration());
.ApplyConfiguration(new DailyNoteEntryConfiguration())
.ApplyConfiguration(new InventoryReliquaryConfiguration());
}
}

View File

@@ -0,0 +1,43 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using CommunityToolkit.WinUI.UI.Behaviors;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
namespace Snap.Hutao.Control.Behavior;
/// <summary>
/// AppTitleBar Workaround
/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756
/// </summary>
internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase<ComboBox>
{
private readonly IMessenger messenger;
/// <summary>
/// AppTitleBar Workaround
/// </summary>
public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior()
{
messenger = Ioc.Default.GetRequiredService<IMessenger>();
}
/// <inheritdoc/>
protected override void OnAssociatedObjectLoaded()
{
AssociatedObject.DropDownOpened += OnDropDownOpened;
AssociatedObject.DropDownClosed += OnDropDownClosed;
}
private void OnDropDownOpened(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(true));
}
private void OnDropDownClosed(object? sender, object e)
{
messenger.Send(new Message.FlyoutOpenCloseMessage(false));
}
}

View File

@@ -25,6 +25,7 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
/// <inheritdoc/>
protected override void OnDetaching()
{
// 由于卸载顺序问题,必须重写此方法才能正确触发命令
if (Command != null && Command.CanExecute(null))
{
Command.Execute(null);
@@ -32,4 +33,4 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase<UIElement>
base.OnDetaching();
}
}
}

View File

@@ -244,6 +244,10 @@ public class ImageCache : IImageCache
logger.LogInformation("Retry after {delay}.", delay);
await Task.Delay(delay).ConfigureAwait(false);
}
else
{
return;
}
}
if (retryCount == 3)

View File

@@ -23,33 +23,6 @@ public static class DbSetExtension
return dbSet.GetService<ICurrentDbContext>().Context;
}
/// <summary>
/// 获取或添加一个对应的实体
/// </summary>
/// <typeparam name="TEntity">实体类型</typeparam>
/// <param name="dbSet">数据库集</param>
/// <param name="predicate">谓词</param>
/// <param name="entityFactory">实体工厂</param>
/// <param name="added">是否添加</param>
/// <returns>实体</returns>
public static TEntity SingleOrAdd<TEntity>(this DbSet<TEntity> dbSet, Func<TEntity, bool> predicate, Func<TEntity> entityFactory, out bool added)
where TEntity : class
{
added = false;
TEntity? entry = dbSet.SingleOrDefault(predicate);
if (entry == null)
{
entry = entityFactory();
dbSet.Add(entry);
dbSet.Context().SaveChanges();
added = true;
}
return entry;
}
/// <summary>
/// 添加并保存
/// </summary>

View File

@@ -15,7 +15,7 @@ public static class JumpListHelper
/// 异步配置跳转列表
/// </summary>
/// <returns>任务</returns>
public static async Task ConfigAsync()
public static async Task ConfigureAsync()
{
if (JumpList.IsSupported())
{

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.WinUI.Notifications;
using Microsoft.Extensions.Caching.Memory;
using Microsoft.Windows.AppLifecycle;
using Snap.Hutao.Extension;
using Snap.Hutao.Service.Abstraction;
@@ -192,9 +193,9 @@ internal static class Activation
private static async Task HandleLaunchGameActionAsync(string? uid = null)
{
Ioc.Default.GetRequiredService<IMemoryCache>().Set(ViewModel.LaunchGameViewModel.DesiredUid, uid);
await ThreadHelper.SwitchToMainThreadAsync();
// TODO auto switch to account
if (!MainWindow.IsPresent)
{
_ = Ioc.Default.GetRequiredService<LaunchGameWindow>();

View File

@@ -0,0 +1,38 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.Windows.AppLifecycle;
using Windows.Win32.Foundation;
using Windows.Win32.Security;
using Windows.Win32.System.Com;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.LifeCycle;
/// <summary>
/// App 实例拓展
/// </summary>
internal static class AppInstanceExtension
{
/// <summary>
/// 同步非阻塞重定向
/// </summary>
/// <param name="appInstance">app实例</param>
/// <param name="args">参数</param>
[SuppressMessage("", "VSTHRD002")]
[SuppressMessage("", "VSTHRD110")]
public static unsafe void RedirectActivationTo(this AppInstance appInstance, AppActivationArguments args)
{
HANDLE redirectEventHandle = CreateEvent((SECURITY_ATTRIBUTES*)null, true, false, null);
Task.Run(() =>
{
appInstance.RedirectActivationToAsync(args).AsTask().Wait();
SetEvent(redirectEventHandle);
});
ReadOnlySpan<HANDLE> handles = new(in redirectEventHandle);
// non-blocking
CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _);
}
}

View File

@@ -23,7 +23,7 @@ namespace Snap.Hutao.Core.Windowing;
/// </summary>
/// <typeparam name="TWindow">窗体类型</typeparam>
[SuppressMessage("", "CA1001")]
internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMessage>
internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMessage>, IRecipient<FlyoutOpenCloseMessage>
where TWindow : Window, IExtendedWindowSource
{
private readonly HWND handle;
@@ -82,6 +82,12 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMe
}
}
/// <inheritdoc/>
public void Receive(FlyoutOpenCloseMessage message)
{
UpdateDragRectangles(appWindow.TitleBar, message.IsOpen);
}
private static void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
{
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
@@ -137,7 +143,9 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMe
bool subClassApplied = subclassManager.TrySetWindowSubclass();
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(WindowSubclassManager<TWindow>), subClassApplied ? "succeed" : "failed");
Ioc.Default.GetRequiredService<IMessenger>().Register(this);
IMessenger messenger = Ioc.Default.GetRequiredService<IMessenger>();
messenger.Register<BackdropTypeChangedMessage>(this);
messenger.Register<FlyoutOpenCloseMessage>(this);
window.Closed += OnWindowClosed;
}
@@ -172,17 +180,25 @@ internal sealed class ExtendedWindow<TWindow> : IRecipient<BackdropTypeChangedMe
}
}
private unsafe void UpdateDragRectangles(AppWindowTitleBar appTitleBar)
private void UpdateDragRectangles(AppWindowTitleBar appTitleBar, bool isFlyoutOpened = false)
{
double scale = Persistence.GetScaleForWindow(handle);
if (isFlyoutOpened)
{
// set to 0
appTitleBar.SetDragRectangles(default(RectInt32).Enumerate().ToArray());
}
else
{
double scale = Persistence.GetScaleForWindow(handle);
// 48 is the navigation button leftInset
RectInt32 dragRect = StructMarshal.RectInt32(new(48, 0), titleBar.ActualSize).Scale(scale);
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
// 48 is the navigation button leftInset
RectInt32 dragRect = StructMarshal.RectInt32(new(48, 0), titleBar.ActualSize).Scale(scale);
appTitleBar.SetDragRectangles(dragRect.Enumerate().ToArray());
// workaround for https://github.com/microsoft/WindowsAppSDK/issues/2976
SizeInt32 size = appWindow.ClientSize;
size.Height -= (int)(31 * scale);
appWindow.ResizeClient(size);
// workaround for https://github.com/microsoft/WindowsAppSDK/issues/2976
SizeInt32 size = appWindow.ClientSize;
size.Height -= (int)(31 * scale);
appWindow.ResizeClient(size);
}
}
}

View File

@@ -0,0 +1,26 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Core.Windowing;
namespace Snap.Hutao.Message;
/// <summary>
/// Flyout开启关闭消息
/// </summary>
internal class FlyoutOpenCloseMessage
{
/// <summary>
/// 构造一个新的Flyout开启关闭消息
/// </summary>
/// <param name="isOpen">是否为开启状态</param>
public FlyoutOpenCloseMessage(bool isOpen)
{
IsOpen = isOpen;
}
/// <summary>
/// 是否为开启状态
/// </summary>
public bool IsOpen { get; }
}

View File

@@ -0,0 +1,512 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
using Snap.Hutao.Context.Database;
#nullable disable
namespace Snap.Hutao.Migrations
{
[DbContext(typeof(AppDbContext))]
[Migration("20221210111128_Inventory")]
partial class Inventory
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "7.0.0");
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<int>("Current")
.HasColumnType("INTEGER");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Info")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("Id")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("Count")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachedUid")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DailyNote")
.HasColumnType("TEXT");
b.Property<bool>("DailyTaskNotify")
.HasColumnType("INTEGER");
b.Property<bool>("DailyTaskNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotify")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("HomeCoinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("HomeCoinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<bool>("ResinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("ResinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<bool>("ShowInHomeWidget")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotify")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("UserId");
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<int>("GachaType")
.HasColumnType("INTEGER");
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("QueryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("Count")
.HasColumnType("INTEGER");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AppendPropIdList")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<int>("MainPropId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("PromoteLevel")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ExpireTime")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Aid")
.HasColumnType("TEXT");
b.Property<string>("CookieToken")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Ltoken")
.HasColumnType("TEXT");
b.Property<string>("Mid")
.HasColumnType("TEXT");
b.Property<string>("Stoken")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("users");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany()
.HasForeignKey("EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Entry");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("User");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.GachaArchive", "Archive")
.WithMany("Items")
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
{
b.Navigation("Items");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -0,0 +1,117 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class Inventory : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<string>(
name: "AttachedUid",
table: "cultivate_projects",
type: "TEXT",
nullable: true);
migrationBuilder.CreateTable(
name: "inventory_items",
columns: table => new
{
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
ProjectId = table.Column<Guid>(type: "TEXT", nullable: false),
ItemId = table.Column<int>(type: "INTEGER", nullable: false),
Count = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_inventory_items", x => x.InnerId);
table.ForeignKey(
name: "FK_inventory_items_cultivate_projects_ProjectId",
column: x => x.ProjectId,
principalTable: "cultivate_projects",
principalColumn: "InnerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "inventory_reliquaries",
columns: table => new
{
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
ProjectId = table.Column<Guid>(type: "TEXT", nullable: false),
ItemId = table.Column<int>(type: "INTEGER", nullable: false),
Level = table.Column<int>(type: "INTEGER", nullable: false),
MainPropId = table.Column<int>(type: "INTEGER", nullable: false),
AppendPropIdList = table.Column<string>(type: "TEXT", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_inventory_reliquaries", x => x.InnerId);
table.ForeignKey(
name: "FK_inventory_reliquaries_cultivate_projects_ProjectId",
column: x => x.ProjectId,
principalTable: "cultivate_projects",
principalColumn: "InnerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "inventory_weapons",
columns: table => new
{
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
ProjectId = table.Column<Guid>(type: "TEXT", nullable: false),
ItemId = table.Column<int>(type: "INTEGER", nullable: false),
Level = table.Column<int>(type: "INTEGER", nullable: false),
PromoteLevel = table.Column<int>(type: "INTEGER", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_inventory_weapons", x => x.InnerId);
table.ForeignKey(
name: "FK_inventory_weapons_cultivate_projects_ProjectId",
column: x => x.ProjectId,
principalTable: "cultivate_projects",
principalColumn: "InnerId",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_inventory_items_ProjectId",
table: "inventory_items",
column: "ProjectId");
migrationBuilder.CreateIndex(
name: "IX_inventory_reliquaries_ProjectId",
table: "inventory_reliquaries",
column: "ProjectId");
migrationBuilder.CreateIndex(
name: "IX_inventory_weapons_ProjectId",
table: "inventory_weapons",
column: "ProjectId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "inventory_items");
migrationBuilder.DropTable(
name: "inventory_reliquaries");
migrationBuilder.DropTable(
name: "inventory_weapons");
migrationBuilder.DropColumn(
name: "AttachedUid",
table: "cultivate_projects");
}
}
}

View File

@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("achievements");
b.ToTable("achievements", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("achievement_archives");
b.ToTable("achievement_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
@@ -79,7 +79,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("avatar_infos");
b.ToTable("avatar_infos", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
@@ -101,7 +101,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
b.ToTable("cultivate_entries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
@@ -123,7 +123,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
b.ToTable("cultivate_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
@@ -132,6 +132,9 @@ namespace Snap.Hutao.Migrations
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachedUid")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
@@ -141,7 +144,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
b.ToTable("cultivate_projects", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
@@ -197,7 +200,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("UserId");
b.ToTable("daily_notes");
b.ToTable("daily_notes", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
@@ -215,7 +218,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("gacha_archives");
b.ToTable("gacha_archives", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
@@ -246,7 +249,7 @@ namespace Snap.Hutao.Migrations
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
b.ToTable("gacha_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
@@ -271,7 +274,83 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("game_accounts");
b.ToTable("game_accounts", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("Count")
.HasColumnType("INTEGER");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_items", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AppendPropIdList")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<int>("MainPropId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("PromoteLevel")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
@@ -287,7 +366,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("object_cache");
b.ToTable("object_cache", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
@@ -300,7 +379,7 @@ namespace Snap.Hutao.Migrations
b.HasKey("Key");
b.ToTable("settings");
b.ToTable("settings", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
@@ -329,13 +408,13 @@ namespace Snap.Hutao.Migrations
b.HasKey("InnerId");
b.ToTable("users");
b.ToTable("users", (string)null);
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.AchievementArchive", "Archive")
.WithMany("Achievements")
.WithMany()
.HasForeignKey("ArchiveId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -346,7 +425,7 @@ namespace Snap.Hutao.Migrations
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany("Entries")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -357,7 +436,7 @@ namespace Snap.Hutao.Migrations
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
{
b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry")
.WithMany("Items")
.WithMany()
.HasForeignKey("EntryId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -387,19 +466,37 @@ namespace Snap.Hutao.Migrations
b.Navigation("Archive");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Navigation("Achievements");
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Navigation("Items");
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Navigation("Entries");
b.HasOne("Snap.Hutao.Model.Entity.CultivateProject", "Project")
.WithMany()
.HasForeignKey("ProjectId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Project");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>

View File

@@ -15,9 +15,6 @@ public class Achievement : ObservableObject
/// </summary>
public const int FullProgressPlaceholder = int.MaxValue;
private readonly Metadata.Achievement.Achievement inner;
private readonly Entity.Achievement entity;
private bool isChecked;
/// <summary>
@@ -27,22 +24,21 @@ public class Achievement : ObservableObject
/// <param name="entity">实体部分</param>
public Achievement(Metadata.Achievement.Achievement inner, Entity.Achievement entity)
{
this.inner = inner;
this.entity = entity;
Inner = inner;
Entity = entity;
// Property should only be set when it's user checking.
isChecked = (int)entity.Status >= 2;
}
/// <summary>
/// 实体
/// </summary>
public Entity.Achievement Entity { get => entity; }
public Entity.Achievement Entity { get; }
/// <summary>
/// 元数据
/// </summary>
public Metadata.Achievement.Achievement Inner { get => inner; }
public Metadata.Achievement.Achievement Inner { get; }
/// <summary>
/// 是否选中
@@ -69,6 +65,6 @@ public class Achievement : ObservableObject
/// </summary>
public string Time
{
get => entity.Time.ToString("yyyy.MM.dd HH:mm:ss");
get => Entity.Time.ToString("yyyy.MM.dd HH:mm:ss");
}
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 养成物品
/// </summary>
public class CultivateEntry : ItemBase
{
/// <summary>
/// Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 入口Id
/// </summary>
public Guid EntryId { get; set; }
/// <summary>
/// 实体
/// </summary>
public List<CultivateItem> Items { get; set; } = default!;
}

View File

@@ -0,0 +1,34 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Model.Binding.Cultivation;
/// <summary>
/// 养成物品
/// </summary>
public class CultivateItem : ObservableObject
{
/// <summary>
/// 养成物品
/// </summary>
/// <param name="inner">元数据</param>
/// <param name="entity">实体</param>
public CultivateItem(Material inner, Entity.CultivateItem entity)
{
Inner = inner;
Entity = entity;
}
/// <summary>
/// 元数据
/// </summary>
public Material Inner { get; }
/// <summary>
/// 实体
/// </summary>
public Entity.CultivateItem Entity { get; }
}

View File

@@ -14,14 +14,9 @@ public enum CultivateType
None,
/// <summary>
/// 角色
/// 角色与技能
/// </summary>
Avatar,
/// <summary>
/// 角色技能
/// </summary>
Skill,
AvatarAndSkill,
/// <summary>
/// 武器

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Metadata;
namespace Snap.Hutao.Model.Binding.Inventory;
/// <summary>
/// 背包物品
/// </summary>
internal class InventoryItem : ObservableObject
{
private uint count;
/// <summary>
/// 创建一个新的背包物品
/// </summary>
/// <param name="inner">元数据</param>
/// <param name="entity">实体</param>
public InventoryItem(Material inner, Entity.InventoryItem entity)
{
Entity = entity;
Inner = inner;
count = entity.Count;
}
/// <summary>
/// 实体
/// </summary>
public Entity.InventoryItem Entity { get; set; }
/// <summary>
/// 元数据
/// </summary>
public Material Inner { get; set; }
/// <summary>
/// 个数
/// </summary>
public uint Count
{
get => count; set
{
if (SetProperty(ref count, value))
{
Entity.Count = value;
Ioc.Default.GetRequiredService<Service.Cultivation.ICultivationService>().SaveInventoryItem(this);
}
}
}
}

View File

@@ -0,0 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata.Builders;
namespace Snap.Hutao.Model.Entity.Configuration;
/// <summary>
/// 背包圣遗物配置
/// </summary>
internal class InventoryReliquaryConfiguration : IEntityTypeConfiguration<InventoryReliquary>
{
/// <inheritdoc/>
public void Configure(EntityTypeBuilder<InventoryReliquary> builder)
{
builder.Property(e => e.AppendPropIdList)
.HasColumnType("TEXT")
.HasConversion(
list => string.Join(',', list),
text => text.Split(',', StringSplitOptions.None).Select(x => int.Parse(x)).ToList());
}
}

View File

@@ -42,7 +42,18 @@ public class CultivateEntry
public int Id { get; set; }
/// <summary>
/// 物品
/// 创建一个新的养成入口点
/// </summary>
public virtual ICollection<CultivateItem> Items { get; set; } = default!;
/// <param name="type">类型</param>
/// <param name="id">主Id</param>
/// <returns>养成入口点</returns>
public static CultivateEntry Create(Guid projectId,CultivateType type, int id)
{
return new()
{
ProjectId = projectId,
Type = type,
Id = id,
};
}
}

View File

@@ -39,4 +39,21 @@ public class CultivateItem
/// 物品个数
/// </summary>
public int Count { get; set; }
/// <summary>
/// 创建一个新的养成物品
/// </summary>
/// <param name="entryId">入口点 Id</param>
/// <param name="itemId">物品 Id</param>
/// <param name="count">个数</param>
/// <returns>养成物品</returns>
public static CultivateItem Create(Guid entryId, int itemId, int count)
{
return new()
{
EntryId = entryId,
ItemId = itemId,
Count = count,
};
}
}

View File

@@ -31,7 +31,18 @@ public class CultivateProject : ISelectable
public string Name { get; set; } = default!;
/// <summary>
/// 入口集合
/// 所属的Uid
/// </summary>
public virtual ICollection<CultivateEntry> Entries { get; set; } = default!;
}
public string? AttachedUid { get; set; }
/// <summary>
/// 创建新的养成计划
/// </summary>
/// <param name="name">名称</param>
/// <param name="attachedUid">绑定的Uid</param>
/// <returns>新的养成计划</returns>
public static CultivateProject Create(string name, string? attachedUid = null)
{
return new() { Name = name, AttachedUid = attachedUid };
}
}

View File

@@ -0,0 +1,57 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Snap.Hutao.Model.Entity;
/// <summary>
/// 背包物品
/// </summary>
[Table("inventory_items")]
public class InventoryItem
{
/// <summary>
/// 内部Id
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
/// <summary>
/// 培养计划Id
/// </summary>
public Guid ProjectId { get; set; }
/// <summary>
/// 所属的计划
/// </summary>
[ForeignKey(nameof(ProjectId))]
public CultivateProject Project { get; set; } = default!;
/// <summary>
/// 物品Id
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 个数 4294967295
/// </summary>
public uint Count { get; set; }
/// <summary>
/// 构造一个新的个数为0的物品
/// </summary>
/// <param name="projectId">项目Id</param>
/// <param name="itemId">物品Id</param>
/// <returns>新的个数为0的物品</returns>
public static InventoryItem Create(Guid projectId, int itemId)
{
return new()
{
ProjectId = projectId,
ItemId = itemId,
};
}
}

View File

@@ -0,0 +1,52 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Snap.Hutao.Model.Entity;
/// <summary>
/// 背包圣遗物
/// </summary>
[Table("inventory_reliquaries")]
public class InventoryReliquary
{
/// <summary>
/// 内部Id
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
/// <summary>
/// 培养计划Id
/// </summary>
public Guid ProjectId { get; set; }
/// <summary>
/// 所属的计划
/// </summary>
[ForeignKey(nameof(ProjectId))]
public CultivateProject Project { get; set; } = default!;
/// <summary>
/// 物品Id
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 等级
/// </summary>
public int Level { get; set; }
/// <summary>
/// 主属性
/// </summary>
public int MainPropId { get; set; }
/// <summary>
/// 副词条Id
/// </summary>
public List<int> AppendPropIdList { get; set; } = default!;
}

View File

@@ -0,0 +1,47 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace Snap.Hutao.Model.Entity;
/// <summary>
/// 背包武器
/// </summary>
[Table("inventory_weapons")]
public class InventoryWeapon
{
/// <summary>
/// 内部Id
/// </summary>
[Key]
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public Guid InnerId { get; set; }
/// <summary>
/// 培养计划Id
/// </summary>
public Guid ProjectId { get; set; }
/// <summary>
/// 所属的计划
/// </summary>
[ForeignKey(nameof(ProjectId))]
public CultivateProject Project { get; set; } = default!;
/// <summary>
/// 物品Id
/// </summary>
public int ItemId { get; set; }
/// <summary>
/// 等级
/// </summary>
public int Level { get; set; }
/// <summary>
/// 精炼等级 0-4
/// </summary>
public int PromoteLevel { get; set; }
}

View File

@@ -5,7 +5,7 @@ namespace Snap.Hutao.Model.InterChange.Achievement;
/// <summary>
/// 统一可交换成就格式
/// https://www.snapgenshin.com/development/UIAF.html
/// https://uigf.org/standards/UIAF.html
/// </summary>
public class UIAF
{

View File

@@ -7,7 +7,7 @@ namespace Snap.Hutao.Model.InterChange.GachaLog;
/// <summary>
/// 统一可交换祈愿格式
/// https://www.snapgenshin.com/development/UIGF.html
/// https://uigf.org/standards/UIGF.html
/// </summary>
public class UIGF
{

View File

@@ -0,0 +1,90 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Collections.Immutable;
namespace Snap.Hutao.Model.InterChange.Inventory;
/// <summary>
/// 统一可交换物品格式
/// </summary>
internal class UIIF
{
/// <summary>
/// 当前发行的版本
/// </summary>
public const string CurrentVersion = "v1.0";
private static readonly ImmutableList<string> SupportedVersion = new List<string>()
{
CurrentVersion,
}.ToImmutableList();
/// <summary>
/// 信息
/// </summary>
[JsonPropertyName("info")]
public UIIFInfo Info { get; set; } = default!;
/// <summary>
/// 列表
/// </summary>
[JsonPropertyName("list")]
public List<UIIFItem> List { get; set; } = default!;
}
/// <summary>
/// UIIF物品
/// </summary>
[JsonDerivedType(typeof(UIIFReliquary))]
[JsonDerivedType(typeof(UIIFWeapon))]
internal class UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("itemId")]
public int ItemId { get; set; }
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("count")]
public int Count { get; set; }
}
/// <summary>
/// UIIF圣遗物
/// </summary>
internal class UIIFReliquary : UIIFItem
{
/// <summary>
/// 物品Id
/// </summary>
[JsonPropertyName("level")]
public int Level { get; set; }
/// <summary>
/// 副属性列表
/// </summary>
[JsonPropertyName("appendPropIdList")]
public List<int> AppendPropIdList { get; set; }
}
/// <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

@@ -0,0 +1,80 @@
using Snap.Hutao.Core;
using Snap.Hutao.Extension;
using System;
using System.Collections.Generic;
using System.Collections.Immutable;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Snap.Hutao.Model.InterChange.Inventory;
/// <summary>
/// UIIF格式的信息
/// </summary>
public class UIIFInfo
{
/// <summary>
/// 用户Uid
/// </summary>
[JsonPropertyName("uid")]
public string Uid { get; set; } = default!;
/// <summary>
/// 语言
/// </summary>
[JsonPropertyName("lang")]
public string Language { get; set; } = default!;
/// <summary>
/// 导出的时间戳
/// </summary>
[JsonPropertyName("export_timestamp")]
[JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)]
public long? ExportTimestamp { get; set; }
/// <summary>
/// 导出时间
/// </summary>
[JsonIgnore]
public DateTimeOffset ExportDateTime
{
get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue);
}
/// <summary>
/// 导出的 App 名称
/// </summary>
[JsonPropertyName("export_app")]
public string ExportApp { get; set; } = default!;
/// <summary>
/// 导出的 App 版本
/// </summary>
[JsonPropertyName("export_app_version")]
public string ExportAppVersion { get; set; } = default!;
/// <summary>
/// 使用的UIGF版本
/// </summary>
[JsonPropertyName("uiif_version")]
public string UIIFVersion { get; set; } = default!;
/// <summary>
/// 构造一个新的专用 UIGF 信息
/// </summary>
/// <param name="uid">uid</param>
/// <returns>专用 UIGF 信息</returns>
public static UIIFInfo Create(string uid)
{
return new()
{
Uid = uid,
Language = "zh-cn",
ExportTimestamp = DateTimeOffset.Now.ToUnixTimeSeconds(),
ExportApp = "胡桃",
ExportAppVersion = CoreEnvironment.Version.ToString(),
UIIFVersion = UIIF.CurrentVersion,
};
}
}

View File

@@ -19,4 +19,5 @@ public interface INameQuality
/// 星级
/// </summary>
ItemQuality Quality { get; }
}
}

View File

@@ -0,0 +1,74 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色的接口实现部分
/// </summary>
public partial class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality, ICalculableSource<ICalculableAvatar>
{
/// <summary>
/// [非元数据] 搭配数据
/// </summary>
[JsonIgnore]
public ComplexAvatarCollocation? Collocation { get; set; }
/// <inheritdoc/>
public ICalculableAvatar ToCalculable()
{
return new CalculableAvatar(this);
}
/// <summary>
/// 转换为基础物品
/// </summary>
/// <returns>基础物品</returns>
public ItemBase ToItemBase()
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
};
}
/// <inheritdoc/>
public StatisticsItem ToStatisticsItem(int count)
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
Count = count,
};
}
/// <inheritdoc/>
public SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp)
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
Time = time,
LastPull = lastPull,
IsUp = isUp,
};
}
}

View File

@@ -2,12 +2,7 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.Json.Annotation;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Avatar;
@@ -15,7 +10,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色
/// </summary>
public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
public partial class Avatar
{
/// <summary>
/// Id
@@ -87,65 +82,4 @@ public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality
/// 皮肤
/// </summary>
public IEnumerable<Costume> Costumes { get; set; } = default!;
/// <summary>
/// [非元数据] 搭配数据
/// </summary>
[JsonIgnore]
public ComplexAvatarCollocation? Collocation { get; set; }
/// <summary>
/// 转换为基础物品
/// </summary>
/// <returns>基础物品</returns>
public ItemBase ToItemBase()
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
};
}
/// <summary>
/// 转换到统计物品
/// </summary>
/// <param name="count">个数</param>
/// <returns>统计物品</returns>
public StatisticsItem ToStatisticsItem(int count)
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
Count = count,
};
}
/// <summary>
/// 转换到简述统计物品
/// </summary>
/// <param name="lastPull">距上个五星</param>
/// <param name="time">时间</param>
/// <param name="isUp">是否为Up物品</param>
/// <returns>简述统计物品</returns>
public SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp)
{
return new()
{
Name = Name,
Icon = AvatarIconConverter.IconNameToUri(Icon),
Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore),
Quality = Quality,
Time = time,
LastPull = lastPull,
IsUp = isUp,
};
}
}
}

View File

@@ -0,0 +1,67 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 可计算角色
/// </summary>
internal class CalculableAvatar : ObservableObject, ICalculableAvatar
{
private int levelCurrent;
private int levelTarget;
/// <summary>
/// 构造一个新的可计算角色
/// </summary>
/// <param name="avatar">角色</param>
public CalculableAvatar(Avatar avatar)
{
AvatarId = avatar.Id;
LevelMin = 1;
LevelMax = int.Parse(avatar.Property.Parameters.Last().Level);
Skills = avatar.SkillDepot.GetCompositeSkillsNoInherents().Select(p => p.ToCalculable()).ToList();
Name = avatar.Name;
Icon = AvatarIconConverter.IconNameToUri(avatar.Icon);
Quality = avatar.Quality;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
public AvatarId AvatarId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
/// <inheritdoc/>
public IList<ICalculableSkill> Skills { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -0,0 +1,58 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 可计算的技能
/// </summary>
internal class CalculableSkill : ObservableObject, ICalculableSkill
{
private int levelCurrent;
private int levelTarget;
/// <summary>
/// 构造一个新的可计算的技能
/// </summary>
/// <param name="skill">技能</param>
public CalculableSkill(ProudableSkill skill)
{
GruopId = skill.GroupId;
LevelMin = 1;
LevelMax = 10; // hard coded 10 here
Name = skill.Name;
Icon = SkillIconConverter.IconNameToUri(skill.Icon);
Quality = ItemQuality.QUALITY_NONE;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
public int GruopId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能信息的接口实现
/// </summary>
public partial class ProudableSkill : ICalculableSource<ICalculableSkill>
{
/// <inheritdoc/>
public ICalculableSkill ToCalculable()
{
return new CalculableSkill(this);
}
}

View File

@@ -6,7 +6,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 技能信息
/// </summary>
public class ProudableSkill : SkillBase
public partial class ProudableSkill : SkillBase
{
/// <summary>
/// 组Id
@@ -17,4 +17,4 @@ public class ProudableSkill : SkillBase
/// 提升属性
/// </summary>
public DescParam Proud { get; set; } = default!;
}
}

View File

@@ -24,7 +24,8 @@ public class SkillDepot
public IList<ProudableSkill> Inherents { get; set; } = default!;
/// <summary>
/// 全部天赋,包括固有天赋
/// 全部天赋包括固有天赋
/// 在 Wiki 中使用
/// </summary>
public IList<ProudableSkill> CompositeSkills
{

View File

@@ -1,6 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Model.Metadata;
@@ -49,4 +50,4 @@ public class GachaEvent
/// 四星列表
/// </summary>
public List<string> UpPurpleList { get; set; } = default!;
}
}

View File

@@ -0,0 +1,48 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Model.Metadata;
/// <summary>
/// 材料
/// </summary>
public class Material
{
/// <summary>
/// 物品Id
/// </summary>
public int Id { get; set; }
/// <summary>
/// 等级
/// </summary>
public ItemQuality RankLevel { get; set; }
/// <summary>
/// 物品类型
/// </summary>
public ItemType ItemType { get; set; }
/// <summary>
/// 图标
/// </summary>
public string Icon { get; set; } = default!;
/// <summary>
/// 名称
/// </summary>
public string Name { get; set; } = default!;
/// <summary>
/// 描述
/// </summary>
public string Description { get; set; } = default!;
/// <summary>
/// 类型描述
/// </summary>
public string TypeDescription { get; set; } = default!;
}

View File

@@ -0,0 +1,63 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 可计算武器
/// </summary>
public class CalculableWeapon : ObservableObject, ICalculableWeapon
{
private int levelCurrent;
private int levelTarget;
/// <summary>
/// 构造一个新的可计算武器
/// </summary>
/// <param name="weapon">武器</param>
public CalculableWeapon(Weapon weapon)
{
WeaponId = weapon.Id;
LevelMin = 1;
LevelMax = int.Parse(weapon.Property.Parameters.Last().Level);
Name = weapon.Name;
Icon = EquipIconConverter.IconNameToUri(weapon.Icon);
Quality = weapon.RankLevel;
LevelCurrent = LevelMin;
LevelTarget = LevelMax;
}
/// <inheritdoc/>
public WeaponId WeaponId { get; }
/// <inheritdoc/>
public int LevelMin { get; }
/// <inheritdoc/>
public int LevelMax { get; }
/// <inheritdoc/>
public string Name { get; }
/// <inheritdoc/>
public Uri Icon { get; }
/// <inheritdoc/>
public ItemQuality Quality { get; }
/// <inheritdoc/>
public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); }
/// <inheritdoc/>
public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); }
}

View File

@@ -0,0 +1,90 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 武器的接口实现
/// </summary>
public partial class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality, ICalculableSource<ICalculableWeapon>
{
/// <summary>
/// [非元数据] 搭配数据
/// </summary>
[JsonIgnore]
public ComplexWeaponCollocation? Collocation { get; set; }
/// <inheritdoc/>
[JsonIgnore]
public ItemQuality Quality
{
get => RankLevel;
}
/// <inheritdoc/>
public ICalculableWeapon ToCalculable()
{
return new CalculableWeapon(this);
}
/// <summary>
/// 转换为基础物品
/// </summary>
/// <returns>基础物品</returns>
public ItemBase ToItemBase()
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Quality = RankLevel,
};
}
/// <summary>
/// 转换到统计物品
/// </summary>
/// <param name="count">个数</param>
/// <returns>统计物品</returns>
public StatisticsItem ToStatisticsItem(int count)
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Quality = RankLevel,
Count = count,
};
}
/// <summary>
/// 转换到简述统计物品
/// </summary>
/// <param name="lastPull">距上个五星</param>
/// <param name="time">时间</param>
/// <param name="isUp">是否为Up物品</param>
/// <returns>简述统计物品</returns>
public SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp)
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Time = time,
Quality = RankLevel,
LastPull = lastPull,
IsUp = isUp,
};
}
}

View File

@@ -1,12 +1,7 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Gacha;
using Snap.Hutao.Model.Binding.Gacha.Abstraction;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Converter;
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Model.Metadata.Weapon;
@@ -14,7 +9,7 @@ namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 武器
/// </summary>
public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
public partial class Weapon
{
/// <summary>
/// Id
@@ -60,70 +55,4 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality
/// 被动信息, 无被动的武器为 <see langword="null"/>
/// </summary>
public AffixInfo? Affix { get; set; } = default!;
/// <summary>
/// [非元数据] 搭配数据
/// </summary>
[JsonIgnore]
public ComplexWeaponCollocation? Collocation { get; set; }
/// <inheritdoc/>
[JsonIgnore]
public ItemQuality Quality
{
get => RankLevel;
}
/// <summary>
/// 转换为基础物品
/// </summary>
/// <returns>基础物品</returns>
public ItemBase ToItemBase()
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Quality = RankLevel,
};
}
/// <summary>
/// 转换到统计物品
/// </summary>
/// <param name="count">个数</param>
/// <returns>统计物品</returns>
public StatisticsItem ToStatisticsItem(int count)
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Quality = RankLevel,
Count = count,
};
}
/// <summary>
/// 转换到简述统计物品
/// </summary>
/// <param name="lastPull">距上个五星</param>
/// <param name="time">时间</param>
/// <param name="isUp">是否为Up物品</param>
/// <returns>简述统计物品</returns>
public SummaryItem ToSummaryItem(int lastPull, DateTimeOffset time, bool isUp)
{
return new()
{
Name = Name,
Icon = EquipIconConverter.IconNameToUri(Icon),
Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType),
Time = time,
Quality = RankLevel,
LastPull = lastPull,
IsUp = isUp,
};
}
}

View File

@@ -1,24 +1,31 @@
// Const value
INFINITE
WM_GETMINMAXINFO
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
// Type definition
CWMO_FLAGS
HRESULT
MINMAXINFO
// Comctl32
// COMCTL32
DefSubclassProc
SetWindowSubclass
RemoveWindowSubclass
// Kernel32
// KERNEL32
CreateEvent
CreateToolhelp32Snapshot
Module32First
Module32Next
ReadProcessMemory
SetEvent
WriteProcessMemory
// User32
// OLE32
CoWaitForMultipleObjects
// USER32
FindWindowEx
GetDpiForWindow

View File

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

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Core.Diagnostics;
@@ -53,7 +54,7 @@ internal class AchievementService : IAchievementService
/// <inheritdoc/>
public ObservableCollection<EntityArchive> GetArchiveCollection()
{
return archiveCollection ??= new(appDbContext.AchievementArchives.ToList());
return archiveCollection ??= new(appDbContext.AchievementArchives.AsNoTracking().ToList());
}
/// <inheritdoc/>
@@ -78,7 +79,7 @@ internal class AchievementService : IAchievementService
}
// 查找是否有相同的名称
if (archiveCollection!.SingleOrDefault(a => a.Name == newArchive.Name) is EntityArchive userWithSameUid)
if (archiveCollection!.SingleOrDefault(a => a.Name == newArchive.Name) != null)
{
return ArchiveAddResult.AlreadyExists;
}

View File

@@ -19,7 +19,7 @@ public enum ArchiveAddResult
InvalidName,
/// <summary>
/// 已经存在该用户
/// 已经存在该存档
/// </summary>
AlreadyExists,
}

View File

@@ -2,9 +2,17 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.Messaging;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.DependencyInjection;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Core.Database;
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Primitive;
using System.Collections.ObjectModel;
using BindingCultivateEntry = Snap.Hutao.Model.Binding.Cultivation.CultivateEntry;
using BindingCultivateItem = Snap.Hutao.Model.Binding.Cultivation.CultivateItem;
using BindingInventoryItem = Snap.Hutao.Model.Binding.Inventory.InventoryItem;
namespace Snap.Hutao.Service.Cultivation;
@@ -14,24 +22,239 @@ namespace Snap.Hutao.Service.Cultivation;
[Injection(InjectAs.Singleton, typeof(ICultivationService))]
internal class CultivationService : ICultivationService
{
private readonly DbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
private readonly IServiceScopeFactory scopeFactory;
private readonly ScopedDbCurrent<CultivateProject, Message.CultivateProjectChangedMessage> dbCurrent;
private ObservableCollection<CultivateProject>? projects;
/// <summary>
/// 构造一个新的养成计算服务
/// </summary>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="scopeFactory">范围工厂</param>
/// <param name="messenger">消息器</param>
public CultivationService(AppDbContext appDbContext, IMessenger messenger)
public CultivationService(IServiceScopeFactory scopeFactory, IMessenger messenger)
{
dbCurrent = new(appDbContext.CultivateProjects, messenger);
this.scopeFactory = scopeFactory;
dbCurrent = new(scopeFactory, provider => provider.GetRequiredService<AppDbContext>().CultivateProjects, messenger);
}
/// <summary>
/// 当前养成计划
/// </summary>
/// <inheritdoc/>
public CultivateProject? Current
{
get => dbCurrent.Current;
set => dbCurrent.Current = value;
}
/// <inheritdoc/>
public ObservableCollection<CultivateProject> GetProjectCollection()
{
if (projects == null)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
projects = new(appDbContext.CultivateProjects.AsNoTracking().ToList());
}
Current ??= projects.SingleOrDefault(proj => proj.IsSelected);
}
return projects;
}
/// <inheritdoc/>
public async Task<ProjectAddResult> TryAddProjectAsync(CultivateProject project)
{
if (string.IsNullOrWhiteSpace(project.Name))
{
return ProjectAddResult.InvalidName;
}
if (projects!.SingleOrDefault(a => a.Name == project.Name) != null)
{
return ProjectAddResult.AlreadyExists;
}
else
{
// Sync cache
await ThreadHelper.SwitchToMainThreadAsync();
projects!.Add(project);
// Sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
await scope.ServiceProvider.GetRequiredService<AppDbContext>().CultivateProjects.AddAndSaveAsync(project).ConfigureAwait(false);
}
return ProjectAddResult.Added;
}
}
/// <inheritdoc/>
public async Task RemoveProjectAsync(CultivateProject project)
{
// Sync cache
// Keep this on main thread.
await ThreadHelper.SwitchToMainThreadAsync();
projects!.Remove(project);
// Sync database
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
await scope.ServiceProvider.GetRequiredService<AppDbContext>().CultivateProjects.RemoveAndSaveAsync(project).ConfigureAwait(false);
}
}
/// <inheritdoc/>
public List<BindingInventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Model.Metadata.Material> metadata)
{
Guid projectId = cultivateProject.InnerId;
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<InventoryItem> entities = appDbContext.InventoryItems
.Where(a => a.ProjectId == projectId)
.ToList();
List<BindingInventoryItem> results = new();
foreach (Model.Metadata.Material meta in metadata.Where(IsInventoryItem).OrderBy(m => m.Id))
{
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.Create(projectId, meta.Id);
results.Add(new(meta, entity));
}
return results;
}
}
/// <inheritdoc/>
public async Task<ObservableCollection<BindingCultivateEntry>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Model.Metadata.Material> metadata,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
{
// TODO: cache the collection
await ThreadHelper.SwitchToBackgroundAsync();
Guid projectId = cultivateProject.InnerId;
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
List<BindingCultivateEntry> bindingEntries = new();
foreach (Model.Entity.CultivateEntry? entry in await appDbContext.CultivateEntries.ToListAsync().ConfigureAwait(false))
{
Guid entryId = entry.InnerId;
List<BindingCultivateItem> items = new();
foreach (Model.Entity.CultivateItem? item in await appDbContext.CultivateItems.Where(i => i.EntryId == entryId).OrderBy(i => i.ItemId).ToListAsync().ConfigureAwait(false))
{
items.Add(new(metadata.Single(m => m.Id == item.ItemId), item));
}
Model.Binding.Gacha.Abstraction.ItemBase itemBase = entry.Type switch
{
CultivateType.AvatarAndSkill => idAvatarMap[entry.Id].ToItemBase(),
CultivateType.Weapon => idWeaponMap[entry.Id].ToItemBase(),
_ => null!, // TODO: support furniture calc
};
bindingEntries.Add(new()
{
Id = entry.Id,
EntryId = entryId,
Name = itemBase.Name,
Icon = itemBase.Icon,
Badge = itemBase.Badge,
Quality = itemBase.Quality,
Items = items,
});
}
return new(bindingEntries);
}
}
/// <inheritdoc/>
public async Task RemoveCultivateEntryAsync(Guid entryId)
{
await ThreadHelper.SwitchToBackgroundAsync();
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await appDbContext.CultivateEntries.Where(i => i.InnerId == entryId).ExecuteDeleteAsync().ConfigureAwait(false);
}
}
/// <inheritdoc/>
public void SaveInventoryItem(BindingInventoryItem item)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
scope.ServiceProvider.GetRequiredService<AppDbContext>().InventoryItems.UpdateAndSave(item.Entity);
}
}
/// <inheritdoc/>
public async Task<bool> SaveConsumptionAsync(CultivateType type, int itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
{
using (IServiceScope scope = scopeFactory.CreateScope())
{
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
Current ??= appDbContext.CultivateProjects.AsNoTracking().SingleOrDefault(proj => proj.IsSelected);
if (Current == null)
{
return false;
}
Guid projectId = Current!.InnerId;
Model.Entity.CultivateEntry? entry = await appDbContext.CultivateEntries
.SingleOrDefaultAsync(e => e.ProjectId == projectId && e.Id == itemId)
.ConfigureAwait(false);
if (entry == null)
{
entry = Model.Entity.CultivateEntry.Create(projectId, type, itemId);
await appDbContext.CultivateEntries.AddAndSaveAsync(entry).ConfigureAwait(false);
}
Guid entryId = entry.InnerId;
await appDbContext.CultivateItems.Where(i => i.EntryId == entryId).ExecuteDeleteAsync().ConfigureAwait(false);
IEnumerable<Model.Entity.CultivateItem> toAdd = items.Select(i => Model.Entity.CultivateItem.Create(entryId, i.Id, i.Num));
await appDbContext.CultivateItems.AddRangeAndSaveAsync(toAdd).ConfigureAwait(false);
}
return true;
}
private bool IsInventoryItem(Model.Metadata.Material material)
{
// 原质
if (material.Id == 112001)
{
return false;
}
// 摩拉
if (material.Id == 202)
{
return true;
}
if (material.TypeDescription.EndsWith("区域特产"))
{
return true;
}
return material.TypeDescription switch
{
"角色经验素材" => true,
"角色培养素材" => true,
"天赋培养素材" => true,
"武器强化素材" => true,
_ => false,
};
}
}

View File

@@ -1,6 +1,13 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
using System.Collections.ObjectModel;
namespace Snap.Hutao.Service.Cultivation;
/// <summary>
@@ -8,4 +15,68 @@ namespace Snap.Hutao.Service.Cultivation;
/// </summary>
internal interface ICultivationService
{
/// <summary>
/// 当前养成计划
/// </summary>
CultivateProject? Current { get; set; }
/// <summary>
/// 获取绑定用的养成列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">材料</param>
/// <param name="idAvatarMap">Id角色映射</param>
/// <param name="idWeaponMap">Id武器映射</param>
/// <returns>绑定用的养成列表</returns>
Task<ObservableCollection<Model.Binding.Cultivation.CultivateEntry>> GetCultivateEntriesAsync(CultivateProject cultivateProject, List<Material> metadata, Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap, Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
/// <summary>
/// 获取物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">元数据</param>
/// <returns>物品列表</returns>
List<Model.Binding.Inventory.InventoryItem> GetInventoryItems(CultivateProject cultivateProject, List<Material> metadata);
/// <summary>
/// 获取用于绑定的项目集合
/// </summary>
/// <returns>项目集合</returns>
ObservableCollection<CultivateProject> GetProjectCollection();
/// <summary>
/// 删除养成清单
/// </summary>
/// <param name="entryId">入口Id</param>
/// <returns>任务</returns>
Task RemoveCultivateEntryAsync(Guid entryId);
/// <summary>
/// 异步移除项目
/// </summary>
/// <param name="project">项目</param>
/// <returns>任务</returns>
Task RemoveProjectAsync(CultivateProject project);
/// <summary>
/// 异步保存养成物品
/// </summary>
/// <param name="type">类型</param>
/// <param name="itemId">主Id</param>
/// <param name="items">待存物品</param>
/// <returns>是否保存成功</returns>
Task<bool> SaveConsumptionAsync(CultivateType type, int itemId, List<Item> items);
/// <summary>
/// 保存单个物品
/// </summary>
/// <param name="item">物品</param>
void SaveInventoryItem(Model.Binding.Inventory.InventoryItem item);
/// <summary>
/// 异步尝试添加新的项目
/// </summary>
/// <param name="project">项目</param>
/// <returns>添加操作的结果</returns>
Task<ProjectAddResult> TryAddProjectAsync(CultivateProject project);
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Cultivation;
/// <summary>
/// 项目添加结果
/// </summary>
public enum ProjectAddResult
{
/// <summary>
/// 添加成功
/// </summary>
Added,
/// <summary>
/// 名称无效
/// </summary>
InvalidName,
/// <summary>
/// 已经存在该存档
/// </summary>
AlreadyExists,
}

View File

@@ -140,4 +140,11 @@ internal interface IMetadataService
/// <param name="token">取消令牌</param>
/// <returns>圣遗物套装列表</returns>
ValueTask<List<ReliquarySet>> GetReliquarySetsAsync(CancellationToken token = default);
/// <summary>
/// 异步获取材料列表
/// </summary>
/// <param name="token">取消令牌</param>
/// <returns>材料列表</returns>
ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default(CancellationToken));
}

View File

@@ -38,6 +38,12 @@ internal partial class MetadataService
return FromCacheOrFileAsync<List<GachaEvent>>("GachaEvent", token);
}
/// <inheritdoc/>
public ValueTask<List<Material>> GetMaterialsAsync(CancellationToken token = default)
{
return FromCacheOrFileAsync<List<Material>>("Material", token);
}
/// <inheritdoc/>
public ValueTask<List<Reliquary>> GetReliquariesAsync(CancellationToken token = default)
{

View File

@@ -69,6 +69,8 @@
<None Remove="View\Dialog\AchievementImportDialog.xaml" />
<None Remove="View\Dialog\AdoptCalculatorDialog.xaml" />
<None Remove="View\Dialog\AvatarInfoQueryDialog.xaml" />
<None Remove="View\Dialog\CultivateProjectDialog.xaml" />
<None Remove="View\Dialog\CultivatePromotionDeltaDialog.xaml" />
<None Remove="View\Dialog\DailyNoteNotificationDialog.xaml" />
<None Remove="View\Dialog\DailyNoteVerificationDialog.xaml" />
<None Remove="View\Dialog\GachaLogImportDialog.xaml" />
@@ -134,12 +136,12 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview1" />
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.1.0-preview2" />
<PackageReference Include="CommunityToolkit.WinUI.Notifications" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Behaviors" Version="7.1.2" />
<PackageReference Include="CommunityToolkit.WinUI.UI.Controls" Version="7.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.0" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.0">
<PackageReference Include="Microsoft.EntityFrameworkCore.Sqlite" Version="7.0.1" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Tools" Version="7.0.1">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
@@ -151,12 +153,12 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.VisualStudio.Validation" Version="17.0.64" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.138-beta">
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.164-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25247-preview" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221116.1" />
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.25267-preview" />
<PackageReference Include="Microsoft.WindowsAppSDK" Version="1.2.221209.1" />
<PackageReference Include="StyleCop.Analyzers.Unstable" Version="1.2.0.435">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
@@ -179,6 +181,16 @@
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\CultivatePromotionDeltaDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Dialog\CultivateProjectDialog.xaml">
<Generator>MSBuild:Compile</Generator>
</Page>
</ItemGroup>
<ItemGroup>
<Page Update="View\Page\CultivationPage.xaml">
<Generator>MSBuild:Compile</Generator>

View File

@@ -16,6 +16,9 @@ public sealed partial class DescParamComboBox : UserControl
private static readonly DependencyProperty SourceProperty = Property<DescParamComboBox>
.Depend<IList<LevelParam<string, ParameterInfo>>>(nameof(Source), default!, OnSourceChanged);
private static readonly DependencyProperty PreferredSelectedIndexProperty = Property<DescParamComboBox>
.Depend<int>(nameof(PreferredSelectedIndex), 0);
/// <summary>
/// 构造一个新的描述参数组合框
/// </summary>
@@ -33,6 +36,15 @@ public sealed partial class DescParamComboBox : UserControl
set => SetValue(SourceProperty, value);
}
/// <summary>
/// 期望的选中索引
/// </summary>
public int PreferredSelectedIndex
{
get { return (int)GetValue(PreferredSelectedIndexProperty); }
set { SetValue(PreferredSelectedIndexProperty, value); }
}
private static void OnSourceChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args)
{
// Some of the {x:Bind} feature is not working properly,
@@ -42,7 +54,7 @@ public sealed partial class DescParamComboBox : UserControl
if (args.NewValue != args.OldValue && args.NewValue is IList<LevelParam<string, ParameterInfo>> list)
{
descParamComboBox.ItemHost.ItemsSource = list;
descParamComboBox.ItemHost.SelectedIndex = 0;
descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count);
}
}
}

View File

@@ -0,0 +1,22 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.WinUI.UI.Converters;
using Microsoft.UI.Xaml;
namespace Snap.Hutao.View.Converter;
/// <summary>
/// This class converts a object? value into a Visibility enumeration.
/// </summary>
public class EmptyObjectToVisibilityConverter : EmptyObjectToObjectConverter
{
/// <summary>
/// Initializes a new instance of the <see cref="EmptyObjectToVisibilityConverter"/> class.
/// </summary>
public EmptyObjectToVisibilityConverter()
{
EmptyValue = Visibility.Collapsed;
NotEmptyValue = Visibility.Visible;
}
}

View File

@@ -0,0 +1,24 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.CultivateProjectDialog"
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"
Title="创建新的养成计划"
CloseButtonText="取消"
DefaultButton="Primary"
PrimaryButtonText="确认"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<StackPanel>
<TextBox
x:Name="InputText"
VerticalAlignment="Top"
PlaceholderText="在此处输入计划名称"/>
<CheckBox
x:Name="AttachUidBox"
Margin="0,8,0,0"
Content="绑定当前选中的账号角色"/>
</StackPanel>
</ContentDialog>

View File

@@ -0,0 +1,59 @@
// Copyright (c) Microsoft Corporation and Contributors.
// Licensed under the MIT License.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Controls.Primitives;
using Microsoft.UI.Xaml.Data;
using Microsoft.UI.Xaml.Input;
using Microsoft.UI.Xaml.Media;
using Microsoft.UI.Xaml.Navigation;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.User;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices.WindowsRuntime;
using Windows.Foundation;
using Windows.Foundation.Collections;
namespace Snap.Hutao.View.Dialog;
/// <summary>
/// <20><><EFBFBD>ɼƻ<C9BC><C6BB>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
public sealed partial class CultivateProjectDialog : ContentDialog
{
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5><EFBFBD><EFBFBD>ɼƻ<C9BC><C6BB>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
public CultivateProjectDialog(Window window)
{
InitializeComponent();
XamlRoot = window.Content.XamlRoot;
}
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µģ<C2B5><C4A3>û<EFBFBD>ָ<EFBFBD><D6B8><EFBFBD>ļƻ<C4BC>
/// </summary>
/// <returns><3E>ƻ<EFBFBD></returns>
public async ValueTask<ValueResult<bool, CultivateProject>> CreateProjectAsync()
{
await ThreadHelper.SwitchToMainThreadAsync();
ContentDialogResult result = await ShowAsync();
if (result == ContentDialogResult.Primary)
{
string text = InputText.Text;
string? uid = AttachUidBox.IsChecked == true
? Ioc.Default.GetRequiredService<IUserService>().Current?.SelectedUserGameRole?.GameUid
: null;
CultivateProject project = CultivateProject.Create(text, uid);
return new(true, project);
}
return new(false, null!);
}
}

View File

@@ -0,0 +1,172 @@
<ContentDialog
x:Class="Snap.Hutao.View.Dialog.CultivatePromotionDeltaDialog"
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"
xmlns:shci="using:Snap.Hutao.Control.Image"
xmlns:shvc="using:Snap.Hutao.View.Control"
xmlns:shvd="using:Snap.Hutao.View.Dialog"
Title="添加到当前养成计划"
d:DataContext="{d:DesignInstance shvd:CultivatePromotionDeltaDialog}"
CloseButtonText="取消"
DefaultButton="Primary"
PrimaryButtonText="确认"
Style="{StaticResource DefaultContentDialogStyle}"
mc:Ignorable="d">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
</Grid.RowDefinitions>
<StackPanel
Grid.Row="0"
Margin="0,8,0,0"
Visibility="{Binding Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Border Style="{StaticResource BorderCardStyle}">
<Grid Margin="8" DataContext="{Binding Avatar}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="36"
Height="36"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"/>
<NumberBox
Grid.Column="2"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelCurrent, Mode=TwoWay}"/>
<FontIcon
Grid.Column="3"
Margin="8"
FontSize="12"
Glyph="&#xEBE7;"/>
<NumberBox
Grid.Column="4"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelTarget, Mode=TwoWay}"/>
</Grid>
</Border>
<ItemsControl ItemsSource="{Binding Avatar.Skills}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Border Margin="0,2,0,0" Style="{StaticResource BorderCardStyle}">
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shci:MonoChrome
Grid.Column="0"
Width="36"
Height="36"
Source="{Binding Icon}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"/>
<NumberBox
Grid.Column="2"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelCurrent, Mode=TwoWay}"/>
<FontIcon
Grid.Column="3"
Margin="8"
FontSize="12"
Glyph="&#xEBE7;"/>
<NumberBox
Grid.Column="4"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelTarget, Mode=TwoWay}"/>
</Grid>
</Border>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
<StackPanel
Grid.Row="1"
Margin="0,8,0,0"
Visibility="{Binding Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
<Border Style="{StaticResource BorderCardStyle}">
<Grid Margin="8" DataContext="{Binding Weapon}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="160"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="36"
Height="36"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
Grid.Column="1"
Margin="16,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"
TextTrimming="CharacterEllipsis"/>
<NumberBox
Grid.Column="2"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelCurrent, Mode=TwoWay}"/>
<FontIcon
Grid.Column="3"
Margin="8"
FontSize="12"
Glyph="&#xEBE7;"/>
<NumberBox
Grid.Column="4"
MinWidth="100"
VerticalAlignment="Center"
Maximum="{Binding LevelMax}"
Minimum="{Binding LevelMin}"
SpinButtonPlacementMode="Compact"
Value="{Binding LevelTarget, Mode=TwoWay}"/>
</Grid>
</Border>
</StackPanel>
</Grid>
</ContentDialog>

View File

@@ -0,0 +1,88 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control;
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
namespace Snap.Hutao.View.Dialog;
/// <summary>
/// <20><><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
public sealed partial class CultivatePromotionDeltaDialog : ContentDialog
{
private static readonly DependencyProperty AvatarProperty = Property<CultivatePromotionDeltaDialog>.Depend<ICalculableAvatar?>(nameof(Avatar));
private static readonly DependencyProperty WeaponProperty = Property<CultivatePromotionDeltaDialog>.Depend<ICalculableAvatar?>(nameof(Weapon));
/// <summary>
/// <20><><EFBFBD><EFBFBD>һ<EFBFBD><D2BB><EFBFBD>µ<EFBFBD><C2B5><EFBFBD><EFBFBD>ɼ<EFBFBD><C9BC><EFBFBD><EFBFBD>Ի<EFBFBD><D4BB><EFBFBD>
/// </summary>
/// <param name="window"><3E><><EFBFBD><EFBFBD></param>
/// <param name="avatar"><3E><>ɫ</param>
/// <param name="weapon"><3E><><EFBFBD><EFBFBD></param>
public CultivatePromotionDeltaDialog(Window window, ICalculableAvatar? avatar, ICalculableWeapon? weapon)
{
InitializeComponent();
XamlRoot = window.Content.XamlRoot;
DataContext = this;
Avatar = avatar;
Weapon = weapon;
}
/// <summary>
/// <20><>ɫ
/// </summary>
public ICalculableAvatar? Avatar
{
get { return (ICalculableAvatar?)GetValue(AvatarProperty); }
set { SetValue(AvatarProperty, value); }
}
/// <summary>
/// <20><><EFBFBD><EFBFBD>
/// </summary>
public ICalculableWeapon? Weapon
{
get { return (ICalculableWeapon?)GetValue(WeaponProperty); }
set { SetValue(WeaponProperty, value); }
}
/// <summary>
/// <20><EFBFBD><ECB2BD>ȡ<EFBFBD><C8A1><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD>
/// </summary>
/// <returns><3E><><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD><EFBFBD></returns>
public async Task<ValueResult<bool, AvatarPromotionDelta>> GetPromotionDeltaAsync()
{
ContentDialogResult result = await ShowAsync();
if (result == ContentDialogResult.Primary)
{
AvatarPromotionDelta delta = new()
{
AvatarId = Avatar?.AvatarId ?? 0,
AvatarLevelCurrent = Avatar?.LevelCurrent ?? 0,
AvatarLevelTarget = Avatar?.LevelTarget ?? 0,
SkillList = Avatar?.Skills.Select(s => new PromotionDelta()
{
Id = s.GruopId,
LevelCurrent = s.LevelCurrent,
LevelTarget = s.LevelTarget,
}),
Weapon = Weapon == null ? null : new PromotionDelta()
{
Id = Weapon.WeaponId,
LevelCurrent = Weapon.LevelCurrent,
LevelTarget = Weapon.LevelTarget,
},
};
return new(true, delta);
}
else
{
return new(false, null!);
}
}
}

View File

@@ -16,6 +16,6 @@
x:Name="InputText"
Margin="0,0,0,0"
VerticalAlignment="Top"
PlaceholderText="在此处输入"/>
PlaceholderText="在此处输入名称"/>
</Grid>
</ContentDialog>

View File

@@ -24,10 +24,7 @@
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid
Background="{StaticResource CardBackgroundFillColorDefaultBrush}"
BorderBrush="{StaticResource CardStrokeColorDefaultBrush}"
BorderThickness="0,0,0,1">
<Grid Background="{StaticResource CardBackgroundFillColorDefaultBrush}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="252"/>
<ColumnDefinition/>
@@ -61,7 +58,11 @@
Margin="2,6,3,6"
DisplayMemberPath="Name"
ItemsSource="{Binding Archives, Mode=OneWay}"
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</AppBarElementContainer>
<AppBarButton
Command="{Binding AddArchiveCommand}"

View File

@@ -1,12 +1,229 @@
<shc:ScopedPage
<shc:ScopedPage
x:Class="Snap.Hutao.View.Page.CultivationPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:cwuc="using:CommunityToolkit.WinUI.UI.Controls"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:mxi="using:Microsoft.Xaml.Interactivity"
xmlns:mxic="using:Microsoft.Xaml.Interactions.Core"
xmlns:mxim="using:Microsoft.Xaml.Interactions.Media"
xmlns:shc="using:Snap.Hutao.Control"
xmlns:shcb="using:Snap.Hutao.Control.Behavior"
xmlns:shcm="using:Snap.Hutao.Control.Markup"
xmlns:shv="using:Snap.Hutao.ViewModel"
xmlns:shvc="using:Snap.Hutao.View.Control"
d:DataContext="{d:DesignInstance shv:CultivationViewModel}"
Background="{ThemeResource ApplicationPageBackgroundThemeBrush}"
mc:Ignorable="d">
<Grid/>
<Page.Resources>
<shc:BindingProxy x:Key="BindingProxy" DataContext="{Binding}"/>
</Page.Resources>
<mxi:Interaction.Behaviors>
<shcb:InvokeCommandOnLoadedBehavior Command="{Binding OpenUICommand}"/>
</mxi:Interaction.Behaviors>
<Grid>
<Rectangle
Height="48"
VerticalAlignment="Top"
Fill="{StaticResource CardBackgroundFillColorDefaultBrush}"
IsHitTestVisible="False"/>
<Pivot>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">
<AppBarElementContainer>
<ComboBox
Height="36"
MinWidth="160"
Margin="6,6,6,6"
DisplayMemberPath="Name"
ItemsSource="{Binding Projects}"
SelectedItem="{Binding SelectedProject, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</AppBarElementContainer>
<AppBarButton
Command="{Binding AddProjectCommand}"
Icon="{shcm:FontIcon Glyph=&#xE710;}"
Label="新建计划"/>
<AppBarButton
Command="{Binding RemoveProjectCommand}"
CommandParameter="{Binding SelectedProject, Mode=OneWay}"
Icon="{shcm:FontIcon Glyph=&#xE74D;}"
Label="删除当前计划"/>
</CommandBar>
</Pivot.RightHeader>
<PivotItem Header="材料清单">
<cwuc:AdaptiveGridView
Padding="16,16,4,4"
DesiredWidth="360"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding CultivateEntries}"
SelectionMode="None">
<cwuc:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Border Style="{StaticResource BorderCardStyle}">
<Grid Background="Transparent">
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition/>
</Grid.RowDefinitions>
<Grid Margin="8">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition/>
<ColumnDefinition Width="auto"/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
Grid.Column="0"
Width="48"
Height="48"
Icon="{Binding Icon}"
Quality="{Binding Quality}"/>
<TextBlock
Grid.Column="1"
Margin="8,0,0,0"
VerticalAlignment="Center"
Text="{Binding Name}"/>
<StackPanel
x:Name="ButtonPanel"
Grid.Column="2"
Orientation="Horizontal"
Visibility="Collapsed">
<Button
Width="48"
Height="48"
Margin="8,0,0,0"
Content="&#xE70F;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="编辑清单"/>
<Button
Width="48"
Height="48"
Margin="8,0,0,0"
Command="{Binding Path=DataContext.RemoveEntryCommand, Source={StaticResource BindingProxy}}"
CommandParameter="{Binding}"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
ToolTipService.ToolTip="删除清单"/>
</StackPanel>
</Grid>
<ItemsControl
Grid.Row="1"
Margin="8,0,8,8"
ItemsSource="{Binding Items}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,4,0,0">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="auto"/>
<ColumnDefinition Width="120"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<shvc:ItemIcon
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"
HorizontalAlignment="Right"
VerticalAlignment="Center"
Text="{Binding Entity.Count}"/>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid.Resources>
<Storyboard x:Name="ButtonPanelVisibleStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Visible</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
<Storyboard x:Name="ButtonPanelCollapsedStoryboard">
<ObjectAnimationUsingKeyFrames Storyboard.TargetName="ButtonPanel" Storyboard.TargetProperty="Visibility">
<DiscreteObjectKeyFrame KeyTime="0">
<DiscreteObjectKeyFrame.Value>
<Visibility>Collapsed</Visibility>
</DiscreteObjectKeyFrame.Value>
</DiscreteObjectKeyFrame>
</ObjectAnimationUsingKeyFrames>
</Storyboard>
</Grid.Resources>
<mxi:Interaction.Behaviors>
<mxic:EventTriggerBehavior EventName="PointerEntered">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelVisibleStoryboard}"/>
</mxic:EventTriggerBehavior>
<mxic:EventTriggerBehavior EventName="PointerExited">
<mxim:ControlStoryboardAction Storyboard="{StaticResource ButtonPanelCollapsedStoryboard}"/>
</mxic:EventTriggerBehavior>
</mxi:Interaction.Behaviors>
</Grid>
</Border>
</DataTemplate>
</cwuc:AdaptiveGridView.ItemTemplate>
</cwuc:AdaptiveGridView>
</PivotItem>
<PivotItem Header="背包物品">
<cwuc:AdaptiveGridView
Padding="16,16,4,4"
ItemContainerStyle="{StaticResource LargeGridViewItemStyle}"
ItemsSource="{Binding InventoryItems}"
SelectionMode="None">
<cwuc:AdaptiveGridView.ItemTemplate>
<DataTemplate>
<Button
Padding="0"
Background="Transparent"
BorderBrush="{x:Null}"
BorderThickness="0"
CornerRadius="{StaticResource CompatCornerRadius}">
<shvc:BottomTextControl Text="{Binding Count, Mode=OneWay}">
<shvc:ItemIcon Icon="{Binding Inner.Icon, Converter={StaticResource ItemIconConverter}}" Quality="{Binding Inner.RankLevel}"/>
</shvc:BottomTextControl>
<Button.Flyout>
<Flyout>
<Grid>
<Grid.RowDefinitions>
<RowDefinition/>
<RowDefinition/>
<RowDefinition/>
</Grid.RowDefinitions>
<TextBlock Style="{StaticResource BaseTextBlockStyle}" Text="{Binding Inner.Name}"/>
<NumberBox
Grid.Row="1"
MinWidth="160"
Margin="0,16,0,0"
Maximum="4294967295"
Minimum="0"
Value="{Binding Count, Mode=TwoWay}"/>
</Grid>
</Flyout>
</Button.Flyout>
</Button>
</DataTemplate>
</cwuc:AdaptiveGridView.ItemTemplate>
</cwuc:AdaptiveGridView>
</PivotItem>
</Pivot>
</Grid>
</shc:ScopedPage>

View File

@@ -23,8 +23,9 @@
<Rectangle
Height="48"
VerticalAlignment="Top"
Fill="{StaticResource CardBackgroundFillColorDefaultBrush}"/>
<Pivot Grid.RowSpan="2">
Fill="{StaticResource CardBackgroundFillColorDefaultBrush}"
IsHitTestVisible="False"/>
<Pivot>
<Pivot.LeftHeader>
<ComboBox
Height="36"
@@ -32,7 +33,11 @@
Margin="16,6,0,6"
DisplayMemberPath="Uid"
ItemsSource="{Binding Archives}"
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"/>
SelectedItem="{Binding SelectedArchive, Mode=TwoWay}">
<mxi:Interaction.Behaviors>
<shcb:ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior/>
</mxi:Interaction.Behaviors>
</ComboBox>
</Pivot.LeftHeader>
<Pivot.RightHeader>
<CommandBar DefaultLabelPosition="Right">

View File

@@ -33,6 +33,7 @@
<shvc:DescParamComboBox
Grid.Column="0"
HorizontalAlignment="Stretch"
PreferredSelectedIndex="9"
Source="{Binding Proud, Mode=OneWay, Converter={StaticResource DescParamDescriptor}}"/>
</StackPanel>
@@ -40,7 +41,10 @@
</DataTemplate>
<DataTemplate x:Key="PropertyDataTemplate">
<shvc:DescParamComboBox HorizontalAlignment="Stretch" Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
<shvc:DescParamComboBox
HorizontalAlignment="Stretch"
PreferredSelectedIndex="13"
Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
</DataTemplate>
<DataTemplate x:Key="TalentDataTemplate">
@@ -68,7 +72,11 @@
<CommandBar.Content>
<shcp:PanelSelector x:Name="ItemsPanelSelector" Margin="6,8,0,0"/>
</CommandBar.Content>
<AppBarButton Icon="{shcm:FontIcon Glyph=&#xE8EF;}" Label="养成计算"/>
<AppBarButton
Command="{Binding CultivateCommand}"
CommandParameter="{Binding Selected}"
Icon="{shcm:FontIcon Glyph=&#xE8EF;}"
Label="养成计算"/>
</CommandBar>
<SplitView
Grid.Row="1"

View File

@@ -23,7 +23,10 @@
<Page.Resources>
<DataTemplate x:Key="PropertyDataTemplate">
<shvc:DescParamComboBox HorizontalAlignment="Stretch" Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
<shvc:DescParamComboBox
HorizontalAlignment="Stretch"
PreferredSelectedIndex="13"
Source="{Binding Converter={StaticResource PropertyDescriptor}}"/>
</DataTemplate>
</Page.Resources>

View File

@@ -25,7 +25,11 @@ public sealed partial class TitleView : UserControl
[SuppressMessage("", "CA1822")]
public string Title
{
#if DEBUG
get => $"胡桃 Dev Build";
#else
get => $"胡桃 {Core.CoreEnvironment.Version}";
#endif
}
/// <summary>

View File

@@ -2,7 +2,17 @@
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using CommunityToolkit.Mvvm.Messaging;
using Snap.Hutao.Control;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Message;
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.View.Dialog;
using System.Collections.ObjectModel;
namespace Snap.Hutao.ViewModel;
@@ -10,8 +20,193 @@ namespace Snap.Hutao.ViewModel;
/// 养成视图模型
/// </summary>
[Injection(InjectAs.Scoped)]
internal class CultivationViewModel : ObservableObject, ISupportCancellation
internal class CultivationViewModel : ObservableObject, ISupportCancellation, IRecipient<CultivateProjectChangedMessage>
{
private readonly ICultivationService cultivationService;
private readonly IInfoBarService infoBarService;
private readonly IMetadataService metadataService;
private readonly ILogger<CultivationViewModel> logger;
private ObservableCollection<CultivateProject>? projects;
private CultivateProject? selectedProject;
private List<Model.Binding.Inventory.InventoryItem>? inventoryItems;
private ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? cultivateEntries;
/// <summary>
/// 构造一个新的养成视图模型
/// </summary>
/// <param name="cultivationService">养成服务</param>
/// <param name="infoBarService">信息服务</param>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
/// <param name="metadataService">元数据服务</param>
/// <param name="logger">日志器</param>
/// <param name="messenger">消息器</param>
public CultivationViewModel(
ICultivationService cultivationService,
IInfoBarService infoBarService,
IAsyncRelayCommandFactory asyncRelayCommandFactory,
IMetadataService metadataService,
ILogger<CultivationViewModel> logger,
IMessenger messenger)
{
this.cultivationService = cultivationService;
this.infoBarService = infoBarService;
this.metadataService = metadataService;
this.logger = logger;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
AddProjectCommand = asyncRelayCommandFactory.Create(AddProjectAsync);
RemoveProjectCommand = asyncRelayCommandFactory.Create<CultivateProject>(RemoveProjectAsync);
RemoveEntryCommand = asyncRelayCommandFactory.Create<Model.Binding.Cultivation.CultivateEntry>(RemoveEntryAsync);
SaveInventoryItemCommand = new RelayCommand<Model.Binding.Inventory.InventoryItem>(SaveInventoryItem);
messenger.Register(this);
}
/// <inheritdoc/>
public CancellationToken CancellationToken { get; set; }
/// <summary>
/// 项目
/// </summary>
public ObservableCollection<CultivateProject>? Projects { get => projects; set => SetProperty(ref projects, value); }
/// <summary>
/// 当前选中的计划
/// </summary>
public CultivateProject? SelectedProject
{
get => selectedProject; set
{
if (SetProperty(ref selectedProject, value))
{
cultivationService.Current = value;
}
}
}
/// <summary>
/// 物品列表
/// </summary>
public List<Model.Binding.Inventory.InventoryItem>? InventoryItems { get => inventoryItems; set => SetProperty(ref inventoryItems, value); }
/// <summary>
/// 养成列表
/// </summary>
public ObservableCollection<Model.Binding.Cultivation.CultivateEntry>? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); }
/// <summary>
/// 打开界面命令
/// </summary>
public ICommand OpenUICommand { get; }
/// <summary>
/// 添加项目命令
/// </summary>
public ICommand AddProjectCommand { get; }
/// <summary>
/// 删除项目命令
/// </summary>
public ICommand RemoveProjectCommand { get; }
/// <summary>
/// 移除
/// </summary>
public ICommand RemoveEntryCommand { get; }
/// <summary>
/// 保存物品命令
/// </summary>
public ICommand SaveInventoryItemCommand { get; }
/// <inheritdoc/>
public void Receive(CultivateProjectChangedMessage message)
{
UpdateCultivateEntriesAndInventoryItemsAsync(message.NewValue).SafeForget(logger);
}
private async Task OpenUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(true))
{
Projects = cultivationService.GetProjectCollection();
SelectedProject = cultivationService.Current;
await UpdateCultivateEntriesAndInventoryItemsAsync(SelectedProject).ConfigureAwait(false);
}
}
private async Task AddProjectAsync()
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
(bool isOk, CultivateProject project) = await new CultivateProjectDialog(mainWindow).CreateProjectAsync().ConfigureAwait(false);
if (isOk)
{
ProjectAddResult result = await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false);
switch (result)
{
case ProjectAddResult.Added:
infoBarService.Success($"添加成功");
break;
case ProjectAddResult.InvalidName:
infoBarService.Information($"不能添加名称无效的计划");
break;
case ProjectAddResult.AlreadyExists:
infoBarService.Information($"不能添加名称重复的计划");
break;
default:
throw Must.NeverHappen();
}
}
}
private async Task RemoveProjectAsync(CultivateProject? project)
{
if (project != null)
{
await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
SelectedProject = Projects!.FirstOrDefault();
}
}
private async Task UpdateCultivateEntriesAndInventoryItemsAsync(CultivateProject? project)
{
if (project != null)
{
List<Model.Metadata.Material> materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false);
Dictionary<Model.Primitive.AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
Dictionary<Model.Primitive.WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
ObservableCollection<Model.Binding.Cultivation.CultivateEntry> entries = await cultivationService
.GetCultivateEntriesAsync(project, materials, idAvatarMap, idWeaponMap)
.ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
CultivateEntries = entries;
InventoryItems = cultivationService.GetInventoryItems(project, materials);
}
}
private Task RemoveEntryAsync(Model.Binding.Cultivation.CultivateEntry? entry)
{
if (entry != null)
{
CultivateEntries!.Remove(entry);
return cultivationService.RemoveCultivateEntryAsync(entry.EntryId);
}
return Task.CompletedTask;
}
private void SaveInventoryItem(Model.Binding.Inventory.InventoryItem? inventoryItem)
{
if (inventoryItem != null)
{
cultivationService.SaveInventoryItem(inventoryItem);
}
}
}

View File

@@ -4,6 +4,7 @@
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Caching.Memory;
using Snap.Hutao.Context.Database;
using Snap.Hutao.Control;
using Snap.Hutao.Core.Database;
@@ -27,11 +28,17 @@ namespace Snap.Hutao.ViewModel;
[Injection(InjectAs.Scoped)]
internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
{
/// <summary>
/// 启动游戏目标 Uid
/// </summary>
public const string DesiredUid = nameof(DesiredUid);
private static readonly string TrueString = true.ToString();
private static readonly string FalseString = false.ToString();
private readonly IGameService gameService;
private readonly AppDbContext appDbContext;
private readonly IMemoryCache memoryCache;
private readonly List<LaunchScheme> knownSchemes = new()
{
@@ -55,12 +62,18 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
/// 构造一个新的启动游戏视图模型
/// </summary>
/// <param name="gameService">游戏服务</param>
/// <param name="memoryCache">内存缓存</param>
/// <param name="appDbContext">数据库上下文</param>
/// <param name="asyncRelayCommandFactory">异步命令工厂</param>
public LaunchGameViewModel(IGameService gameService, AppDbContext appDbContext, IAsyncRelayCommandFactory asyncRelayCommandFactory)
public LaunchGameViewModel(
IGameService gameService,
IMemoryCache memoryCache,
AppDbContext appDbContext,
IAsyncRelayCommandFactory asyncRelayCommandFactory)
{
this.gameService = gameService;
this.appDbContext = appDbContext;
this.memoryCache = memoryCache;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
LaunchCommand = asyncRelayCommandFactory.Create(LaunchAsync);
@@ -169,13 +182,21 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation
SelectedScheme = KnownSchemes.FirstOrDefault(s => s.Channel == multi.Channel && s.SubChannel == multi.SubChannel);
GameAccounts = gameService.GetGameAccountCollection();
// Sync uid
if (memoryCache.TryGetValue(DesiredUid, out object? value) && value is string uid)
{
SelectedGameAccount = GameAccounts.SingleOrDefault(g => g.AttachUid == uid);
}
// Sync from Settings
RetiveSetting();
}
else
{
Ioc.Default.GetRequiredService<IInfoBarService>().Warning("游戏路径不正确,前往设置更改游戏路径。");
await Ioc.Default.GetRequiredService<INavigationService>().NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true).ConfigureAwait(false);
await Ioc.Default.GetRequiredService<INavigationService>()
.NavigateAsync<View.Page.SettingPage>(INavigationAwaiter.Default, true)
.ConfigureAwait(false);
}
}

View File

@@ -5,12 +5,22 @@ using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.WinUI.UI;
using Snap.Hutao.Factory.Abstraction;
using Snap.Hutao.Model;
using Snap.Hutao.Model.Binding.Cultivation;
using Snap.Hutao.Model.Binding.Hutao;
using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Abstraction;
using Snap.Hutao.Service.Cultivation;
using Snap.Hutao.Service.Hutao;
using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.User;
using Snap.Hutao.View.Dialog;
using CalcAvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta;
using CalcClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient;
using CalcConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Consumption;
using CalcItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item;
using CalcItemHelper = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.ItemHelper;
namespace Snap.Hutao.ViewModel;
@@ -44,6 +54,7 @@ internal class WikiAvatarViewModel : ObservableObject
this.metadataService = metadataService;
this.hutaoCache = hutaoCache;
OpenUICommand = asyncRelayCommandFactory.Create(OpenUIAsync);
CultivateCommand = asyncRelayCommandFactory.Create<Avatar>(CultivateAsync);
filterElementInfos = new()
{
@@ -147,6 +158,11 @@ internal class WikiAvatarViewModel : ObservableObject
/// </summary>
public ICommand OpenUICommand { get; }
/// <summary>
/// 养成命令
/// </summary>
public ICommand CultivateCommand { get; }
private async Task OpenUIAsync()
{
if (await metadataService.InitializeAsync().ConfigureAwait(false))
@@ -160,8 +176,6 @@ internal class WikiAvatarViewModel : ObservableObject
await CombineWithAvatarCollocationsAsync(sorted).ConfigureAwait(false);
await ThreadHelper.SwitchToMainThreadAsync();
// RPC_E_WRONG_THREAD ?
Avatars = new AdvancedCollectionView(sorted, true);
Selected = Avatars.Cast<Avatar>().FirstOrDefault();
}
@@ -222,4 +236,47 @@ internal class WikiAvatarViewModel : ObservableObject
}
}
}
private async Task CultivateAsync(Avatar? avatar)
{
IInfoBarService infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
if (avatar != null)
{
IUserService userService = Ioc.Default.GetRequiredService<IUserService>();
if (userService.Current != null)
{
MainWindow mainWindow = Ioc.Default.GetRequiredService<MainWindow>();
(bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, avatar.ToCalculable(), null)
.GetPromotionDeltaAsync()
.ConfigureAwait(false);
if (isOk)
{
CalcClient calculateClient = Ioc.Default.GetRequiredService<CalcClient>();
CalcConsumption? consumption = await calculateClient.ComputeAsync(userService.Current.Entity, delta).ConfigureAwait(false);
if (consumption != null)
{
List<CalcItem> items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
bool saved = await Ioc.Default
.GetRequiredService<ICultivationService>()
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items)
.ConfigureAwait(false);
if (saved)
{
infoBarService.Success("已成功添加至当前养成计划");
}
else
{
infoBarService.Warning("请先前往养成计划页面创建计划并选中");
}
}
}
}
else
{
infoBarService.Warning("必须先选择一个用户与角色");
}
}
}
}

View File

@@ -51,7 +51,9 @@ public partial class Cookie
string name = parts[0].Trim();
string value = parts.Length == 1 ? string.Empty : parts[1].Trim();
cookieMap.Add(name, value);
// System.ArgumentException: An item with the same key has already been added.
// cookieMap.Add(name, value);
cookieMap[name] = value;
}
return new(cookieMap);

View File

@@ -0,0 +1,88 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Request;
using System.Net.Http;
using System.Text;
namespace Snap.Hutao.Web.Hoyolab;
/// <summary>
/// Hoyolab HttpClient 拓展
/// </summary>
internal static class HoyolabHttpClientExtensions
{
/// <summary>
/// 设置用户的 Cookie
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="user">实体用户</param>
/// <param name="cookie">Cookie类型</param>
/// <returns>客户端</returns>
internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType cookie)
{
httpClient.DefaultRequestHeaders.Remove("Cookie");
StringBuilder stringBuilder = new();
if ((cookie & CookieType.CookieToken) == CookieType.CookieToken)
{
stringBuilder.Append(user.CookieToken).AppendIf(user.CookieToken != null, ';');
}
if ((cookie & CookieType.Ltoken) == CookieType.Ltoken)
{
stringBuilder.Append(user.Ltoken).AppendIf(user.Ltoken != null, ';');
}
if ((cookie & CookieType.Stoken) == CookieType.Stoken)
{
stringBuilder.Append(user.Stoken).AppendIf(user.Stoken != null, ';');
}
if ((cookie & CookieType.Mid) == CookieType.Mid)
{
stringBuilder.Append("mid=").Append(user.Mid).Append(';');
}
httpClient.DefaultRequestHeaders.Set("Cookie", stringBuilder.ToString());
return httpClient;
}
/// <summary>
/// 设置Referer
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="referer">用户</param>
/// <returns>客户端</returns>
internal static HttpClient SetReferer(this HttpClient httpClient, string referer)
{
httpClient.DefaultRequestHeaders.Set("Referer", referer);
return httpClient;
}
/// <summary>
/// 设置验证流水号
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="challenge">验证流水号</param>
/// <returns>客户端</returns>
internal static HttpClient SetXrpcChallenge(this HttpClient httpClient, string challenge)
{
httpClient.DefaultRequestHeaders.Set("x-rpc-challenge", challenge);
return httpClient;
}
/// <summary>
/// 设置头
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>客户端</returns>
internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value)
{
httpClient.DefaultRequestHeaders.Set(key, value);
return httpClient;
}
}

View File

@@ -39,7 +39,7 @@ internal class CalculateClient
/// <param name="delta">差异</param>
/// <param name="token">取消令牌</param>
/// <returns>消耗结果</returns>
public async Task<Consumption?> ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token)
public async Task<Consumption?> ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token = default)
{
Response<Consumption>? resp = await httpClient
.SetUser(user, CookieType.CookieToken)

View File

@@ -8,6 +8,11 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// </summary>
public enum ElementAttributeId
{
/// <summary>
/// 无
/// </summary>
None = 0,
/// <summary>
/// 火元素
/// </summary>

View File

@@ -0,0 +1,37 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Intrinsic;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 可计算源
/// </summary>
public interface ICalculable
{
/// <summary>
/// 名称
/// </summary>
string Name { get; }
/// <summary>
/// 图标
/// </summary>
Uri Icon { get; }
/// <summary>
/// 星级
/// </summary>
ItemQuality Quality { get; }
/// <summary>
/// 当前等级
/// </summary>
int LevelCurrent { get; set; }
/// <summary>
/// 目标等级
/// </summary>
int LevelTarget { get; set; }
}

View File

@@ -0,0 +1,32 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 可计算的角色
/// </summary>
public interface ICalculableAvatar : ICalculable
{
/// <summary>
/// 角色Id
/// </summary>
AvatarId AvatarId { get; }
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
/// <summary>
/// 技能组
/// </summary>
IList<ICalculableSkill> Skills { get; }
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 可计算的技能
/// </summary>
public interface ICalculableSkill : ICalculable
{
/// <summary>
/// 技能组Id
/// </summary>
int GruopId { get; }
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
}

View File

@@ -0,0 +1,18 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 可计算物品的源
/// </summary>
/// <typeparam name="T">可计算类型</typeparam>
public interface ICalculableSource<T>
where T : ICalculable
{
/// <summary>
/// 转换到可计算的对象
/// </summary>
/// <returns>可计算物品</returns>
public T ToCalculable();
}

View File

@@ -0,0 +1,27 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Primitive;
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 可计算的武器
/// </summary>
public interface ICalculableWeapon : ICalculable
{
/// <summary>
/// 武器Id
/// </summary>
WeaponId WeaponId { get; }
/// <summary>
/// 最小等级
/// </summary>
int LevelMin { get; }
/// <summary>
/// 最大等级
/// </summary>
int LevelMax { get; }
}

View File

@@ -37,6 +37,6 @@ public class Item
/// <summary>
/// 物品星级 仅有家具为有效值
/// </summary>
[JsonPropertyName("num")]
[JsonPropertyName("level")]
public ItemQuality Level { get; set; }
}

View File

@@ -0,0 +1,51 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
/// <summary>
/// 物品帮助类
/// </summary>
public static class ItemHelper
{
/// <summary>
/// 合并两个物品列表
/// </summary>
/// <param name="left">左列表</param>
/// <param name="right">右列表</param>
/// <returns>合并且排序好的列表</returns>
public static List<Item> Merge(List<Item>? left, List<Item>? right)
{
if (left == null && right == null)
{
return new(0);
}
if (right == null)
{
return left!;
}
if (left == null)
{
return right!;
}
List<Item> result = new(left.Count + right.Count);
result.AddRange(left);
foreach (Item item in right)
{
if (result.SingleOrDefault(i => i.Id == item.Id) is Item existed)
{
existed.Num += item.Num;
}
else
{
result.Add(item);
}
}
return result;
}
}

View File

@@ -3,13 +3,15 @@
using Snap.Hutao.Core.Logging;
using Snap.Hutao.Extension;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Request;
using System.IO;
using System.Net.Http;
using System.Net.Http.Json;
using System.Net.Sockets;
using System.Text;
namespace Snap.Hutao.Web.Hoyolab;
namespace Snap.Hutao.Web;
/// <summary>
/// <see cref="HttpClient"/> 扩展
@@ -34,6 +36,16 @@ internal static class HttpClientExtensions
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
catch (JsonException ex)
{
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
catch (IOException ex)
{
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
}
/// <inheritdoc cref="HttpClientJsonExtensions.PostAsJsonAsync{TValue}(HttpClient, string?, TValue, JsonSerializerOptions?, CancellationToken)"/>
@@ -55,6 +67,16 @@ internal static class HttpClientExtensions
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
catch (JsonException ex)
{
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
catch (IOException ex)
{
logger.LogWarning(EventIds.HttpException, ex, "请求异常已忽略");
return null;
}
}
/// <inheritdoc cref="HttpClientJsonExtensions.PostAsJsonAsync{TValue}(HttpClient, string?, TValue, JsonSerializerOptions?, CancellationToken)"/>
@@ -74,78 +96,13 @@ internal static class HttpClientExtensions
{
return null;
}
}
/// <summary>
/// 设置用户的 Cookie
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="user">实体用户</param>
/// <param name="cookie">Cookie类型</param>
/// <returns>客户端</returns>
internal static HttpClient SetUser(this HttpClient httpClient, Model.Entity.User user, CookieType cookie)
{
httpClient.DefaultRequestHeaders.Remove("Cookie");
StringBuilder stringBuilder = new();
if ((cookie & CookieType.CookieToken) == CookieType.CookieToken)
catch (JsonException)
{
stringBuilder.Append(user.CookieToken).AppendIf(user.CookieToken != null, ';');
return null;
}
if ((cookie & CookieType.Ltoken) == CookieType.Ltoken)
catch (IOException)
{
stringBuilder.Append(user.Ltoken).AppendIf(user.Ltoken != null, ';');
return null;
}
if ((cookie & CookieType.Stoken) == CookieType.Stoken)
{
stringBuilder.Append(user.Stoken).AppendIf(user.Stoken != null, ';');
}
if ((cookie & CookieType.Mid) == CookieType.Mid)
{
stringBuilder.Append("mid=").Append(user.Mid).Append(';');
}
httpClient.DefaultRequestHeaders.Set("Cookie", stringBuilder.ToString());
return httpClient;
}
/// <summary>
/// 设置Referer
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="referer">用户</param>
/// <returns>客户端</returns>
internal static HttpClient SetReferer(this HttpClient httpClient, string referer)
{
httpClient.DefaultRequestHeaders.Set("Referer", referer);
return httpClient;
}
/// <summary>
/// 设置验证流水号
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="challenge">验证流水号</param>
/// <returns>客户端</returns>
internal static HttpClient SetXrpcChallenge(this HttpClient httpClient, string challenge)
{
httpClient.DefaultRequestHeaders.Set("x-rpc-challenge", challenge);
return httpClient;
}
/// <summary>
/// 设置头
/// </summary>
/// <param name="httpClient">http客户端</param>
/// <param name="key">键</param>
/// <param name="value">值</param>
/// <returns>客户端</returns>
internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value)
{
httpClient.DefaultRequestHeaders.Set(key, value);
return httpClient;
}
}

View File

@@ -101,7 +101,7 @@ internal class HomaClient
public async Task<List<AvatarAppearanceRank>> GetAvatarAttendanceRatesAsync(CancellationToken token = default)
{
Response<List<AvatarAppearanceRank>>? resp = await httpClient
.GetFromJsonAsync<Response<List<AvatarAppearanceRank>>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token)
.TryCatchGetFromJsonAsync<Response<List<AvatarAppearanceRank>>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -116,7 +116,7 @@ internal class HomaClient
public async Task<List<AvatarUsageRank>> GetAvatarUtilizationRatesAsync(CancellationToken token = default)
{
Response<List<AvatarUsageRank>>? resp = await httpClient
.GetFromJsonAsync<Response<List<AvatarUsageRank>>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", token)
.TryCatchGetFromJsonAsync<Response<List<AvatarUsageRank>>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -131,7 +131,7 @@ internal class HomaClient
public async Task<List<AvatarCollocation>> GetAvatarCollocationsAsync(CancellationToken token = default)
{
Response<List<AvatarCollocation>>? resp = await httpClient
.GetFromJsonAsync<Response<List<AvatarCollocation>>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token)
.TryCatchGetFromJsonAsync<Response<List<AvatarCollocation>>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -146,7 +146,7 @@ internal class HomaClient
public async Task<List<WeaponCollocation>> GetWeaponCollocationsAsync(CancellationToken token = default)
{
Response<List<WeaponCollocation>>? resp = await httpClient
.GetFromJsonAsync<Response<List<WeaponCollocation>>>($"{HutaoAPI}/Statistics/Weapon/WeaponCollocation", token)
.TryCatchGetFromJsonAsync<Response<List<WeaponCollocation>>>($"{HutaoAPI}/Statistics/Weapon/WeaponCollocation", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -161,7 +161,7 @@ internal class HomaClient
public async Task<List<AvatarConstellationInfo>> GetAvatarHoldingRatesAsync(CancellationToken token = default)
{
Response<List<AvatarConstellationInfo>>? resp = await httpClient
.GetFromJsonAsync<Response<List<AvatarConstellationInfo>>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token)
.TryCatchGetFromJsonAsync<Response<List<AvatarConstellationInfo>>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);
@@ -176,7 +176,7 @@ internal class HomaClient
public async Task<List<TeamAppearance>> GetTeamCombinationsAsync(CancellationToken token = default)
{
Response<List<TeamAppearance>>? resp = await httpClient
.GetFromJsonAsync<Response<List<TeamAppearance>>>($"{HutaoAPI}/Statistics/Team/Combination", token)
.TryCatchGetFromJsonAsync<Response<List<TeamAppearance>>>($"{HutaoAPI}/Statistics/Team/Combination", options, logger, token)
.ConfigureAwait(false);
return EnumerableExtension.EmptyIfNull(resp?.Data);

View File

@@ -2,7 +2,6 @@
// Licensed under the MIT license.
using Snap.Hutao.Core.DependencyInjection.Annotation.HttpClient;
using Snap.Hutao.Web.Hoyolab;
using Snap.Hutao.Web.Hutao.Log;
using Snap.Hutao.Web.Response;
using System.Net.Http;

View File

@@ -53,6 +53,11 @@ public enum KnownReturnCode : int
/// </summary>
PleaseOpenInBbsApp = -1104,
/// <summary>
/// 天赋等级超出限制~
/// </summary>
SkillLevelLimitExcceed = -1009,
/// <summary>
/// 登录信息已失效,请重新登录
/// </summary>