Add Gacha pull prediction

This commit is contained in:
DismissedLight
2023-07-13 15:39:10 +08:00
parent 27b79659a4
commit 30e888ffb2
18 changed files with 472 additions and 22 deletions

View File

@@ -86,4 +86,18 @@ internal static partial class EnumerableExtension
return results;
}
/// <summary>
/// 按元素的键排序
/// </summary>
/// <typeparam name="TSource">元素类型</typeparam>
/// <typeparam name="TKey">键类型</typeparam>
/// <param name="list">列表</param>
/// <param name="keySelector">键选择器</param>
[MethodImpl(MethodImplOptions.AggressiveOptimization)]
public static void SortBy<TSource, TKey>(this List<TSource> list, Func<TSource, TKey> keySelector)
where TKey : IComparable
{
list.Sort((left, right) => keySelector(left).CompareTo(keySelector(right)));
}
}

View File

@@ -0,0 +1,36 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using System.Numerics;
namespace Snap.Hutao.Extension;
/// <summary>
/// Span 拓展
/// </summary>
internal static class SpanExtension
{
/// <summary>
/// 获取最大值的下标
/// </summary>
/// <typeparam name="T">值的类型</typeparam>
/// <param name="span">Span</param>
/// <returns>最大值的下标</returns>
public static int IndexOfMax<T>(this in ReadOnlySpan<T> span)
where T : INumber<T>
{
T max = T.Zero;
int maxIndex = 0;
for (int i = 0; i < span.Length; i++)
{
ref readonly T current = ref span[i];
if (current > max)
{
maxIndex = i;
max = current;
}
}
return maxIndex;
}
}

View File

@@ -2634,6 +2634,24 @@ namespace Snap.Hutao.Resource.Localization {
}
}
/// <summary>
/// 查找类似 预计还需要 {0} 抽到五星,概率:{1:P3} 的本地化字符串。
/// </summary>
internal static string ViewModelGachaLogPredictedPullLeftToOrange {
get {
return ResourceManager.GetString("ViewModelGachaLogPredictedPullLeftToOrange", resourceCulture);
}
}
/// <summary>
/// 查找类似 下一抽抽到五星的概率:{0:P3} 的本地化字符串。
/// </summary>
internal static string ViewModelGachaLogProbabilityOfNextPullIsOrange {
get {
return ResourceManager.GetString("ViewModelGachaLogProbabilityOfNextPullIsOrange", resourceCulture);
}
}
/// <summary>
/// 查找类似 获取祈愿记录失败 的本地化字符串。
/// </summary>

View File

@@ -2070,4 +2070,10 @@
<data name="ModelMetadataMaterialLocalSpecialtyRegex" xml:space="preserve">
<value>[\u4e00-\u9fa5]{2}区域特产$</value>
</data>
<data name="ViewModelGachaLogPredictedPullLeftToOrange" xml:space="preserve">
<value>预计还需要 {0} 抽到五星,概率:{1:P3}</value>
</data>
<data name="ViewModelGachaLogProbabilityOfNextPullIsOrange" xml:space="preserve">
<value>下一抽抽到五星的概率:{0:P3}</value>
</data>
</root>

View File

