refresh inventory

This commit is contained in:
qhy040404
2024-06-09 22:58:03 +08:00
parent 50389ac06c
commit 063665e77e
14 changed files with 232 additions and 10 deletions

View File

@@ -12,7 +12,7 @@ namespace Snap.Hutao.Model.Entity;
/// </summary>
[HighQuality]
[Table("inventory_items")]
internal sealed class InventoryItem : IDbMappingForeignKeyFrom<InventoryItem, uint>
internal sealed class InventoryItem : IDbMappingForeignKeyFrom<InventoryItem, uint>, IDbMappingForeignKeyFrom<InventoryItem, uint, uint>
{
/// <summary>
/// 内部Id
@@ -56,4 +56,21 @@ internal sealed class InventoryItem : IDbMappingForeignKeyFrom<InventoryItem, ui
ItemId = itemId,
};
}
/// <summary>
/// 构造一个新的个数不为0的物品
/// </summary>
/// <param name="projectId">项目Id</param>
/// <param name="itemId">物品Id</param>
/// <param name="count">物品个数</param>
/// <returns>新的个数不为0的物品</returns>
public static InventoryItem From(in Guid projectId, in uint itemId, in uint count)
{
return new()
{
ProjectId = projectId,
ItemId = itemId,
Count = count,
};
}
}

View File

@@ -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<MaterialId> CultivationItems { get; }
}

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Metadata.Avatar;
/// <summary>
/// 角色的接口实现部分
/// </summary>
internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource<ICalculableAvatar>
internal partial class Avatar : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource<ICalculableAvatar>, ICultivatable
{
/// <summary>
/// [非元数据] 搭配数据

View File

@@ -14,7 +14,7 @@ namespace Snap.Hutao.Model.Metadata.Weapon;
/// <summary>
/// 武器的接口实现
/// </summary>
internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource<ICalculableWeapon>
internal sealed partial class Weapon : IStatisticsItemSource, ISummaryItemSource, IItemSource, INameQuality, ICalculableSource<ICalculableWeapon>, ICultivatable
{
/// <summary>
/// [非元数据] 搭配数据

View File

@@ -1568,6 +1568,9 @@
<data name="ViewModelCultivationProjectInvalidName" xml:space="preserve">
<value>不能添加名称无效的计划</value>
</data>
<data name="ViewModelCultivationRefreshInventoryProgress" xml:space="preserve">
<value>正在同步背包物品</value>
</data>
<data name="ViewModelCultivationRemoveProjectContent" xml:space="preserve">
<value>此操作不可逆,此计划的养成物品与背包材料将会丢失</value>
</data>
@@ -1925,6 +1928,9 @@
<data name="ViewPageCultivationNavigateAction" xml:space="preserve">
<value>前往</value>
</data>
<data name="ViewPageCultivationRefreshInventory" xml:space="preserve">
<value>同步背包物品</value>
</data>
<data name="ViewPageCultivationRemoveEntry" xml:space="preserve">
<value>删除清单</value>
</data>

View File

@@ -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<CultivateProject, Message.CultivateProjectChangedMessage> 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<CultivateProject>? projects;
@@ -242,4 +259,133 @@ internal sealed partial class CultivationService : ICultivationService
await taskContext.SwitchToBackgroundAsync();
await cultivationDbService.RemoveCultivateProjectByIdAsync(project.InnerId).ConfigureAwait(false);
}
/// <inheritdoc/>
public async ValueTask RefreshInventoryAsync(CultivateProject project)
{
List<ICultivatable> 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<CalculateClient>();
Response<BatchConsumption>? 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<AvatarPromotionDelta> GeneratePromotionDeltas(List<ICultivatable> cultivatables)
{
List<Avatar> avatars = [];
List<Weapon> weapons = [];
HashSet<MaterialId> 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<AvatarPromotionDelta> 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;
}
}

View File

@@ -66,4 +66,6 @@ internal interface ICultivationService
/// <param name="project">项目</param>
/// <returns>添加操作的结果</returns>
ValueTask<ProjectAddResultKind> TryAddProjectAsync(CultivateProject project);
ValueTask RefreshInventoryAsync(CultivateProject project);
}

