cultivation wip [skip ci]

This commit is contained in:
Lightczx
2023-12-07 17:25:48 +08:00
parent bd344e50ab
commit 559ae250bd
33 changed files with 1252 additions and 253 deletions

View File

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

View File

@@ -207,4 +207,4 @@ internal static partial class EnumerableExtension
list.Sort((left, right) => keySelector(right).CompareTo(keySelector(left)));
return list;
}
}
}

View File

@@ -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<T> AsSpan<T>(this List<T> list)
{
return CollectionsMarshal.AsSpan(list);
}
}

View File

@@ -0,0 +1,612 @@
// <auto-generated />
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
{
/// <inheritdoc />
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<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<uint>("Current")
.HasColumnType("INTEGER");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("achievements");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AchievementArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("achievement_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.AvatarInfo", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CalculatorRefreshTime")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("GameRecordRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Info")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ShowcaseRefreshTime")
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("avatar_infos");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Id")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("AvatarLevelTo")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<uint>("SkillALevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillALevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("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<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<bool>("IsFinished")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("EntryId");
b.ToTable("cultivate_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateProject", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachedUid")
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("cultivate_projects");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.DailyNoteEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("DailyNote")
.HasColumnType("TEXT");
b.Property<bool>("DailyTaskNotify")
.HasColumnType("INTEGER");
b.Property<bool>("DailyTaskNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotify")
.HasColumnType("INTEGER");
b.Property<bool>("ExpeditionNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<bool>("HomeCoinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("HomeCoinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("RefreshTime")
.HasColumnType("TEXT");
b.Property<bool>("ResinNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<int>("ResinNotifyThreshold")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotify")
.HasColumnType("INTEGER");
b.Property<bool>("TransformerNotifySuppressed")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.Property<Guid>("UserId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("UserId");
b.ToTable("daily_notes");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaArchive", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("gacha_archives");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GachaItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<Guid>("ArchiveId")
.HasColumnType("TEXT");
b.Property<int>("GachaType")
.HasColumnType("INTEGER");
b.Property<long>("Id")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("QueryType")
.HasColumnType("INTEGER");
b.Property<DateTimeOffset>("Time")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ArchiveId");
b.ToTable("gacha_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.GameAccount", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AttachUid")
.HasColumnType("TEXT");
b.Property<string>("MihoyoSDK")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.ToTable("game_accounts");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryItem", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<uint>("ItemId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_items");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryReliquary", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("AppendPropIdList")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<int>("MainPropId")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_reliquaries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.InventoryWeapon", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("ItemId")
.HasColumnType("INTEGER");
b.Property<int>("Level")
.HasColumnType("INTEGER");
b.Property<Guid>("ProjectId")
.HasColumnType("TEXT");
b.Property<int>("PromoteLevel")
.HasColumnType("INTEGER");
b.HasKey("InnerId");
b.HasIndex("ProjectId");
b.ToTable("inventory_weapons");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.ObjectCacheEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("ExpireTime")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("object_cache");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SettingEntry", b =>
{
b.Property<string>("Key")
.HasColumnType("TEXT");
b.Property<string>("Value")
.HasColumnType("TEXT");
b.HasKey("Key");
b.ToTable("settings");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.SpiralAbyssEntry", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("ScheduleId")
.HasColumnType("INTEGER");
b.Property<string>("SpiralAbyss")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Uid")
.IsRequired()
.HasColumnType("TEXT");
b.HasKey("InnerId");
b.ToTable("spiral_abysses");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.User", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<string>("Aid")
.HasColumnType("TEXT");
b.Property<string>("CookieToken")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("CookieTokenLastUpdateTime")
.HasColumnType("TEXT");
b.Property<string>("Fingerprint")
.HasColumnType("TEXT");
b.Property<DateTimeOffset>("FingerprintLastUpdateTime")
.HasColumnType("TEXT");
b.Property<bool>("IsOversea")
.HasColumnType("INTEGER");
b.Property<bool>("IsSelected")
.HasColumnType("INTEGER");
b.Property<string>("LToken")
.HasColumnType("TEXT")
.HasColumnName("Ltoken");
b.Property<string>("Mid")
.HasColumnType("TEXT");
b.Property<string>("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
}
}
}

View File

@@ -0,0 +1,56 @@
// <auto-generated />
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Snap.Hutao.Migrations
{
/// <inheritdoc />
public partial class AddCultivateEntryLevelInformation : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "cultivate_entry_level_informations",
columns: table => new
{
InnerId = table.Column<Guid>(type: "TEXT", nullable: false),
EntryId = table.Column<Guid>(type: "TEXT", nullable: false),
AvatarLevelFrom = table.Column<uint>(type: "INTEGER", nullable: false),
AvatarLevelTo = table.Column<uint>(type: "INTEGER", nullable: false),
SkillALevelFrom = table.Column<uint>(type: "INTEGER", nullable: false),
SkillALevelTo = table.Column<uint>(type: "INTEGER", nullable: false),
SkillELevelFrom = table.Column<uint>(type: "INTEGER", nullable: false),
SkillELevelTo = table.Column<uint>(type: "INTEGER", nullable: false),
SkillQLevelFrom = table.Column<uint>(type: "INTEGER", nullable: false),
SkillQLevelTo = table.Column<uint>(type: "INTEGER", nullable: false),
WeaponLevelFrom = table.Column<uint>(type: "INTEGER", nullable: false),
WeaponLevelTo = table.Column<uint>(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");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "cultivate_entry_level_informations");
}
}
}

View File

@@ -113,13 +113,59 @@ namespace Snap.Hutao.Migrations
b.ToTable("cultivate_entries");
});
modelBuilder.Entity("Snap.Hutao.Model.Entity.CultivateEntryLevelInformation", b =>
{
b.Property<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<uint>("AvatarLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("AvatarLevelTo")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
.HasColumnType("TEXT");
b.Property<uint>("SkillALevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillALevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillELevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("SkillQLevelTo")
.HasColumnType("INTEGER");
b.Property<uint>("WeaponLevelFrom")
.HasColumnType("INTEGER");
b.Property<uint>("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<Guid>("InnerId")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT");
b.Property<int>("Count")
b.Property<uint>("Count")
.HasColumnType("INTEGER");
b.Property<Guid>("EntryId")
@@ -128,7 +174,7 @@ namespace Snap.Hutao.Migrations
b.Property<bool>("IsFinished")
.HasColumnType("INTEGER");
b.Property<int>("ItemId")
b.Property<uint>("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")

View File

@@ -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<CultivateEntryLevelInformation, Guid, LevelInformation>
{
[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,
};
}
}

View File

@@ -35,12 +35,12 @@ internal sealed class CultivateItem : IDbMappingForeignKeyFrom<CultivateItem, We
/// <summary>
/// 物品 Id
/// </summary>
public int ItemId { get; set; }
public uint ItemId { get; set; }
/// <summary>
/// 物品个数
/// </summary>
public int Count { get; set; }
public uint Count { get; set; }
/// <summary>
/// 是否完成此项

View File

@@ -37,89 +37,40 @@ internal sealed class AppDbContext : DbContext
logger.LogInformation("{Name}[{Id}] created", nameof(AppDbContext), ContextId);
}
/// <summary>
/// 设置
/// </summary>
public DbSet<SettingEntry> Settings { get; set; } = default!;
/// <summary>
/// 用户
/// </summary>
public DbSet<User> Users { get; set; } = default!;
/// <summary>
/// 成就
/// </summary>
public DbSet<Achievement> Achievements { get; set; } = default!;
/// <summary>
/// 成就存档
/// </summary>
public DbSet<AchievementArchive> AchievementArchives { get; set; } = default!;
/// <summary>
/// 卡池数据
/// </summary>
public DbSet<GachaItem> GachaItems { get; set; } = default!;
/// <summary>
/// 卡池存档
/// </summary>
public DbSet<GachaArchive> GachaArchives { get; set; } = default!;
/// <summary>
/// 角色信息
/// </summary>
public DbSet<AvatarInfo> AvatarInfos { get; set; } = default!;
/// <summary>
/// 游戏内账号
/// </summary>
public DbSet<GameAccount> GameAccounts { get; set; } = default!;
/// <summary>
/// 实时便笺
/// </summary>
public DbSet<DailyNoteEntry> DailyNotes { get; set; } = default!;
/// <summary>
/// 对象缓存
/// </summary>
public DbSet<ObjectCacheEntry> ObjectCache { get; set; } = default!;
/// <summary>
/// 培养计划
/// </summary>
public DbSet<CultivateProject> CultivateProjects { get; set; } = default!;
/// <summary>
/// 培养入口点
/// </summary>
public DbSet<CultivateEntry> CultivateEntries { get; set; } = default!;
/// <summary>
/// 培养消耗物品
/// </summary>
public DbSet<CultivateEntryLevelInformation> LevelInformations { get; set; } = default!;
public DbSet<CultivateItem> CultivateItems { get; set; } = default!;
/// <summary>
/// 背包内物品
/// </summary>
public DbSet<InventoryItem> InventoryItems { get; set; } = default!;
/// <summary>
/// 背包内武器
/// </summary>
public DbSet<InventoryWeapon> InventoryWeapons { get; set; } = default!;
/// <summary>
/// 背包内圣遗物
/// </summary>
public DbSet<InventoryReliquary> InventoryReliquaries { get; set; } = default!;
/// <summary>
/// 深渊记录
/// </summary>
public DbSet<SpiralAbyssEntry> SpiralAbysses { get; set; } = default!;
/// <summary>

View File

@@ -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<AppDbContext>();
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<AppDbContext>();
await appDbContext.LevelInformations.AddAndSaveAsync(levelInformation).ConfigureAwait(false);
}
}
}

View File

@@ -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<Material> Materials { get; set; } = default!;
public Dictionary<MaterialId, Material> IdMaterialMap { get; set; } = default!;
public Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> IdAvatarMap { get; set; } = default!;
public Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> IdWeaponMap { get; set; } = default!;
}

View File

@@ -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;
/// <inheritdoc/>
public List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, List<Material> metadata, ICommand saveCommand)
public List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand)
{
using (IServiceScope scope = serviceProvider.CreateScope())
{
@@ -39,7 +39,7 @@ internal sealed partial class CultivationService : ICultivationService
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> 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
}
/// <inheritdoc/>
public async ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap)
public async ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context)
{
await taskContext.SwitchToBackgroundAsync();
List<CultivateEntry> entries = await cultivationDbService
@@ -67,13 +63,13 @@ internal sealed partial class CultivationService : ICultivationService
List<CultivateItemView> 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
/// <inheritdoc/>
public async ValueTask<ObservableCollection<StatisticsCultivateItem>> GetStatisticsCultivateItemCollectionAsync(
CultivateProject cultivateProject,
List<Material> materials,
CancellationToken token)
CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token)
{
await taskContext.SwitchToBackgroundAsync();
List<StatisticsCultivateItem> 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
}
/// <inheritdoc/>
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> items)
public async ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Web.Hoyolab.Takumi.Event.Calculate.Item> 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<CultivateItem> toAdd = items.Select(item => CultivateItem.From(entryId, item));
await cultivationDbService.AddCultivateItemRangeAsync(toAdd).ConfigureAwait(false);

