diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs index 4959215d..8aba7d71 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/InventoryItem.cs @@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Entity; /// [HighQuality] [Table("inventory_items")] -internal sealed class InventoryItem : IDbMappingForeignKeyFrom +internal sealed class InventoryItem : IDbMappingForeignKeyFrom, IDbMappingForeignKeyFrom { /// /// 内部Id @@ -56,4 +56,21 @@ internal sealed class InventoryItem : IDbMappingForeignKeyFrom + /// 构造一个新的个数不为0的物品 + /// + /// 项目Id + /// 物品Id + /// 物品个数 + /// 新的个数不为0的物品 + public static InventoryItem From(in Guid projectId, in uint itemId, in uint count) + { + return new() + { + ProjectId = projectId, + ItemId = itemId, + Count = count, + }; + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ICultivatable.cs b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ICultivatable.cs new file mode 100644 index 00000000..285d20c9 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Abstraction/ICultivatable.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.Model.Metadata.Abstraction; + +internal interface ICultivatable +{ + List CultivationItems { get; } +} 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 index 4871692d..19ca1bd8 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.Implementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Avatar/Avatar.Implementation.cs @@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar; /// /// 角色的接口实现部分 /// -internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource +internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource, ICultivatable { /// /// [非元数据] 搭配数据 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 index 86a4465c..060a0d6d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.Implementation.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Metadata/Weapon/Weapon.Implementation.cs @@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Metadata.Weapon; /// /// 武器的接口实现 /// -internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource +internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource, ICultivatable { /// /// [非元数据] 搭配数据 diff --git a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx index 0b2806e1..6da301bb 100644 --- a/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx +++ b/src/Snap.Hutao/Snap.Hutao/Resource/Localization/SH.resx @@ -1568,6 +1568,9 @@ 不能添加名称无效的计划 + + 正在同步背包物品 + 此操作不可逆,此计划的养成物品与背包材料将会丢失 @@ -1925,6 +1928,9 @@ 前往 + + 同步背包物品 + 删除清单 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs index d1fddb5e..b3a3724f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/CultivationService.cs @@ -2,15 +2,29 @@ // Licensed under the MIT license. using Snap.Hutao.Core.Database; +using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Model; using Snap.Hutao.Model.Entity; using Snap.Hutao.Model.Entity.Primitive; +using Snap.Hutao.Model.Metadata.Abstraction; +using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Item; +using Snap.Hutao.Model.Metadata.Weapon; +using Snap.Hutao.Model.Primitive; using Snap.Hutao.Service.Inventory; +using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata.ContextAbstraction; +using Snap.Hutao.Service.User; using Snap.Hutao.ViewModel.Cultivation; +using Snap.Hutao.ViewModel.User; +using Snap.Hutao.Web.Response; using System.Collections.ObjectModel; +using System.Runtime.InteropServices; +using AvatarPromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.AvatarPromotionDelta; +using BatchConsumption = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.BatchConsumption; +using CalculateClient = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.CalculateClient; using CalculateItem = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.Item; +using PromotionDelta = Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate.PromotionDelta; namespace Snap.Hutao.Service.Cultivation; @@ -24,7 +38,10 @@ internal sealed partial class CultivationService : ICultivationService { private readonly ScopedDbCurrent dbCurrent; private readonly ICultivationDbService cultivationDbService; + private readonly IServiceScopeFactory serviceScopeFactory; private readonly IInventoryDbService inventoryDbService; + private readonly IMetadataService metadataService; + private readonly IUserService userService; private readonly ITaskContext taskContext; private ObservableCollection? projects; @@ -242,4 +259,133 @@ internal sealed partial class CultivationService : ICultivationService await taskContext.SwitchToBackgroundAsync(); await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false); } + + /// + public async ValueTask RefreshInventoryAsync(CultivateProject project) + { + List cultivatables = + [ + .. await metadataService.GetAvatarListAsync().ConfigureAwait(false), + .. (await metadataService.GetWeaponListAsync().ConfigureAwait(false)).Where(weapon => weapon.Quality >= Model.Intrinsic.QualityType.QUALITY_BLUE), + ]; + + BatchConsumption? batchConsumption = default; + using (IServiceScope scope = serviceScopeFactory.CreateScope()) + { + if (UserAndUid.TryFromUser(userService.Current, out UserAndUid? userAndUid)) + { + CalculateClient calculateClient = scope.ServiceProvider.GetRequiredService(); + + Response? resp = await calculateClient + .BatchComputeAsync(userAndUid, GeneratePromotionDeltas(cultivatables)) + .ConfigureAwait(false); + + if (!resp.IsOk()) + { + return; + } + + batchConsumption = resp.Data; + } + } + + if (batchConsumption is { OverallConsume: { } items }) + { + await inventoryDbService.RemoveInventoryItemRangeByProjectId(project.InnerId, true).ConfigureAwait(false); + await inventoryDbService.AddInventoryItemRangeByProjectId(items.SelectList(item => InventoryItem.From(project.InnerId, item.Id, (uint)((int)item.Num - item.LackNum)))).ConfigureAwait(false); + } + } + + private static List GeneratePromotionDeltas(List cultivatables) + { + List avatars = []; + List weapons = []; + HashSet materialIds = []; + + while (cultivatables.Count > 0) + { + ICultivatable bestItem = cultivatables.OrderByDescending(item => item.CultivationItems.Count(material => !materialIds.Contains(material))).First(); + + if (bestItem.CultivationItems.All(materialIds.Contains)) + { + break; + } + + switch (bestItem) + { + case Avatar avatar: + avatars.Add(avatar); + break; + case Weapon weapon: + weapons.Add(weapon); + break; + default: + throw HutaoException.NotSupported(); + } + + foreach (ref readonly MaterialId materialId in CollectionsMarshal.AsSpan(bestItem.CultivationItems)) + { + materialIds.Add(materialId); + } + + cultivatables.Remove(bestItem); + } + + List deltas = []; + + for (int i = 0; i < Math.Max(avatars.Count, weapons.Count); i++) + { + Avatar? avatar = avatars.ElementAtOrDefault(i); + Weapon? weapon = weapons.ElementAtOrDefault(i); + + if (avatar is not null) + { + AvatarPromotionDelta delta = new() + { + AvatarId = avatar.Id, + AvatarLevelCurrent = 1, + AvatarLevelTarget = 90, + SkillList = avatar.SkillDepot.CompositeSkillsNoInherents().SelectList(skill => new PromotionDelta() + { + Id = skill.GroupId, + LevelCurrent = 1, + LevelTarget = 10, + }), + }; + + if (weapon is not null) + { + delta.Weapon = new() + { + Id = weapon.Id, + LevelCurrent = 1, + LevelTarget = 90, + }; + } + + deltas.Add(delta); + + continue; + } + + if (weapon is not null) + { + AvatarPromotionDelta delta = new() + { + Weapon = new() + { + Id = weapon.Id, + LevelCurrent = 1, + LevelTarget = 90, + }, + }; + + deltas.Add(delta); + + continue; + } + } + + return deltas; + } } \ 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 0a83a3e0..9d2b7c55 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Cultivation/ICultivationService.cs @@ -66,4 +66,6 @@ internal interface ICultivationService /// 项目 /// 添加操作的结果 ValueTask TryAddProjectAsync(CultivateProject project); + + ValueTask RefreshInventoryAsync(CultivateProject project); } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Inventory/IInventoryDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Inventory/IInventoryDbService.cs index 14430ec9..db4c8c53 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Inventory/IInventoryDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Inventory/IInventoryDbService.cs @@ -9,7 +9,7 @@ internal interface IInventoryDbService { ValueTask AddInventoryItemRangeByProjectId(List items); - ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId); + ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId, bool includeMora = false); void UpdateInventoryItem(InventoryItem item); diff --git a/src/Snap.Hutao/Snap.Hutao/Service/Inventory/InventoryDbService.cs b/src/Snap.Hutao/Snap.Hutao/Service/Inventory/InventoryDbService.cs index bf34de1b..67a66ec2 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/Inventory/InventoryDbService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/Inventory/InventoryDbService.cs @@ -14,14 +14,14 @@ internal sealed partial class InventoryDbService : IInventoryDbService { private readonly IServiceProvider serviceProvider; - public async ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId) + public async ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId, bool includeMora = false) { using (IServiceScope scope = serviceProvider.CreateScope()) { AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService(); await appDbContext.InventoryItems .AsNoTracking() - .Where(a => a.ProjectId == projectId && a.ItemId != 202U) // 摩拉 + .Where(a => a.ProjectId == projectId && (includeMora || a.ItemId != 202U)) // 摩拉 .ExecuteDeleteAsync() .ConfigureAwait(false); } diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml index df1c44af..f9e05085 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/AvatarPropertyPage.xaml @@ -11,7 +11,6 @@ xmlns:mxi="using:Microsoft.Xaml.Interactivity" xmlns:shc="using:Snap.Hutao.Control" xmlns:shcb="using:Snap.Hutao.Control.Behavior" - xmlns:shcca="using:Snap.Hutao.Control.Collection.Alternating" xmlns:shci="using:Snap.Hutao.Control.Image" xmlns:shcm="using:Snap.Hutao.Control.Markup" xmlns:shcp="using:Snap.Hutao.Control.Panel" diff --git a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml index 9eca6358..e1883a5c 100644 --- a/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml +++ b/src/Snap.Hutao/Snap.Hutao/View/Page/CultivationPage.xaml @@ -269,6 +269,10 @@ Style="{ThemeResource CommandBarComboBoxStyle}"/> + ().ConfigureAwait(false); + + await taskContext.SwitchToMainThreadAsync(); + InventoryItems = cultivationService.GetInventoryItemViews(SelectedProject, context, SaveInventoryItemCommand); + } + } + [Command("NavigateToPageCommand")] private void NavigateToPage(string? typeString) { 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 6e0aa7dd..29a1bdbb 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 @@ -36,7 +36,7 @@ internal sealed partial class CalculateClient public async ValueTask> BatchComputeAsync(UserAndUid userAndUid, List deltas, CancellationToken token = default) { - ArgumentOutOfRangeException.ThrowIfGreaterThan(deltas.Count, 8); + //ArgumentOutOfRangeException.ThrowIfGreaterThan(deltas.Count, 8); BatchConsumptionData data = new() { 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 b0a7b62e..58cda946 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 @@ -42,5 +42,5 @@ internal sealed class Item public QualityType Level { get; set; } [JsonPropertyName("lack_num")] - public uint LackNum { get; set; } + public int LackNum { get; set; } } \ No newline at end of file