View File

@@ -9,7 +9,7 @@ internal interface IInventoryDbService
{
ValueTask AddInventoryItemRangeByProjectId(List<InventoryItem> items);
ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId);
ValueTask RemoveInventoryItemRangeByProjectId(Guid projectId, bool includeMora = false);
void UpdateInventoryItem(InventoryItem item);

View File

@@ -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<AppDbContext>();
await appDbContext.InventoryItems
.AsNoTracking()
.Where(a => a.ProjectId == projectId && a.ItemId != 202U) // 摩拉
.Where(a => a.ProjectId == projectId && (includeMora || a.ItemId != 202U)) // 摩拉
.ExecuteDeleteAsync()
.ConfigureAwait(false);
}

View File

@@ -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"

View File

@@ -269,6 +269,10 @@
Style="{ThemeResource CommandBarComboBoxStyle}"/>
</shc:SizeRestrictedContentControl>
</AppBarElementContainer>
<AppBarButton
Command="{Binding RefreshInventoryCommand}"
Icon="{shcm:FontIcon Glyph=&#xE72C;}"
Label="{shcm:ResourceString Name=ViewPageCultivationRefreshInventory}"/>
<AppBarButton
Command="{Binding AddProjectCommand}"
Icon="{shcm:FontIcon Glyph=&#xE710;}"

View File

@@ -2,6 +2,7 @@
// Licensed under the MIT license.
using Microsoft.UI.Xaml.Controls;
using Snap.Hutao.Control.Extension;
using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Factory.ContentDialog;
using Snap.Hutao.Model.Entity;
@@ -140,8 +141,8 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
CultivateEntries = entries;
InventoryItems = cultivationService.GetInventoryItemViews(project, context, SaveInventoryItemCommand);
await UpdateInventoryItemsAsync().ConfigureAwait(false);
await UpdateStatisticsItemsAsync().ConfigureAwait(false);
}
@@ -178,6 +179,30 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
}
}
[Command("RefreshInventoryCommand")]
private async Task RefreshInventoryAsync()
{
if (SelectedProject is null)
{
return;
}
using (await EnterCriticalExecutionAsync().ConfigureAwait(false))
{
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelCultivationRefreshInventoryProgress)
.ConfigureAwait(false);
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{
await cultivationService.RefreshInventoryAsync(SelectedProject).ConfigureAwait(false);
await UpdateInventoryItemsAsync().ConfigureAwait(false);
await UpdateStatisticsItemsAsync().ConfigureAwait(false);
}
}
}
private async ValueTask UpdateStatisticsItemsAsync()
{
if (SelectedProject is not null)
@@ -201,6 +226,18 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
}
}
private async ValueTask UpdateInventoryItemsAsync()
{
if (SelectedProject is not null)
{
await taskContext.SwitchToBackgroundAsync();
CultivationMetadataContext context = await metadataService.GetContextAsync<CultivationMetadataContext>().ConfigureAwait(false);
await taskContext.SwitchToMainThreadAsync();
InventoryItems = cultivationService.GetInventoryItemViews(SelectedProject, context, SaveInventoryItemCommand);
}
}
[Command("NavigateToPageCommand")]
private void NavigateToPage(string? typeString)
{

View File

@@ -36,7 +36,7 @@ internal sealed partial class CalculateClient
public async ValueTask<Response<BatchConsumption>> BatchComputeAsync(UserAndUid userAndUid, List<AvatarPromotionDelta> deltas, CancellationToken token = default)
{
ArgumentOutOfRangeException.ThrowIfGreaterThan(deltas.Count, 8);
//ArgumentOutOfRangeException.ThrowIfGreaterThan(deltas.Count, 8);
BatchConsumptionData data = new()
{

View File

@@ -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; }
}