From 42a0a56230522a5ae1d36435d8e3ed1eb0b52284 Mon Sep 17 00:00:00 2001 From: Jay Malhotra <5047192+SapiensAnatis@users.noreply.github.com> Date: Sat, 8 Jun 2024 19:31:17 +0100 Subject: [PATCH] Actually remove dungeon sessions (#862) Today, when you finish a dungeon, the session remains in the cache for the remainder of its expiry time, because issues were previously encountered with the client retrying record requests if they take too long, and then encountering errors because the session had been deleted on the first request. Now that the codebase makes better use of CancellationToken, this is much less likely if we put the eviction of the session right at the end of the response method. It is still possible if the request is cancelled during serialization, however. If we encounter issues we can make the RemoveDungeon method instead mark the session to be removed in 1 minute's time or similar. Also includes a refactoring of the DungeonService to implement a unit-of-work pattern, where the current session can be cached and modified in-memory without round-trips to the cache. This is primarily aimed at the DungeonStart logic which can require several updates after creating the session, arising from how the code is structured. Finally, makes sessions keyed by viewer ID, so that it isn't possible to get another player's session. It probably wasn't really possible before, given the astronomical likelihood of guessing/colliding GUIDs, but it makes sense to segregate this data by tenant all the same. **This key change is breaking, and the server should be taken down during the deployment.** --- .../Features/Dungeon/DungeonRecordTest.cs | 21 ++-- .../Features/Summoning/SummonTest.cs | 20 +++- .../Features/Wall/WallRecordTest.cs | 16 ++- .../Features/Wall/WallTest.cs | 10 +- .../TestFixture.cs | 20 +++- .../Definitions/Enums/VariationTypes.cs | 1 + .../Controllers/DungeonControllerTest.cs | 28 +++-- .../Features/Dungeon/DungeonServiceTest.cs | 9 +- .../Features/Wall/WallRecordControllerTest.cs | 7 +- .../Extensions/DistributedCacheExtensions.cs | 12 +- .../Features/Dungeon/DungeonController.cs | 27 ++++- .../Features/Dungeon/DungeonMapper.cs | 37 ++++++ .../Features/Dungeon/DungeonService.cs | 113 +++++++++--------- .../Features/Dungeon/DungeonSession.cs | 34 ++++++ .../Features/Dungeon/IDungeonService.cs | 13 +- .../Features/Dungeon/IDungeonStartService.cs | 11 +- .../Dungeon/Record/DungeonRecordController.cs | 20 +++- .../Dungeon/Start/DungeonStartController.cs | 25 +++- .../Dungeon/Start/DungeonStartService.cs | 34 +++--- .../Features/Quest/IQuestService.cs | 4 +- .../Features/Quest/QuestService.cs | 1 + .../Features/Wall/WallController.cs | 11 +- .../Features/Wall/WallRecordController.cs | 9 +- .../Features/Wall/WallStartController.cs | 23 ++-- .../DragaliaAPI/Models/DungeonSession.cs | 34 ------ 25 files changed, 365 insertions(+), 175 deletions(-) create mode 100644 DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonMapper.cs create mode 100644 DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonSession.cs delete mode 100644 DragaliaAPI/DragaliaAPI/Models/DungeonSession.cs diff --git a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Dungeon/DungeonRecordTest.cs b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Dungeon/DungeonRecordTest.cs index 07a75ba25..ee4fe5c88 100644 --- a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Dungeon/DungeonRecordTest.cs +++ b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Dungeon/DungeonRecordTest.cs @@ -103,7 +103,7 @@ await AddToDatabase( } }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); DungeonRecordRecordResponse response = ( await Client.PostMsgpack( @@ -229,7 +229,7 @@ await Client.PostMsgpack( } }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); DungeonRecordRecordResponse response = ( await Client.PostMsgpack( @@ -280,7 +280,7 @@ await Client.PostMsgpack( } }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); DungeonRecordRecordResponse response = ( await Client.PostMsgpack( @@ -864,7 +864,7 @@ public async Task Record_HandlesNonExistentQuestData() } }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); DragaliaResponse response = await Client.PostMsgpack( @@ -988,7 +988,7 @@ await AddToDatabase( EnemyList = new Dictionary>() }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); ( await Client.PostMsgpack( @@ -1043,7 +1043,7 @@ await AddToDatabase( EnemyList = new Dictionary>() }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); ( await Client.PostMsgpack( @@ -1209,8 +1209,13 @@ await Client.PostMsgpack("/dungeon_record/record", response.UpdateDataList.UserData.TutorialStatus.Should().Be(20501); } - private async Task StartDungeon(DungeonSession session) => - await Services.GetRequiredService().StartDungeon(session); + private async Task StartDungeon(DungeonSession session) + { + string key = this.DungeonService.CreateSession(session); + await this.DungeonService.SaveSession(CancellationToken.None); + + return key; + } private void SetupPhotonAuthentication() { diff --git a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Summoning/SummonTest.cs b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Summoning/SummonTest.cs index befb0cd63..8357f03d4 100644 --- a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Summoning/SummonTest.cs +++ b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Summoning/SummonTest.cs @@ -869,10 +869,22 @@ await this.Client.PostMsgpack( response.Data.ResultUnitList.Should().Contain(x => x.Rarity == 5); - this.ApiContext.PlayerBannerData.AsNoTracking() - .First(x => x.SummonBannerId == TestGalaBannerId) - .SummonCountSinceLastFiveStar.Should() - .Be(0); + SummonGetOddsDataResponse oddsResponse = ( + await this.Client.PostMsgpack( + "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] diff --git a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallRecordTest.cs b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallRecordTest.cs index 6b9c1b16b..fe44de6e8 100644 --- a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallRecordTest.cs +++ b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallRecordTest.cs @@ -62,7 +62,7 @@ await this.AddRangeToDatabase( WallLevel = wallLevel + 1 // Client passes (db wall level + 1) }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); WallRecordRecordResponse response = ( await Client.PostMsgpack( @@ -160,7 +160,7 @@ await this.AddRangeToDatabase( WallLevel = wallLevel }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); WallRecordRecordResponse response = ( await Client.PostMsgpack( @@ -239,7 +239,7 @@ await this.AddRangeToDatabase( WallLevel = 6 }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); WallRecordRecordResponse response = ( await Client.PostMsgpack( @@ -309,7 +309,7 @@ await this.AddRangeToDatabase( WallLevel = 80 }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); WallRecordRecordResponse response = ( await Client.PostMsgpack( @@ -329,4 +329,12 @@ await Client.PostMsgpack( missionNotice.Should().BeNull(); } + + private async Task StartDungeon(DungeonSession session) + { + string key = this.DungeonService.CreateSession(session); + await this.DungeonService.SaveSession(CancellationToken.None); + + return key; + } } diff --git a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallTest.cs b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallTest.cs index 92a8759fe..5109a67fb 100644 --- a/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallTest.cs +++ b/DragaliaAPI/DragaliaAPI.Integration.Test/Features/Wall/WallTest.cs @@ -38,7 +38,7 @@ await AddToDatabase( WallLevel = expectedWallLevel }; - string key = await Services.GetRequiredService().StartDungeon(mockSession); + string key = await this.StartDungeon(mockSession); WallFailResponse response = ( await Client.PostMsgpack( @@ -256,4 +256,12 @@ await this.Client.PostMsgpack( response.DataHeaders.ResultCode.Should().Be(ResultCode.CommonInvalidArgument); } + + private async Task StartDungeon(DungeonSession session) + { + string key = this.DungeonService.CreateSession(session); + await this.DungeonService.SaveSession(CancellationToken.None); + + return key; + } } diff --git a/DragaliaAPI/DragaliaAPI.Integration.Test/TestFixture.cs b/DragaliaAPI/DragaliaAPI.Integration.Test/TestFixture.cs index 9e7283c0a..70fb04664 100644 --- a/DragaliaAPI/DragaliaAPI.Integration.Test/TestFixture.cs +++ b/DragaliaAPI/DragaliaAPI.Integration.Test/TestFixture.cs @@ -3,8 +3,10 @@ using DragaliaAPI.Database.Entities; using DragaliaAPI.Database.Entities.Abstract; using DragaliaAPI.Extensions; +using DragaliaAPI.Features.Dungeon; using DragaliaAPI.Features.Fort; using DragaliaAPI.Models; +using DragaliaAPI.Models.Options; using DragaliaAPI.Services; using DragaliaAPI.Services.Api; using DragaliaAPI.Shared.PlayerDetails; @@ -13,8 +15,11 @@ using Microsoft.AspNetCore.TestHost; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Caching.Distributed; +using Microsoft.Extensions.Caching.StackExchangeRedis; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; +using Microsoft.Extensions.Logging.Abstractions; +using Microsoft.Extensions.Options; using Microsoft.Extensions.Time.Testing; using Npgsql; @@ -34,10 +39,12 @@ public class TestFixture protected const string SessionId = "session_id"; private readonly CustomWebApplicationFactory factory; + private readonly IPlayerIdentityService stubPlayerIdentityService; protected TestFixture(CustomWebApplicationFactory factory, ITestOutputHelper testOutputHelper) { this.factory = factory; + this.TestOutputHelper = testOutputHelper; this.Client = this.CreateClient(); @@ -52,11 +59,20 @@ protected TestFixture(CustomWebApplicationFactory factory, ITestOutputHelper tes this.SeedDatabase().Wait(); this.SeedCache().Wait(); + this.stubPlayerIdentityService = new StubPlayerIdentityService(this.ViewerId); + DbContextOptions options = this.Services.GetRequiredService< DbContextOptions >(); - this.ApiContext = new ApiContext(options, new StubPlayerIdentityService(this.ViewerId)); + this.ApiContext = new ApiContext(options, this.stubPlayerIdentityService); this.ApiContext.ChangeTracker.QueryTrackingBehavior = QueryTrackingBehavior.NoTracking; + + this.DungeonService = new DungeonService( + this.Services.GetRequiredService(), + this.Services.GetRequiredService>(), + this.stubPlayerIdentityService, + NullLogger.Instance + ); } protected DateTimeOffset LastDailyReset { get; } @@ -84,6 +100,8 @@ protected TestFixture(CustomWebApplicationFactory factory, ITestOutputHelper tes protected IMapper Mapper { get; } + protected IDungeonService DungeonService { get; } + /// /// Instance of to use for setting up / interrogating the database in tests. /// diff --git a/DragaliaAPI/DragaliaAPI.Shared/Definitions/Enums/VariationTypes.cs b/DragaliaAPI/DragaliaAPI.Shared/Definitions/Enums/VariationTypes.cs index 37f54b5ef..8e4ceaf7f 100644 --- a/DragaliaAPI/DragaliaAPI.Shared/Definitions/Enums/VariationTypes.cs +++ b/DragaliaAPI/DragaliaAPI.Shared/Definitions/Enums/VariationTypes.cs @@ -7,6 +7,7 @@ namespace DragaliaAPI.Shared.Definitions.Enums; /// public enum VariationTypes { + None = 0, Normal = 1, Hard = 2, VeryHard = 3, diff --git a/DragaliaAPI/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs b/DragaliaAPI/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs index 53fe80ee4..8b47969f5 100644 --- a/DragaliaAPI/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs +++ b/DragaliaAPI/DragaliaAPI.Test/Controllers/DungeonControllerTest.cs @@ -66,22 +66,28 @@ public async Task Fail_IsMultiFalse_ReturnsExpectedResponse() } }; - this.mockDungeonService.Setup(x => x.FinishDungeon("my key")) + this.mockDungeonService.Setup(x => x.GetSession("my key", CancellationToken.None)) .ReturnsAsync( new DungeonSession() { - QuestData = MasterAsset.QuestData.Get(questId), - Party = new List(), + Party = [], IsMulti = false, SupportViewerId = 4, + QuestData = MasterAsset.QuestData[questId] } ); + this.mockDungeonService.Setup(x => x.RemoveSession("my key", CancellationToken.None)) + .Returns(Task.CompletedTask); + this.mockDungeonRecordHelperService.Setup(x => x.ProcessHelperDataSolo(4)) .ReturnsAsync((userSupportList, supportDetailList)); DungeonFailResponse? response = ( - await this.dungeonController.Fail(new DungeonFailRequest() { DungeonKey = "my key" }) + await this.dungeonController.Fail( + new DungeonFailRequest() { DungeonKey = "my key" }, + CancellationToken.None + ) ).GetData(); response.Should().NotBeNull(); @@ -126,23 +132,29 @@ public async Task Fail_IsMultiTrue_RespondsExpectedResponse() } }; - this.mockDungeonService.Setup(x => x.FinishDungeon("my key")) + this.mockDungeonService.Setup(x => x.GetSession("my key", CancellationToken.None)) .ReturnsAsync( new DungeonSession() { - QuestData = MasterAsset.QuestData.Get(questId), - Party = new List(), + Party = [], IsMulti = true, + QuestData = MasterAsset.QuestData[questId] } ); + this.mockDungeonService.Setup(x => x.RemoveSession("my key", CancellationToken.None)) + .Returns(Task.CompletedTask); + this.mockDungeonRecordHelperService.Setup(x => x.ProcessHelperDataMulti()) .ReturnsAsync((userSupportList, supportDetailList)); this.mockMatchingService.Setup(x => x.GetIsHost()).ReturnsAsync(false); DungeonFailResponse? response = ( - await this.dungeonController.Fail(new DungeonFailRequest() { DungeonKey = "my key" }) + await this.dungeonController.Fail( + new DungeonFailRequest() { DungeonKey = "my key" }, + CancellationToken.None + ) ).GetData(); response.Should().NotBeNull(); diff --git a/DragaliaAPI/DragaliaAPI.Test/Features/Dungeon/DungeonServiceTest.cs b/DragaliaAPI/DragaliaAPI.Test/Features/Dungeon/DungeonServiceTest.cs index 77e258f8a..551c86914 100644 --- a/DragaliaAPI/DragaliaAPI.Test/Features/Dungeon/DungeonServiceTest.cs +++ b/DragaliaAPI/DragaliaAPI.Test/Features/Dungeon/DungeonServiceTest.cs @@ -3,6 +3,7 @@ using DragaliaAPI.Models.Generated; using DragaliaAPI.Models.Options; using DragaliaAPI.Shared.MasterAsset; +using DragaliaAPI.Test.Utils; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Caching.Memory; using Microsoft.Extensions.Logging; @@ -33,6 +34,7 @@ public DungeonServiceTest() dungeonService = new DungeonService( testCache, this.mockOptions.Object, + IdentityTestUtils.MockPlayerDetailsService.Object, this.mockLogger.Object ); } @@ -50,8 +52,11 @@ public async Task StartDungeon_CanGetAfterwards() } }; - string key = await dungeonService.StartDungeon(session); + string key = dungeonService.CreateSession(session); + await dungeonService.SaveSession(CancellationToken.None); - (await dungeonService.GetDungeon(key)).Should().BeEquivalentTo(session); + (await dungeonService.GetSession(key, CancellationToken.None)) + .Should() + .BeEquivalentTo(session); } } diff --git a/DragaliaAPI/DragaliaAPI.Test/Features/Wall/WallRecordControllerTest.cs b/DragaliaAPI/DragaliaAPI.Test/Features/Wall/WallRecordControllerTest.cs index b74e3ad8d..cabb3d5ea 100644 --- a/DragaliaAPI/DragaliaAPI.Test/Features/Wall/WallRecordControllerTest.cs +++ b/DragaliaAPI/DragaliaAPI.Test/Features/Wall/WallRecordControllerTest.cs @@ -75,7 +75,12 @@ public async Task Record_ReturnsData() WallLevel = wallLevel }; - mockDungeonService.Setup(x => x.FinishDungeon(dungeonKey)).ReturnsAsync(session); + this.mockDungeonService.Setup(x => x.GetSession(dungeonKey, CancellationToken.None)) + .ReturnsAsync(session); + + mockDungeonService + .Setup(x => x.RemoveSession(dungeonKey, CancellationToken.None)) + .Returns(Task.CompletedTask); mockWallService.Setup(x => x.GetQuestWall(wallId)).ReturnsAsync(playerQuestWall); mockWallService.Setup(x => x.LevelupQuestWall(wallId)).Returns(Task.CompletedTask); diff --git a/DragaliaAPI/DragaliaAPI/Extensions/DistributedCacheExtensions.cs b/DragaliaAPI/DragaliaAPI/Extensions/DistributedCacheExtensions.cs index 4c3b8f67b..9d37d2b27 100644 --- a/DragaliaAPI/DragaliaAPI/Extensions/DistributedCacheExtensions.cs +++ b/DragaliaAPI/DragaliaAPI/Extensions/DistributedCacheExtensions.cs @@ -6,13 +6,16 @@ public static class DistributedCacheExtensions { public static async Task GetJsonAsync( this IDistributedCache cache, - string key + string key, + CancellationToken cancellationToken = default ) where TObject : class { - string? json = await cache.GetStringAsync(key); + string? json = await cache.GetStringAsync(key, cancellationToken); if (json == null) + { return default; + } return JsonSerializer.Deserialize(json); } @@ -21,6 +24,7 @@ public static Task SetJsonAsync( this IDistributedCache cache, string key, object entry, - DistributedCacheEntryOptions options - ) => cache.SetStringAsync(key, JsonSerializer.Serialize(entry), options); + DistributedCacheEntryOptions options, + CancellationToken cancellationToken = default + ) => cache.SetStringAsync(key, JsonSerializer.Serialize(entry), options, cancellationToken); } diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonController.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonController.cs index 814bc722e..255b515f7 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonController.cs @@ -24,9 +24,15 @@ ILogger logger ) : DragaliaControllerBase { [HttpPost("get_area_odds")] - public async Task GetAreaOdds(DungeonGetAreaOddsRequest request) + public async Task GetAreaOdds( + DungeonGetAreaOddsRequest request, + CancellationToken cancellationToken + ) { - DungeonSession session = await dungeonService.GetDungeon(request.DungeonKey); + DungeonSession session = await dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); ArgumentNullException.ThrowIfNull(session.QuestData); @@ -34,16 +40,25 @@ public async Task GetAreaOdds(DungeonGetAreaOddsRequest request) await dungeonService.ModifySession( request.DungeonKey, - s => s.EnemyList[request.AreaIdx] = oddsInfo.Enemy + s => s.EnemyList[request.AreaIdx] = oddsInfo.Enemy, + cancellationToken ); + await dungeonService.SaveSession(cancellationToken); + return Ok(new DungeonGetAreaOddsResponse() { OddsInfo = oddsInfo }); } [HttpPost("fail")] - public async Task Fail(DungeonFailRequest request) + public async Task Fail( + DungeonFailRequest request, + CancellationToken cancellationToken + ) { - DungeonSession session = await dungeonService.FinishDungeon(request.DungeonKey); + DungeonSession session = await dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); logger.LogDebug("Processing fail request for quest {QuestId}", session.QuestId); @@ -76,6 +91,8 @@ public async Task Fail(DungeonFailRequest request) logger.LogDebug("Final response: {@Response}", response); + await dungeonService.RemoveSession(request.DungeonKey, cancellationToken); + return this.Ok(response); } diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonMapper.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonMapper.cs new file mode 100644 index 000000000..431508e75 --- /dev/null +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonMapper.cs @@ -0,0 +1,37 @@ +using System.Collections; +using DragaliaAPI.Database.Entities; +using DragaliaAPI.Database.Entities.Scaffold; +using DragaliaAPI.Models.Generated; +using DragaliaAPI.Shared.MasterAsset.Models; +using Riok.Mapperly.Abstractions; + +namespace DragaliaAPI.Features.Dungeon; + +[Mapper( + RequiredMappingStrategy = RequiredMappingStrategy.Target, + IgnoreObsoleteMembersStrategy = IgnoreObsoleteMembersStrategy.Target +)] +public static partial class DungeonMapper +{ + public static partial IEnumerable MapToPartyUnitList( + this IEnumerable detailedPartyUnits + ); + + public static partial IEnumerable MapToPartySettingList( + this IEnumerable partyUnits + ); + + public static partial IEnumerable MapToAreaInfoList( + this IEnumerable areaInfo + ); + + [MapperIgnoreTarget(nameof(CharaList.StatusPlusCount))] + private static partial CharaList MapToCharaList(DbPlayerCharaData charaData); + + [MapperIgnoreTarget(nameof(DragonList.StatusPlusCount))] + private static partial DragonList MapToDragonList(DbPlayerDragonData charaData); + + [MapProperty(nameof(DbAbilityCrest.AbilityLevel), nameof(GameAbilityCrest.Ability1Level))] + [MapProperty(nameof(DbAbilityCrest.AbilityLevel), nameof(GameAbilityCrest.Ability2Level))] + private static partial GameAbilityCrest? MapToGameAbilityCrest(DbAbilityCrest? abilityCrest); +} diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonService.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonService.cs index 7121e6f8e..39028b2fd 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonService.cs @@ -1,15 +1,20 @@ using DragaliaAPI.Models; using DragaliaAPI.Models.Options; +using DragaliaAPI.Shared.PlayerDetails; using Microsoft.Extensions.Caching.Distributed; using Microsoft.Extensions.Options; namespace DragaliaAPI.Features.Dungeon; -public class DungeonService : IDungeonService +public class DungeonService( + IDistributedCache cache, + IOptionsMonitor options, + IPlayerIdentityService playerIdentityService, + ILogger logger +) : IDungeonService { - private readonly IDistributedCache cache; - private readonly IOptionsMonitor options; - private readonly ILogger logger; + private DungeonSession? currentSession; + private string? currentKey; private DistributedCacheEntryOptions CacheOptions => new() @@ -19,77 +24,75 @@ public class DungeonService : IDungeonService private static class Schema { - public static string DungeonKey_DungeonData(string dungeonKey) => $":dungeon:{dungeonKey}"; + public static string DungeonKey_DungeonData(long viewerId, string dungeonKey) => + $":dungeon:{viewerId}:{dungeonKey}"; } - public DungeonService( - IDistributedCache cache, - IOptionsMonitor options, - ILogger logger - ) - { - this.cache = cache; - this.options = options; - this.logger = logger; - } - - public async Task StartDungeon(DungeonSession dungeonSession) + public string CreateSession(DungeonSession dungeonSession) { - string dungeonKey = Guid.NewGuid().ToString(); - await WriteDungeon(dungeonKey, dungeonSession); + this.currentKey = Guid.NewGuid().ToString(); + this.currentSession = dungeonSession; - this.logger.LogDebug("Issued dungeon key {key}", dungeonKey); + logger.LogDebug("Created dungeon session with key {Key}", currentKey); - return dungeonKey; + return currentKey; } - public async Task GetDungeon(string dungeonKey) + public async Task GetSession( + string dungeonKey, + CancellationToken cancellationToken + ) { - string json = - await cache.GetStringAsync(Schema.DungeonKey_DungeonData(dungeonKey)) - ?? throw new DungeonException(dungeonKey); + DungeonSession session = + await cache.GetJsonAsync( + Schema.DungeonKey_DungeonData(playerIdentityService.ViewerId, dungeonKey), + cancellationToken + ) ?? throw new DungeonException(dungeonKey); + + this.currentSession = session; + this.currentKey = dungeonKey; - return JsonSerializer.Deserialize(json) - ?? throw new JsonException("Could not deserialize dungeon session."); + return session; } - private async Task WriteDungeon(string dungeonKey, DungeonSession session) + public async Task SaveSession(CancellationToken cancellationToken) { - await cache.SetStringAsync( - Schema.DungeonKey_DungeonData(dungeonKey), - JsonSerializer.Serialize(session), - CacheOptions + if (this.currentSession == null || this.currentKey == null) + { + throw new InvalidOperationException( + "Cannot perform WriteSession when no dungeon session is being tracked. " + + "A session must be loaded either via CreateSession or GetSession before one can be saved." + ); + } + + await cache.SetJsonAsync( + Schema.DungeonKey_DungeonData(playerIdentityService.ViewerId, this.currentKey), + this.currentSession, + CacheOptions, + cancellationToken ); } - /// - /// Update a session already in the cache. - /// - /// - /// This method should not exist. The dungeon_start code could be better structured to avoid its use. - /// TODO: Remove all usages - /// - /// The dungeon key. - /// The action to update with. - /// A task. - public async Task ModifySession(string dungeonKey, Action update) + public async Task ModifySession( + string dungeonKey, + Action update, + CancellationToken cancellationToken + ) { - DungeonSession session = await this.GetDungeon(dungeonKey); - - update.Invoke(session); - - await WriteDungeon(dungeonKey, session); + this.currentSession ??= await this.GetSession(dungeonKey, cancellationToken); + update.Invoke(this.currentSession); } - public async Task FinishDungeon(string dungeonKey) + public async Task RemoveSession(string dungeonKey, CancellationToken cancellationToken) { - DungeonSession session = await GetDungeon(dungeonKey); + logger.LogDebug("Removing dungeon session with key {Key}", dungeonKey); - this.logger.LogDebug("Completed dungeon with key {key}", dungeonKey); - - // Don't remove in case the client re-calls due to timeout - // await cache.RemoveAsync(Schema.DungeonKey_DungeonData(dungeonKey)); + await cache.RemoveAsync( + Schema.DungeonKey_DungeonData(playerIdentityService.ViewerId, dungeonKey), + cancellationToken + ); - return session; + this.currentSession = null; + this.currentKey = null; } } diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonSession.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonSession.cs new file mode 100644 index 000000000..345030e81 --- /dev/null +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/DungeonSession.cs @@ -0,0 +1,34 @@ +using DragaliaAPI.Models.Generated; +using DragaliaAPI.Shared.Definitions.Enums; +using DragaliaAPI.Shared.MasterAsset.Models; + +namespace DragaliaAPI.Features.Dungeon; + +public class DungeonSession +{ + public required IEnumerable Party { get; init; } + + public QuestData? QuestData { get; init; } + + public bool IsHost { get; set; } = true; + + public bool IsMulti { get; set; } + + public ulong? SupportViewerId { get; init; } + + public DateTimeOffset StartTime { get; init; } + + public Dictionary> EnemyList { get; set; } = new(); + + public int PlayCount { get; init; } = 1; + + public int WallId { get; init; } + + public int WallLevel { get; init; } + + public int QuestId => this.QuestData?.Id ?? 0; + + public int QuestGid => this.QuestData?.Gid ?? 0; + + public VariationTypes QuestVariation => this.QuestData?.VariationType ?? VariationTypes.None; +} diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonService.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonService.cs index eb5f012df..52fb17401 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonService.cs @@ -4,8 +4,13 @@ namespace DragaliaAPI.Features.Dungeon; public interface IDungeonService { - Task FinishDungeon(string dungeonKey); - Task GetDungeon(string dungeonKey); - Task ModifySession(string dungeonKey, Action update); - Task StartDungeon(DungeonSession dungeonSession); + Task RemoveSession(string dungeonKey, CancellationToken cancellationToken); + Task GetSession(string dungeonKey, CancellationToken cancellationToken); + Task ModifySession( + string dungeonKey, + Action update, + CancellationToken cancellationToken + ); + string CreateSession(DungeonSession dungeonSession); + Task SaveSession(CancellationToken cancellationToken); } diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonStartService.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonStartService.cs index c15571a59..c234b3a2e 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonStartService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/IDungeonStartService.cs @@ -19,18 +19,23 @@ Task GetAssignUnitIngameData( RepeatSetting? repeatSetting = null ); - Task InitiateQuest(int questId); + Task UpdateDbQuest(int questId); + Task ValidateStamina(int questId, StaminaType staminaType); + Task GetWallIngameData( int wallId, int wallLevel, int partyNo, - ulong? supportViewerId = null + ulong? supportViewerId ); + Task GetWallIngameData( int wallId, int wallLevel, IList party, - ulong? supportViewerId = null + ulong? supportViewerId ); + + Task SaveSession(CancellationToken cancellationToken); } diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Record/DungeonRecordController.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Record/DungeonRecordController.cs index 30b2dc9dc..e21ccab83 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Record/DungeonRecordController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Record/DungeonRecordController.cs @@ -28,7 +28,10 @@ public async Task> Record( CancellationToken cancellationToken ) { - DungeonSession session = await dungeonService.FinishDungeon(request.DungeonKey); + DungeonSession session = await dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); IngameResultData ingameResultData = await dungeonRecordService.GenerateIngameResultData( request.DungeonKey, @@ -66,17 +69,22 @@ IEnumerable helperDetailList ); } + await dungeonService.RemoveSession(request.DungeonKey, cancellationToken); + return response; } [HttpPost("record_multi")] [Authorize(AuthenticationSchemes = nameof(PhotonAuthenticationHandler))] - public async Task RecordMulti( + public async Task> RecordMulti( DungeonRecordRecordMultiRequest request, CancellationToken cancellationToken ) { - DungeonSession session = await dungeonService.FinishDungeon(request.DungeonKey); + DungeonSession session = await dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); IngameResultData ingameResultData = await dungeonRecordService.GenerateIngameResultData( request.DungeonKey, @@ -95,7 +103,7 @@ IEnumerable helperDetailList UpdateDataList updateDataList = await updateDataService.SaveChangesAsync(cancellationToken); - DungeonRecordRecordResponse response = + DungeonRecordRecordMultiResponse response = new() { IngameResultData = ingameResultData, UpdateDataList = updateDataList, }; if (session.QuestData?.IsSumUpTotalDamage ?? false) @@ -106,7 +114,9 @@ IEnumerable helperDetailList ); } - return Ok(response); + await dungeonService.RemoveSession(request.DungeonKey, cancellationToken); + + return response; } [HttpPost("record_time_attack")] diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartController.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartController.cs index 2ca8f88c4..4f6a5e882 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartController.cs @@ -36,7 +36,9 @@ CancellationToken cancellationToken ); if (!await dungeonStartService.ValidateStamina(request.QuestId, StaminaType.Single)) + { return this.Code(ResultCode.QuestStaminaSingleShort); + } IngameData ingameData = await dungeonStartService.GetIngameData( request.QuestId, @@ -51,6 +53,8 @@ CancellationToken cancellationToken cancellationToken ); + await dungeonService.SaveSession(cancellationToken); + return Ok(response); } @@ -61,7 +65,9 @@ CancellationToken cancellationToken ) { if (!await dungeonStartService.ValidateStamina(request.QuestId, StaminaType.Multi)) + { return this.Code(ResultCode.QuestStaminaMultiShort); + } IngameData ingameData = await dungeonStartService.GetIngameData( request.QuestId, @@ -87,7 +93,8 @@ await dungeonService.ModifySession( { session.IsHost = ingameData.IsHost; session.IsMulti = true; - } + }, + cancellationToken ); DungeonStartStartResponse response = await BuildResponse( @@ -96,6 +103,8 @@ await dungeonService.ModifySession( cancellationToken ); + await dungeonStartService.SaveSession(cancellationToken); + return Ok(response); } @@ -123,6 +132,8 @@ CancellationToken cancellationToken response.IngameData.RepeatState = request.RepeatState; + await dungeonStartService.SaveSession(cancellationToken); + return Ok(response); } @@ -162,7 +173,8 @@ await dungeonService.ModifySession( { session.IsHost = ingameData.IsHost; session.IsMulti = true; - } + }, + cancellationToken ); DungeonStartStartResponse response = await BuildResponse( @@ -171,6 +183,8 @@ await dungeonService.ModifySession( cancellationToken ); + await dungeonStartService.SaveSession(cancellationToken); + return Ok(response); } @@ -182,14 +196,15 @@ CancellationToken cancellationToken { logger.LogDebug("Starting dungeon for quest id {questId}", questId); - IngameQuestData ingameQuestData = await dungeonStartService.InitiateQuest(questId); - + IngameQuestData ingameQuestData = await dungeonStartService.UpdateDbQuest(questId); UpdateDataList updateData = await updateDataService.SaveChangesAsync(cancellationToken); OddsInfo oddsInfo = oddsInfoService.GetOddsInfo(questId, 0); + await dungeonService.ModifySession( ingameData.DungeonKey, - session => session.EnemyList[0] = oddsInfo.Enemy + session => session.EnemyList[0] = oddsInfo.Enemy, + cancellationToken ); if (questId == 204270302) diff --git a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartService.cs b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartService.cs index c8c9f3bab..25cfb555c 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Dungeon/Start/DungeonStartService.cs @@ -1,5 +1,4 @@ -using AutoMapper; -using DragaliaAPI.Database.Entities; +using DragaliaAPI.Database.Entities; using DragaliaAPI.Database.Entities.Scaffold; using DragaliaAPI.Database.Repositories; using DragaliaAPI.Features.Dungeon.AutoRepeat; @@ -30,7 +29,6 @@ public partial class DungeonStartService( IBonusService bonusService, IHelperService helperService, IUserService userService, - IMapper mapper, ILogger logger, IPaymentService paymentService, IEventService eventService, @@ -92,7 +90,7 @@ await partyQuery.ToListAsync(), QuestData questInfo = MasterAsset.QuestData.Get(questId); result.PartyInfo.PartyUnitList = await ProcessDetailedUnitList(detailedPartyUnits); - result.DungeonKey = await dungeonService.StartDungeon( + result.DungeonKey = dungeonService.CreateSession( new() { QuestData = questInfo, @@ -153,7 +151,7 @@ await detailQuery.AsNoTracking().FirstOrDefaultAsync() QuestData questInfo = MasterAsset.QuestData.Get(questId); result.PartyInfo.PartyUnitList = await ProcessDetailedUnitList(detailedPartyUnits); - result.DungeonKey = await dungeonService.StartDungeon( + result.DungeonKey = dungeonService.CreateSession( new() { QuestData = questInfo, @@ -169,7 +167,7 @@ public async Task GetWallIngameData( int wallId, int wallLevel, int partyNo, - ulong? supportViewerId = null + ulong? supportViewerId ) { Log.LoadingFromPartyNumber(logger, partyNo); @@ -185,7 +183,7 @@ public async Task GetWallIngameData( .ToListAsync(); result.PartyInfo.PartyUnitList = await ProcessDetailedUnitList(detailedPartyUnits); - result.DungeonKey = await dungeonService.StartDungeon( + result.DungeonKey = dungeonService.CreateSession( new() { Party = party.Where(x => x.CharaId != 0), @@ -202,7 +200,7 @@ public async Task GetWallIngameData( int wallId, int wallLevel, IList party, - ulong? supportViewerId = null + ulong? supportViewerId ) { IngameData result = await InitializeIngameData(0, supportViewerId); @@ -224,7 +222,7 @@ await detailQuery.AsNoTracking().FirstOrDefaultAsync() } result.PartyInfo.PartyUnitList = await ProcessDetailedUnitList(detailedPartyUnits); - result.DungeonKey = await dungeonService.StartDungeon( + result.DungeonKey = dungeonService.CreateSession( new() { Party = party.Where(x => x.CharaId != 0), @@ -237,7 +235,7 @@ await detailQuery.AsNoTracking().FirstOrDefaultAsync() return result; } - public async Task InitiateQuest(int questId) + public async Task UpdateDbQuest(int questId) { DbQuest? quest = await questRepository.Quests.FirstOrDefaultAsync(x => x.QuestId == questId @@ -259,6 +257,9 @@ public async Task InitiateQuest(int questId) }; } + public Task SaveSession(CancellationToken cancellationToken) => + dungeonService.SaveSession(cancellationToken); + private async Task GetSupportData(ulong supportViewerId) { QuestGetSupportUserListResponse helperList = await helperService.GetHelpers(); @@ -305,7 +306,7 @@ x is not null List units = detailedPartyUnits .OrderBy(x => x.Position) - .Select(mapper.Map) + .MapToPartyUnitList() .ToList(); if (units.Count != 4) @@ -334,15 +335,20 @@ x is not null return units; } - private List ProcessUnitList(List partyUnits, int firstPartyNo) + private static List ProcessUnitList( + List partyUnits, + int firstPartyNo + ) { foreach (DbPartyUnit unit in partyUnits) { if (unit.PartyNo != firstPartyNo) + { unit.UnitNo += 4; + } } - return partyUnits.Select(mapper.Map).OrderBy(x => x.UnitNo).ToList(); + return partyUnits.MapToPartySettingList().OrderBy(x => x.UnitNo).ToList(); } private async Task InitializeIngameData(int questId, ulong? supportViewerId = null) @@ -372,7 +378,7 @@ await paymentService.ProcessPayment( ); } - result.AreaInfoList = questInfo.AreaInfo.Select(mapper.Map); + result.AreaInfoList = questInfo.AreaInfo.MapToAreaInfoList(); result.DungeonType = questInfo.DungeonType; result.RebornLimit = questInfo.RebornLimit; result.ContinueLimit = questInfo.ContinueLimit; diff --git a/DragaliaAPI/DragaliaAPI/Features/Quest/IQuestService.cs b/DragaliaAPI/DragaliaAPI/Features/Quest/IQuestService.cs index 30370142b..bb549ebc6 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Quest/IQuestService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Quest/IQuestService.cs @@ -1,4 +1,5 @@ -using DragaliaAPI.Features.Player; +using DragaliaAPI.Features.Dungeon; +using DragaliaAPI.Features.Player; using DragaliaAPI.Models; using DragaliaAPI.Models.Generated; @@ -7,6 +8,7 @@ namespace DragaliaAPI.Features.Quest; public interface IQuestService { Task GetQuestStamina(int questId, StaminaType type); + Task<(bool BestClearTime, IEnumerable Bonus)> ProcessQuestCompletion( DungeonSession session, PlayRecord playRecord diff --git a/DragaliaAPI/DragaliaAPI/Features/Quest/QuestService.cs b/DragaliaAPI/DragaliaAPI/Features/Quest/QuestService.cs index 10bbef3bd..f7c6acb0b 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Quest/QuestService.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Quest/QuestService.cs @@ -1,5 +1,6 @@ using DragaliaAPI.Database.Entities; using DragaliaAPI.Database.Repositories; +using DragaliaAPI.Features.Dungeon; using DragaliaAPI.Features.Missions; using DragaliaAPI.Features.Player; using DragaliaAPI.Features.Reward; diff --git a/DragaliaAPI/DragaliaAPI/Features/Wall/WallController.cs b/DragaliaAPI/DragaliaAPI/Features/Wall/WallController.cs index 8d773e571..db02d898a 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Wall/WallController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Wall/WallController.cs @@ -23,9 +23,16 @@ ILogger logger ) : DragaliaControllerBase { [HttpPost("fail")] - public async Task Fail(WallFailRequest request) + public async Task Fail( + WallFailRequest request, + CancellationToken cancellationToken + ) { - DungeonSession session = await dungeonService.FinishDungeon(request.DungeonKey); + DungeonSession session = await dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); + await dungeonService.RemoveSession(request.DungeonKey, cancellationToken); return Ok( new WallFailResponse() diff --git a/DragaliaAPI/DragaliaAPI/Features/Wall/WallRecordController.cs b/DragaliaAPI/DragaliaAPI/Features/Wall/WallRecordController.cs index 558fe5856..3ee85d24c 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Wall/WallRecordController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Wall/WallRecordController.cs @@ -50,7 +50,11 @@ public async Task Record( CancellationToken cancellationToken ) { - DungeonSession dungeonSession = await dungeonService.FinishDungeon(request.DungeonKey); + DungeonSession dungeonSession = await this.dungeonService.GetSession( + request.DungeonKey, + cancellationToken + ); + DbPlayerQuestWall questWall = await this.wallService.GetQuestWall(request.WallId); int finishedLevel = dungeonSession.WallLevel; // ex: if you finish level 2, this value should be 2 @@ -127,6 +131,9 @@ IEnumerable helperDetailList WallDropReward = wallDropReward, WallUnitInfo = wallUnitInfo }; + + await dungeonService.RemoveSession(request.DungeonKey, cancellationToken); + return Ok(data); } diff --git a/DragaliaAPI/DragaliaAPI/Features/Wall/WallStartController.cs b/DragaliaAPI/DragaliaAPI/Features/Wall/WallStartController.cs index 5b55c7edd..b2713bd91 100644 --- a/DragaliaAPI/DragaliaAPI/Features/Wall/WallStartController.cs +++ b/DragaliaAPI/DragaliaAPI/Features/Wall/WallStartController.cs @@ -11,19 +11,12 @@ namespace DragaliaAPI.Features.Wall; [Route("wall_start")] public class WallStartController( - IMapper mapper, IUpdateDataService updateDataService, IOddsInfoService oddsInfoService, IWallService wallService, IDungeonStartService dungeonStartService ) : DragaliaControllerBase { - private readonly IMapper mapper = mapper; - private readonly IUpdateDataService updateDataService = updateDataService; - private readonly IOddsInfoService oddsInfoService = oddsInfoService; - private readonly IWallService wallService = wallService; - private readonly IDungeonStartService dungeonStartService = dungeonStartService; - // Called when starting a Mercurial Gauntlet quest [HttpPost("start")] public async Task Start( @@ -38,19 +31,21 @@ CancellationToken cancellationToken request.WallLevel ); - IngameData ingameData = await this.dungeonStartService.GetWallIngameData( + IngameData ingameData = await dungeonStartService.GetWallIngameData( request.WallId, request.WallLevel, request.PartyNo, request.SupportViewerId ); - ingameData.AreaInfoList = questWallDetail.AreaInfo.Select(mapper.Map); + await dungeonStartService.SaveSession(cancellationToken); + + ingameData.AreaInfoList = questWallDetail.AreaInfo.MapToAreaInfoList(); IngameWallData ingameWallData = new() { WallId = request.WallId, WallLevel = request.WallLevel }; - OddsInfo oddsInfo = this.oddsInfoService.GetWallOddsInfo(request.WallId, request.WallLevel); + OddsInfo oddsInfo = oddsInfoService.GetWallOddsInfo(request.WallId, request.WallLevel); UpdateDataList updateDataList = await updateDataService.SaveChangesAsync(cancellationToken); @@ -78,19 +73,21 @@ CancellationToken cancellationToken request.WallLevel ); - IngameData ingameData = await this.dungeonStartService.GetWallIngameData( + IngameData ingameData = await dungeonStartService.GetWallIngameData( request.WallId, request.WallLevel, request.RequestPartySettingList, request.SupportViewerId ); - ingameData.AreaInfoList = questWallDetail.AreaInfo.Select(mapper.Map); + await dungeonStartService.SaveSession(cancellationToken); + + ingameData.AreaInfoList = questWallDetail.AreaInfo.MapToAreaInfoList(); IngameWallData ingameWallData = new() { WallId = request.WallId, WallLevel = request.WallLevel }; - OddsInfo oddsInfo = this.oddsInfoService.GetWallOddsInfo(request.WallId, request.WallLevel); + OddsInfo oddsInfo = oddsInfoService.GetWallOddsInfo(request.WallId, request.WallLevel); UpdateDataList updateDataList = await updateDataService.SaveChangesAsync(cancellationToken); diff --git a/DragaliaAPI/DragaliaAPI/Models/DungeonSession.cs b/DragaliaAPI/DragaliaAPI/Models/DungeonSession.cs deleted file mode 100644 index 81b4ca05c..000000000 --- a/DragaliaAPI/DragaliaAPI/Models/DungeonSession.cs +++ /dev/null @@ -1,34 +0,0 @@ -using DragaliaAPI.Models.Generated; -using DragaliaAPI.Shared.Definitions.Enums; -using DragaliaAPI.Shared.MasterAsset.Models; - -namespace DragaliaAPI.Models; - -public class DungeonSession -{ - public required IEnumerable Party { get; set; } - - public QuestData? QuestData { get; set; } - - public int QuestId => QuestData?.Id ?? 0; - - public int QuestGid => QuestData?.Gid ?? 0; - - public VariationTypes QuestVariation => QuestData?.VariationType ?? VariationTypes.Normal; - - public bool IsHost { get; set; } = true; - - public bool IsMulti { get; set; } - - public ulong? SupportViewerId { get; set; } - - public DateTimeOffset StartTime { get; set; } - - public Dictionary> EnemyList { get; set; } = new(); - - public int PlayCount { get; set; } = 1; - - public int WallId { get; set; } - - public int WallLevel { get; set; } -}