@@ -20,6 +20,7 @@ namespace Snap.Hutao.Service.GachaLog.Factory;
[Injection(InjectAs.Scoped, typeof(IGachaStatisticsFactory))]
internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
{
private readonly IServiceProvider serviceProvider;
private readonly IMetadataService metadataService;
private readonly AppOptions options;
@@ -29,18 +30,37 @@ internal sealed partial class GachaStatisticsFactory : IGachaStatisticsFactory
List<GachaEvent> gachaEvents = await metadataService.GetGachaEventsAsync().ConfigureAwait(false);
List<HistoryWishBuilder> historyWishBuilders = gachaEvents.SelectList(g => new HistoryWishBuilder(g, context));
return CreateCore(items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
return CreateCore(serviceProvider, items, historyWishBuilders, context, options.IsEmptyHistoryWishVisible);
}
private static GachaStatistics CreateCore(
IServiceProvider serviceProvider,
IOrderedQueryable<GachaItem> items,
List<HistoryWishBuilder> historyWishBuilders,
in GachaLogServiceContext context,
bool isEmptyHistoryWishVisible)
{
TypedWishSummaryBuilder standardWishBuilder = new(SH.ServiceGachaLogFactoryPermanentWishName, TypedWishSummaryBuilder.IsStandardWish, 90, 10);
TypedWishSummaryBuilder avatarWishBuilder = new(SH.ServiceGachaLogFactoryAvatarWishName, TypedWishSummaryBuilder.IsAvatarEventWish, 90, 10);
TypedWishSummaryBuilder weaponWishBuilder = new(SH.ServiceGachaLogFactoryWeaponWishName, TypedWishSummaryBuilder.IsWeaponEventWish, 80, 10);
TypedWishSummaryBuilder standardWishBuilder = new(
serviceProvider,
SH.ServiceGachaLogFactoryPermanentWishName,
TypedWishSummaryBuilder.IsStandardWish,
Web.Hutao.GachaLog.GachaDistributionType.Standard,
90,
10);
TypedWishSummaryBuilder avatarWishBuilder = new(
serviceProvider,
SH.ServiceGachaLogFactoryAvatarWishName,
TypedWishSummaryBuilder.IsAvatarEventWish,
Web.Hutao.GachaLog.GachaDistributionType.AvatarEvent,
90,
10);
TypedWishSummaryBuilder weaponWishBuilder = new(
serviceProvider,
SH.ServiceGachaLogFactoryWeaponWishName,
TypedWishSummaryBuilder.IsWeaponEventWish,
Web.Hutao.GachaLog.GachaDistributionType.WeaponEvent,
80,
10);
Dictionary<Avatar, int> orangeAvatarCounter = new();
Dictionary<Avatar, int> purpleAvatarCounter = new();

View File

@@ -0,0 +1,127 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.ViewModel.GachaLog;
using Snap.Hutao.Web.Hutao;
using Snap.Hutao.Web.Hutao.GachaLog;
using Snap.Hutao.Web.Response;
using System.Runtime.InteropServices;
namespace Snap.Hutao.Service.GachaLog.Factory;
/// <summary>
/// 抽数预计
/// </summary>
internal sealed class PullPrediction
{
private readonly IServiceProvider serviceProvider;
private readonly ITaskContext taskContext;
private readonly TypedWishSummary typedWishSummary;
private readonly GachaDistributionType distributionType;
/// <summary>
/// 构造一个新的抽数预计
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="typedWishSummary">类型化祈愿统计信息</param>
/// <param name="distributionType">分布类型</param>
public PullPrediction(IServiceProvider serviceProvider, TypedWishSummary typedWishSummary, GachaDistributionType distributionType)
{
this.serviceProvider = serviceProvider;
taskContext = serviceProvider.GetRequiredService<ITaskContext>();
this.typedWishSummary = typedWishSummary;
this.distributionType = distributionType;
}
public async Task PredictAsync()
{
await taskContext.SwitchToBackgroundAsync();
HomaGachaLogClient gachaLogClient = serviceProvider.GetRequiredService<HomaGachaLogClient>();
Response<GachaDistribution> response = await gachaLogClient.GetGachaDistributionAsync(distributionType).ConfigureAwait(false);
if (response.IsOk())
{
PredictResult result = PredictCore(response.Data.Distribution, typedWishSummary);
await taskContext.SwitchToMainThreadAsync();
typedWishSummary.ProbabilityOfNextPullIsOrange = result.ProbabilityOfNextPullIsOrange;
typedWishSummary.ProbabilityOfPredictedPullLeftToOrange = result.ProbabilityOfPredictedPullLeftToOrange;
typedWishSummary.PredictedPullLeftToOrange = result.PredictedPullLeftToOrange;
typedWishSummary.IsPredictPullAvailable = true;
}
}
private static PredictResult PredictCore(List<PullCount> distribution, TypedWishSummary typedWishSummary)
{
// 0 - 89/79
ReadOnlySpan<double> baseFunction = ToProbabilityFunction(distribution, typedWishSummary.GuaranteeOrangeThreshold);
// n - 89/79
ReadOnlySpan<double> function = PartitionFunction(baseFunction[typedWishSummary.LastOrangePull..]);
double nextProbability = function[0];
int maxIndex = function.IndexOfMax();
int leftToOrangePulls = maxIndex + 1;
double leftToOrangeProbability = function[maxIndex];
return new(nextProbability, leftToOrangeProbability, leftToOrangePulls);
}
/// <summary>
/// 转换到概率函数
/// </summary>
/// <param name="distribution">分布</param>
/// <returns>概率函数</returns>
private static ReadOnlySpan<double> ToProbabilityFunction(List<PullCount> distribution, int threshold)
{
Span<double> results = new double[threshold];
double totalCount = distribution.Sum(x => x.Count);
foreach (ref readonly PullCount pullCount in CollectionsMarshal.AsSpan(distribution))
{
// Zero start index
results[pullCount.Pull - 1] = pullCount.Count / totalCount;
}
return results;
}
private static ReadOnlySpan<double> PartitionFunction(in ReadOnlySpan<double> function)
{
double sum = Sum(function);
Span<double> results = new double[function.Length];
for (int i = 0; i < function.Length; i++)
{
results[i] = function[i] / sum;
}
return results;
}
private static double Sum(in ReadOnlySpan<double> function)
{
double sum = 0;
foreach (ref readonly double probability in function)
{
sum += probability;
}
return sum;
}
private readonly struct PredictResult
{
public readonly double ProbabilityOfNextPullIsOrange;
public readonly double ProbabilityOfPredictedPullLeftToOrange;
public readonly int PredictedPullLeftToOrange;
public PredictResult(double probabilityOfNextPullIsOrange, double probabilityOfPredictedPullLeftToOrange, int predictedPullLeftToOrange)
{
ProbabilityOfNextPullIsOrange = probabilityOfNextPullIsOrange;
ProbabilityOfPredictedPullLeftToOrange = probabilityOfPredictedPullLeftToOrange;
PredictedPullLeftToOrange = predictedPullLeftToOrange;
}
}
}

View File

@@ -30,10 +30,12 @@ internal sealed class TypedWishSummaryBuilder
/// </summary>
public static readonly Func<GachaConfigType, bool> IsWeaponEventWish = type => type is GachaConfigType.WeaponEventWish;
private readonly IServiceProvider serviceProvider;
private readonly string name;
private readonly int guaranteeOrangeThreshold;
private readonly int guaranteePurpleThreshold;
private readonly Func<GachaConfigType, bool> typeEvaluator;
private readonly Web.Hutao.GachaLog.GachaDistributionType distributionType;
private readonly List<int> averageOrangePullTracker = new();
private readonly List<int> averageUpOrangePullTracker = new();
@@ -55,16 +57,26 @@ internal sealed class TypedWishSummaryBuilder
/// <summary>
/// 构造一个新的类型化祈愿统计信息构建器
/// </summary>
/// <param name="serviceProvider">服务提供器</param>
/// <param name="name">祈愿配置</param>
/// <param name="typeEvaluator">祈愿类型判断器</param>
/// <param name="distributionType">分布类型</param>
/// <param name="guaranteeOrangeThreshold">五星保底</param>
/// <param name="guaranteePurpleThreshold">四星保底</param>
public TypedWishSummaryBuilder(string name, Func<GachaConfigType, bool> typeEvaluator, int guaranteeOrangeThreshold, int guaranteePurpleThreshold)
public TypedWishSummaryBuilder(
IServiceProvider serviceProvider,
string name,
Func<GachaConfigType, bool> typeEvaluator,
Web.Hutao.GachaLog.GachaDistributionType distributionType,
int guaranteeOrangeThreshold,
int guaranteePurpleThreshold)
{
this.serviceProvider = serviceProvider;
this.name = name;
this.typeEvaluator = typeEvaluator;
this.guaranteeOrangeThreshold = guaranteeOrangeThreshold;
this.guaranteePurpleThreshold = guaranteePurpleThreshold;
this.distributionType = distributionType;
}
/// <summary>
@@ -133,7 +145,7 @@ internal sealed class TypedWishSummaryBuilder
summaryItems.CompleteAdding();
double totalCount = totalCountTracker;
return new()
TypedWishSummary summary = new()
{
// base
Name = name,
@@ -158,6 +170,10 @@ internal sealed class TypedWishSummaryBuilder
AverageUpOrangePull = averageUpOrangePullTracker.SpanAverage(),
OrangeList = summaryItems,
};
new PullPrediction(serviceProvider, summary, distributionType).PredictAsync().SafeForget();
return summary;
}
private void TrackMinMaxOrangePull(int lastOrangePull)

View File

@@ -276,7 +276,7 @@
</StackPanel>
</StackPanel>
<MenuFlyoutSeparator Margin="-12,0"/>
<StackPanel Grid.Row="3" Margin="0,12,0,0">
<StackPanel Margin="0,12,0,0">
<Grid>
<TextBlock
Foreground="{StaticResource OrangeBrush}"
@@ -343,6 +343,11 @@
Style="{StaticResource BodyTextBlockStyle}"
Text="{Binding MinOrangePullFormatted}"/>
</Grid>
<StackPanel Margin="0,2,0,0" Visibility="{Binding IsPredictPullAvailable, Converter={StaticResource BoolToVisibilityConverter}}">
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding PredictedPullLeftToOrangeFormatted}"/>
<TextBlock Style="{StaticResource BodyTextBlockStyle}" Text="{Binding ProbabilityOfNextPullIsOrangeFormatted}"/>
</StackPanel>
</StackPanel>
<MenuFlyoutSeparator Margin="-12,12,-12,0"/>
</StackPanel>

