basic support

This commit is contained in:
Lightczx
2024-03-06 11:50:55 +08:00
parent 44e7f7482c
commit 4c38bb528f
22 changed files with 185 additions and 242 deletions

View File

@@ -39,7 +39,7 @@ internal sealed partial class CultivationService : ICultivationService
List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId); List<InventoryItem> entities = cultivationDbService.GetInventoryItemListByProjectId(projectId);
List<InventoryItemView> results = []; List<InventoryItemView> results = [];
foreach (Material meta in context.EnumerateInventroyMaterial()) foreach (Material meta in context.EnumerateInventoryMaterial())
{ {
InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id); InventoryItem entity = entities.SingleOrDefault(e => e.ItemId == meta.Id) ?? InventoryItem.From(projectId, meta.Id);
results.Add(new(entity, meta, saveCommand)); results.Add(new(entity, meta, saveCommand));

View File

@@ -3,6 +3,7 @@
using Snap.Hutao.Model.Metadata.Abstraction; using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.ViewModel.GachaLog; using Snap.Hutao.ViewModel.GachaLog;
using System.Runtime.InteropServices;
using System.Security.Cryptography; using System.Security.Cryptography;
using System.Text; using System.Text;
using Windows.UI; using Windows.UI;
@@ -25,7 +26,7 @@ internal static class GachaStatisticsExtension
bool isPreviousUp = true; bool isPreviousUp = true;
// mark the IsGuarantee // mark the IsGuarantee
foreach (SummaryItem item in summaryItems) foreach (ref readonly SummaryItem item in CollectionsMarshal.AsSpan(summaryItems))
{ {
if (item.IsUp && (!isPreviousUp)) if (item.IsUp && (!isPreviousUp))
{ {

View File

@@ -7,6 +7,7 @@ using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog; using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog; using Snap.Hutao.Web.Hutao.GachaLog;
@@ -31,9 +32,8 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context) public async ValueTask<GachaStatistics> CreateAsync(List<Model.Entity.GachaItem> items, GachaLogServiceMetadataContext context)
{ {
await taskContext.SwitchToBackgroundAsync(); await taskContext.SwitchToBackgroundAsync();
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventListAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
List<HistoryWishBuilder> historyWishBuilders = context.GachaEvents.SelectList(gachaEvent => new HistoryWishBuilder(gachaEvent, context));
return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible); return CreateCore(taskContext, homaGachaLogClient, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
} }
@@ -70,18 +70,19 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
// Items are ordered by precise time, first is oldest // Items are ordered by precise time, first is oldest
// 'ref' is not allowed here because we have lambda below // 'ref' is not allowed here because we have lambda below
foreach (Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items)) foreach (ref readonly Model.Entity.GachaItem item in CollectionsMarshal.AsSpan(items))
{ {
// Find target history wish to operate. // w.From <= item.Time <= w.To // Find target history wish to operate. // banner.From <= item.Time <= banner.To
Model.Entity.GachaItem pinned = item;
HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaType.Standard or GachaType.NewBie) HistoryWishBuilder? targetHistoryWishBuilder = item.GachaType is not (GachaType.Standard or GachaType.NewBie)
? historyWishBuilderMap[item.GachaType].BinarySearch(w => item.Time < w.From ? -1 : item.Time > w.To ? 1 : 0) ? historyWishBuilderMap[item.GachaType].BinarySearch(banner => pinned.Time < banner.From ? -1 : pinned.Time > banner.To ? 1 : 0)
: default; : default;
switch (item.ItemId.StringLength()) switch (item.ItemId.StringLength())
{ {
case 8U: case 8U:
{ {
Avatar avatar = context.IdAvatarMap[item.ItemId]; Avatar avatar = context.GetAvatar(item.ItemId);
bool isUp = false; bool isUp = false;
switch (avatar.Quality) switch (avatar.Quality)
@@ -142,7 +143,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
} }
} }
AsyncBarrier barrier = new(3); AsyncBarrier barrier = new(4);
return new() return new()
{ {
@@ -150,7 +151,7 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
HistoryWishes = historyWishBuilders HistoryWishes = historyWishBuilders
.Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty)) .Where(b => isEmptyHistoryWishVisible || (!b.IsEmpty))
.OrderByDescending(builder => builder.From) .OrderByDescending(builder => builder.From)
.ThenBy(builder => builder.ConfigType, GachaConfigTypeComparer.Shared) .ThenBy(builder => builder.ConfigType, GachaTypeComparer.Shared)
.Select(builder => builder.ToHistoryWish()) .Select(builder => builder.ToHistoryWish())
.ToList(), .ToList(),

View File

@@ -10,22 +10,23 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary> /// <summary>
/// 祈愿配置类型比较器 /// 祈愿配置类型比较器
/// </summary> /// </summary>
internal sealed class GachaConfigTypeComparer : IComparer<GachaType> internal sealed class GachaTypeComparer : IComparer<GachaType>
{ {
private static readonly Lazy<GachaConfigTypeComparer> LazyShared = new(() => new()); private static readonly Lazy<GachaTypeComparer> LazyShared = new(() => new());
private static readonly FrozenDictionary<GachaConfigType, int> OrderMap = FrozenDictionary.ToFrozenDictionary( private static readonly FrozenDictionary<GachaType, int> OrderMap = FrozenDictionary.ToFrozenDictionary(
[ [
KeyValuePair.Create(GachaConfigType.AvatarEventWish, 0), KeyValuePair.Create(GachaType.ActivityAvatar, 0),
KeyValuePair.Create(GachaConfigType.AvatarEventWish2, 1), KeyValuePair.Create(GachaType.SpecialActivityAvatar, 1),
KeyValuePair.Create(GachaConfigType.WeaponEventWish, 2), KeyValuePair.Create(GachaType.ActivityWeapon, 2),
KeyValuePair.Create(GachaConfigType.StandardWish, 3), KeyValuePair.Create(GachaType.Standard, 3),
KeyValuePair.Create(GachaConfigType.NoviceWish, 4), KeyValuePair.Create(GachaType.NewBie, 4),
KeyValuePair.Create(GachaType.ActivityCity, 5),
]); ]);
/// <summary> /// <summary>
/// 共享的比较器 /// 共享的比较器
/// </summary> /// </summary>
public static GachaConfigTypeComparer Shared { get => LazyShared.Value; } public static GachaTypeComparer Shared { get => LazyShared.Value; }
/// <inheritdoc/> /// <inheritdoc/>
public int Compare(GachaType x, GachaType y) public int Compare(GachaType x, GachaType y)

View File

@@ -29,7 +29,6 @@ internal sealed class HistoryWishBuilder
/// </summary> /// </summary>
/// <param name="gachaEvent">卡池配置</param> /// <param name="gachaEvent">卡池配置</param>
/// <param name="context">祈愿记录上下文</param> /// <param name="context">祈愿记录上下文</param>
[SuppressMessage("", "SH002")]
public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceMetadataContext context) public HistoryWishBuilder(GachaEvent gachaEvent, GachaLogServiceMetadataContext context)
{ {
this.gachaEvent = gachaEvent; this.gachaEvent = gachaEvent;
@@ -112,13 +111,13 @@ internal sealed class HistoryWishBuilder
{ {
HistoryWish historyWish = new() HistoryWish historyWish = new()
{ {
// base // Base
Name = gachaEvent.Name, Name = gachaEvent.Name,
From = gachaEvent.From, From = gachaEvent.From,
To = gachaEvent.To, To = gachaEvent.To,
TotalCount = totalCountTracker, TotalCount = totalCountTracker,
// fill // Fill
Version = gachaEvent.Version, Version = gachaEvent.Version,
BannerImage = gachaEvent.Banner, BannerImage = gachaEvent.Banner,
OrangeUpList = orangeUpCounter.ToStatisticsList(), OrangeUpList = orangeUpCounter.ToStatisticsList(),

View File

@@ -5,6 +5,7 @@ using Snap.Hutao.Core.ExceptionService;
using Snap.Hutao.Model.Intrinsic; using Snap.Hutao.Model.Intrinsic;
using Snap.Hutao.Model.Metadata; using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction; using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog; using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Hutao.GachaLog; using Snap.Hutao.Web.Hutao.GachaLog;
@@ -56,12 +57,13 @@ internal sealed class HutaoStatisticsFactory
{ {
IStatisticsItemSource source = item.Item.StringLength() switch IStatisticsItemSource source = item.Item.StringLength() switch
{ {
8U => context.IdAvatarMap[item.Item], 8U => context.GetAvatar(item.Item),
5U => context.IdWeaponMap[item.Item], 5U => context.GetWeapon(item.Item),
_ => throw HutaoException.GachaStatisticsInvalidItemId(item.Item), _ => throw HutaoException.GachaStatisticsInvalidItemId(item.Item),
}; };
StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count)); StatisticsItem statisticsItem = source.ToStatisticsItem(unchecked((int)item.Count));
// Put UP items to a separate list
if (gachaEvent.UpOrangeList.Contains(item.Item) || gachaEvent.UpPurpleList.Contains(item.Item)) if (gachaEvent.UpOrangeList.Contains(item.Item) || gachaEvent.UpPurpleList.Contains(item.Item))
{ {
upItems.Add(statisticsItem); upItems.Add(statisticsItem);

View File

@@ -15,21 +15,6 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
[HighQuality] [HighQuality]
internal sealed class TypedWishSummaryBuilder internal sealed class TypedWishSummaryBuilder
{ {
/// <summary>
/// 常驻祈愿
/// </summary>
public static readonly Func<GachaType, bool> IsStandardWish = type => type is GachaType.Standard;
/// <summary>
/// 角色活动
/// </summary>
public static readonly Func<GachaType, bool> IsAvatarEventWish = type => type is GachaType.ActivityAvatar or GachaType.SpecialActivityAvatar;
/// <summary>
/// 武器活动
/// </summary>
public static readonly Func<GachaType, bool> IsWeaponEventWish = type => type is GachaType.ActivityWeapon;
private readonly TypedWishSummaryBuilderContext context; private readonly TypedWishSummaryBuilderContext context;
private readonly List<int> averageOrangePullTracker = []; private readonly List<int> averageOrangePullTracker = [];
@@ -62,52 +47,54 @@ internal sealed class TypedWishSummaryBuilder
/// <param name="isUp">是否为Up物品</param> /// <param name="isUp">是否为Up物品</param>
public void Track(GachaItem item, ISummaryItemSource source, bool isUp) public void Track(GachaItem item, ISummaryItemSource source, bool isUp)
{ {
if (context.TypeEvaluator(item.GachaType)) if (!context.TypeEvaluator(item.GachaType))
{ {
++lastOrangePullTracker; return;
++lastPurplePullTracker; }
++lastUpOrangePullTracker;
// track total pulls ++lastOrangePullTracker;
++totalCountTracker; ++lastPurplePullTracker;
TrackFromToTime(item.Time); ++lastUpOrangePullTracker;
switch (source.Quality) // track total pulls
{ ++totalCountTracker;
case QualityType.QUALITY_ORANGE: TrackFromToTime(item.Time);
switch (source.Quality)
{
case QualityType.QUALITY_ORANGE:
{
TrackMinMaxOrangePull(lastOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker);
if (isUp)
{ {
TrackMinMaxOrangePull(lastOrangePullTracker); averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
averageOrangePullTracker.Add(lastOrangePullTracker); lastUpOrangePullTracker = 0;
if (isUp)
{
averageUpOrangePullTracker.Add(lastUpOrangePullTracker);
lastUpOrangePullTracker = 0;
}
summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
lastOrangePullTracker = 0;
++totalOrangePullTracker;
break;
} }
case QualityType.QUALITY_PURPLE: summaryItems.Add(source.ToSummaryItem(lastOrangePullTracker, item.Time, isUp));
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
case QualityType.QUALITY_BLUE: lastOrangePullTracker = 0;
{ ++totalOrangePullTracker;
++totalBluePullTracker;
break;
}
default:
break; break;
} }
case QualityType.QUALITY_PURPLE:
{
lastPurplePullTracker = 0;
++totalPurplePullTracker;
break;
}
case QualityType.QUALITY_BLUE:
{
++totalBluePullTracker;
break;
}
default:
break;
} }
} }

View File

@@ -15,12 +15,14 @@ internal static class GachaArchiveOperation
{ {
archive = archives.SingleOrDefault(a => a.Uid == uid); archive = archives.SingleOrDefault(a => a.Uid == uid);
if (archive is null) if (archive is not null)
{ {
GachaArchive created = GachaArchive.From(uid); return;
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
} }
GachaArchive created = GachaArchive.From(uid);
gachaLogDbService.AddGachaArchive(created);
taskContext.InvokeOnMainThread(() => archives.Add(created));
archive = created;
} }
} }

View File

@@ -1,60 +0,0 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Model.Entity;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿物品保存上下文
/// </summary>
internal readonly struct GachaItemSaveContext
{
/// <summary>
/// 待添加物品
/// </summary>
public readonly List<GachaItem> ItemsToAdd;
/// <summary>
/// 是否懒惰
/// </summary>
public readonly bool IsLazy;
public readonly GachaType QueryType;
/// <summary>
/// 结尾 Id
/// </summary>
public readonly long EndId;
/// <summary>
/// 数据集
/// </summary>
public readonly IGachaLogDbService GachaLogDbService;
public GachaItemSaveContext(List<GachaItem> itemsToAdd, bool isLazy, GachaType queryType, long endId, IGachaLogDbService gachaLogDbService)
{
ItemsToAdd = itemsToAdd;
IsLazy = isLazy;
QueryType = queryType;
EndId = endId;
GachaLogDbService = gachaLogDbService;
}
public void SaveItems(GachaArchive archive)
{
if (ItemsToAdd.Count <= 0)
{
return;
}
// 全量刷新
if (!IsLazy)
{
GachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(archive.InnerId, QueryType, EndId);
}
GachaLogDbService.AddGachaItemRange(ItemsToAdd);
}
}

View File

@@ -53,7 +53,7 @@ internal struct GachaLogFetchContext
private readonly ITaskContext taskContext; private readonly ITaskContext taskContext;
private readonly bool isLazy; private readonly bool isLazy;
public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, in GachaLogServiceMetadataContext serviceContext, bool isLazy) public GachaLogFetchContext(IGachaLogDbService gachaLogDbService, ITaskContext taskContext, GachaLogServiceMetadataContext serviceContext, bool isLazy)
{ {
this.gachaLogDbService = gachaLogDbService; this.gachaLogDbService = gachaLogDbService;
this.taskContext = taskContext; this.taskContext = taskContext;
@@ -140,8 +140,18 @@ internal struct GachaLogFetchContext
// While no item is fetched, archive can be null. // While no item is fetched, archive can be null.
if (TargetArchive is not null) if (TargetArchive is not null)
{ {
GachaItemSaveContext saveContext = new(ItemsToAdd, isLazy, QueryOptions.Type, QueryOptions.EndId, gachaLogDbService); if (ItemsToAdd.Count <= 0)
saveContext.SaveItems(TargetArchive); {
return;
}
// 全量刷新
if (!isLazy)
{
gachaLogDbService.RemoveNewerGachaItemRangeByArchiveIdQueryTypeAndEndId(TargetArchive.InnerId, QueryOptions.Type, QueryOptions.EndId);
}
gachaLogDbService.AddGachaItemRange(ItemsToAdd);
} }
} }

View File

@@ -6,33 +6,17 @@ using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog; namespace Snap.Hutao.Service.GachaLog;
/// <summary>
/// 祈愿记录获取状态
/// </summary>
internal sealed class GachaLogFetchStatus internal sealed class GachaLogFetchStatus
{ {
/// <summary>
/// 构造一个新的祈愿记录获取状态
/// </summary>
/// <param name="configType">卡池类型</param>
public GachaLogFetchStatus(GachaType configType) public GachaLogFetchStatus(GachaType configType)
{ {
ConfigType = configType; ConfigType = configType;
} }
/// <summary>
/// 验证密钥是否过期
/// </summary>
public bool AuthKeyTimeout { get; set; } public bool AuthKeyTimeout { get; set; }
/// <summary>
/// 卡池类型
/// </summary>
public GachaType ConfigType { get; set; } public GachaType ConfigType { get; set; }
/// <summary>
/// 当前获取的物品
/// </summary>
public List<Item> Items { get; set; } = new(20); public List<Item> Items { get; set; } = new(20);
public string Header public string Header

View File

@@ -56,14 +56,16 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
} }
/// <inheritdoc/> /// <inheritdoc/>
public async ValueTask<ValueResult<bool, Guid>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default) public async ValueTask<ValueResult<bool, Guid>> RetrieveGachaArchiveIdAsync(string uid, CancellationToken token = default)
{ {
GachaArchive? archive = await gachaLogDbService GachaArchive? archive = await gachaLogDbService
.GetGachaArchiveByUidAsync(uid, token) .GetGachaArchiveByUidAsync(uid, token)
.ConfigureAwait(false); .ConfigureAwait(false);
EndIds endIds = await CreateEndIdsAsync(archive, token).ConfigureAwait(false); EndIds endIds = await CreateEndIdsAsync(archive, token).ConfigureAwait(false);
Response<List<Web.Hutao.GachaLog.GachaItem>> resp = await homaGachaLogClient.RetrieveGachaItemsAsync(uid, endIds, token).ConfigureAwait(false); Response<List<Web.Hutao.GachaLog.GachaItem>> resp = await homaGachaLogClient
.RetrieveGachaItemsAsync(uid, endIds, token)
.ConfigureAwait(false);
if (!resp.IsOk()) if (!resp.IsOk())
{ {
@@ -76,7 +78,8 @@ internal sealed partial class GachaLogHutaoCloudService : IGachaLogHutaoCloudSer
await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false); await gachaLogDbService.AddGachaArchiveAsync(archive).ConfigureAwait(false);
} }
List<Model.Entity.GachaItem> gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archive.InnerId, i)); Guid archiveId = archive.InnerId;
List<Model.Entity.GachaItem> gachaItems = resp.Data.SelectList(i => Model.Entity.GachaItem.From(archiveId, i));
await gachaLogDbService.AddGachaItemsAsync(gachaItems).ConfigureAwait(false); await gachaLogDbService.AddGachaItemsAsync(gachaItems).ConfigureAwait(false);
return new(true, archive.InnerId); return new(true, archive.InnerId);
} }

View File

@@ -9,6 +9,7 @@ using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.GachaLog.Factory; using Snap.Hutao.Service.GachaLog.Factory;
using Snap.Hutao.Service.GachaLog.QueryProvider; using Snap.Hutao.Service.GachaLog.QueryProvider;
using Snap.Hutao.Service.Metadata; using Snap.Hutao.Service.Metadata;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.ViewModel.GachaLog; using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
using Snap.Hutao.Web.Response; using Snap.Hutao.Web.Response;
@@ -62,13 +63,7 @@ internal sealed partial class GachaLogService : IGachaLogService
if (await metadataService.InitializeAsync().ConfigureAwait(false)) if (await metadataService.InitializeAsync().ConfigureAwait(false))
{ {
Dictionary<AvatarId, Model.Metadata.Avatar.Avatar> idAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); context = await metadataService.GetContextAsync<GachaLogServiceMetadataContext>(token).ConfigureAwait(false);
Dictionary<WeaponId, Model.Metadata.Weapon.Weapon> idWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
Dictionary<string, Model.Metadata.Avatar.Avatar> nameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
Dictionary<string, Model.Metadata.Weapon.Weapon> nameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
context = new(idAvatarMap, idWeaponMap, nameAvatarMap, nameWeaponMap);
ArchiveCollection = gachaLogDbService.GetGachaArchiveCollection(); ArchiveCollection = gachaLogDbService.GetGachaArchiveCollection();
return true; return true;
} }

View File

@@ -2,10 +2,12 @@
// Licensed under the MIT license. // Licensed under the MIT license.
using Snap.Hutao.Model; using Snap.Hutao.Model;
using Snap.Hutao.Model.Metadata;
using Snap.Hutao.Model.Metadata.Abstraction; using Snap.Hutao.Model.Metadata.Abstraction;
using Snap.Hutao.Model.Metadata.Avatar; using Snap.Hutao.Model.Metadata.Avatar;
using Snap.Hutao.Model.Metadata.Weapon; using Snap.Hutao.Model.Metadata.Weapon;
using Snap.Hutao.Model.Primitive; using Snap.Hutao.Model.Primitive;
using Snap.Hutao.Service.Metadata.ContextAbstraction;
using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo; using Snap.Hutao.Web.Hoyolab.Hk4e.Event.GachaInfo;
namespace Snap.Hutao.Service.GachaLog; namespace Snap.Hutao.Service.GachaLog;
@@ -13,65 +15,28 @@ namespace Snap.Hutao.Service.GachaLog;
/// <summary> /// <summary>
/// 祈愿记录服务上下文 /// 祈愿记录服务上下文
/// </summary> /// </summary>
internal readonly struct GachaLogServiceMetadataContext internal sealed class GachaLogServiceMetadataContext : IMetadataContext,
IMetadataSupportInitialization,
IMetadataListGachaEventSource,
IMetadataDictionaryIdAvatarSource,
IMetadataDictionaryIdWeaponSource,
IMetadataDictionaryNameAvatarSource,
IMetadataDictionaryNameWeaponSource
{ {
/// <summary> public Dictionary<string, Item> ItemCache { get; set; } = [];
/// 物品缓存
/// </summary>
public readonly Dictionary<string, Item> ItemCache = [];
/// <summary> public List<GachaEvent> GachaEvents { get; set; } = default!;
/// Id 角色 映射
/// </summary>
public readonly Dictionary<AvatarId, Avatar> IdAvatarMap;
/// <summary> public Dictionary<AvatarId, Avatar> IdAvatarMap { get; set; } = default!;
/// Id 武器 映射
/// </summary>
public readonly Dictionary<WeaponId, Weapon> IdWeaponMap;
/// <summary> public Dictionary<WeaponId, Weapon> IdWeaponMap { get; set; } = default!;
/// 名称 角色 映射
/// </summary>
public readonly Dictionary<string, Avatar> NameAvatarMap;
/// <summary> public Dictionary<string, Avatar> NameAvatarMap { get; set; } = default!;
/// 名称 武器 映射
/// </summary>
public readonly Dictionary<string, Weapon> NameWeaponMap;
/// <summary> public Dictionary<string, Weapon> NameWeaponMap { get; set; } = default!;
/// 是否初始化完成
/// </summary>
public readonly bool IsInitialized;
/// <summary> public bool IsInitialized { get; set; }
/// 构造一个新的祈愿记录服务上下文
/// </summary>
/// <param name="idAvatarMap">Id 角色 映射</param>
/// <param name="idWeaponMap">Id 武器 映射</param>
/// <param name="nameAvatarMap">名称 角色 映射</param>
/// <param name="nameWeaponMap">名称 武器 映射</param>
public GachaLogServiceMetadataContext(
Dictionary<AvatarId, Avatar> idAvatarMap,
Dictionary<WeaponId, Weapon> idWeaponMap,
Dictionary<string, Avatar> nameAvatarMap,
Dictionary<string, Weapon> nameWeaponMap)
{
IdAvatarMap = idAvatarMap;
IdWeaponMap = idWeaponMap;
NameAvatarMap = nameAvatarMap;
NameWeaponMap = nameWeaponMap;
IsInitialized = true;
}
/// <summary>
/// 按名称获取物品
/// </summary>
/// <param name="name">名称</param>
/// <param name="type">类型</param>
/// <returns>物品</returns>
public Item GetItemByNameAndType(string name, string type) public Item GetItemByNameAndType(string name, string type)
{ {
if (!ItemCache.TryGetValue(name, out Item? result)) if (!ItemCache.TryGetValue(name, out Item? result))
@@ -93,11 +58,6 @@ internal readonly struct GachaLogServiceMetadataContext
return result; return result;
} }
/// <summary>
/// 按物品 Id 获取名称星级
/// </summary>
/// <param name="id">Id</param>
/// <returns>名称星级</returns>
public INameQuality GetNameQualityByItemId(uint id) public INameQuality GetNameQualityByItemId(uint id)
{ {
uint place = id.StringLength(); uint place = id.StringLength();
@@ -109,12 +69,6 @@ internal readonly struct GachaLogServiceMetadataContext
}; };
} }
/// <summary>
/// 获取物品 Id
/// O(1)
/// </summary>
/// <param name="item">祈愿物品</param>
/// <returns>物品 Id</returns>
public uint GetItemId(GachaLogItem item) public uint GetItemId(GachaLogItem item)
{ {
if (item.ItemType == SH.ModelInterchangeUIGFItemTypeAvatar) if (item.ItemType == SH.ModelInterchangeUIGFItemTypeAvatar)

View File

@@ -36,7 +36,7 @@ internal interface IGachaLogHutaoCloudService
/// <param name="uid">uid</param> /// <param name="uid">uid</param>
/// <param name="token">取消令牌</param> /// <param name="token">取消令牌</param>
/// <returns>是否获取成功</returns> /// <returns>是否获取成功</returns>
ValueTask<ValueResult<bool, Guid>> RetrieveGachaItemsAsync(string uid, CancellationToken token = default); ValueTask<ValueResult<bool, Guid>> RetrieveGachaArchiveIdAsync(string uid, CancellationToken token = default);
/// <summary> /// <summary>
/// 异步上传祈愿记录 /// 异步上传祈愿记录

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameAvatarSource
{
public Dictionary<string, Model.Metadata.Avatar.Avatar> NameAvatarMap { get; set; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataDictionaryNameWeaponSource
{
public Dictionary<string, Model.Metadata.Weapon.Weapon> NameWeaponMap { get; set; }
}

View File

@@ -0,0 +1,9 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Service.Metadata.ContextAbstraction;
internal interface IMetadataSupportInitialization
{
bool IsInitialized { get; set; }
}

View File

@@ -30,27 +30,42 @@ internal static class MetadataServiceContextExtension
// Dictionary // Dictionary
{ {
if (context is IMetadataDictionaryIdAvatarSource dictionaryAvatarSource) if (context is IMetadataDictionaryIdAvatarSource dictionaryIdAvatarSource)
{ {
dictionaryAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false); dictionaryIdAvatarSource.IdAvatarMap = await metadataService.GetIdToAvatarMapAsync(token).ConfigureAwait(false);
} }
if (context is IMetadataDictionaryIdMaterialSource dictionaryMaterialSource) if (context is IMetadataDictionaryIdMaterialSource dictionaryIdMaterialSource)
{ {
dictionaryMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false); dictionaryIdMaterialSource.IdMaterialMap = await metadataService.GetIdToMaterialMapAsync(token).ConfigureAwait(false);
} }
if (context is IMetadataDictionaryIdWeaponSource dictionaryWeaponSource) if (context is IMetadataDictionaryIdWeaponSource dictionaryIdWeaponSource)
{ {
dictionaryWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false); dictionaryIdWeaponSource.IdWeaponMap = await metadataService.GetIdToWeaponMapAsync(token).ConfigureAwait(false);
} }
if (context is IMetadataDictionaryNameAvatarSource dictionaryNameAvatarSource)
{
dictionaryNameAvatarSource.NameAvatarMap = await metadataService.GetNameToAvatarMapAsync(token).ConfigureAwait(false);
}
if (context is IMetadataDictionaryNameWeaponSource dictionaryNameWeaponSource)
{
dictionaryNameWeaponSource.NameWeaponMap = await metadataService.GetNameToWeaponMapAsync(token).ConfigureAwait(false);
}
}
if (context is IMetadataSupportInitialization supportInitialization)
{
supportInitialization.IsInitialized = true;
} }
return context; return context;
} }
#pragma warning disable SH002 #pragma warning disable SH002
public static IEnumerable<Material> EnumerateInventroyMaterial(this IMetadataListMaterialSource context) public static IEnumerable<Material> EnumerateInventoryMaterial(this IMetadataListMaterialSource context)
{ {
return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value); return context.Materials.Where(m => m.IsInventoryItem()).OrderBy(m => m.Id.Value);
} }
@@ -60,6 +75,11 @@ internal static class MetadataServiceContextExtension
return context.IdAvatarMap[id]; return context.IdAvatarMap[id];
} }
public static Avatar GetAvatar(this IMetadataDictionaryNameAvatarSource context, string name)
{
return context.NameAvatarMap[name];
}
public static Material GetMaterial(this IMetadataDictionaryIdMaterialSource context, MaterialId id) public static Material GetMaterial(this IMetadataDictionaryIdMaterialSource context, MaterialId id)
{ {
return context.IdMaterialMap[id]; return context.IdMaterialMap[id];
@@ -69,5 +89,10 @@ internal static class MetadataServiceContextExtension
{ {
return context.IdWeaponMap[id]; return context.IdWeaponMap[id];
} }
public static Weapon GetWeapon(this IMetadataDictionaryNameWeaponSource context, string name)
{
return context.NameWeaponMap[name];
}
#pragma warning restore SH002 #pragma warning restore SH002
} }

View File

@@ -60,7 +60,7 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
using (await dialog.BlockAsync(taskContext).ConfigureAwait(false)) using (await dialog.BlockAsync(taskContext).ConfigureAwait(false))
{ {
return await hutaoCloudService.RetrieveGachaItemsAsync(uid).ConfigureAwait(false); return await hutaoCloudService.RetrieveGachaArchiveIdAsync(uid).ConfigureAwait(false);
} }
} }

View File

@@ -34,6 +34,12 @@ internal sealed class EndIds
[JsonPropertyName("302")] [JsonPropertyName("302")]
public long WeaponEventWish { get; set; } public long WeaponEventWish { get; set; }
/// <summary>
/// 集录祈愿
/// </summary>
[JsonPropertyName("500")]
public long ChronicledWish { get; set; }
/// <summary> /// <summary>
/// 获取 Last Id /// 获取 Last Id
/// </summary> /// </summary>
@@ -83,5 +89,6 @@ internal sealed class EndIds
yield return new(GachaType.Standard, StandardWish); yield return new(GachaType.Standard, StandardWish);
yield return new(GachaType.ActivityAvatar, AvatarEventWish); yield return new(GachaType.ActivityAvatar, AvatarEventWish);
yield return new(GachaType.ActivityWeapon, WeaponEventWish); yield return new(GachaType.ActivityWeapon, WeaponEventWish);
yield return new(GachaType.ActivityCity, ChronicledWish);
} }
} }

View File

@@ -10,6 +10,11 @@ internal sealed class GachaEntry
/// </summary> /// </summary>
public string Uid { get; set; } = default!; public string Uid { get; set; } = default!;
/// <summary>
/// 是否被排除出了全球统计
/// </summary>
public bool Excluded { get; set; }
/// <summary> /// <summary>
/// 物品个数 /// 物品个数
/// </summary> /// </summary>