mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
impl #1016
This commit is contained in:
@@ -38,9 +38,23 @@ internal static class StringBuilderExtension
|
||||
return condition ? sb.Append(value) : sb;
|
||||
}
|
||||
|
||||
public static string ToStringTrimEnd(this StringBuilder builder)
|
||||
{
|
||||
if (builder.Length > 1 && char.IsWhiteSpace(builder[^1]))
|
||||
{
|
||||
return builder.ToString(0, builder.Length - 1);
|
||||
}
|
||||
|
||||
return builder.ToString();
|
||||
}
|
||||
|
||||
public static string ToStringTrimEndReturn(this StringBuilder builder)
|
||||
{
|
||||
Must.Argument(builder.Length >= 1, "StringBuilder 的长度必须大于 0");
|
||||
if (builder.Length < 1)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
|
||||
int remove = 0;
|
||||
if (builder[^1] is '\n')
|
||||
{
|
||||
|
||||
@@ -42,7 +42,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("achievements");
|
||||
b.ToTable("achievements", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
|
||||
@@ -60,7 +60,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("achievement_archives");
|
||||
b.ToTable("achievement_archives", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
|
||||
@@ -88,7 +88,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("avatar_infos");
|
||||
b.ToTable("avatar_infos", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
|
||||
@@ -110,7 +110,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("cultivate_entries");
|
||||
b.ToTable("cultivate_entries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
|
||||
@@ -156,7 +156,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("EntryId");
|
||||
|
||||
b.ToTable("cultivate_entry_level_informations");
|
||||
b.ToTable("cultivate_entry_level_informations", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateItem", b =>
|
||||
@@ -181,7 +181,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("EntryId");
|
||||
|
||||
b.ToTable("cultivate_items");
|
||||
b.ToTable("cultivate_items", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
|
||||
@@ -202,7 +202,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("cultivate_projects");
|
||||
b.ToTable("cultivate_projects", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
|
||||
@@ -258,7 +258,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("UserId");
|
||||
|
||||
b.ToTable("daily_notes");
|
||||
b.ToTable("daily_notes", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
|
||||
@@ -276,7 +276,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("gacha_archives");
|
||||
b.ToTable("gacha_archives", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
|
||||
@@ -307,7 +307,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ArchiveId");
|
||||
|
||||
b.ToTable("gacha_items");
|
||||
b.ToTable("gacha_items", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
|
||||
@@ -332,7 +332,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("game_accounts");
|
||||
b.ToTable("game_accounts", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
|
||||
@@ -354,7 +354,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_items");
|
||||
b.ToTable("inventory_items", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
|
||||
@@ -383,7 +383,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_reliquaries");
|
||||
b.ToTable("inventory_reliquaries", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
|
||||
@@ -408,7 +408,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasIndex("ProjectId");
|
||||
|
||||
b.ToTable("inventory_weapons");
|
||||
b.ToTable("inventory_weapons", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
|
||||
@@ -424,7 +424,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("object_cache");
|
||||
b.ToTable("object_cache", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
|
||||
@@ -437,7 +437,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("Key");
|
||||
|
||||
b.ToTable("settings");
|
||||
b.ToTable("settings", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
|
||||
@@ -459,7 +459,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("spiral_abysses");
|
||||
b.ToTable("spiral_abysses", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
|
||||
@@ -502,7 +502,7 @@ namespace Snap.Hutao.Migrations
|
||||
|
||||
b.HasKey("InnerId");
|
||||
|
||||
b.ToTable("users");
|
||||
b.ToTable("users", (string)null);
|
||||
});
|
||||
|
||||
modelBuilder.Entity("Snap.Hutao.Model.Entity.Achievement", b =>
|
||||
|
||||
@@ -33,6 +33,8 @@ internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry,
|
||||
[ForeignKey(nameof(ProjectId))]
|
||||
public CultivateProject Project { get; set; } = default!;
|
||||
|
||||
public CultivateEntryLevelInformation? LevelInformation { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 养成类型
|
||||
/// </summary>
|
||||
@@ -59,4 +61,10 @@ internal sealed class CultivateEntry : IDbMappingForeignKeyFrom<CultivateEntry,
|
||||
Id = id,
|
||||
};
|
||||
}
|
||||
|
||||
public static CultivateEntry Join(CultivateEntry entry, CultivateEntryLevelInformation levelInformation)
|
||||
{
|
||||
entry.LevelInformation = levelInformation;
|
||||
return entry;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Core.Abstraction;
|
||||
using Snap.Hutao.Model.Entity.Primitive;
|
||||
using Snap.Hutao.Service.Cultivation;
|
||||
using System.ComponentModel.DataAnnotations;
|
||||
using System.ComponentModel.DataAnnotations.Schema;
|
||||
@@ -9,7 +10,7 @@ using System.ComponentModel.DataAnnotations.Schema;
|
||||
namespace Snap.Hutao.Model.Entity;
|
||||
|
||||
[Table("cultivate_entry_level_informations")]
|
||||
internal sealed class CultivateEntryLevelInformation : IMappingFrom<CultivateEntryLevelInformation, Guid, LevelInformation>
|
||||
internal sealed class CultivateEntryLevelInformation : IMappingFrom<CultivateEntryLevelInformation, Guid, CultivateType, LevelInformation>
|
||||
{
|
||||
[Key]
|
||||
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
|
||||
@@ -40,21 +41,29 @@ internal sealed class CultivateEntryLevelInformation : IMappingFrom<CultivateEnt
|
||||
|
||||
public uint WeaponLevelTo { get; set; }
|
||||
|
||||
public static CultivateEntryLevelInformation From(Guid entryId, LevelInformation source)
|
||||
public static CultivateEntryLevelInformation From(Guid entryId, CultivateType type, LevelInformation source)
|
||||
{
|
||||
return new()
|
||||
return type switch
|
||||
{
|
||||
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,
|
||||
CultivateType.AvatarAndSkill => 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,
|
||||
},
|
||||
CultivateType.Weapon => new()
|
||||
{
|
||||
EntryId = entryId,
|
||||
WeaponLevelFrom = source.WeaponLevelFrom,
|
||||
WeaponLevelTo = source.WeaponLevelTo,
|
||||
},
|
||||
_ => throw Must.NeverHappen($"不支持的养成类型{type}"),
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1406,6 +1406,9 @@
|
||||
<data name="ViewModelCultivationEntryAddWarning" xml:space="preserve">
|
||||
<value>请先前往养成计划页面创建计划并选中</value>
|
||||
</data>
|
||||
<data name="ViewModelCultivationEntryViewDescriptionDefault" xml:space="preserve">
|
||||
<value>重新添加物品以查看养成描述</value>
|
||||
</data>
|
||||
<data name="ViewModelCultivationProjectAdded" xml:space="preserve">
|
||||
<value>添加成功</value>
|
||||
</data>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Microsoft.EntityFrameworkCore.Internal;
|
||||
using Snap.Hutao.Core.Database;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using Snap.Hutao.Model.Entity.Database;
|
||||
@@ -51,6 +52,20 @@ internal sealed partial class CultivationDbService : ICultivationDbService
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
{
|
||||
AppDbContext appDbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
|
||||
return await appDbContext.CultivateEntries
|
||||
.AsNoTracking()
|
||||
.Where(e => e.ProjectId == projectId)
|
||||
.Include(e => e.LevelInformation)
|
||||
.ToListAsync()
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
public async ValueTask<List<CultivateItem>> GetCultivateItemListByEntryIdAsync(Guid entryId)
|
||||
{
|
||||
using (IServiceScope scope = serviceProvider.CreateScope())
|
||||
|
||||
@@ -54,19 +54,19 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
{
|
||||
await taskContext.SwitchToBackgroundAsync();
|
||||
List<CultivateEntry> entries = await cultivationDbService
|
||||
.GetCultivateEntryListByProjectIdAsync(cultivateProject.InnerId)
|
||||
.GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(cultivateProject.InnerId)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
List<CultivateEntryView> resultEntries = new(entries.Count);
|
||||
foreach (CultivateEntry entry in entries)
|
||||
{
|
||||
List<CultivateItemView> entryItems = [];
|
||||
foreach (CultivateItem item in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
|
||||
foreach (CultivateItem cultivateItem in await cultivationDbService.GetCultivateItemListByEntryIdAsync(entry.InnerId).ConfigureAwait(false))
|
||||
{
|
||||
entryItems.Add(new(item, context.GetMaterial(item.ItemId)));
|
||||
entryItems.Add(new(cultivateItem, context.GetMaterial(cultivateItem.ItemId)));
|
||||
}
|
||||
|
||||
Item itemBase = entry.Type switch
|
||||
Item item = entry.Type switch
|
||||
{
|
||||
CultivateType.AvatarAndSkill => context.GetAvatar(entry.Id).ToItem(),
|
||||
CultivateType.Weapon => context.GetWeapon(entry.Id).ToItem(),
|
||||
@@ -75,7 +75,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
_ => default!,
|
||||
};
|
||||
|
||||
resultEntries.Add(new(entry, itemBase, entryItems));
|
||||
resultEntries.Add(new(entry, item, entryItems));
|
||||
}
|
||||
|
||||
return resultEntries
|
||||
@@ -184,7 +184,7 @@ internal sealed partial class CultivationService : ICultivationService
|
||||
Guid entryId = entry.InnerId;
|
||||
|
||||
await cultivationDbService.RemoveLevelInformationByEntryIdAsync(entryId).ConfigureAwait(false);
|
||||
CultivateEntryLevelInformation entryLevelInformation = CultivateEntryLevelInformation.From(entryId, levelInformation);
|
||||
CultivateEntryLevelInformation entryLevelInformation = CultivateEntryLevelInformation.From(entryId, type, levelInformation);
|
||||
await cultivationDbService.AddLevelInformationAsync(entryLevelInformation).ConfigureAwait(false);
|
||||
|
||||
await cultivationDbService.RemoveCultivateItemRangeByEntryIdAsync(entryId).ConfigureAwait(false);
|
||||
|
||||
@@ -35,6 +35,10 @@ internal interface ICultivationDbService
|
||||
void UpdateCultivateItem(CultivateItem item);
|
||||
|
||||
ValueTask UpdateCultivateItemAsync(CultivateItem item);
|
||||
|
||||
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId);
|
||||
|
||||
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation);
|
||||
|
||||
ValueTask<List<CultivateEntry>> GetCultivateEntryIncludeLevelInformationListByProjectIdAsync(Guid projectId);
|
||||
}
|
||||
@@ -74,7 +74,7 @@
|
||||
Margin="0,8,0,0"
|
||||
Visibility="{x:Bind Avatar, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
|
||||
<Grid
|
||||
Margin="8"
|
||||
Padding="8"
|
||||
DataContext="{x:Bind Avatar}"
|
||||
Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -124,10 +124,10 @@
|
||||
|
||||
<StackPanel
|
||||
Grid.Row="1"
|
||||
Margin="0,8,0,0"
|
||||
Margin="0,6,0,0"
|
||||
Visibility="{x:Bind Weapon, Converter={StaticResource EmptyObjectToVisibilityConverter}}">
|
||||
<Grid
|
||||
Margin="8"
|
||||
Padding="8"
|
||||
DataContext="{x:Bind Weapon}"
|
||||
Style="{ThemeResource GridCardStyle}">
|
||||
<Grid.ColumnDefinitions>
|
||||
@@ -172,6 +172,6 @@
|
||||
Value="{Binding LevelTarget, Mode=TwoWay}"/>
|
||||
</Grid>
|
||||
</StackPanel>
|
||||
|
||||
|
||||
</Grid>
|
||||
</ContentDialog>
|
||||
|
||||
@@ -130,11 +130,17 @@
|
||||
Height="48"
|
||||
Icon="{Binding Icon}"
|
||||
Quality="{Binding Quality}"/>
|
||||
<TextBlock
|
||||
<StackPanel
|
||||
Grid.Column="1"
|
||||
Margin="8,0,0,0"
|
||||
VerticalAlignment="Center"
|
||||
Text="{Binding Name}"/>
|
||||
VerticalAlignment="Center">
|
||||
<TextBlock Text="{Binding Name}"/>
|
||||
<TextBlock
|
||||
Opacity="0.7"
|
||||
Style="{StaticResource CaptionTextBlockStyle}"
|
||||
Text="{Binding Description}"/>
|
||||
</StackPanel>
|
||||
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal">
|
||||
<Button
|
||||
Width="48"
|
||||
|
||||
@@ -18,6 +18,7 @@ using Snap.Hutao.Service.Notification;
|
||||
using Snap.Hutao.Service.User;
|
||||
using Snap.Hutao.View.Dialog;
|
||||
using Snap.Hutao.ViewModel.User;
|
||||
using Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
|
||||
using Snap.Hutao.Web.Response;
|
||||
using Windows.Graphics.Imaging;
|
||||
using Windows.Storage.Streams;
|
||||
@@ -51,6 +52,13 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
private Summary? summary;
|
||||
private AvatarView? selectedAvatar;
|
||||
|
||||
private enum CultivateCoreResult
|
||||
{
|
||||
Ok,
|
||||
ComputeConsumptionFailed,
|
||||
SaveConsumptionFailed,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 简述对象
|
||||
/// </summary>
|
||||
@@ -187,153 +195,86 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
return;
|
||||
}
|
||||
|
||||
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
|
||||
.ComputeAsync(userService.Current.Entity, delta)
|
||||
.ConfigureAwait(false);
|
||||
CultivateCoreResult result = await CultivateCoreAsync(userService.Current.Entity, delta, avatar).ConfigureAwait(false);
|
||||
|
||||
if (!consumptionResponse.IsOk())
|
||||
switch (result)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
CalculatorConsumption consumption = consumptionResponse.Data;
|
||||
LevelInformation levelInformation = LevelInformation.From(delta);
|
||||
|
||||
List<CalculatorItem> 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 (avatarAndWeaponSaved)
|
||||
{
|
||||
case CultivateCoreResult.Ok:
|
||||
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
|
||||
}
|
||||
else
|
||||
{
|
||||
break;
|
||||
case CultivateCoreResult.SaveConsumptionFailed:
|
||||
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
|
||||
}
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
[Command("BatchCultivateCommand")]
|
||||
private async Task BatchCultivateAsync()
|
||||
{
|
||||
if (summary is { Avatars: { } avatars })
|
||||
if (summary is not { Avatars: { } avatars })
|
||||
{
|
||||
if (userService.Current is null)
|
||||
return;
|
||||
}
|
||||
|
||||
if (userService.Current is null)
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
return;
|
||||
}
|
||||
|
||||
CultivatePromotionDeltaBatchDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>().ConfigureAwait(false);
|
||||
(bool isOk, CalculatorAvatarPromotionDelta baseline) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false);
|
||||
|
||||
if (!isOk)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
ArgumentNullException.ThrowIfNull(baseline.SkillList);
|
||||
ArgumentNullException.ThrowIfNull(baseline.Weapon);
|
||||
|
||||
ContentDialog progressDialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle)
|
||||
.ConfigureAwait(false);
|
||||
using (await progressDialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
BatchCultivateResult result = default;
|
||||
foreach (AvatarView avatar in avatars)
|
||||
{
|
||||
infoBarService.Warning(SH.MustSelectUserAndUid);
|
||||
if (!baseline.TryGetNonErrorCopy(avatar, out CalculatorAvatarPromotionDelta? copy))
|
||||
{
|
||||
++result.SkippedCount;
|
||||
continue;
|
||||
}
|
||||
|
||||
CultivateCoreResult coreResult = await CultivateCoreAsync(userService.Current.Entity, copy, avatar).ConfigureAwait(false);
|
||||
|
||||
switch (coreResult)
|
||||
{
|
||||
case CultivateCoreResult.Ok:
|
||||
++result.SucceedCount;
|
||||
break;
|
||||
case CultivateCoreResult.ComputeConsumptionFailed:
|
||||
result.Interrupted = true;
|
||||
break;
|
||||
case CultivateCoreResult.SaveConsumptionFailed:
|
||||
result.Interrupted = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (result.Interrupted)
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Interrupted)
|
||||
{
|
||||
infoBarService.Warning(SH.FormatViewModelCultivationBatchAddIncompletedFormat(result.SucceedCount, result.SkippedCount));
|
||||
}
|
||||
else
|
||||
{
|
||||
CultivatePromotionDeltaBatchDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivatePromotionDeltaBatchDialog>().ConfigureAwait(false);
|
||||
(bool isOk, CalculatorAvatarPromotionDelta baseline) = await dialog.GetPromotionDeltaBaselineAsync().ConfigureAwait(false);
|
||||
|
||||
if (isOk)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(baseline.SkillList);
|
||||
ArgumentNullException.ThrowIfNull(baseline.Weapon);
|
||||
|
||||
ContentDialog progressDialog = await contentDialogFactory
|
||||
.CreateForIndeterminateProgressAsync(SH.ViewModelAvatarPropertyBatchCultivateProgressTitle)
|
||||
.ConfigureAwait(false);
|
||||
using (await progressDialog.BlockAsync(taskContext).ConfigureAwait(false))
|
||||
{
|
||||
BatchCultivateResult result = default;
|
||||
foreach (AvatarView avatar in avatars)
|
||||
{
|
||||
baseline.AvatarId = avatar.Id;
|
||||
baseline.AvatarLevelCurrent = Math.Min(avatar.LevelNumber, baseline.AvatarLevelTarget);
|
||||
|
||||
if (avatar.Skills.Count < 3)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
baseline.SkillList[0].Id = avatar.Skills[0].GroupId;
|
||||
baseline.SkillList[0].LevelCurrent = Math.Min(avatar.Skills[0].LevelNumber, baseline.SkillList[0].LevelTarget);
|
||||
baseline.SkillList[1].Id = avatar.Skills[1].GroupId;
|
||||
baseline.SkillList[1].LevelCurrent = Math.Min(avatar.Skills[1].LevelNumber, baseline.SkillList[1].LevelTarget);
|
||||
baseline.SkillList[2].Id = avatar.Skills[2].GroupId;
|
||||
baseline.SkillList[2].LevelCurrent = Math.Min(avatar.Skills[2].LevelNumber, baseline.SkillList[2].LevelTarget);
|
||||
|
||||
if (avatar.Weapon is null)
|
||||
{
|
||||
result.SkippedCount++;
|
||||
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<CalculatorConsumption> consumptionResponse = await calculatorClient
|
||||
.ComputeAsync(userService.Current.Entity, baseline)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
if (!consumptionResponse.IsOk())
|
||||
{
|
||||
result.Interrupted = true;
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
CalculatorConsumption consumption = consumptionResponse.Data;
|
||||
LevelInformation levelInformation = LevelInformation.From(baseline);
|
||||
|
||||
List<CalculatorItem> 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 (avatarAndWeaponSaved)
|
||||
{
|
||||
result.SucceedCount++;
|
||||
}
|
||||
else
|
||||
{
|
||||
result.Interrupted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (result.Interrupted)
|
||||
{
|
||||
infoBarService.Warning(SH.ViewModelCultivationEntryAddWarning);
|
||||
infoBarService.Warning(SH.FormatViewModelCultivationBatchAddIncompletedFormat(result.SucceedCount, result.SkippedCount));
|
||||
}
|
||||
else
|
||||
{
|
||||
infoBarService.Success(SH.FormatViewModelCultivationBatchAddCompletedFormat(result.SucceedCount, result.SkippedCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
infoBarService.Success(SH.FormatViewModelCultivationBatchAddCompletedFormat(result.SucceedCount, result.SkippedCount));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -372,4 +313,43 @@ internal sealed partial class AvatarPropertyViewModel : Abstraction.ViewModel, I
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<CultivateCoreResult> CultivateCoreAsync(Model.Entity.User user, CalculatorAvatarPromotionDelta delta, AvatarView avatar)
|
||||
{
|
||||
Response<CalculatorConsumption> consumptionResponse = await calculatorClient.ComputeAsync(user, delta).ConfigureAwait(false);
|
||||
|
||||
if (!consumptionResponse.IsOk())
|
||||
{
|
||||
return CultivateCoreResult.ComputeConsumptionFailed;
|
||||
}
|
||||
|
||||
CalculatorConsumption consumption = consumptionResponse.Data;
|
||||
LevelInformation levelInformation = LevelInformation.From(delta);
|
||||
|
||||
List<CalculatorItem> items = CalculatorItemHelper.Merge(consumption.AvatarConsume, consumption.AvatarSkillConsume);
|
||||
bool avatarSaved = await cultivationService
|
||||
.SaveConsumptionAsync(CultivateType.AvatarAndSkill, avatar.Id, items, levelInformation)
|
||||
.ConfigureAwait(false);
|
||||
|
||||
try
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(avatar.Weapon);
|
||||
|
||||
// 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 (!avatarAndWeaponSaved)
|
||||
{
|
||||
return CultivateCoreResult.SaveConsumptionFailed;
|
||||
}
|
||||
}
|
||||
catch (Core.ExceptionService.UserdataCorruptedException ex)
|
||||
{
|
||||
infoBarService.Error(ex, SH.ViewModelCultivationAddWarning);
|
||||
}
|
||||
|
||||
return CultivateCoreResult.Ok;
|
||||
}
|
||||
}
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.Model;
|
||||
using Snap.Hutao.Model.Entity;
|
||||
using System.Text;
|
||||
|
||||
namespace Snap.Hutao.ViewModel.Cultivation;
|
||||
|
||||
@@ -11,13 +13,7 @@ namespace Snap.Hutao.ViewModel.Cultivation;
|
||||
[HighQuality]
|
||||
internal sealed class CultivateEntryView : Item
|
||||
{
|
||||
/// <summary>
|
||||
/// 构造一个新的养成清单入口
|
||||
/// </summary>
|
||||
/// <param name="entry">实体入口</param>
|
||||
/// <param name="item">对应物品</param>
|
||||
/// <param name="items">物品列表</param>
|
||||
public CultivateEntryView(Model.Entity.CultivateEntry entry, Item item, List<CultivateItemView> items)
|
||||
public CultivateEntryView(CultivateEntry entry, Item item, List<CultivateItemView> items)
|
||||
{
|
||||
Id = entry.Id;
|
||||
EntryId = entry.InnerId;
|
||||
@@ -26,33 +22,75 @@ internal sealed class CultivateEntryView : Item
|
||||
Badge = item.Badge;
|
||||
Quality = item.Quality;
|
||||
Items = items;
|
||||
|
||||
Description = ParseDescription(entry);
|
||||
IsToday = items.Any(i => i.IsToday);
|
||||
DaysOfWeek = items.FirstOrDefault(i => i.DaysOfWeek != DaysOfWeek.Any)?.DaysOfWeek ?? DaysOfWeek.Any;
|
||||
|
||||
static string ParseDescription(CultivateEntry entry)
|
||||
{
|
||||
if (entry.LevelInformation is null)
|
||||
{
|
||||
return SH.ViewModelCultivationEntryViewDescriptionDefault;
|
||||
}
|
||||
|
||||
CultivateEntryLevelInformation info = entry.LevelInformation;
|
||||
|
||||
switch (entry.Type)
|
||||
{
|
||||
case Model.Entity.Primitive.CultivateType.AvatarAndSkill:
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
|
||||
if (info.AvatarLevelFrom != info.AvatarLevelTo)
|
||||
{
|
||||
stringBuilder.Append("Lv.").Append(info.AvatarLevelFrom).Append(" → Lv.").Append(info.AvatarLevelTo).Append(' ');
|
||||
}
|
||||
|
||||
if (info.SkillALevelFrom != info.SkillALevelTo)
|
||||
{
|
||||
stringBuilder.Append("A: ").Append(info.SkillALevelFrom).Append(" → ").Append(info.SkillALevelTo).Append(' ');
|
||||
}
|
||||
|
||||
if (info.SkillELevelFrom != info.SkillELevelTo)
|
||||
{
|
||||
stringBuilder.Append("E: ").Append(info.SkillELevelFrom).Append(" → ").Append(info.SkillELevelTo).Append(' ');
|
||||
}
|
||||
|
||||
if (info.SkillQLevelFrom != info.SkillQLevelTo)
|
||||
{
|
||||
stringBuilder.Append("Q: ").Append(info.SkillQLevelFrom).Append(" → ").Append(info.SkillQLevelTo).Append(' ');
|
||||
}
|
||||
|
||||
return stringBuilder.ToStringTrimEnd();
|
||||
}
|
||||
|
||||
case Model.Entity.Primitive.CultivateType.Weapon:
|
||||
{
|
||||
StringBuilder stringBuilder = new();
|
||||
|
||||
if (info.WeaponLevelFrom != info.WeaponLevelTo)
|
||||
{
|
||||
stringBuilder.Append("Lv.").Append(info.WeaponLevelFrom).Append(" → Lv.").Append(info.WeaponLevelTo);
|
||||
}
|
||||
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
|
||||
return string.Empty;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 实体
|
||||
/// </summary>
|
||||
public List<CultivateItemView> Items { get; set; } = default!;
|
||||
|
||||
/// <summary>
|
||||
/// 是否为今日的材料
|
||||
/// </summary>
|
||||
public bool IsToday { get => Items.Any(i => i.IsToday); }
|
||||
public bool IsToday { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 星期中的日期
|
||||
/// </summary>
|
||||
public DaysOfWeek DaysOfWeek
|
||||
{
|
||||
get => Items.FirstOrDefault(i => i.DaysOfWeek != DaysOfWeek.Any)?.DaysOfWeek ?? DaysOfWeek.Any;
|
||||
}
|
||||
public DaysOfWeek DaysOfWeek { get; }
|
||||
|
||||
public string Description { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Id
|
||||
/// </summary>
|
||||
internal uint Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 入口Id
|
||||
/// </summary>
|
||||
internal Guid EntryId { get; set; }
|
||||
}
|
||||
@@ -3,7 +3,6 @@
|
||||
|
||||
using Snap.Hutao.Core.Setting;
|
||||
using Snap.Hutao.Model.Primitive;
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
|
||||
|
||||
@@ -63,41 +62,4 @@ 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;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// Copyright (c) DGP Studio. All rights reserved.
|
||||
// Licensed under the MIT license.
|
||||
|
||||
using Snap.Hutao.ViewModel.AvatarProperty;
|
||||
|
||||
namespace Snap.Hutao.Web.Hoyolab.Takumi.Event.Calculate;
|
||||
|
||||
internal static class AvatarPromotionDeltaExtension
|
||||
{
|
||||
public static bool TryGetNonErrorCopy(this AvatarPromotionDelta source, AvatarView avatar, [NotNullWhen(true)] out AvatarPromotionDelta? copy)
|
||||
{
|
||||
copy = new()
|
||||
{
|
||||
AvatarId = avatar.Id,
|
||||
AvatarLevelCurrent = Math.Min(avatar.LevelNumber, source.AvatarLevelTarget),
|
||||
AvatarLevelTarget = source.AvatarLevelTarget,
|
||||
};
|
||||
|
||||
if (avatar.Skills is not ([SkillView skillViewA, SkillView skillViewE, SkillView skillViewQ, ..]) ||
|
||||
source.SkillList is not ([PromotionDelta deltaA, PromotionDelta deltaE, PromotionDelta deltaQ, ..]))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
copy.SkillList = new(3)
|
||||
{
|
||||
new()
|
||||
{
|
||||
Id = skillViewA.GroupId,
|
||||
LevelCurrent = Math.Min(skillViewA.LevelNumber, deltaA.LevelTarget),
|
||||
LevelTarget = deltaA.LevelTarget,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = skillViewE.GroupId,
|
||||
LevelCurrent = Math.Min(skillViewE.LevelNumber, deltaE.LevelTarget),
|
||||
LevelTarget = deltaE.LevelTarget,
|
||||
},
|
||||
new()
|
||||
{
|
||||
Id = skillViewQ.GroupId,
|
||||
LevelCurrent = Math.Min(skillViewQ.LevelNumber, deltaQ.LevelTarget),
|
||||
LevelTarget = deltaQ.LevelTarget,
|
||||
},
|
||||
};
|
||||
|
||||
if (avatar.Weapon is not WeaponView weaponView || source.Weapon is not { } deltaWeapon)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
copy.Weapon = new()
|
||||
{
|
||||
Id = weaponView.Id,
|
||||
LevelCurrent = Math.Min(weaponView.LevelNumber, deltaWeapon.LevelTarget),
|
||||
LevelTarget = Math.Min(weaponView.MaxLevel, deltaWeapon.LevelTarget),
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user