View File

@@ -76,10 +76,6 @@
</mxi:Interaction.Behaviors>
<Grid Visibility="{Binding HutaoCloudViewModel.IsInitialized, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid Visibility="{Binding HutaoCloudViewModel.Options.IsCloudServiceAllowed, Converter={StaticResource BoolToVisibilityConverter}}">
<Grid.Resources>
<shc:BindingProxy x:Key="ViewModelBindingProxy" DataContext="{Binding}"/>
<shc:BindingProxy x:Key="HutaoCloudViewModelBindingProxy" DataContext="{Binding HutaoCloudViewModel}"/>
</Grid.Resources>
<Grid.RowDefinitions>
<RowDefinition Height="auto"/>
<RowDefinition Height="auto"/>
@@ -100,7 +96,11 @@
</StackPanel>
</Grid>
<ScrollViewer Grid.Row="1" Margin="0,0,0,8">
<ItemsControl ItemsSource="{Binding HutaoCloudViewModel.Uids}">
<ScrollViewer.Resources>
<shc:BindingProxy x:Key="ViewModelBindingProxy" DataContext="{Binding}"/>
<shc:BindingProxy x:Key="HutaoCloudViewModelBindingProxy" DataContext="{Binding HutaoCloudViewModel}"/>
</ScrollViewer.Resources>
<ItemsControl ItemsSource="{Binding HutaoCloudViewModel.UidOperations}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Margin="0,0,0,8">
@@ -112,7 +112,7 @@
Grid.Column="0"
VerticalAlignment="Center"
Style="{StaticResource BaseTextBlockStyle}"
Text="{Binding}"/>
Text="{Binding Uid}"/>
<StackPanel
Grid.Column="1"
Margin="6,0,0,0"
@@ -121,8 +121,8 @@
Margin="6,0,0,0"
VerticalAlignment="Stretch"
Background="Transparent"
Command="{Binding DataContext.RetrieveFromCloudCommand, Source={StaticResource ViewModelBindingProxy}}"
CommandParameter="{Binding}"
Command="{Binding RetrieveCommand}"
CommandParameter="{Binding Uid}"
Content="&#xE896;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Style="{StaticResource ButtonRevealStyle}"
@@ -132,8 +132,8 @@
HorizontalAlignment="Right"
VerticalAlignment="Stretch"
Background="Transparent"
Command="{Binding DataContext.DeleteCommand, Source={StaticResource HutaoCloudViewModelBindingProxy}}"
CommandParameter="{Binding}"
Command="{Binding DeleteCommand}"
CommandParameter="{Binding Uid}"
Content="&#xE74D;"
FontFamily="{StaticResource SymbolThemeFontFamily}"
Style="{StaticResource ButtonRevealStyle}"

