From 559ae250bd34f17039c7e02c45eb583107ceca90 Mon Sep 17 00:00:00 2001 From: Lightczx <1686188646@qq.com> Date: Thu, 7 Dec 2023 17:25:48 +0800 Subject: [PATCH] cultivation wip [skip ci] --- .../BaseClassLibrary/LinqTest.cs | 34 + .../Extension/EnumerableExtension.List.cs | 2 +- .../Snap.Hutao/Extension/SpanExtension.cs | 6 - ...CultivateEntryLevelInformation.Designer.cs | 612 ++++++++++++++++++ ...85530_AddCultivateEntryLevelInformation.cs | 56 ++ .../Migrations/AppDbContextModelSnapshot.cs | 61 +- .../Entity/CultivateEntryLevelInformation.cs | 60 ++ .../Snap.Hutao/Model/Entity/CultivateItem.cs | 4 +- .../Model/Entity/Database/AppDbContext.cs | 53 +- .../Cultivation/CultivationDbService.cs | 18 + .../Cultivation/CultivationMetadataContext.cs | 18 + .../Service/Cultivation/CultivationService.cs | 32 +- .../Cultivation/ICultivationDbService.cs | 2 + .../ICultivationMetadataContext.cs | 14 + .../Cultivation/ICultivationService.cs | 43 +- .../Service/Cultivation/LevelInformation.cs | 59 ++ .../ContextAbstraction/IMetadataContext.cs | 6 + .../IMetadataDictionaryIdAvatarSource.cs | 11 + .../IMetadataDictionaryIdMaterialSource.cs | 12 + .../IMetadataDictionaryIdWeaponSource.cs | 11 + .../IMetadataListMaterialSource.cs | 11 + .../MetadataServiceContextExtension.cs | 68 ++ ...CultivatePromotionDeltaBatchDialog.xaml.cs | 27 +- .../AvatarProperty/AvatarPropertyViewModel.cs | 94 +-- .../Cultivation/CultivationViewModel.cs | 99 ++- .../Cultivation/StatisticsCultivateItem.cs | 2 +- .../Snap.Hutao/ViewModel/TestViewModel.cs | 18 +- .../ViewModel/Wiki/WikiAvatarViewModel.cs | 5 +- .../ViewModel/Wiki/WikiWeaponViewModel.cs | 3 +- .../Event/Calculate/AvatarPromotionDelta.cs | 38 ++ .../Takumi/Event/Calculate/CalculateClient.cs | 4 +- .../Hoyolab/Takumi/Event/Calculate/Item.cs | 4 +- .../Takumi/Event/Calculate/ItemHelper.cs | 18 +- 33 files changed, 1252 insertions(+), 253 deletions(-) create mode 100644 src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/LinqTest.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.Designer.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntryLevelInformation.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationMetadataContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationMetadataContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Cultivation/LevelInformation.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataContext.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAvatarSource.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdMaterialSource.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdWeaponSource.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListMaterialSource.cs create mode 100644 src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs diff --git a/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/LinqTest.cs b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/LinqTest.cs new file mode 100644 index 00000000..e5f4f20b --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Test/BaseClassLibrary/LinqTest.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Snap.Hutao.Test.BaseClassLibrary; + +[TestClass] +public sealed class LinqTest +{ + [TestMethod] + [ExpectedException(typeof(InvalidOperationException))] + public void LinqOrderByWithWrapperStruct() + { + List list = [1, 5, 2, 6, 3, 7, 4, 8]; + string result = string.Join(", ", list.OrderBy(i => i).Select(i => i.Value)); + + Console.WriteLine(result); + } + + private readonly struct MyUInt32 + { + public readonly uint Value; + + public MyUInt32(uint value) + { + Value = value; + } + + public static implicit operator MyUInt32(uint value) + { + return new(value); + } + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs index 68b066d5..d1519d60 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/EnumerableExtension.List.cs @@ -207,4 +207,4 @@ internal static partial class EnumerableExtension list.Sort((left, right) => keySelector(right).CompareTo(keySelector(left))); return list; } -} +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs index 67435eb5..9f60a0fd 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs @@ -2,7 +2,6 @@ // Licensed under the MIT license. using System.Numerics; -using System.Runtime.InteropServices; namespace Snap.Hutao.Extension; @@ -75,9 +74,4 @@ internal static class SpanExtension return unchecked((byte)(sum / count)); } - - public static Span AsSpan(this List list) - { - return CollectionsMarshal.AsSpan(list); - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.Designer.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.Designer.cs new file mode 100644 index 00000000..c3e5e810 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.Designer.cs @@ -0,0 +1,612 @@ +// +using System; +using Microsoft.EntityFrameworkCore; +using Microsoft.EntityFrameworkCore.Infrastructure; +using Microsoft.EntityFrameworkCore.Migrations; +using Microsoft.EntityFrameworkCore.Storage.ValueConversion; +using Snap.Hutao.Model.Entity.Database; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + [DbContext(typeof(AppDbContext))] + [Migration("20231207085530_AddCultivateEntryLevelInformation")] + partial class AddCultivateEntryLevelInformation + { + /// + protected override void BuildTargetModel(ModelBuilder modelBuilder) + { +#pragma warning disable 612, 618 + modelBuilder.HasAnnotation("ProductVersion", "8.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("CalculatorRefreshTime") + .HasColumnType("TEXT"); + + b.Property("GameRecordRefreshTime") + .HasColumnType("TEXT"); + + b.Property("Info") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("ShowcaseRefreshTime") + .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.CultivateEntryLevelInformation", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("AvatarLevelTo") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("TEXT"); + + b.Property("SkillALevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillALevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelTo") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelTo") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("EntryId"); + + b.ToTable("cultivate_entry_level_informations"); + }); + + 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("IsFinished") + .HasColumnType("INTEGER"); + + 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("RefreshTime") + .HasColumnType("TEXT"); + + b.Property("ResinNotifySuppressed") + .HasColumnType("INTEGER"); + + b.Property("ResinNotifyThreshold") + .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.SpiralAbyssEntry", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("ScheduleId") + .HasColumnType("INTEGER"); + + b.Property("SpiralAbyss") + .IsRequired() + .HasColumnType("TEXT"); + + b.Property("Uid") + .IsRequired() + .HasColumnType("TEXT"); + + b.HasKey("InnerId"); + + b.ToTable("spiral_abysses"); + }); + + 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("CookieTokenLastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("Fingerprint") + .HasColumnType("TEXT"); + + b.Property("FingerprintLastUpdateTime") + .HasColumnType("TEXT"); + + b.Property("IsOversea") + .HasColumnType("INTEGER"); + + b.Property("IsSelected") + .HasColumnType("INTEGER"); + + b.Property("LToken") + .HasColumnType("TEXT") + .HasColumnName("Ltoken"); + + b.Property("Mid") + .HasColumnType("TEXT"); + + b.Property("SToken") + .HasColumnType("TEXT") + .HasColumnName("Stoken"); + + 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.CultivateEntryLevelInformation", 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.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() + .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"); + }); +#pragma warning restore 612, 618 + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.cs new file mode 100644 index 00000000..37a9b8f8 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/20231207085530_AddCultivateEntryLevelInformation.cs @@ -0,0 +1,56 @@ +// +using System; +using Microsoft.EntityFrameworkCore.Migrations; + +#nullable disable + +namespace Snap.Hutao.Migrations +{ + /// + public partial class AddCultivateEntryLevelInformation : Migration + { + /// + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.CreateTable( + name: "cultivate_entry_level_informations", + columns: table => new + { + InnerId = table.Column(type: "TEXT", nullable: false), + EntryId = table.Column(type: "TEXT", nullable: false), + AvatarLevelFrom = table.Column(type: "INTEGER", nullable: false), + AvatarLevelTo = table.Column(type: "INTEGER", nullable: false), + SkillALevelFrom = table.Column(type: "INTEGER", nullable: false), + SkillALevelTo = table.Column(type: "INTEGER", nullable: false), + SkillELevelFrom = table.Column(type: "INTEGER", nullable: false), + SkillELevelTo = table.Column(type: "INTEGER", nullable: false), + SkillQLevelFrom = table.Column(type: "INTEGER", nullable: false), + SkillQLevelTo = table.Column(type: "INTEGER", nullable: false), + WeaponLevelFrom = table.Column(type: "INTEGER", nullable: false), + WeaponLevelTo = table.Column(type: "INTEGER", nullable: false) + }, + constraints: table => + { + table.PrimaryKey("PK_cultivate_entry_level_informations", x => x.InnerId); + table.ForeignKey( + name: "FK_cultivate_entry_level_informations_cultivate_entries_EntryId", + column: x => x.EntryId, + principalTable: "cultivate_entries", + principalColumn: "InnerId", + onDelete: ReferentialAction.Cascade); + }); + + migrationBuilder.CreateIndex( + name: "IX_cultivate_entry_level_informations_EntryId", + table: "cultivate_entry_level_informations", + column: "EntryId"); + } + + /// + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.DropTable( + name: "cultivate_entry_level_informations"); + } + } +} diff --git a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs index 6c6a556f..9129b6e7 100644 --- a/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs +++ b/src/Snap.Hutao/Snap.Hutao/Migrations/AppDbContextModelSnapshot.cs @@ -113,13 +113,59 @@ namespace Snap.Hutao.Migrations b.ToTable("cultivate_entries"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b => + { + b.Property("InnerId") + .ValueGeneratedOnAdd() + .HasColumnType("TEXT"); + + b.Property("AvatarLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("AvatarLevelTo") + .HasColumnType("INTEGER"); + + b.Property("EntryId") + .HasColumnType("TEXT"); + + b.Property("SkillALevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillALevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillELevelTo") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("SkillQLevelTo") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelFrom") + .HasColumnType("INTEGER"); + + b.Property("WeaponLevelTo") + .HasColumnType("INTEGER"); + + b.HasKey("InnerId"); + + b.HasIndex("EntryId"); + + b.ToTable("cultivate_entry_level_informations"); + }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b => { b.Property("InnerId") .ValueGeneratedOnAdd() .HasColumnType("TEXT"); - b.Property("Count") + b.Property("Count") .HasColumnType("INTEGER"); b.Property("EntryId") @@ -128,7 +174,7 @@ namespace Snap.Hutao.Migrations b.Property("IsFinished") .HasColumnType("INTEGER"); - b.Property("ItemId") + b.Property("ItemId") .HasColumnType("INTEGER"); b.HasKey("InnerId"); @@ -481,6 +527,17 @@ namespace Snap.Hutao.Migrations b.Navigation("Project"); }); + modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", 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.CultivateItem", b => { b.HasOne("Snap.Hutao.Model.Entity.CultivateEntry", "Entry") diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntryLevelInformation.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntryLevelInformation.cs new file mode 100644 index 00000000..6e71d625 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateEntryLevelInformation.cs @@ -0,0 +1,60 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; +using Snap.Hutao.Service.Cultivation; +using System.ComponentModel.DataAnnotations; +using System.ComponentModel.DataAnnotations.Schema; + +namespace Snap.Hutao.Model.Entity; + +[Table("cultivate_entry_level_informations")] +internal sealed class CultivateEntryLevelInformation : IMappingFrom +{ + [Key] + [DatabaseGenerated(DatabaseGeneratedOption.Identity)] + public Guid InnerId { get; set; } + + public Guid EntryId { get; set; } + + [ForeignKey(nameof(EntryId))] + public CultivateEntry? Entry { get; set; } + + public uint AvatarLevelFrom { get; set; } + + public uint AvatarLevelTo { get; set; } + + public uint SkillALevelFrom { get; set; } + + public uint SkillALevelTo { get; set; } + + public uint SkillELevelFrom { get; set; } + + public uint SkillELevelTo { get; set; } + + public uint SkillQLevelFrom { get; set; } + + public uint SkillQLevelTo { get; set; } + + public uint WeaponLevelFrom { get; set; } + + public uint WeaponLevelTo { get; set; } + + public static CultivateEntryLevelInformation From(Guid entryId, LevelInformation source) + { + return new() + { + EntryId = entryId, + AvatarLevelFrom = source.AvatarLevelFrom, + AvatarLevelTo = source.AvatarLevelTo, + SkillALevelFrom = source.SkillALevelFrom, + SkillALevelTo = source.SkillALevelTo, + SkillELevelFrom = source.SkillELevelFrom, + SkillELevelTo = source.SkillELevelTo, + SkillQLevelFrom = source.SkillQLevelFrom, + SkillQLevelTo = source.SkillQLevelTo, + WeaponLevelFrom = source.WeaponLevelFrom, + WeaponLevelTo = source.WeaponLevelTo, + }; + } +} \ 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 af0ffec5..eef1e917 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/CultivateItem.cs @@ -35,12 +35,12 @@ internal sealed class CultivateItem : IDbMappingForeignKeyFrom /// 物品 Id /// - public int ItemId { get; set; } + public uint ItemId { get; set; } /// /// 物品个数 /// - public int Count { get; set; } + public uint Count { get; set; } /// /// 是否完成此项 diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs index 8d3b18e3..fc1146be 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/Database/AppDbContext.cs @@ -37,89 +37,40 @@ internal sealed class AppDbContext : DbContext logger.LogInformation("{Name}[{Id}] created", nameof(AppDbContext), ContextId); } - /// - /// 设置 - /// public DbSet Settings { get; set; } = default!; - /// - /// 用户 - /// public DbSet Users { get; set; } = default!; - /// - /// 成就 - /// public DbSet Achievements { get; set; } = default!; - /// - /// 成就存档 - /// public DbSet AchievementArchives { get; set; } = default!; - /// - /// 卡池数据 - /// public DbSet GachaItems { get; set; } = default!; - /// - /// 卡池存档 - /// public DbSet GachaArchives { get; set; } = default!; - /// - /// 角色信息 - /// public DbSet AvatarInfos { get; set; } = default!; - /// - /// 游戏内账号 - /// public DbSet GameAccounts { get; set; } = default!; - /// - /// 实时便笺 - /// public DbSet DailyNotes { get; set; } = default!; - /// - /// 对象缓存 - /// public DbSet ObjectCache { get; set; } = default!; - /// - /// 培养计划 - /// public DbSet CultivateProjects { get; set; } = default!; - /// - /// 培养入口点 - /// public DbSet CultivateEntries { get; set; } = default!; - /// - /// 培养消耗物品 - /// + public DbSet LevelInformations { get; set; } = default!; + public DbSet CultivateItems { get; set; } = default!; - /// - /// 背包内物品 - /// public DbSet InventoryItems { get; set; } = default!; - /// - /// 背包内武器 - /// public DbSet InventoryWeapons { get; set; } = default!; - /// - /// 背包内圣遗物 - /// public DbSet InventoryReliquaries { get; set; } = default!; - /// - /// 深渊记录 - /// public DbSet SpiralAbysses { get; set; } = default!; /// diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs index cba52ed6..77e4516d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationDbService.cs @@ -161,4 +161,22 @@ internal sealed partial class CultivationDbService : ICultivationDbService return appDbContext.CultivateProjects.AsNoTracking().ToObservableCollection(); } } + + public async ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.LevelInformations.ExecuteDeleteWhereAsync(l => l.EntryId == entryId).ConfigureAwait(false); + } + } + + public async ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation) + { + using (IServiceScope scope = serviceProvider.CreateScope()) + { + AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); + await appDbContext.LevelInformations.AddAndSaveAsync(levelInformation).ConfigureAwait(false); + } + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationMetadataContext.cs new file mode 100644 index 00000000..35996d60 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationMetadataContext.cs @@ -0,0 +1,18 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Cultivation; + +internal sealed class CultivationMetadataContext : ICultivationMetadataContext +{ + public List Materials { get; set; } = default!; + + public Dictionary IdMaterialMap { get; set; } = default!; + + public Dictionary IdAvatarMap { get; set; } = default!; + + public Dictionary IdWeaponMap { get; set; } = default!; +} \ 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 67b1ecb8..5f429153 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -7,8 +7,8 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Database; using Snap.Hutao.Model.Entity.Primitive; using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Inventroy; +using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.ViewModel.Cultivation; using System.Collections.ObjectModel; @@ -29,7 +29,7 @@ internal sealed partial class CultivationService : ICultivationService private readonly ITaskContext taskContext; /// - public List GetInventoryItemViews(CultivateProject cultivateProject, List metadata, ICommand saveCommand) + public List GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand) { using (IServiceScope scope = serviceProvider.CreateScope()) { @@ -39,7 +39,7 @@ internal sealed partial class CultivationService : ICultivationService List entities = cultivationDbService.GetInventoryItemListByProjectId(projectId); List results = []; - foreach (Material meta in metadata.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value)) + foreach (Material meta in context.EnumerateInventroyMaterial()) { InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id); results.Add(new(entity, meta, saveCommand)); @@ -50,11 +50,7 @@ internal sealed partial class CultivationService : ICultivationService } /// - public async ValueTask> GetCultivateEntriesAsync( - CultivateProject cultivateProject, - List materials, - Dictionary idAvatarMap, - Dictionary idWeaponMap) + public async ValueTask> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context) { await taskContext.SwitchToBackgroundAsync(); List entries = await cultivationDbService @@ -67,13 +63,13 @@ internal sealed partial class CultivationService : ICultivationService List entryItems = []; foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false)) { - entryItems.Add(new(item, materials.Single(m => m.Id == item.ItemId))); + entryItems.Add(new(item, context.GetMaterial(item.ItemId))); } Item itemBase = entry.Type switch { - CultivateType.AvatarAndSkill => idAvatarMap[entry.Id].ToItem(), - CultivateType.Weapon => idWeaponMap[entry.Id].ToItem(), + CultivateType.AvatarAndSkill => context.GetAvatar(entry.Id).ToItem(), + CultivateType.Weapon => context.GetWeapon(entry.Id).ToItem(), // TODO: support furniture calc _ => default!, @@ -89,9 +85,7 @@ internal sealed partial class CultivationService : ICultivationService /// public async ValueTask> GetStatisticsCultivateItemCollectionAsync( - CultivateProject cultivateProject, - List materials, - CancellationToken token) + CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token) { await taskContext.SwitchToBackgroundAsync(); List resultItems = []; @@ -115,7 +109,7 @@ internal sealed partial class CultivationService : ICultivationService } else { - resultItems.Add(new(materials.Single(m => m.Id == item.ItemId), item)); + resultItems.Add(new(context.GetMaterial(item.ItemId), item)); } } } @@ -158,7 +152,7 @@ internal sealed partial class CultivationService : ICultivationService } /// - public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items) + public async ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation) { if (items.Count == 0) { @@ -188,8 +182,12 @@ internal sealed partial class CultivationService : ICultivationService } Guid entryId = entry.InnerId; - await cultivationDbService.RemoveCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false); + await cultivationDbService.RemoveLevelInformationByEntryIdAsync(entryId).ConfigureAwait(false); + CultivateEntryLevelInformation entryLevelInformation = CultivateEntryLevelInformation.From(entryId, levelInformation); + await cultivationDbService.AddLevelInformationAsync(entryLevelInformation).ConfigureAwait(false); + + await cultivationDbService.RemoveCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false); IEnumerable toAdd = items.Select(item => CultivateItem.From(entryId, item)); await cultivationDbService.AddCultivateItemRangeAsync(toAdd).ConfigureAwait(false); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs index ffb2ed1f..5c1fca8c 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationDbService.cs @@ -35,4 +35,6 @@ internal interface ICultivationDbService void UpdateCultivateItem(CultivateItem item); ValueTask UpdateCultivateItemAsync(CultivateItem item); + ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId); + ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationMetadataContext.cs new file mode 100644 index 00000000..43c05dfb --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationMetadataContext.cs @@ -0,0 +1,14 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Service.Metadata.ContextAbstraction; + +namespace Snap.Hutao.Service.Cultivation; + +internal interface ICultivationMetadataContext : IMetadataContext, + IMetadataListMaterialSource, + IMetadataDictionaryIdMaterialSource, + IMetadataDictionaryIdAvatarSource, + IMetadataDictionaryIdWeaponSource +{ +} \ 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 2b690bb0..2beb88ab 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -3,8 +3,6 @@ using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Primitive; using Snap.Hutao.ViewModel.Cultivation; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; using System.Collections.ObjectModel; @@ -27,38 +25,12 @@ internal interface ICultivationService /// ObservableCollection ProjectCollection { get; } - /// - /// 获取绑定用的养成列表 - /// - /// 养成计划 - /// 材料 - /// Id 角色映射 - /// Id 武器映射 - /// 绑定用的养成列表 - ValueTask> GetCultivateEntriesAsync( - CultivateProject cultivateProject, - List materials, - Dictionary idAvatarMap, - Dictionary idWeaponMap); + ValueTask> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context); - /// - /// 获取物品列表 - /// - /// 养成计划 - /// 元数据 - /// 保存命令 - /// 物品列表 - List GetInventoryItemViews(CultivateProject cultivateProject, List metadata, ICommand saveCommand); + List GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand); - /// - /// 异步获取统计物品列表 - /// - /// 养成计划 - /// 材料 - /// 取消令牌 - /// 统计物品列表 ValueTask> GetStatisticsCultivateItemCollectionAsync( - CultivateProject cultivateProject, List materials, CancellationToken token); + CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token); /// /// 删除养成清单 @@ -74,14 +46,7 @@ internal interface ICultivationService /// 任务 ValueTask RemoveProjectAsync(CultivateProject project); - /// - /// 异步保存养成物品 - /// - /// 类型 - /// 主Id - /// 待存物品 - /// 是否保存成功 - ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items); + ValueTask SaveConsumptionAsync(CultivateType type, uint itemId, List items, LevelInformation levelInformation); /// /// 保存养成物品状态 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/LevelInformation.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/LevelInformation.cs new file mode 100644 index 00000000..13a5f527 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/LevelInformation.cs @@ -0,0 +1,59 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Core.Abstraction; +using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; + +namespace Snap.Hutao.Service.Cultivation; + +internal sealed class LevelInformation : IMappingFrom +{ + public uint AvatarLevelFrom { get; private set; } + + public uint AvatarLevelTo { get; private set; } + + public uint SkillALevelFrom { get; private set; } + + public uint SkillALevelTo { get; private set; } + + public uint SkillELevelFrom { get; private set; } + + public uint SkillELevelTo { get; private set; } + + public uint SkillQLevelFrom { get; private set; } + + public uint SkillQLevelTo { get; private set; } + + public uint WeaponLevelFrom { get; private set; } + + public uint WeaponLevelTo { get; private set; } + + public static LevelInformation From(AvatarPromotionDelta delta) + { + LevelInformation levelInformation = new(); + + if (delta.AvatarId != 0U) + { + levelInformation.AvatarLevelFrom = delta.AvatarLevelCurrent; + levelInformation.AvatarLevelTo = delta.AvatarLevelTarget; + } + + if (delta.SkillList is [PromotionDelta skillA, PromotionDelta skillE, PromotionDelta skillQ, ..]) + { + levelInformation.SkillALevelFrom = skillA.LevelCurrent; + levelInformation.SkillALevelTo = skillA.LevelTarget; + levelInformation.SkillELevelFrom = skillE.LevelCurrent; + levelInformation.SkillELevelTo = skillE.LevelTarget; + levelInformation.SkillQLevelFrom = skillQ.LevelCurrent; + levelInformation.SkillQLevelTo = skillQ.LevelTarget; + } + + if (delta.Weapon is { } weapon) + { + levelInformation.WeaponLevelFrom = weapon.LevelCurrent; + levelInformation.WeaponLevelTo = weapon.LevelTarget; + } + + return levelInformation; + } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataContext.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataContext.cs new file mode 100644 index 00000000..ca35233e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataContext.cs @@ -0,0 +1,6 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataContext; \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAvatarSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAvatarSource.cs new file mode 100644 index 00000000..d8cb7428 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdAvatarSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdAvatarSource +{ + public Dictionary IdAvatarMap { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdMaterialSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdMaterialSource.cs new file mode 100644 index 00000000..dff1b88e --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdMaterialSource.cs @@ -0,0 +1,12 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdMaterialSource +{ + public Dictionary IdMaterialMap { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdWeaponSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdWeaponSource.cs new file mode 100644 index 00000000..d664ecd5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataDictionaryIdWeaponSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataDictionaryIdWeaponSource +{ + public Dictionary IdWeaponMap { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListMaterialSource.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListMaterialSource.cs new file mode 100644 index 00000000..7a54f01d --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/IMetadataListMaterialSource.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Item; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal interface IMetadataListMaterialSource +{ + public List Materials { get; set; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs new file mode 100644 index 00000000..114b56f5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Service/Metadata/ContextAbstraction/MetadataServiceContextExtension.cs @@ -0,0 +1,68 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Model.Metadata.Avatar; +using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; + +namespace Snap.Hutao.Service.Metadata.ContextAbstraction; + +internal static class MetadataServiceContextExtension +{ + public static async ValueTask GetContextAsync(this IMetadataService metadataService, CancellationToken token = default) + where TContext : IMetadataContext, new() + { + TContext context = new(); + + // List + { + if (context is IMetadataListMaterialSource listMaterialSource) + { + listMaterialSource.Materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); + } + } + + // Dictionary + { + if (context is IMetadataDictionaryIdAvatarSource dictionaryAvatarSource) + { + dictionaryAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); + } + + if (context is IMetadataDictionaryIdMaterialSource dictionaryMaterialSource) + { + dictionaryMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); + } + + if (context is IMetadataDictionaryIdWeaponSource dictionaryWeaponSource) + { + dictionaryWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); + } + } + + return context; + } + +#pragma warning disable SH002 + public static IEnumerable EnumerateInventroyMaterial(this IMetadataListMaterialSource context) + { + return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value); + } + + public static Avatar GetAvatar(this IMetadataDictionaryIdAvatarSource context, AvatarId id) + { + return context.IdAvatarMap[id]; + } + + public static Material GetMaterial(this IMetadataDictionaryIdMaterialSource context, MaterialId id) + { + return context.IdMaterialMap[id]; + } + + public static Weapon GetWeapon(this IMetadataDictionaryIdWeaponSource context, WeaponId id) + { + return context.IdWeaponMap[id]; + } +#pragma warning restore SH002 +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs index eeeb6742..758ca5de 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/View/Dialog/CultivatePromotionDeltaBatchDialog.xaml.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml.Controls; +using Snap.Hutao.Core.Setting; using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; namespace Snap.Hutao.View.Dialog; @@ -25,6 +26,30 @@ internal sealed partial class CultivatePromotionDeltaBatchDialog : ContentDialog await taskContext.SwitchToMainThreadAsync(); ContentDialogResult result = await ShowAsync(); - return new(result == ContentDialogResult.Primary, PromotionDelta); + if (result is not ContentDialogResult.Primary) + { + return new(false, default!); + } + + LocalSetting.Set(SettingKeys.CultivationAvatarLevelCurrent, PromotionDelta.AvatarLevelCurrent); + LocalSetting.Set(SettingKeys.CultivationAvatarLevelTarget, PromotionDelta.AvatarLevelTarget); + + if (PromotionDelta.SkillList is [PromotionDelta skillA, PromotionDelta skillE, PromotionDelta skillQ, ..]) + { + LocalSetting.Set(SettingKeys.CultivationAvatarSkillACurrent, skillA.LevelCurrent); + LocalSetting.Set(SettingKeys.CultivationAvatarSkillATarget, skillA.LevelTarget); + LocalSetting.Set(SettingKeys.CultivationAvatarSkillECurrent, skillE.LevelCurrent); + LocalSetting.Set(SettingKeys.CultivationAvatarSkillETarget, skillE.LevelTarget); + LocalSetting.Set(SettingKeys.CultivationAvatarSkillQCurrent, skillQ.LevelCurrent); + LocalSetting.Set(SettingKeys.CultivationAvatarSkillQTarget, skillQ.LevelTarget); + } + + if (PromotionDelta.Weapon is { } weapon) + { + LocalSetting.Set(SettingKeys.CultivationWeapon90LevelCurrent, weapon.LevelCurrent); + LocalSetting.Set(SettingKeys.CultivationWeapon90LevelTarget, weapon.LevelTarget); + } + + return new(true, PromotionDelta); } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs index cf249276..e68a7dbf 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/AvatarProperty/AvatarPropertyViewModel.cs @@ -169,61 +169,61 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I if (userService.Current is null) { infoBarService.Warning(SH.MustSelectUserAndUid); + return; } - else + + if (avatar.Weapon is null) { - if (avatar.Weapon is null) - { - infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull); - return; - } + infoBarService.Warning(SH.ViewModelAvatarPropertyCalculateWeaponNull); + return; + } - CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable()); - CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); - (bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); + CalculableOptions options = new(avatar.ToCalculable(), avatar.Weapon.ToCalculable()); + CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); + (bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); - if (!isOk) - { - return; - } + if (!isOk) + { + return; + } - Response consumptionResponse = await calculatorClient - .ComputeAsync(userService.Current.Entity, delta) + Response consumptionResponse = await calculatorClient + .ComputeAsync(userService.Current.Entity, delta) + .ConfigureAwait(false); + + if (!consumptionResponse.IsOk()) + { + return; + } + + CalculatorConsumption consumption = consumptionResponse.Data; + LevelInformation levelInformation = LevelInformation.From(delta); + + List items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); + bool avatarSaved = await cultivationService + .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items, levelInformation) + .ConfigureAwait(false); + + try + { + // Take a hot path if avatar is not saved. + bool avatarAndWeaponSaved = avatarSaved && await cultivationService + .SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull(), levelInformation) .ConfigureAwait(false); - if (!consumptionResponse.IsOk()) + if (avatarAndWeaponSaved) { - return; + infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); } - - CalculatorConsumption consumption = consumptionResponse.Data; - - List items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); - bool avatarSaved = await cultivationService - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) - .ConfigureAwait(false); - - try + else { - // take a hot path if avatar is not saved. - bool avatarAndWeaponSaved = avatarSaved && await cultivationService - .SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull()) - .ConfigureAwait(false); - - if (avatarAndWeaponSaved) - { - infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess); - } - else - { - infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); - } - } - catch (Core.ExceptionService.UserdataCorruptedException ex) - { - infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); + infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning); } } + catch (Core.ExceptionService.UserdataCorruptedException ex) + { + infoBarService.Error(ex, SH.ViewModelCultivationAddWarning); + } } [Command("BatchCultivateCommand")] @@ -274,8 +274,11 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I continue; } + // TODO: handle this correctly. + // We should always create a new baseline each time. baseline.Weapon.Id = avatar.Weapon.Id; baseline.Weapon.LevelCurrent = Math.Min(avatar.Weapon.LevelNumber, baseline.Weapon.LevelTarget); + baseline.Weapon.LevelTarget = Math.Min(avatar.Weapon.MaxLevel, baseline.Weapon.LevelTarget); Response consumptionResponse = await calculatorClient .ComputeAsync(userService.Current.Entity, baseline) @@ -289,17 +292,18 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I else { CalculatorConsumption consumption = consumptionResponse.Data; + LevelInformation levelInformation = LevelInformation.From(baseline); List items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); bool avatarSaved = await cultivationService - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) + .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items, levelInformation) .ConfigureAwait(false); try { // take a hot path if avatar is not saved. bool avatarAndWeaponSaved = avatarSaved && await cultivationService - .SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull()) + .SaveConsumptionAsync(CultivateType.Weapon, avatar.Weapon.Id, consumption.WeaponConsume.EmptyIfNull(), levelInformation) .ConfigureAwait(false); if (avatarAndWeaponSaved) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs index 19b51d44..fa7424cd 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/CultivationViewModel.cs @@ -3,10 +3,9 @@ using Snap.Hutao.Factory.ContentDialog; using Snap.Hutao.Model.Entity; -using Snap.Hutao.Model.Metadata.Item; -using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Cultivation; using Snap.Hutao.Service.Metadata; +using Snap.Hutao.Service.Metadata.ContextAbstraction; using Snap.Hutao.Service.Navigation; using Snap.Hutao.Service.Notification; using Snap.Hutao.View.Dialog; @@ -38,14 +37,8 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel private ObservableCollection? cultivateEntries; private ObservableCollection? statisticsItems; - /// - /// 项目 - /// public ObservableCollection? Projects { get => projects; set => SetProperty(ref projects, value); } - /// - /// 当前选中的计划 - /// public CultivateProject? SelectedProject { get => selectedProject; set @@ -58,19 +51,10 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel } } - /// - /// 物品列表 - /// public List? InventoryItems { get => inventoryItems; set => SetProperty(ref inventoryItems, value); } - /// - /// 养成列表 - /// public ObservableCollection? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); } - /// - /// 统计列表 - /// public ObservableCollection? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); } protected override async ValueTask InitializeUIAsync() @@ -95,60 +79,61 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel CultivateProjectDialog dialog = await contentDialogFactory.CreateInstanceAsync().ConfigureAwait(false); (bool isOk, CultivateProject project) = await dialog.CreateProjectAsync().ConfigureAwait(false); - if (isOk) + if (!isOk) { - ProjectAddResult result = await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false); + return; + } - switch (result) - { - case ProjectAddResult.Added: - infoBarService.Success(SH.ViewModelCultivationProjectAdded); - await taskContext.SwitchToMainThreadAsync(); - SelectedProject = project; - break; - case ProjectAddResult.InvalidName: - infoBarService.Information(SH.ViewModelCultivationProjectInvalidName); - break; - case ProjectAddResult.AlreadyExists: - infoBarService.Information(SH.ViewModelCultivationProjectAlreadyExists); - break; - default: - throw Must.NeverHappen(); - } + switch (await cultivationService.TryAddProjectAsync(project).ConfigureAwait(false)) + { + case ProjectAddResult.Added: + infoBarService.Success(SH.ViewModelCultivationProjectAdded); + await taskContext.SwitchToMainThreadAsync(); + SelectedProject = project; + break; + case ProjectAddResult.InvalidName: + infoBarService.Information(SH.ViewModelCultivationProjectInvalidName); + break; + case ProjectAddResult.AlreadyExists: + infoBarService.Information(SH.ViewModelCultivationProjectAlreadyExists); + break; + default: + throw Must.NeverHappen(); } } [Command("RemoveProjectCommand")] private async Task RemoveProjectAsync(CultivateProject? project) { - if (project is not null) + if (project is null) { - await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false); - - await taskContext.SwitchToMainThreadAsync(); - ArgumentNullException.ThrowIfNull(Projects); - SelectedProject = Projects.FirstOrDefault(); + return; } + + await cultivationService.RemoveProjectAsync(project).ConfigureAwait(false); + await taskContext.SwitchToMainThreadAsync(); + ArgumentNullException.ThrowIfNull(Projects); + SelectedProject = Projects.FirstOrDefault(); } private async ValueTask UpdateEntryCollectionAsync(CultivateProject? project) { - if (project is not null) + if (project is null) { - List materials = await metadataService.GetMaterialListAsync().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 taskContext.SwitchToMainThreadAsync(); - CultivateEntries = entries; - InventoryItems = cultivationService.GetInventoryItemViews(project, materials, SaveInventoryItemCommand); - - await UpdateStatisticsItemsAsync().ConfigureAwait(false); + return; } + + CultivationMetadataContext context = await metadataService.GetContextAsync().ConfigureAwait(false); + + ObservableCollection entries = await cultivationService + .GetCultivateEntriesAsync(project, context) + .ConfigureAwait(false); + + await taskContext.SwitchToMainThreadAsync(); + CultivateEntries = entries; + InventoryItems = cultivationService.GetInventoryItemViews(project, context, SaveInventoryItemCommand); + + await UpdateStatisticsItemsAsync().ConfigureAwait(false); } [Command("RemoveEntryCommand")] @@ -194,8 +179,8 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel ObservableCollection statistics; try { - List materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false); - statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, materials, token).ConfigureAwait(false); + CultivationMetadataContext context = await metadataService.GetContextAsync().ConfigureAwait(false); + statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, context, token).ConfigureAwait(false); } catch (OperationCanceledException) { diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs index ea004849..359cb342 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Cultivation/StatisticsCultivateItem.cs @@ -30,7 +30,7 @@ internal sealed class StatisticsCultivateItem /// /// 对应背包物品的个数 /// - public int Count { get; set; } + public uint Count { get; set; } /// /// 对应背包物品的个数 diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index e894f15b..ddd8383f 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -16,10 +16,10 @@ namespace Snap.Hutao.ViewModel; [Injection(InjectAs.Scoped)] internal sealed partial class TestViewModel : Abstraction.ViewModel { - private readonly MainWindow mainWindow; + private readonly HutaoAsAServiceClient homaAsAServiceClient; private readonly IInfoBarService infoBarService; private readonly ITaskContext taskContext; - private readonly HutaoAsAServiceClient homaAsAServiceClient; + private readonly MainWindow mainWindow; private UploadAnnouncement announcement = new(); @@ -39,15 +39,10 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel set => LocalSetting.Set(SettingKeys.OverrideElevationRequirement, value); } - protected override ValueTask InitializeUIAsync() - { - return ValueTask.FromResult(true); - } - [Command("ResetGuideStateCommand")] private static void ResetGuideState() { - LocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, (uint)GuideState.Language); + UnsafeLocalSetting.Set(SettingKeys.Major1Minor7Revision0GuideState, GuideState.Language); } [Command("ExceptionCommand")] @@ -59,11 +54,12 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel [Command("ResetMainWindowSizeCommand")] private void ResetMainWindowSize() { - mainWindow.AppWindow.Resize(new(1280, 720)); + double scale = mainWindow.WindowOptions.GetRasterizationScale(); + mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1280, 720).Scale(scale)); } [Command("UploadAnnouncementCommand")] - private async void UploadAnnouncementAsync() + private async Task UploadAnnouncementAsync() { Web.Response.Response response = await homaAsAServiceClient.UploadAnnouncementAsync(Announcement).ConfigureAwait(false); if (response.IsOk()) @@ -75,7 +71,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel } [Command("CompensationGachaLogServiceTimeCommand")] - private async void CompensationGachaLogServiceTimeAsync() + private async Task CompensationGachaLogServiceTimeAsync() { Web.Response.Response response = await homaAsAServiceClient.GachaLogCompensationAsync(15).ConfigureAwait(false); if (response.IsOk()) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs index 68d0116d..e0b4bad6 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiAvatarViewModel.cs @@ -135,8 +135,6 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel return; } - // ContentDialog must be created by main thread. - await taskContext.SwitchToMainThreadAsync(); CalculableOptions options = new(avatar.ToCalculable(), null); CultivatePromotionDeltaDialog dialog = await contentDialogFactory.CreateInstanceAsync(options).ConfigureAwait(false); (bool isOk, CalculateAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false); @@ -156,11 +154,12 @@ internal sealed partial class WikiAvatarViewModel : Abstraction.ViewModel } CalculateConsumption consumption = consumptionResponse.Data; + LevelInformation levelInformation = LevelInformation.From(delta); List items = CalculateItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume); try { bool saved = await cultivationService - .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items) + .SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items, levelInformation) .ConfigureAwait(false); if (saved) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs index 17544deb..d64196c8 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/Wiki/WikiWeaponViewModel.cs @@ -148,10 +148,11 @@ internal sealed partial class WikiWeaponViewModel : Abstraction.ViewModel } CalculateConsumption consumption = consumptionResponse.Data; + LevelInformation levelInformation = LevelInformation.From(delta); try { bool saved = await cultivationService - .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull()) + .SaveConsumptionAsync(CultivateType.Weapon, weapon.Id, consumption.WeaponConsume.EmptyIfNull(), levelInformation) .ConfigureAwait(false); if (saved) diff --git a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs index 39578208..fd886db4 100644 --- a/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs +++ b/src/Snap.Hutao/Snap.Hutao/Web/Hoyolab/Takumi/Event/Calculate/AvatarPromotionDelta.cs @@ -3,6 +3,7 @@ using Snap.Hutao.Core.Setting; using Snap.Hutao.Model.Primitive; +using Snap.Hutao.ViewModel.AvatarProperty; namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; @@ -62,4 +63,41 @@ internal sealed class AvatarPromotionDelta Weapon = new() { LevelTarget = LocalSetting.Get(SettingKeys.CultivationWeapon90LevelTarget, 90U), }, }; } + + public static bool TryGetNonErrorCopy(AvatarPromotionDelta source, AvatarView avatar, out AvatarPromotionDelta? copy) + { + copy = new() + { + AvatarId = avatar.Id, + AvatarLevelCurrent = Math.Min(avatar.LevelNumber, source.AvatarLevelTarget), + }; + + if (avatar.Skills is [SkillView skillA, SkillView skillE, SkillView skillQ, ..]) + { + copy.SkillList = new(3); + copy.SkillList.Add(new() + { + Id = skillA.GroupId, + LevelCurrent = Math.Min(skillA.LevelNumber, skillA.LevelTarget), + }); + copy.SkillList[0].Id = skillA.GroupId; + copy.SkillList[0].LevelCurrent = Math.Min(skillA.LevelNumber, copy.SkillList[0].LevelTarget); + } + else + { + return false; + } + + target = new() + { + AvatarId = source.AvatarId, + AvatarLevelCurrent = source.AvatarLevelCurrent, + AvatarLevelTarget = source.AvatarLevelTarget, + ElementAttrId = source.ElementAttrId, + SkillList = source.SkillList?.Select(i => i.Clone()).ToList(), + Weapon = source.Weapon?.Clone(), + }; + + return true; + } } \ 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 da9af651..8d08d3be 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 @@ -187,9 +187,9 @@ internal sealed partial class CalculateClient private class IdCount { [JsonPropertyName("cnt")] - public int Count { get; set; } + public uint Count { get; set; } [JsonPropertyName("id")] - public int Id { get; set; } + public uint Id { get; set; } } } \ 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 2ef8573d..ba897985 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 @@ -15,7 +15,7 @@ internal sealed class Item /// Id /// [JsonPropertyName("id")] - public int Id { get; set; } + public uint Id { get; set; } /// /// 名称 @@ -33,7 +33,7 @@ internal sealed class Item /// 数量 /// [JsonPropertyName("num")] - public int Num { get; set; } + public uint Num { get; set; } /// /// 物品星级 仅有家具为有效值 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 index cb79da8c..a27f98b2 100644 --- 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 @@ -1,25 +1,18 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using System.Runtime.InteropServices; + namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate; -/// -/// 物品帮助类 -/// [HighQuality] internal static class ItemHelper { - /// - /// 合并两个物品列表 - /// - /// 左列表 - /// 右列表 - /// 合并且排序好的列表 public static List Merge(List? left, List? right) { if (left.IsNullOrEmpty() && right.IsNullOrEmpty()) { - return new(0); + return []; } if (left.IsNullOrEmpty() && !right.IsNullOrEmpty()) @@ -38,9 +31,10 @@ internal static class ItemHelper List result = new(left.Count + right.Count); result.AddRange(left); - foreach (Item item in right) + foreach (ref readonly Item item in CollectionsMarshal.AsSpan(right)) { - if (result.SingleOrDefault(i => i.Id == item.Id) is { } existed) + uint id = item.Id; + if (result.SingleOrDefault(i => i.Id == id) is { } existed) { existed.Num += item.Num; }