mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
Add Gacha pull prediction
This commit is contained in:
@@ -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)));
|
||||
}
|
||||
}
|
||||
|
||||
36
src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs
Normal file
36
src/Snap.Hutao/Snap.Hutao/Extension/SpanExtension.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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=""
|
||||
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=""
|
||||
FontFamily="{StaticResource SymbolThemeFontFamily}"
|
||||
Style="{StaticResource ButtonRevealStyle}"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
@@ -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!;
|
||||
}
|
||||
@@ -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,
|
||||
}
|
||||
20
src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/PullCount.cs
Normal file
20
src/Snap.Hutao/Snap.Hutao/Web/Hutao/GachaLog/PullCount.cs
Normal 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; }
|
||||
}
|
||||
@@ -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>
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user