View File

@@ -103,6 +103,8 @@ internal sealed partial class GachaLogViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
Archives = archives;
SetSelectedArchiveAndUpdateStatistics(Archives.SelectedOrDefault(), true);
HutaoCloudViewModel.RetrieveCommand = RetrieveFromCloudCommand;
}
}
catch (OperationCanceledException)

View File

@@ -0,0 +1,35 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
/// 胡桃云Uid操作视图模型
/// </summary>
internal sealed class HutaoCloudUidOperationViewModel
{
/// <summary>
/// 构造一个新的 胡桃云Uid操作视图模型
/// </summary>
/// <param name="uid">Uid</param>
/// <param name="retrieve">获取记录</param>
/// <param name="delete">删除记录</param>
public HutaoCloudUidOperationViewModel(string uid, ICommand retrieve, ICommand delete)
{
Uid = uid;
RetrieveCommand = retrieve;
DeleteCommand = delete;
}
public string Uid { get; }
/// <summary>
/// 获取云端数据
/// </summary>
public ICommand RetrieveCommand { get; }
/// <summary>
/// 删除云端数据
/// </summary>
public ICommand DeleteCommand { get; }
}

View File

@@ -28,24 +28,29 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
private readonly ITaskContext taskContext;
private readonly HutaoUserOptions options;
private ObservableCollection<string>? uids;
private ObservableCollection<HutaoCloudUidOperationViewModel>? uidOperations;
/// <summary>
/// Uid集合
/// </summary>
public ObservableCollection<string>? Uids { get => uids; set => SetProperty(ref uids, value); }
public ObservableCollection<HutaoCloudUidOperationViewModel>? UidOperations { get => uidOperations; set => SetProperty(ref uidOperations, value); }
/// <summary>
/// 选项
/// </summary>
public HutaoUserOptions Options { get => options; }
/// <summary>
/// 获取记录命令
/// </summary>
internal ICommand RetrieveCommand { get; set; }
/// <summary>
/// 异步获取祈愿记录
/// </summary>
/// <param name="uid">uid</param>
/// <returns>祈愿记录</returns>
public async Task<ValueResult<bool, GachaArchive?>> RetrieveAsync(string uid)
internal async Task<ValueResult<bool, GachaArchive?>> RetrieveAsync(string uid)
{
ContentDialog dialog = await contentDialogFactory
.CreateForIndeterminateProgressAsync(SH.ViewModelGachaLogRetrieveFromHutaoCloudProgress)
@@ -137,7 +142,9 @@ internal sealed partial class HutaoCloudViewModel : Abstraction.ViewModel
await taskContext.SwitchToMainThreadAsync();
if (resp.IsOk())
{
Uids = resp.Data!.ToObservableCollection();
UidOperations = resp.Data!
.SelectList(uid => new HutaoCloudUidOperationViewModel(uid, RetrieveCommand, DeleteCommand))
.ToObservableCollection();
}
}
}

