diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml b/src/Snap.Hutao/Snap.Hutao/App.xaml index 16daa88f..0a40b759 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml @@ -59,6 +59,7 @@ + + + diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index 57ffc0b8..6ab7d94f 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -36,28 +36,35 @@ public partial class App : Application } /// - [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(); } } diff --git a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs index 794b107f..c93c649e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Context/Database/AppDbContext.cs @@ -102,6 +102,21 @@ public sealed class AppDbContext : DbContext /// public DbSet CultivateItems { get; set; } = default!; + /// + /// 背包内物品 + /// + public DbSet InventoryItems { get; set; } = default!; + + /// + /// 背包内武器 + /// + public DbSet InventoryWeapons { get; set; } = default!; + + /// + /// 背包内圣遗物 + /// + public DbSet InventoryReliquaries { get; set; } = default!; + /// /// 构造一个临时的应用程序数据库上下文 /// @@ -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()); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs new file mode 100644 index 00000000..bb75282d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior.cs @@ -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; + +/// +/// AppTitleBar Workaround +/// https://github.com/microsoft/microsoft-ui-xaml/issues/7756 +/// +internal class ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior : BehaviorBase +{ + private readonly IMessenger messenger; + + /// + /// AppTitleBar Workaround + /// + public ComboBoxExtendsContentIntoTitleBarWorkaroundBehavior() + { + messenger = Ioc.Default.GetRequiredService(); + } + + /// + 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)); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs index 7976e75b..0603e64e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Behavior/InvokeCommandOnUnloadedBehavior.cs @@ -25,6 +25,7 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase /// protected override void OnDetaching() { + // 由于卸载顺序问题,必须重写此方法才能正确触发命令 if (Command != null && Command.CanExecute(null)) { Command.Execute(null); @@ -32,4 +33,4 @@ internal class InvokeCommandOnUnloadedBehavior : BehaviorBase base.OnDetaching(); } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs index 151205b5..dfda991c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Caching/ImageCache.cs @@ -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) diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs index ce0590bd..e3eb44af 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Database/DbSetExtension.cs @@ -23,33 +23,6 @@ public static class DbSetExtension return dbSet.GetService().Context; } - /// - /// 获取或添加一个对应的实体 - /// - /// 实体类型 - /// 数据库集 - /// 谓词 - /// 实体工厂 - /// 是否添加 - /// 实体 - public static TEntity SingleOrAdd(this DbSet dbSet, Func predicate, Func 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; - } - /// /// 添加并保存 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs index 612ac475..9dab2b4e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/JumpListHelper.cs @@ -15,7 +15,7 @@ public static class JumpListHelper /// 异步配置跳转列表 /// /// 任务 - public static async Task ConfigAsync() + public static async Task ConfigureAsync() { if (JumpList.IsSupported()) { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs index 89ce1c48..e3f1680e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/Activation.cs @@ -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().Set(ViewModel.LaunchGameViewModel.DesiredUid, uid); await ThreadHelper.SwitchToMainThreadAsync(); - // TODO auto switch to account if (!MainWindow.IsPresent) { _ = Ioc.Default.GetRequiredService(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs new file mode 100644 index 00000000..ea719c8e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/LifeCycle/AppInstanceExtension.cs @@ -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; + +/// +/// App 实例拓展 +/// +internal static class AppInstanceExtension +{ + /// + /// 同步非阻塞重定向 + /// + /// app实例 + /// 参数 + [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 handles = new(in redirectEventHandle); + + // non-blocking + CoWaitForMultipleObjects((uint)CWMO_FLAGS.CWMO_DEFAULT, INFINITE, handles, out uint _); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs index 54391558..63b3cf11 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs @@ -23,7 +23,7 @@ namespace Snap.Hutao.Core.Windowing; /// /// 窗体类型 [SuppressMessage("", "CA1001")] -internal sealed class ExtendedWindow : IRecipient +internal sealed class ExtendedWindow : IRecipient, IRecipient where TWindow : Window, IExtendedWindowSource { private readonly HWND handle; @@ -82,6 +82,12 @@ internal sealed class ExtendedWindow : IRecipient + 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 : IRecipient), subClassApplied ? "succeed" : "failed"); - Ioc.Default.GetRequiredService().Register(this); + IMessenger messenger = Ioc.Default.GetRequiredService(); + messenger.Register(this); + messenger.Register(this); window.Closed += OnWindowClosed; } @@ -172,17 +180,25 @@ internal sealed class ExtendedWindow : IRecipient +/// Flyout开启关闭消息 +/// +internal class FlyoutOpenCloseMessage +{ + /// + /// 构造一个新的Flyout开启关闭消息 + /// + /// 是否为开启状态 + public FlyoutOpenCloseMessage(bool isOpen) + { + IsOpen = isOpen; + } + + /// + /// 是否为开启状态 + /// + public bool IsOpen { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.Designer.cs new file mode 100644 index 00000000..cb43c6b6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.Designer.cs @@ -0,0 +1,512 @@ +// +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 + { + /// + 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("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("Current") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("Status") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("achievements"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("achievement_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Info") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("avatar_infos"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("cultivate_entries"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("EntryId"); + + b.ToTable("cultivate_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachedUid") + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("cultivate_projects"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("DailyNote") + .HasColumnType("TEXT"); + + b.Property("DailyTaskNotify") + .HasColumnType("INTEGER"); + + b.Property("DailyTaskNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotify") + .HasColumnType("INTEGER"); + + b.Property("ExpeditionNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("HomeCoinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifyThreshold") + .HasColumnType("INTEGER"); + + b.Property("ShowInHomeWidget") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotify") + .HasColumnType("INTEGER"); + + b.Property("TransformerNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("UserId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("UserId"); + + b.ToTable("daily_notes"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("gacha_archives"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ArchiveId") + .HasColumnType("TEXT"); + + b.Property("GachaType") + .HasColumnType("INTEGER"); + + b.Property("Id") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("QueryType") + .HasColumnType("INTEGER"); + + b.Property("Time") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ArchiveId"); + + b.ToTable("gacha_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AttachUid") + .HasColumnType("TEXT"); + + b.Property("MihoyoSDK") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Name") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Type") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.ToTable("game_accounts"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_items"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppendPropIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("MainPropId") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_reliquaries"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("PromoteLevel") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("ProjectId"); + + b.ToTable("inventory_weapons"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("ExpireTime") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("object_cache"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b => + { + b.Property("Key") + .HasColumnType("TEXT"); + + b.Property("Value") + .HasColumnType("TEXT"); + + b.HasKey("Key"); + + b.ToTable("settings"); + }); + + modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Aid") + .HasColumnType("TEXT"); + + b.Property("CookieToken") + .HasColumnType("TEXT"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("Ltoken") + .HasColumnType("TEXT"); + + b.Property("Mid") + .HasColumnType("TEXT"); + + b.Property("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 + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.cs new file mode 100644 index 00000000..c80c927c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20221210111128_Inventory.cs @@ -0,0 +1,117 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + /// + public partial class Inventory : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.AddColumn( + name: "AttachedUid", + table: "cultivate_projects", + type: "TEXT", + nullable: true); + + migrationBuilder.CreateTable( + name: "inventory_items", + columns: table => new + { + InnerId = table.Column(type: "TEXT", nullable: false), + ProjectId = table.Column(type: "TEXT", nullable: false), + ItemId = table.Column(type: "INTEGER", nullable: false), + Count = table.Column(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(type: "TEXT", nullable: false), + ProjectId = table.Column(type: "TEXT", nullable: false), + ItemId = table.Column(type: "INTEGER", nullable: false), + Level = table.Column(type: "INTEGER", nullable: false), + MainPropId = table.Column(type: "INTEGER", nullable: false), + AppendPropIdList = table.Column(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(type: "TEXT", nullable: false), + ProjectId = table.Column(type: "TEXT", nullable: false), + ItemId = table.Column(type: "INTEGER", nullable: false), + Level = table.Column(type: "INTEGER", nullable: false), + PromoteLevel = table.Column(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"); + } + + /// + 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"); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs index bd0a41da..d2a50f77 100644 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs @@ -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("AttachedUid") + .HasColumnType("TEXT"); + b.Property("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("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("Count") + .HasColumnType("INTEGER"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("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("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AppendPropIdList") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("MainPropId") + .HasColumnType("INTEGER"); + + b.Property("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("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ItemId") + .HasColumnType("INTEGER"); + + b.Property("Level") + .HasColumnType("INTEGER"); + + b.Property("ProjectId") + .HasColumnType("TEXT"); + + b.Property("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 => diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs index cd60b691..c88a37f7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Achievement.cs @@ -15,9 +15,6 @@ public class Achievement : ObservableObject /// public const int FullProgressPlaceholder = int.MaxValue; - private readonly Metadata.Achievement.Achievement inner; - private readonly Entity.Achievement entity; - private bool isChecked; /// @@ -27,22 +24,21 @@ public class Achievement : ObservableObject /// 实体部分 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; } /// /// 实体 /// - public Entity.Achievement Entity { get => entity; } + public Entity.Achievement Entity { get; } /// /// 元数据 /// - public Metadata.Achievement.Achievement Inner { get => inner; } + public Metadata.Achievement.Achievement Inner { get; } /// /// 是否选中 @@ -69,6 +65,6 @@ public class Achievement : ObservableObject /// public string Time { - get => entity.Time.ToString("yyyy.MM.dd HH:mm:ss"); + get => Entity.Time.ToString("yyyy.MM.dd HH:mm:ss"); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateEntry.cs new file mode 100644 index 00000000..b3d2fdcb --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateEntry.cs @@ -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; + +/// +/// 养成物品 +/// +public class CultivateEntry : ItemBase +{ + /// + /// Id + /// + public int Id { get; set; } + + /// + /// 入口Id + /// + public Guid EntryId { get; set; } + + /// + /// 实体 + /// + public List Items { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs new file mode 100644 index 00000000..353a0d6c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateItem.cs @@ -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; + +/// +/// 养成物品 +/// +public class CultivateItem : ObservableObject +{ + /// + /// 养成物品 + /// + /// 元数据 + /// 实体 + public CultivateItem(Material inner, Entity.CultivateItem entity) + { + Inner = inner; + Entity = entity; + } + + /// + /// 元数据 + /// + public Material Inner { get; } + + /// + /// 实体 + /// + public Entity.CultivateItem Entity { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateType.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateType.cs index e86901f8..d432a991 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateType.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Cultivation/CultivateType.cs @@ -14,14 +14,9 @@ public enum CultivateType None, /// - /// 角色 + /// 角色与技能 /// - Avatar, - - /// - /// 角色技能 - /// - Skill, + AvatarAndSkill, /// /// 武器 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Binding/Inventory/InventoryItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Inventory/InventoryItem.cs new file mode 100644 index 00000000..0def3ec8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Binding/Inventory/InventoryItem.cs @@ -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; + +/// +/// 背包物品 +/// +internal class InventoryItem : ObservableObject +{ + private uint count; + + /// + /// 创建一个新的背包物品 + /// + /// 元数据 + /// 实体 + public InventoryItem(Material inner, Entity.InventoryItem entity) + { + Entity = entity; + Inner = inner; + count = entity.Count; + } + + /// + /// 实体 + /// + public Entity.InventoryItem Entity { get; set; } + + /// + /// 元数据 + /// + public Material Inner { get; set; } + + /// + /// 个数 + /// + public uint Count + { + get => count; set + { + if (SetProperty(ref count, value)) + { + Entity.Count = value; + Ioc.Default.GetRequiredService().SaveInventoryItem(this); + } + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/InventoryReliquaryConfiguration.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/InventoryReliquaryConfiguration.cs new file mode 100644 index 00000000..90027de5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Configuration/InventoryReliquaryConfiguration.cs @@ -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; + +/// +/// 背包圣遗物配置 +/// +internal class InventoryReliquaryConfiguration : IEntityTypeConfiguration +{ + /// + public void Configure(EntityTypeBuilder builder) + { + builder.Property(e => e.AppendPropIdList) + .HasColumnType("TEXT") + .HasConversion( + list => string.Join(',', list), + text => text.Split(',', StringSplitOptions.None).Select(x => int.Parse(x)).ToList()); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs index 0ba5a4c2..5203ab3d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntry.cs @@ -42,7 +42,18 @@ public class CultivateEntry public int Id { get; set; } /// - /// 物品 + /// 创建一个新的养成入口点 /// - public virtual ICollection Items { get; set; } = default!; + /// 类型 + /// 主Id + /// 养成入口点 + public static CultivateEntry Create(Guid projectId,CultivateType type, int id) + { + return new() + { + ProjectId = projectId, + Type = type, + Id = id, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs index 5d91fae4..879fdf7b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs @@ -39,4 +39,21 @@ public class CultivateItem /// 物品个数 /// public int Count { get; set; } + + /// + /// 创建一个新的养成物品 + /// + /// 入口点 Id + /// 物品 Id + /// 个数 + /// 养成物品 + public static CultivateItem Create(Guid entryId, int itemId, int count) + { + return new() + { + EntryId = entryId, + ItemId = itemId, + Count = count, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs index 6cd5bfaa..b24d6fe2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateProject.cs @@ -31,7 +31,18 @@ public class CultivateProject : ISelectable public string Name { get; set; } = default!; /// - /// 入口集合 + /// 所属的Uid /// - public virtual ICollection Entries { get; set; } = default!; -} + public string? AttachedUid { get; set; } + + /// + /// 创建新的养成计划 + /// + /// 名称 + /// 绑定的Uid + /// 新的养成计划 + public static CultivateProject Create(string name, string? attachedUid = null) + { + return new() { Name = name, AttachedUid = attachedUid }; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs new file mode 100644 index 00000000..db94d93a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs @@ -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; + +/// +/// 背包物品 +/// +[Table("inventory_items")] +public class InventoryItem +{ + /// + /// 内部Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + /// + /// 培养计划Id + /// + public Guid ProjectId { get; set; } + + /// + /// 所属的计划 + /// + [ForeignKey(nameof(ProjectId))] + public CultivateProject Project { get; set; } = default!; + + /// + /// 物品Id + /// + public int ItemId { get; set; } + + /// + /// 个数 4294967295 + /// + public uint Count { get; set; } + + /// + /// 构造一个新的个数为0的物品 + /// + /// 项目Id + /// 物品Id + /// 新的个数为0的物品 + public static InventoryItem Create(Guid projectId, int itemId) + { + return new() + { + ProjectId = projectId, + ItemId = itemId, + }; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryReliquary.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryReliquary.cs new file mode 100644 index 00000000..e9d6aff3 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryReliquary.cs @@ -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; + +/// +/// 背包圣遗物 +/// +[Table("inventory_reliquaries")] +public class InventoryReliquary +{ + /// + /// 内部Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + /// + /// 培养计划Id + /// + public Guid ProjectId { get; set; } + + /// + /// 所属的计划 + /// + [ForeignKey(nameof(ProjectId))] + public CultivateProject Project { get; set; } = default!; + + /// + /// 物品Id + /// + public int ItemId { get; set; } + + /// + /// 等级 + /// + public int Level { get; set; } + + /// + /// 主属性 + /// + public int MainPropId { get; set; } + + /// + /// 副词条Id + /// + public List AppendPropIdList { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryWeapon.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryWeapon.cs new file mode 100644 index 00000000..e382a4ca --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryWeapon.cs @@ -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; + +/// +/// 背包武器 +/// +[Table("inventory_weapons")] +public class InventoryWeapon +{ + /// + /// 内部Id + /// + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + /// + /// 培养计划Id + /// + public Guid ProjectId { get; set; } + + /// + /// 所属的计划 + /// + [ForeignKey(nameof(ProjectId))] + public CultivateProject Project { get; set; } = default!; + + /// + /// 物品Id + /// + public int ItemId { get; set; } + + /// + /// 等级 + /// + public int Level { get; set; } + + /// + /// 精炼等级 0-4 + /// + public int PromoteLevel { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs index f1cfadac..58aa97d2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Achievement/UIAF.cs @@ -5,7 +5,7 @@ namespace Snap.Hutao.Model.InterChange.Achievement; /// /// 统一可交换成就格式 -/// https://www.snapgenshin.com/development/UIAF.html +/// https://uigf.org/standards/UIAF.html /// public class UIAF { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs index cd294e6f..25b19967 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/GachaLog/UIGF.cs @@ -7,7 +7,7 @@ namespace Snap.Hutao.Model.InterChange.GachaLog; /// /// 统一可交换祈愿格式 -/// https://www.snapgenshin.com/development/UIGF.html +/// https://uigf.org/standards/UIGF.html /// public class UIGF { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIF.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIF.cs new file mode 100644 index 00000000..e447e50d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIF.cs @@ -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; + +/// +/// 统一可交换物品格式 +/// +internal class UIIF +{ + /// + /// 当前发行的版本 + /// + public const string CurrentVersion = "v1.0"; + + private static readonly ImmutableList SupportedVersion = new List() + { + CurrentVersion, + }.ToImmutableList(); + + /// + /// 信息 + /// + [JsonPropertyName("info")] + public UIIFInfo Info { get; set; } = default!; + + /// + /// 列表 + /// + [JsonPropertyName("list")] + public List List { get; set; } = default!; +} + +/// +/// UIIF物品 +/// +[JsonDerivedType(typeof(UIIFReliquary))] +[JsonDerivedType(typeof(UIIFWeapon))] +internal class UIIFItem +{ + /// + /// 物品Id + /// + [JsonPropertyName("itemId")] + public int ItemId { get; set; } + + /// + /// 物品Id + /// + [JsonPropertyName("count")] + public int Count { get; set; } +} + +/// +/// UIIF圣遗物 +/// +internal class UIIFReliquary : UIIFItem +{ + /// + /// 物品Id + /// + [JsonPropertyName("level")] + public int Level { get; set; } + + /// + /// 副属性列表 + /// + [JsonPropertyName("appendPropIdList")] + public List AppendPropIdList { get; set; } +} + +/// +/// UIIF武器 +/// +internal class UIIFWeapon : UIIFItem +{ + /// + /// 物品Id + /// + [JsonPropertyName("level")] + public int Level { get; set; } + + /// + /// 精炼等级 0-4 + /// + [JsonPropertyName("promoteLevel")] + public int PromoteLevel { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs new file mode 100644 index 00000000..a4ad940b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/InterChange/Inventory/UIIFInfo.cs @@ -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; + +/// +/// UIIF格式的信息 +/// +public class UIIFInfo +{ + /// + /// 用户Uid + /// + [JsonPropertyName("uid")] + public string Uid { get; set; } = default!; + + /// + /// 语言 + /// + [JsonPropertyName("lang")] + public string Language { get; set; } = default!; + + /// + /// 导出的时间戳 + /// + [JsonPropertyName("export_timestamp")] + [JsonNumberHandling(JsonNumberHandling.AllowReadingFromString)] + public long? ExportTimestamp { get; set; } + + /// + /// 导出时间 + /// + [JsonIgnore] + public DateTimeOffset ExportDateTime + { + get => DateTimeOffsetExtension.FromUnixTime(ExportTimestamp, DateTimeOffset.MinValue); + } + + /// + /// 导出的 App 名称 + /// + [JsonPropertyName("export_app")] + public string ExportApp { get; set; } = default!; + + /// + /// 导出的 App 版本 + /// + [JsonPropertyName("export_app_version")] + public string ExportAppVersion { get; set; } = default!; + + /// + /// 使用的UIGF版本 + /// + [JsonPropertyName("uiif_version")] + public string UIIFVersion { get; set; } = default!; + + /// + /// 构造一个新的专用 UIGF 信息 + /// + /// uid + /// 专用 UIGF 信息 + 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, + }; + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/INameQuality.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/INameQuality.cs index eb4e1341..7199e3d7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/INameQuality.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/INameQuality.cs @@ -19,4 +19,5 @@ public interface INameQuality /// 星级 /// ItemQuality Quality { get; } -} \ No newline at end of file +} + diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.Implementation.cs new file mode 100644 index 00000000..a950592c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.Implementation.cs @@ -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; + +/// +/// 角色的接口实现部分 +/// +public partial class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality, ICalculableSource +{ + /// + /// [非元数据] 搭配数据 + /// + [JsonIgnore] + public ComplexAvatarCollocation? Collocation { get; set; } + + /// + public ICalculableAvatar ToCalculable() + { + return new CalculableAvatar(this); + } + + /// + /// 转换为基础物品 + /// + /// 基础物品 + public ItemBase ToItemBase() + { + return new() + { + Name = Name, + Icon = AvatarIconConverter.IconNameToUri(Icon), + Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore), + Quality = Quality, + }; + } + + /// + public StatisticsItem ToStatisticsItem(int count) + { + return new() + { + Name = Name, + Icon = AvatarIconConverter.IconNameToUri(Icon), + Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore), + Quality = Quality, + + Count = count, + }; + } + + /// + 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, + }; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs index e2809a43..65b5d653 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.cs @@ -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; /// /// 角色 /// -public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality +public partial class Avatar { /// /// Id @@ -87,65 +82,4 @@ public class Avatar : IStatisticsItemSource, ISummaryItemSource, INameQuality /// 皮肤 /// public IEnumerable Costumes { get; set; } = default!; - - /// - /// [非元数据] 搭配数据 - /// - [JsonIgnore] - public ComplexAvatarCollocation? Collocation { get; set; } - - /// - /// 转换为基础物品 - /// - /// 基础物品 - public ItemBase ToItemBase() - { - return new() - { - Name = Name, - Icon = AvatarIconConverter.IconNameToUri(Icon), - Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore), - Quality = Quality, - }; - } - - /// - /// 转换到统计物品 - /// - /// 个数 - /// 统计物品 - public StatisticsItem ToStatisticsItem(int count) - { - return new() - { - Name = Name, - Icon = AvatarIconConverter.IconNameToUri(Icon), - Badge = ElementNameIconConverter.ElementNameToIconUri(FetterInfo.VisionBefore), - Quality = Quality, - - Count = count, - }; - } - - /// - /// 转换到简述统计物品 - /// - /// 距上个五星 - /// 时间 - /// 是否为Up物品 - /// 简述统计物品 - 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, - }; - } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableAvatar.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableAvatar.cs new file mode 100644 index 00000000..e7a45d68 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableAvatar.cs @@ -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; + +/// +/// 可计算角色 +/// +internal class CalculableAvatar : ObservableObject, ICalculableAvatar +{ + private int levelCurrent; + private int levelTarget; + + /// + /// 构造一个新的可计算角色 + /// + /// 角色 + 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; + } + + /// + public AvatarId AvatarId { get; } + + /// + public int LevelMin { get; } + + /// + public int LevelMax { get; } + + /// + public IList Skills { get; } + + /// + public string Name { get; } + + /// + public Uri Icon { get; } + + /// + public ItemQuality Quality { get; } + + /// + public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); } + + /// + public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableSkill.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableSkill.cs new file mode 100644 index 00000000..bba49bb4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/CalculableSkill.cs @@ -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; + +/// +/// 可计算的技能 +/// +internal class CalculableSkill : ObservableObject, ICalculableSkill +{ + private int levelCurrent; + private int levelTarget; + + /// + /// 构造一个新的可计算的技能 + /// + /// 技能 + 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; + } + + /// + public int GruopId { get; } + + /// + public int LevelMin { get; } + + /// + public int LevelMax { get; } + + /// + public string Name { get; } + + /// + public Uri Icon { get; } + + /// + public ItemQuality Quality { get; } + + /// + public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); } + + /// + public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.Implementation.cs new file mode 100644 index 00000000..3f00c30b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.Implementation.cs @@ -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; + +/// +/// 技能信息的接口实现 +/// +public partial class ProudableSkill : ICalculableSource +{ + /// + public ICalculableSkill ToCalculable() + { + return new CalculableSkill(this); + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs index 37c4366f..321da60d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/ProudableSkill.cs @@ -6,7 +6,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar; /// /// 技能信息 /// -public class ProudableSkill : SkillBase +public partial class ProudableSkill : SkillBase { /// /// 组Id @@ -17,4 +17,4 @@ public class ProudableSkill : SkillBase /// 提升属性 /// public DescParam Proud { get; set; } = default!; -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/SkillDepot.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/SkillDepot.cs index 391bf5be..6f54bfe3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/SkillDepot.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/SkillDepot.cs @@ -24,7 +24,8 @@ public class SkillDepot public IList Inherents { get; set; } = default!; /// - /// 全部天赋,包括固有天赋 + /// 全部天赋,包括固有天赋 + /// 在 Wiki 中使用 /// public IList CompositeSkills { diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs index 3440e005..c1f76014 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/GachaEvent.cs @@ -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 /// 四星列表 /// public List UpPurpleList { get; set; } = default!; -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Material.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Material.cs new file mode 100644 index 00000000..3168901a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Material.cs @@ -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; + +/// +/// 材料 +/// +public class Material +{ + /// + /// 物品Id + /// + public int Id { get; set; } + + /// + /// 等级 + /// + public ItemQuality RankLevel { get; set; } + + /// + /// 物品类型 + /// + public ItemType ItemType { get; set; } + + /// + /// 图标 + /// + public string Icon { get; set; } = default!; + + /// + /// 名称 + /// + public string Name { get; set; } = default!; + + /// + /// 描述 + /// + public string Description { get; set; } = default!; + + /// + /// 类型描述 + /// + public string TypeDescription { get; set; } = default!; +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/CalculableWeapon.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/CalculableWeapon.cs new file mode 100644 index 00000000..a98ea482 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/CalculableWeapon.cs @@ -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; + +/// +/// 可计算武器 +/// +public class CalculableWeapon : ObservableObject, ICalculableWeapon +{ + private int levelCurrent; + private int levelTarget; + + /// + /// 构造一个新的可计算武器 + /// + /// 武器 + 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; + } + + /// + public WeaponId WeaponId { get; } + + /// + public int LevelMin { get; } + + /// + public int LevelMax { get; } + + /// + public string Name { get; } + + /// + public Uri Icon { get; } + + /// + public ItemQuality Quality { get; } + + /// + public int LevelCurrent { get => levelCurrent; set => SetProperty(ref levelCurrent, value); } + + /// + public int LevelTarget { get => levelTarget; set => SetProperty(ref levelTarget, value); } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.Implementation.cs new file mode 100644 index 00000000..fe6f5ff6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.Implementation.cs @@ -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; + +/// +/// 武器的接口实现 +/// +public partial class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality, ICalculableSource +{ + /// + /// [非元数据] 搭配数据 + /// + [JsonIgnore] + public ComplexWeaponCollocation? Collocation { get; set; } + + /// + [JsonIgnore] + public ItemQuality Quality + { + get => RankLevel; + } + + /// + public ICalculableWeapon ToCalculable() + { + return new CalculableWeapon(this); + } + + /// + /// 转换为基础物品 + /// + /// 基础物品 + public ItemBase ToItemBase() + { + return new() + { + Name = Name, + Icon = EquipIconConverter.IconNameToUri(Icon), + Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType), + Quality = RankLevel, + }; + } + + /// + /// 转换到统计物品 + /// + /// 个数 + /// 统计物品 + public StatisticsItem ToStatisticsItem(int count) + { + return new() + { + Name = Name, + Icon = EquipIconConverter.IconNameToUri(Icon), + Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType), + Quality = RankLevel, + Count = count, + }; + } + + /// + /// 转换到简述统计物品 + /// + /// 距上个五星 + /// 时间 + /// 是否为Up物品 + /// 简述统计物品 + 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, + }; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs index 5fd2b8f4..ca96c6e9 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.cs @@ -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; /// /// 武器 /// -public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality +public partial class Weapon { /// /// Id @@ -60,70 +55,4 @@ public class Weapon : IStatisticsItemSource, ISummaryItemSource, INameQuality /// 被动信息, 无被动的武器为 /// public AffixInfo? Affix { get; set; } = default!; - - /// - /// [非元数据] 搭配数据 - /// - [JsonIgnore] - public ComplexWeaponCollocation? Collocation { get; set; } - - /// - [JsonIgnore] - public ItemQuality Quality - { - get => RankLevel; - } - - /// - /// 转换为基础物品 - /// - /// 基础物品 - public ItemBase ToItemBase() - { - return new() - { - Name = Name, - Icon = EquipIconConverter.IconNameToUri(Icon), - Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType), - Quality = RankLevel, - }; - } - - /// - /// 转换到统计物品 - /// - /// 个数 - /// 统计物品 - public StatisticsItem ToStatisticsItem(int count) - { - return new() - { - Name = Name, - Icon = EquipIconConverter.IconNameToUri(Icon), - Badge = WeaponTypeIconConverter.WeaponTypeToIconUri(WeaponType), - Quality = RankLevel, - Count = count, - }; - } - - /// - /// 转换到简述统计物品 - /// - /// 距上个五星 - /// 时间 - /// 是否为Up物品 - /// 简述统计物品 - 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, - }; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt index face9562..e8b1642b 100644 --- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt +++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt @@ -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 \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index a8fe02c5..07daf684 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -12,7 +12,7 @@ + Version="1.2.10.0" /> 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs index 8ccc1112..1d43218b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/AchievementService.cs @@ -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 /// public ObservableCollection GetArchiveCollection() { - return archiveCollection ??= new(appDbContext.AchievementArchives.ToList()); + return archiveCollection ??= new(appDbContext.AchievementArchives.AsNoTracking().ToList()); } /// @@ -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; } diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs index 82798eaa..d5910f9b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Achievement/ArchiveAddResult.cs @@ -19,7 +19,7 @@ public enum ArchiveAddResult InvalidName, /// - /// 已经存在该用户 + /// 已经存在该存档 /// AlreadyExists, } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index f4260061..a0fc37b1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -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 dbCurrent; + private readonly IServiceScopeFactory scopeFactory; + private readonly ScopedDbCurrent dbCurrent; + + private ObservableCollection? projects; /// /// 构造一个新的养成计算服务 /// - /// 数据库上下文 + /// 范围工厂 /// 消息器 - 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().CultivateProjects, messenger); } - /// - /// 当前养成计划 - /// + /// public CultivateProject? Current { get => dbCurrent.Current; set => dbCurrent.Current = value; } + + /// + public ObservableCollection GetProjectCollection() + { + if (projects == null) + { + using (IServiceScope scope = scopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + projects = new(appDbContext.CultivateProjects.AsNoTracking().ToList()); + } + + Current ??= projects.SingleOrDefault(proj => proj.IsSelected); + } + + return projects; + } + + /// + public async Task 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().CultivateProjects.AddAndSaveAsync(project).ConfigureAwait(false); + } + + return ProjectAddResult.Added; + } + } + + /// + 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().CultivateProjects.RemoveAndSaveAsync(project).ConfigureAwait(false); + } + } + + /// + public List GetInventoryItems(CultivateProject cultivateProject, List metadata) + { + Guid projectId = cultivateProject.InnerId; + using (IServiceScope scope = scopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + List entities = appDbContext.InventoryItems + .Where(a => a.ProjectId == projectId) + .ToList(); + + List 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; + } + } + + /// + public async Task> GetCultivateEntriesAsync( + CultivateProject cultivateProject, + List metadata, + Dictionary idAvatarMap, + Dictionary idWeaponMap) + { + // TODO: cache the collection + await ThreadHelper.SwitchToBackgroundAsync(); + Guid projectId = cultivateProject.InnerId; + using (IServiceScope scope = scopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + List bindingEntries = new(); + foreach (Model.Entity.CultivateEntry? entry in await appDbContext.CultivateEntries.ToListAsync().ConfigureAwait(false)) + { + Guid entryId = entry.InnerId; + + List 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); + } + } + + /// + public async Task RemoveCultivateEntryAsync(Guid entryId) + { + await ThreadHelper.SwitchToBackgroundAsync(); + using (IServiceScope scope = scopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.CultivateEntries.Where(i => i.InnerId == entryId).ExecuteDeleteAsync().ConfigureAwait(false); + } + } + + /// + public void SaveInventoryItem(BindingInventoryItem item) + { + using (IServiceScope scope = scopeFactory.CreateScope()) + { + scope.ServiceProvider.GetRequiredService().InventoryItems.UpdateAndSave(item.Entity); + } + } + + /// + public async Task SaveConsumptionAsync(CultivateType type, int itemId, List items) + { + using (IServiceScope scope = scopeFactory.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + + 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 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, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs index 89b40e76..8dfb8cc8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -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; /// @@ -8,4 +15,68 @@ namespace Snap.Hutao.Service.Cultivation; /// internal interface ICultivationService { + /// + /// 当前养成计划 + /// + CultivateProject? Current { get; set; } + + /// + /// 获取绑定用的养成列表 + /// + /// 养成计划 + /// 材料 + /// Id角色映射 + /// Id武器映射 + /// 绑定用的养成列表 + Task> GetCultivateEntriesAsync(CultivateProject cultivateProject, List metadata, Dictionary idAvatarMap, Dictionary idWeaponMap); + + /// + /// 获取物品列表 + /// + /// 养成计划 + /// 元数据 + /// 物品列表 + List GetInventoryItems(CultivateProject cultivateProject, List metadata); + + /// + /// 获取用于绑定的项目集合 + /// + /// 项目集合 + ObservableCollection GetProjectCollection(); + + /// + /// 删除养成清单 + /// + /// 入口Id + /// 任务 + Task RemoveCultivateEntryAsync(Guid entryId); + + /// + /// 异步移除项目 + /// + /// 项目 + /// 任务 + Task RemoveProjectAsync(CultivateProject project); + + /// + /// 异步保存养成物品 + /// + /// 类型 + /// 主Id + /// 待存物品 + /// 是否保存成功 + Task SaveConsumptionAsync(CultivateType type, int itemId, List items); + + /// + /// 保存单个物品 + /// + /// 物品 + void SaveInventoryItem(Model.Binding.Inventory.InventoryItem item); + + /// + /// 异步尝试添加新的项目 + /// + /// 项目 + /// 添加操作的结果 + Task TryAddProjectAsync(CultivateProject project); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs new file mode 100644 index 00000000..7adadf8f --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ProjectAddResult.cs @@ -0,0 +1,25 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Cultivation; + +/// +/// 项目添加结果 +/// +public enum ProjectAddResult +{ + /// + /// 添加成功 + /// + Added, + + /// + /// 名称无效 + /// + InvalidName, + + /// + /// 已经存在该存档 + /// + AlreadyExists, +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs index b19954a7..a3e2c2aa 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/IMetadataService.cs @@ -140,4 +140,11 @@ internal interface IMetadataService /// 取消令牌 /// 圣遗物套装列表 ValueTask> GetReliquarySetsAsync(CancellationToken token = default); + + /// + /// 异步获取材料列表 + /// + /// 取消令牌 + /// 材料列表 + ValueTask> GetMaterialsAsync(CancellationToken token = default(CancellationToken)); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs index c6c6c203..4df0341a 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/MetadataService.Implementation.cs @@ -38,6 +38,12 @@ internal partial class MetadataService return FromCacheOrFileAsync>("GachaEvent", token); } + /// + public ValueTask> GetMaterialsAsync(CancellationToken token = default) + { + return FromCacheOrFileAsync>("Material", token); + } + /// public ValueTask> GetReliquariesAsync(CancellationToken token = default) { diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index cfe85dee..7f9de3c3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -69,6 +69,8 @@ + + @@ -134,12 +136,12 @@ - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -151,12 +153,12 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + all runtime; build; native; contentfiles; analyzers; buildtransitive - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive @@ -179,6 +181,16 @@ + + + MSBuild:Compile + + + + + MSBuild:Compile + + MSBuild:Compile diff --git a/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs index 86a1237f..97c20a25 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Control/DescParamComboBox.xaml.cs @@ -16,6 +16,9 @@ public sealed partial class DescParamComboBox : UserControl private static readonly DependencyProperty SourceProperty = Property .Depend>>(nameof(Source), default!, OnSourceChanged); + private static readonly DependencyProperty PreferredSelectedIndexProperty = Property + .Depend(nameof(PreferredSelectedIndex), 0); + /// /// 构造一个新的描述参数组合框 /// @@ -33,6 +36,15 @@ public sealed partial class DescParamComboBox : UserControl set => SetValue(SourceProperty, value); } + /// + /// 期望的选中索引 + /// + 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> list) { descParamComboBox.ItemHost.ItemsSource = list; - descParamComboBox.ItemHost.SelectedIndex = 0; + descParamComboBox.ItemHost.SelectedIndex = Math.Min(descParamComboBox.PreferredSelectedIndex, list.Count); } } } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs new file mode 100644 index 00000000..cbb9c1d6 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Converter/EmptyObjectToVisibilityConverter.cs @@ -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; + +/// +/// This class converts a object? value into a Visibility enumeration. +/// +public class EmptyObjectToVisibilityConverter : EmptyObjectToObjectConverter +{ + /// + /// Initializes a new instance of the class. + /// + public EmptyObjectToVisibilityConverter() + { + EmptyValue = Visibility.Collapsed; + NotEmptyValue = Visibility.Visible; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml new file mode 100644 index 00000000..c6ce2502 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml @@ -0,0 +1,24 @@ + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml.cs new file mode 100644 index 00000000..aacd5e91 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivateProjectDialog.xaml.cs @@ -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; + +/// +/// ɼƻԻ +/// +public sealed partial class CultivateProjectDialog : ContentDialog +{ + /// + /// һµɼƻԻ + /// + /// + public CultivateProjectDialog(Window window) + { + InitializeComponent(); + XamlRoot = window.Content.XamlRoot; + } + + /// + /// һµģûָļƻ + /// + /// ƻ + public async ValueTask> CreateProjectAsync() + { + await ThreadHelper.SwitchToMainThreadAsync(); + ContentDialogResult result = await ShowAsync(); + if (result == ContentDialogResult.Primary) + { + string text = InputText.Text; + string? uid = AttachUidBox.IsChecked == true + ? Ioc.Default.GetRequiredService().Current?.SelectedUserGameRole?.GameUid + : null; + + CultivateProject project = CultivateProject.Create(text, uid); + return new(true, project); + } + + return new(false, null!); + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml new file mode 100644 index 00000000..9e9a2d70 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml @@ -0,0 +1,172 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs new file mode 100644 index 00000000..cc6b50b8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaDialog.xaml.cs @@ -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; + +/// +/// ɼԻ +/// +public sealed partial class CultivatePromotionDeltaDialog : ContentDialog +{ + private static readonly DependencyProperty AvatarProperty = Property.Depend(nameof(Avatar)); + private static readonly DependencyProperty WeaponProperty = Property.Depend(nameof(Weapon)); + + /// + /// һµɼԻ + /// + /// + /// ɫ + /// + public CultivatePromotionDeltaDialog(Window window, ICalculableAvatar? avatar, ICalculableWeapon? weapon) + { + InitializeComponent(); + XamlRoot = window.Content.XamlRoot; + DataContext = this; + Avatar = avatar; + Weapon = weapon; + } + + /// + /// ɫ + /// + public ICalculableAvatar? Avatar + { + get { return (ICalculableAvatar?)GetValue(AvatarProperty); } + set { SetValue(AvatarProperty, value); } + } + + /// + /// + /// + public ICalculableWeapon? Weapon + { + get { return (ICalculableWeapon?)GetValue(WeaponProperty); } + set { SetValue(WeaponProperty, value); } + } + + /// + /// 첽ȡ + /// + /// + public async Task> 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!); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml index 86b43e08..d1144c2c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/GameAccountNameDialog.xaml @@ -16,6 +16,6 @@ x:Name="InputText" Margin="0,0,0,0" VerticalAlignment="Top" - PlaceholderText="在此处输入"/> + PlaceholderText="在此处输入名称"/> diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml index c8373362..db5ad0bf 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AchievementPage.xaml @@ -24,10 +24,7 @@ - + @@ -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}"> + + + + - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml index 7193cfb2..01d86a11 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/GachaLogPage.xaml @@ -23,8 +23,9 @@ - + Fill="{StaticResource CardBackgroundFillColorDefaultBrush}" + IsHitTestVisible="False"/> + + SelectedItem="{Binding SelectedArchive, Mode=TwoWay}"> + + + + diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml index 1a6c0fab..5fb97a35 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/WikiAvatarPage.xaml @@ -33,6 +33,7 @@ @@ -40,7 +41,10 @@ - + @@ -68,7 +72,11 @@ - + - + diff --git a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs index 2a35e584..3c0a5159 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/TitleView.xaml.cs @@ -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 } /// diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs index f81eacf8..aae44fe2 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/CultivationViewModel.cs @@ -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; /// 养成视图模型 /// [Injection(InjectAs.Scoped)] -internal class CultivationViewModel : ObservableObject, ISupportCancellation +internal class CultivationViewModel : ObservableObject, ISupportCancellation, IRecipient { + private readonly ICultivationService cultivationService; + private readonly IInfoBarService infoBarService; + private readonly IMetadataService metadataService; + private readonly ILogger logger; + + private ObservableCollection? projects; + private CultivateProject? selectedProject; + private List? inventoryItems; + private ObservableCollection? cultivateEntries; + + /// + /// 构造一个新的养成视图模型 + /// + /// 养成服务 + /// 信息服务 + /// 异步命令工厂 + /// 元数据服务 + /// 日志器 + /// 消息器 + public CultivationViewModel( + ICultivationService cultivationService, + IInfoBarService infoBarService, + IAsyncRelayCommandFactory asyncRelayCommandFactory, + IMetadataService metadataService, + ILogger 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(RemoveProjectAsync); + RemoveEntryCommand = asyncRelayCommandFactory.Create(RemoveEntryAsync); + SaveInventoryItemCommand = new RelayCommand(SaveInventoryItem); + + messenger.Register(this); + } + /// public CancellationToken CancellationToken { get; set; } + + /// + /// 项目 + /// + public ObservableCollection? Projects { get => projects; set => SetProperty(ref projects, value); } + + /// + /// 当前选中的计划 + /// + public CultivateProject? SelectedProject + { + get => selectedProject; set + { + if (SetProperty(ref selectedProject, value)) + { + cultivationService.Current = value; + } + } + } + + /// + /// 物品列表 + /// + public List? InventoryItems { get => inventoryItems; set => SetProperty(ref inventoryItems, value); } + + /// + /// 养成列表 + /// + public ObservableCollection? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); } + + /// + /// 打开界面命令 + /// + public ICommand OpenUICommand { get; } + + /// + /// 添加项目命令 + /// + public ICommand AddProjectCommand { get; } + + /// + /// 删除项目命令 + /// + public ICommand RemoveProjectCommand { get; } + + /// + /// 移除 + /// + public ICommand RemoveEntryCommand { get; } + + /// + /// 保存物品命令 + /// + public ICommand SaveInventoryItemCommand { get; } + + /// + 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(); + (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 materials = await metadataService.GetMaterialsAsync().ConfigureAwait(false); + Dictionary idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false); + Dictionary idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false); + + ObservableCollection 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); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs index a1bc28a6..b0c1268d 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/LaunchGameViewModel.cs @@ -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 { + /// + /// 启动游戏目标 Uid + /// + 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 knownSchemes = new() { @@ -55,12 +62,18 @@ internal class LaunchGameViewModel : ObservableObject, ISupportCancellation /// 构造一个新的启动游戏视图模型 /// /// 游戏服务 + /// 内存缓存 /// 数据库上下文 /// 异步命令工厂 - 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().Warning("游戏路径不正确,前往设置更改游戏路径。"); - await Ioc.Default.GetRequiredService().NavigateAsync(INavigationAwaiter.Default, true).ConfigureAwait(false); + await Ioc.Default.GetRequiredService() + .NavigateAsync(INavigationAwaiter.Default, true) + .ConfigureAwait(false); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs index a8c772c7..6d166c6a 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/WikiAvatarViewModel.cs @@ -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(CultivateAsync); filterElementInfos = new() { @@ -147,6 +158,11 @@ internal class WikiAvatarViewModel : ObservableObject /// public ICommand OpenUICommand { get; } + /// + /// 养成命令 + /// + 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().FirstOrDefault(); } @@ -222,4 +236,47 @@ internal class WikiAvatarViewModel : ObservableObject } } } + + private async Task CultivateAsync(Avatar? avatar) + { + IInfoBarService infoBarService = Ioc.Default.GetRequiredService(); + if (avatar != null) + { + IUserService userService = Ioc.Default.GetRequiredService(); + if (userService.Current != null) + { + MainWindow mainWindow = Ioc.Default.GetRequiredService(); + (bool isOk, CalcAvatarPromotionDelta delta) = await new CultivatePromotionDeltaDialog(mainWindow, avatar.ToCalculable(), null) + .GetPromotionDeltaAsync() + .ConfigureAwait(false); + + if (isOk) + { + CalcClient calculateClient = Ioc.Default.GetRequiredService(); + CalcConsumption? consumption = await calculateClient.ComputeAsync(userService.Current.Entity, delta).ConfigureAwait(false); + if (consumption != null) + { + List items = CalcItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); + bool saved = await Ioc.Default + .GetRequiredService() + .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) + .ConfigureAwait(false); + + if (saved) + { + infoBarService.Success("已成功添加至当前养成计划"); + } + else + { + infoBarService.Warning("请先前往养成计划页面创建计划并选中"); + } + } + } + } + else + { + infoBarService.Warning("必须先选择一个用户与角色"); + } + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs index c56fb805..d20a8ac3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Cookie.cs @@ -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); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs new file mode 100644 index 00000000..df0bc994 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HoyolabHttpClientExtensions.cs @@ -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; + +/// +/// Hoyolab HttpClient 拓展 +/// +internal static class HoyolabHttpClientExtensions +{ + /// + /// 设置用户的 Cookie + /// + /// http客户端 + /// 实体用户 + /// Cookie类型 + /// 客户端 + 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; + } + + /// + /// 设置Referer + /// + /// http客户端 + /// 用户 + /// 客户端 + internal static HttpClient SetReferer(this HttpClient httpClient, string referer) + { + httpClient.DefaultRequestHeaders.Set("Referer", referer); + return httpClient; + } + + /// + /// 设置验证流水号 + /// + /// http客户端 + /// 验证流水号 + /// 客户端 + internal static HttpClient SetXrpcChallenge(this HttpClient httpClient, string challenge) + { + httpClient.DefaultRequestHeaders.Set("x-rpc-challenge", challenge); + return httpClient; + } + + /// + /// 设置头 + /// + /// http客户端 + /// 键 + /// 值 + /// 客户端 + internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value) + { + httpClient.DefaultRequestHeaders.Set(key, value); + return httpClient; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs index 20e217dc..a4dc536b 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/CalculateClient.cs @@ -39,7 +39,7 @@ internal class CalculateClient /// 差异 /// 取消令牌 /// 消耗结果 - public async Task ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token) + public async Task ComputeAsync(User user, AvatarPromotionDelta delta, CancellationToken token = default) { Response? resp = await httpClient .SetUser(user, CookieType.CookieToken) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ElementAttributeId.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ElementAttributeId.cs index 4b117fa2..063c8685 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ElementAttributeId.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ElementAttributeId.cs @@ -8,6 +8,11 @@ namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; /// public enum ElementAttributeId { + /// + /// 无 + /// + None = 0, + /// /// 火元素 /// diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculable.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculable.cs new file mode 100644 index 00000000..eecffd54 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculable.cs @@ -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; + +/// +/// 可计算源 +/// +public interface ICalculable +{ + /// + /// 名称 + /// + string Name { get; } + + /// + /// 图标 + /// + Uri Icon { get; } + + /// + /// 星级 + /// + ItemQuality Quality { get; } + + /// + /// 当前等级 + /// + int LevelCurrent { get; set; } + + /// + /// 目标等级 + /// + int LevelTarget { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableAvatar.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableAvatar.cs new file mode 100644 index 00000000..1bcd9141 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableAvatar.cs @@ -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; + +/// +/// 可计算的角色 +/// +public interface ICalculableAvatar : ICalculable +{ + /// + /// 角色Id + /// + AvatarId AvatarId { get; } + + /// + /// 最小等级 + /// + int LevelMin { get; } + + /// + /// 最大等级 + /// + int LevelMax { get; } + + /// + /// 技能组 + /// + IList Skills { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSkill.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSkill.cs new file mode 100644 index 00000000..7c425ab4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSkill.cs @@ -0,0 +1,25 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; + +/// +/// 可计算的技能 +/// +public interface ICalculableSkill : ICalculable +{ + /// + /// 技能组Id + /// + int GruopId { get; } + + /// + /// 最小等级 + /// + int LevelMin { get; } + + /// + /// 最大等级 + /// + int LevelMax { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSource.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSource.cs new file mode 100644 index 00000000..356529a2 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableSource.cs @@ -0,0 +1,18 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; + +/// +/// 可计算物品的源 +/// +/// 可计算类型 +public interface ICalculableSource + where T : ICalculable +{ + /// + /// 转换到可计算的对象 + /// + /// 可计算物品 + public T ToCalculable(); +} diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableWeapon.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableWeapon.cs new file mode 100644 index 00000000..e865e25c --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ICalculableWeapon.cs @@ -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; + +/// +/// 可计算的武器 +/// +public interface ICalculableWeapon : ICalculable +{ + /// + /// 武器Id + /// + WeaponId WeaponId { get; } + + /// + /// 最小等级 + /// + int LevelMin { get; } + + /// + /// 最大等级 + /// + int LevelMax { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/Item.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/Item.cs index 302e30eb..a2abf2bb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/Item.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/Item.cs @@ -37,6 +37,6 @@ public class Item /// /// 物品星级 仅有家具为有效值 /// - [JsonPropertyName("num")] + [JsonPropertyName("level")] public ItemQuality Level { get; set; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ItemHelper.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ItemHelper.cs new file mode 100644 index 00000000..931aaaf8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/ItemHelper.cs @@ -0,0 +1,51 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; + +/// +/// 物品帮助类 +/// +public static class ItemHelper +{ + /// + /// 合并两个物品列表 + /// + /// 左列表 + /// 右列表 + /// 合并且排序好的列表 + public static List Merge(List? left, List? right) + { + if (left == null && right == null) + { + return new(0); + } + + if (right == null) + { + return left!; + } + + if (left == null) + { + return right!; + } + + List 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; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Web/HttpClientExtensions.cs similarity index 56% rename from src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs rename to src/Snap.Hutao/Snap.Hutao/Web/HttpClientExtensions.cs index c4d93f63..cb568140 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/HttpClientExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/HttpClientExtensions.cs @@ -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; /// /// 扩展 @@ -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; + } } /// @@ -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; + } } /// @@ -74,78 +96,13 @@ internal static class HttpClientExtensions { return null; } - } - - /// - /// 设置用户的 Cookie - /// - /// http客户端 - /// 实体用户 - /// Cookie类型 - /// 客户端 - 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; - } - - /// - /// 设置Referer - /// - /// http客户端 - /// 用户 - /// 客户端 - internal static HttpClient SetReferer(this HttpClient httpClient, string referer) - { - httpClient.DefaultRequestHeaders.Set("Referer", referer); - return httpClient; - } - - /// - /// 设置验证流水号 - /// - /// http客户端 - /// 验证流水号 - /// 客户端 - internal static HttpClient SetXrpcChallenge(this HttpClient httpClient, string challenge) - { - httpClient.DefaultRequestHeaders.Set("x-rpc-challenge", challenge); - return httpClient; - } - - /// - /// 设置头 - /// - /// http客户端 - /// 键 - /// 值 - /// 客户端 - internal static HttpClient SetHeader(this HttpClient httpClient, string key, string value) - { - httpClient.DefaultRequestHeaders.Set(key, value); - return httpClient; } } diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs index 033b856b..522ac6e3 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient.cs @@ -101,7 +101,7 @@ internal class HomaClient public async Task> GetAvatarAttendanceRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AttendanceRate", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -116,7 +116,7 @@ internal class HomaClient public async Task> GetAvatarUtilizationRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/UtilizationRate", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -131,7 +131,7 @@ internal class HomaClient public async Task> GetAvatarCollocationsAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/AvatarCollocation", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -146,7 +146,7 @@ internal class HomaClient public async Task> GetWeaponCollocationsAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Weapon/WeaponCollocation", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Weapon/WeaponCollocation", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -161,7 +161,7 @@ internal class HomaClient public async Task> GetAvatarHoldingRatesAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Avatar/HoldingRate", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); @@ -176,7 +176,7 @@ internal class HomaClient public async Task> GetTeamCombinationsAsync(CancellationToken token = default) { Response>? resp = await httpClient - .GetFromJsonAsync>>($"{HutaoAPI}/Statistics/Team/Combination", token) + .TryCatchGetFromJsonAsync>>($"{HutaoAPI}/Statistics/Team/Combination", options, logger, token) .ConfigureAwait(false); return EnumerableExtension.EmptyIfNull(resp?.Data); diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs index d6b3af74..731fc333 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hutao/HomaClient2.cs @@ -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; diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs index a99707f4..6f9e7ff2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Response/KnownReturnCode.cs @@ -53,6 +53,11 @@ public enum KnownReturnCode : int /// PleaseOpenInBbsApp = -1104, + /// + /// 天赋等级超出限制~ + /// + SkillLevelLimitExcceed = -1009, + /// /// 登录信息已失效,请重新登录 ///