Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pity rate accumulation #858

Merged
merged 2 commits into from
Jun 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ namespace DragaliaAPI.Integration.Test.Features.Summoning;
public class SummonTest : TestFixture
{
private const int TestBannerId = 1020121;
private const int TestGalaBannerId = 1020183;

public SummonTest(CustomWebApplicationFactory factory, ITestOutputHelper outputHelper)
: base(factory, outputHelper)
Expand Down Expand Up @@ -195,6 +196,47 @@ await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
);
}

[Fact]
public async Task SummonGetOddsData_IncludesPityRate()
{
await this.AddToDatabase(
new DbPlayerBannerData()
{
SummonBannerId = TestBannerId,
SummonCountSinceLastFiveStar = 20,
}
);

SummonGetOddsDataResponse response = (
await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
"summon/get_odds_data",
new SummonGetOddsDataRequest(TestBannerId)
)
).Data;

OddsRate normalOdds = response.OddsRateList.Normal;
OddsRate guaranteeOdds = response.OddsRateList.Guarantee;

normalOdds
.RarityList.Should()
.BeEquivalentTo(
[
new AtgenRarityList { Rarity = 5, TotalRate = "5.00%" },
new AtgenRarityList { Rarity = 4, TotalRate = "16.00%" },
new AtgenRarityList { Rarity = 3, TotalRate = "79.00%" },
]
);

guaranteeOdds
.RarityList.Should()
.BeEquivalentTo(
[
new AtgenRarityList { Rarity = 5, TotalRate = "5.00%" },
new AtgenRarityList { Rarity = 4, TotalRate = "95.00%" },
]
);
}