View File

@@ -1,14 +1,23 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using CommunityToolkit.Mvvm.ComponentModel;
namespace Snap.Hutao.ViewModel.GachaLog;
/// <summary>
/// 类型化的祈愿概览
/// </summary>
[HighQuality]
internal sealed class TypedWishSummary : Wish
[INotifyPropertyChanged]
[SuppressMessage("", "SA1124")]
internal sealed partial class TypedWishSummary : Wish
{
private bool isPredictPullAvailable;
private int predictedPullLeftToOrange;
private double probabilityOfPredictedPullLeftToOrange;
private double probabilityOfNextPullIsOrange;
/// <summary>
/// 最大五星抽数
/// </summary>
@@ -77,6 +86,11 @@ internal sealed class TypedWishSummary : Wish
get => string.Format(SH.ModelBindingGachaTypedWishSummaryAveragePullFormat, AverageOrangePull);
}
/// <summary>
/// 抽数预测是否可用
/// </summary>
public bool IsPredictPullAvailable { get => isPredictPullAvailable; set => SetProperty(ref isPredictPullAvailable, value); }
/// <summary>
/// 平均Up五星抽数
/// </summary>
@@ -85,11 +99,29 @@ internal sealed class TypedWishSummary : Wish
get => string.Format(SH.ModelBindingGachaTypedWishSummaryAveragePullFormat, AverageUpOrangePull);
}
/// <summary>
/// 预计出金的抽数与概率
/// </summary>
public string PredictedPullLeftToOrangeFormatted
{
get => string.Format(SH.ViewModelGachaLogPredictedPullLeftToOrange, PredictedPullLeftToOrange, ProbabilityOfPredictedPullLeftToOrange);
}
/// <summary>
/// 预计出金的抽数与概率
/// </summary>
public string ProbabilityOfNextPullIsOrangeFormatted
{
get => string.Format(SH.ViewModelGachaLogProbabilityOfNextPullIsOrange, ProbabilityOfNextPullIsOrange);
}
/// <summary>
/// 五星列表
/// </summary>
public List<SummaryItem> OrangeList { get; set; } = default!;
#region Internal properties for string formatting
/// <summary>
/// 最大五星抽数
/// </summary>
@@ -139,4 +171,44 @@ internal sealed class TypedWishSummary : Wish
/// 平均Up五星抽数
/// </summary>
internal double AverageUpOrangePull { get; set; }
/// <summary>
/// 预测的x抽后出金
/// </summary>
internal int PredictedPullLeftToOrange
{
get => predictedPullLeftToOrange;
set
{
predictedPullLeftToOrange = value;
OnPropertyChanged(nameof(PredictedPullLeftToOrangeFormatted));
}
}
/// <summary>
/// 预测的x抽后出金概率
/// </summary>
internal double ProbabilityOfPredictedPullLeftToOrange
{
get => probabilityOfPredictedPullLeftToOrange;
set
{
probabilityOfPredictedPullLeftToOrange = value;
OnPropertyChanged(nameof(PredictedPullLeftToOrangeFormatted));
}
}
/// <summary>
/// 下抽出金的概率
/// </summary>
internal double ProbabilityOfNextPullIsOrange
{
get => probabilityOfNextPullIsOrange;
set
{
probabilityOfNextPullIsOrange = value;
OnPropertyChanged(nameof(ProbabilityOfNextPullIsOrangeFormatted));
}
}
#endregion
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao.GachaLog;
/// <summary>
/// 祈愿分布
/// </summary>
internal sealed class GachaDistribution
{
/// <summary>
/// 总有效抽数
/// </summary>
public long TotalValidPulls { get; set; }
/// <summary>
/// 分布
/// </summary>
public List<PullCount> Distribution { get; set; } = default!;
}