View File

@@ -35,4 +35,6 @@ internal interface ICultivationDbService
void UpdateCultivateItem(CultivateItem item);
ValueTask UpdateCultivateItemAsync(CultivateItem item);
ValueTask RemoveLevelInformationByEntryIdAsync(Guid entryId);
ValueTask AddLevelInformationAsync(CultivateEntryLevelInformation levelInformation);
}

View File

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

View File

@@ -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
/// </summary>
ObservableCollection<CultivateProject> ProjectCollection { get; }
/// <summary>
/// 获取绑定用的养成列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">材料</param>
/// <param name="idAvatarMap">Id 角色映射</param>
/// <param name="idWeaponMap">Id 武器映射</param>
/// <returns>绑定用的养成列表</returns>
ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntriesAsync(
CultivateProject cultivateProject,
List<Material> materials,
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap,
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap);
ValueTask<ObservableCollection<CultivateEntryView>> GetCultivateEntriesAsync(CultivateProject cultivateProject, ICultivationMetadataContext context);
/// <summary>
/// 获取物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="metadata">元数据</param>
/// <param name="saveCommand">保存命令</param>
/// <returns>物品列表</returns>
List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, List<Material> metadata, ICommand saveCommand);
List<InventoryItemView> GetInventoryItemViews(CultivateProject cultivateProject, ICultivationMetadataContext context, ICommand saveCommand);
/// <summary>
/// 异步获取统计物品列表
/// </summary>
/// <param name="cultivateProject">养成计划</param>
/// <param name="materials">材料</param>
/// <param name="token">取消令牌</param>
/// <returns>统计物品列表</returns>
ValueTask<ObservableCollection<StatisticsCultivateItem>> GetStatisticsCultivateItemCollectionAsync(
CultivateProject cultivateProject, List<Material> materials, CancellationToken token);
CultivateProject cultivateProject, ICultivationMetadataContext context, CancellationToken token);
/// <summary>
/// 删除养成清单
@@ -74,14 +46,7 @@ internal interface ICultivationService
/// <returns>任务</returns>
ValueTask RemoveProjectAsync(CultivateProject project);
/// <summary>
/// 异步保存养成物品
/// </summary>
/// <param name="type">类型</param>
/// <param name="itemId">主Id</param>
/// <param name="items">待存物品</param>
/// <returns>是否保存成功</returns>
ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items);
ValueTask<bool> SaveConsumptionAsync(CultivateType type, uint itemId, List<Item> items, LevelInformation levelInformation);
/// <summary>
/// 保存养成物品状态