[Fact]
public async Task SummonGetSummonHistory_ReturnsAnyData()
{
Expand Down Expand Up @@ -287,7 +329,8 @@ await this.Client.PostMsgpack<SummonGetSummonListResponse>("summon/get_summon_li

response
.SummonList.Should()
.ContainSingle()
.HaveCount(2)
.And.Contain(x => x.SummonId == TestBannerId)
.Which.Should()
.BeEquivalentTo(
new SummonList()
Expand Down Expand Up @@ -733,6 +776,164 @@ await this.Client.PostMsgpack<SummonRequestResponse>(
response.ResultUnitList.Should().ContainSingle().Which.Rarity.Should().Be(5);
}

[Fact]
public async Task SummonRequest_MaxPity_GrantsGuaranteedFiveStar()
{
await this.AddToDatabase(
new DbPlayerBannerData()
{
SummonBannerId = TestBannerId,
SummonCountSinceLastFiveStar = 100,
}
);

SummonGetOddsDataResponse oddsResponse = (
await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
"summon/get_odds_data",
new SummonGetOddsDataRequest(TestBannerId)
)
).Data;

oddsResponse
.OddsRateList.Normal.RarityList.Should()
.BeEquivalentTo(
[
new AtgenRarityList { Rarity = 5, TotalRate = "9.00%" },
new AtgenRarityList { Rarity = 4, TotalRate = "16.00%" },
new AtgenRarityList { Rarity = 3, TotalRate = "75.00%" },
]
);

DbPlayerUserData userData = await this
.ApiContext.PlayerUserData.AsNoTracking()
.SingleAsync(x => x.ViewerId == this.ViewerId);

DragaliaResponse<SummonRequestResponse> response =
await this.Client.PostMsgpack<SummonRequestResponse>(
"summon/request",
new SummonRequestRequest(
TestBannerId,
SummonExecTypes.Single,
1,
PaymentTypes.Wyrmite,
new PaymentTarget(userData.Crystal, 120)
)
);

response.Data.ResultUnitList.Should().Contain(x => x.Rarity == 5);

oddsResponse = (
await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
"summon/get_odds_data",
new SummonGetOddsDataRequest(TestBannerId)
)
).Data;

oddsResponse
.OddsRateList.Normal.RarityList.Should()
.BeEquivalentTo(
[
new AtgenRarityList { Rarity = 5, TotalRate = "4.00%" },
new AtgenRarityList { Rarity = 4, TotalRate = "16.00%" },
new AtgenRarityList { Rarity = 3, TotalRate = "80.00%" },
]
);
}

[Fact]
public async Task SummonRequest_MaxPity_Gala_GrantsGuaranteedFiveStar()
{
await this.AddToDatabase(
new DbPlayerBannerData()
{
SummonBannerId = TestGalaBannerId,
SummonCountSinceLastFiveStar = 60,
}
);

DbPlayerUserData userData = await this
.ApiContext.PlayerUserData.AsNoTracking()
.SingleAsync(x => x.ViewerId == this.ViewerId);

DragaliaResponse<SummonRequestResponse> response =
await this.Client.PostMsgpack<SummonRequestResponse>(
"summon/request",
new SummonRequestRequest(
TestGalaBannerId,
SummonExecTypes.Single,
1,
PaymentTypes.Wyrmite,
new PaymentTarget(userData.Crystal, 120)
)
);

response.Data.ResultUnitList.Should().Contain(x => x.Rarity == 5);

this.ApiContext.PlayerBannerData.AsNoTracking()
.First(x => x.SummonBannerId == TestGalaBannerId)
.SummonCountSinceLastFiveStar.Should()
.Be(0);
}

[Fact]
public async Task SummonRequest_NoFiveStars_IncrementsPityRate()
{
SummonGetOddsDataResponse oddsResponse = (
await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
"summon/get_odds_data",
new SummonGetOddsDataRequest(TestBannerId)
)
).Data;

oddsResponse
.OddsRateList.Normal.RarityList.Should()
.BeEquivalentTo(
[
new AtgenRarityList { Rarity = 5, TotalRate = "4.00%" },
new AtgenRarityList { Rarity = 4, TotalRate = "16.00%" },
new AtgenRarityList { Rarity = 3, TotalRate = "80.00%" },
]
);

IEnumerable<AtgenResultUnitList> result;

do
{
DbPlayerUserData userData = await this
.ApiContext.PlayerUserData.AsNoTracking()
.SingleAsync(x => x.ViewerId == this.ViewerId);

DragaliaResponse<SummonRequestResponse> response =
await this.Client.PostMsgpack<SummonRequestResponse>(
"summon/request",
new SummonRequestRequest(
TestBannerId,
SummonExecTypes.Tenfold,
1,
PaymentTypes.Wyrmite,
new PaymentTarget(userData.Crystal, 1200)
)
);

result = response.Data.ResultUnitList;
} while (result.Any(x => x.Rarity == 5));

oddsResponse = (
await this.Client.PostMsgpack<SummonGetOddsDataResponse>(
"summon/get_odds_data",
new SummonGetOddsDataRequest(TestBannerId)
)
).Data;

double fiveStarRate = double.Parse(
oddsResponse
.OddsRateList.Normal.RarityList.First(x => x.Rarity == 5)
.TotalRate.TrimEnd('%')
);

fiveStarRate.Should().BeGreaterOrEqualTo(4.5d);
}

[Fact]
public async Task SummonPointTrade_Chara_Success_ReturnsData()
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@ IConfiguration config
serviceCollection
.Configure<SummonBannerOptions>(config.GetRequiredSection(nameof(SummonBannerOptions)))
.AddOptions<SummonBannerOptions>()
.PostConfigure(opts => opts.PostConfigure())
.Validate(
opts => opts.Banners.DistinctBy(x => x.Id).Count() == opts.Banners.Count,
"bannerConfig.json IDs must be unique!"
)
.ValidateOnStart();
.ValidateOnStart()
.PostConfigure(opts => opts.PostConfigure());

return serviceCollection;
}
Expand Down
11 changes: 11 additions & 0 deletions DragaliaAPI/DragaliaAPI/Features/Summoning/SummonBannerOptions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ namespace DragaliaAPI.Features.Summoning;
public class SummonBannerOptions
{
private readonly List<Banner> banners = [];
private Dictionary<int, Banner>? bannerDict;

/// <summary>
/// Gets a list of active summoning banners.
Expand All @@ -21,6 +22,14 @@ public required IReadOnlyList<Banner> Banners
init => this.banners = value as List<Banner> ?? value.ToList();
}