View File

@@ -0,0 +1,25 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao.GachaLog;
/// <summary>
/// 祈愿分布类型
/// </summary>
internal enum GachaDistributionType
{
/// <summary>
/// 角色活动
/// </summary>
AvatarEvent,
/// <summary>
/// 武器活动
/// </summary>
WeaponEvent,
/// <summary>
/// 常驻
/// </summary>
Standard,
}

View File

@@ -0,0 +1,20 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
namespace Snap.Hutao.Web.Hutao.GachaLog;
/// <summary>
/// 抽数与个数
/// </summary>
internal sealed class PullCount
{
/// <summary>
/// 抽数
/// </summary>
public int Pull { get; set; }
/// <summary>
/// 对应个数
/// </summary>
public long Count { get; set; }
}

View File

@@ -49,6 +49,21 @@ internal sealed class HomaGachaLogClient
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 异步获取祈愿分布
/// </summary>
/// <param name="distributionType">分布类型</param>
/// <param name="token">取消令牌</param>
/// <returns>祈愿分布</returns>
public async Task<Response<GachaDistribution>> GetGachaDistributionAsync(GachaDistributionType distributionType, CancellationToken token = default)
{
Response<GachaDistribution>? resp = await httpClient
.TryCatchGetFromJsonAsync<Response<GachaDistribution>>(HutaoEndpoints.GachaLogStatisticsDistribution(distributionType), options, logger, token)
.ConfigureAwait(false);
return Response.Response.DefaultIfNull(resp);
}
/// <summary>
/// 异步获取 Uid 列表
/// </summary>

View File

@@ -1,6 +1,8 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
using Snap.Hutao.Web.Hutao.GachaLog;
namespace Snap.Hutao.Web;
/// <summary>
@@ -57,6 +59,16 @@ internal static class HutaoEndpoints
/// 获取祈愿统计信息
/// </summary>
public const string GachaLogStatisticsCurrentEvents = $"{HomaSnapGenshinApi}/GachaLog/Statistics/CurrentEventStatistics";
/// <summary>
/// 获取祈愿统计信息
/// </summary>
/// <param name="distributionType">分布类型</param>
/// <returns>祈愿统计信息Url</returns>
public static string GachaLogStatisticsDistribution(GachaDistributionType distributionType)
{
return $"{HomaSnapGenshinApi}/GachaLog/Statistics/Distribution/{distributionType}";
}
#endregion
#region Passport