View File

@@ -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<LevelInformation, AvatarPromotionDelta>
{
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;
}
}

View File

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

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.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdAvatarSource
{
public Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> IdAvatarMap { get; set; }
}

View File

@@ -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<MaterialId, Material> IdMaterialMap { get; set; }
}

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.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryIdWeaponSource
{
public Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> IdWeaponMap { get; set; }
}

View File

@@ -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<Material> Materials { get; set; }
}

View File

@@ -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<TContext> GetContextAsync<TContext>(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<Material> 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
}

View File

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

View File

@@ -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<CultivatePromotionDeltaDialog>(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<CultivatePromotionDeltaDialog>(options).ConfigureAwait(false);
(bool isOk, CalculatorAvatarPromotionDelta delta) = await dialog.GetPromotionDeltaAsync().ConfigureAwait(false);
if (!isOk)
{
return;
}
if (!isOk)
{
return;
}
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, delta)
Response<CalculatorConsumption> consumptionResponse = await calculatorClient
.ComputeAsync(userService.Current.Entity, delta)
.ConfigureAwait(false);
if (!consumptionResponse.IsOk())
{
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 (!consumptionResponse.IsOk())
if (avatarAndWeaponSaved)
{
return;
infoBarService.Success(SH.ViewModelCultivationEntryAddSuccess);
}
CalculatorConsumption consumption = consumptionResponse.Data;
List<CalculatorItem> 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<CalculatorConsumption> 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<CalculatorItem> 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)