/// <summary>
/// Gets a dictionary of <see cref="Banner"/> objects, keyed by each banner's <see cref="Banner.Id"/>.
/// </summary>
/// <exception cref="InvalidOperationException"><see cref="PostConfigure"/> has not yet been called on this instance.</exception>
public IReadOnlyDictionary<int, Banner> BannerDict =>
this.bannerDict
?? throw new InvalidOperationException("SummonBannerOptions not yet initialized!");

/// <summary>
/// Initializes the instance, intended for use with <see cref="Microsoft.Extensions.Options.OptionsBuilder{T}.PostConfigure"/>.
/// </summary>
Expand All @@ -36,6 +45,8 @@ public void PostConfigure()
{
banner.PostConfigure();
}

this.bannerDict = this.banners.ToDictionary(x => x.Id, x => x);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -180,7 +180,8 @@ EntityResult entityResult

UserSummonList userSummonList = await summonService.UpdateUserSummonInformation(
summonList,
summonCount
summonCount,
metaInfo
);

SummonEffect effect = SummonEffectHelper.CalculateEffect(metaInfo);
Expand Down
16 changes: 4 additions & 12 deletions DragaliaAPI/DragaliaAPI/Features/Summoning/SummonOddsService.cs
Original file line number Diff line number Diff line change
@@ -1,13 +1,9 @@
using System.Diagnostics;
using DragaliaAPI.Database;
using DragaliaAPI.Database.Entities;
using DragaliaAPI.Models.Generated;
using DragaliaAPI.Services.Exceptions;
using DragaliaAPI.Shared.Definitions.Enums;
using DragaliaAPI.Shared.Definitions.Enums.Summon;
using DragaliaAPI.Shared.Features.Summoning;
using DragaliaAPI.Shared.MasterAsset;
using DragaliaAPI.Shared.MasterAsset.Models;
using Microsoft.EntityFrameworkCore;
using Microsoft.Extensions.Options;

Expand All @@ -30,8 +26,8 @@ public OddsRate GetNormalOddsRate(int bannerId, int summonCountSinceLastFiveStar
public OddsRate? GetGuaranteeOddsRate(int bannerId, int summonCountSinceLastFiveStar)
{
if (
optionsMonitor.CurrentValue.Banners.FirstOrDefault(x => x.Id == bannerId) is
{ SummonType: not SummonTypes.Normal }
optionsMonitor.CurrentValue.BannerDict.TryGetValue(bannerId, out Banner? banner)
&& banner.SummonType != SummonTypes.Normal
)
{
// Certain special banners, like the 5* summon voucher ones, have no concept
Expand All @@ -51,9 +47,7 @@ public OddsRate GetNormalOddsRate(int bannerId, int summonCountSinceLastFiveStar

public UnitRateCollection GetUnitRates(int bannerId, int summonCountSinceLastFiveStar)
{
Banner? banner = optionsMonitor.CurrentValue.Banners.SingleOrDefault(x => x.Id == bannerId);

if (banner is null)
if (!optionsMonitor.CurrentValue.BannerDict.TryGetValue(bannerId, out Banner? banner))
{
throw new DragaliaException(
ResultCode.CommonInvalidArgument,
Expand All @@ -66,9 +60,7 @@ public UnitRateCollection GetUnitRates(int bannerId, int summonCountSinceLastFiv

public UnitRateCollection GetGuaranteeUnitRates(int bannerId, int summonCountSinceLastFiveStar)
{
Banner? banner = optionsMonitor.CurrentValue.Banners.SingleOrDefault(x => x.Id == bannerId);

if (banner is null)
if (!optionsMonitor.CurrentValue.BannerDict.TryGetValue(bannerId, out Banner? banner))
{
throw new DragaliaException(
ResultCode.CommonInvalidArgument,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,5 +30,8 @@ public static partial void AddingTradeToGiftBox(
ILogger logger,
AtgenSummonPointTradeList trade
);

[LoggerMessage(LogLevel.Information, "Granting guaranteed 5* reward for banner {BannerId}")]
public static partial void GrantingGuaranteedFiveStar(ILogger logger, int bannerId);
}
}
Loading
Loading