This commit is contained in:
DismissedLight
2023-12-07 22:38:21 +08:00
parent 559ae250bd
commit a5bfdbaa4b
14 changed files with 343 additions and 243 deletions

View File

@@ -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')
{

View File

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

View File

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

View File

@@ -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}"),
};
}
}

View File

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

View File

@@ -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())

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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