View File

@@ -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<CultivateEntryView>? cultivateEntries;
private ObservableCollection<StatisticsCultivateItem>? statisticsItems;
/// <summary>
/// 项目
/// </summary>
public ObservableCollection<CultivateProject>? Projects { get => projects; set => SetProperty(ref projects, value); }
/// <summary>
/// 当前选中的计划
/// </summary>
public CultivateProject? SelectedProject
{
get => selectedProject; set
@@ -58,19 +51,10 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
}
}
/// <summary>
/// 物品列表
/// </summary>
public List<InventoryItemView>? InventoryItems { get => inventoryItems; set => SetProperty(ref inventoryItems, value); }
/// <summary>
/// 养成列表
/// </summary>
public ObservableCollection<CultivateEntryView>? CultivateEntries { get => cultivateEntries; set => SetProperty(ref cultivateEntries, value); }
/// <summary>
/// 统计列表
/// </summary>
public ObservableCollection<StatisticsCultivateItem>? StatisticsItems { get => statisticsItems; set => SetProperty(ref statisticsItems, value); }
protected override async ValueTask<bool> InitializeUIAsync()
@@ -95,60 +79,61 @@ internal sealed partial class CultivationViewModel : Abstraction.ViewModel
CultivateProjectDialog dialog = await contentDialogFactory.CreateInstanceAsync<CultivateProjectDialog>().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<Material> materials = await metadataService.GetMaterialListAsync().ConfigureAwait(false);
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync().ConfigureAwait(false);
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync().ConfigureAwait(false);
ObservableCollection<CultivateEntryView> 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<CultivationMetadataContext>().ConfigureAwait(false);
ObservableCollection<CultivateEntryView> 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<StatisticsCultivateItem> statistics;
try
{
List<Material> materials = await metadataService.GetMaterialListAsync(token).ConfigureAwait(false);
statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, materials, token).ConfigureAwait(false);
CultivationMetadataContext context = await metadataService.GetContextAsync<CultivationMetadataContext>().ConfigureAwait(false);
statistics = await cultivationService.GetStatisticsCultivateItemCollectionAsync(SelectedProject, context, token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{

View File

@@ -30,7 +30,7 @@ internal sealed class StatisticsCultivateItem
/// <summary>
/// 对应背包物品的个数
/// </summary>
public int Count { get; set; }
public uint Count { get; set; }
/// <summary>
/// 对应背包物品的个数

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,7 +15,7 @@ internal sealed class Item
/// Id
/// </summary>
[JsonPropertyName("id")]
public int Id { get; set; }
public uint Id { get; set; }
/// <summary>
/// 名称
@@ -33,7 +33,7 @@ internal sealed class Item
/// 数量
/// </summary>
[JsonPropertyName("num")]
public int Num { get; set; }
public uint Num { get; set; }
/// <summary>
/// 物品星级 仅有家具为有效值

View File

@@ -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;
/// <summary>
/// 物品帮助类
/// </summary>
[HighQuality]
internal static class ItemHelper
{
/// <summary>
/// 合并两个物品列表
/// </summary>
/// <param name="left">左列表</param>
/// <param name="right">右列表</param>
/// <returns>合并且排序好的列表</returns>
public static List<Item> Merge(List<Item>? left, List<Item>? right)
{
if (left.IsNullOrEmpty() && right.IsNullOrEmpty())
{
return new(0);
return [];
}
if (left.IsNullOrEmpty() && !right.IsNullOrEmpty())
@@ -38,9 +31,10 @@ internal static class ItemHelper
List<Item> 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;
}