diff --git a/SVSim.EmulatedEntrypoint/Controllers/ArenaColosseumBattleController.cs b/SVSim.EmulatedEntrypoint/Controllers/ArenaColosseumBattleController.cs index 12762dc4..799d9643 100644 --- a/SVSim.EmulatedEntrypoint/Controllers/ArenaColosseumBattleController.cs +++ b/SVSim.EmulatedEntrypoint/Controllers/ArenaColosseumBattleController.cs @@ -2,7 +2,9 @@ using System.Text.Json; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using SVSim.BattleNode.Bridge; +using SVSim.Database; using SVSim.Database.Repositories.Viewer; +using SVSim.Database.Services.BattleXp; using SVSim.EmulatedEntrypoint.Constants; using SVSim.EmulatedEntrypoint.Matching; using SVSim.EmulatedEntrypoint.Models.Dtos.ArenaColosseum; @@ -36,6 +38,9 @@ public sealed class ArenaColosseumBattleController : ControllerBase private readonly IMatchingResolver _resolver; private readonly IArenaColosseumRunRepository _runs; private readonly IColosseumProgressionService _progression; + private readonly IViewerRepository _viewers; + private readonly IBattleXpService _xp; + private readonly SVSimDbContext _db; private readonly ILogger _log; public ArenaColosseumBattleController( @@ -43,12 +48,18 @@ public sealed class ArenaColosseumBattleController : ControllerBase IMatchingResolver resolver, IArenaColosseumRunRepository runs, IColosseumProgressionService progression, + IViewerRepository viewers, + IBattleXpService xp, + SVSimDbContext db, ILogger log) { _ctxBuilder = ctxBuilder; _resolver = resolver; _runs = runs; _progression = progression; + _viewers = viewers; + _xp = xp; + _db = db; _log = log; } @@ -162,10 +173,27 @@ public sealed class ArenaColosseumBattleController : ControllerBase await _runs.UpsertAsync(run); } + int gainXp = 0, totalXp = 0, level = 1; + var viewer = await _viewers.LoadForBattleXpGrantAsync(vid, ct); + if (viewer is not null) + { + var xp = await _xp.GrantAsync(viewer, req.ClassId, req.BattleResult == 1, BattleXpMode.Colosseum, ct); + await _db.SaveChangesAsync(ct); + gainXp = xp.GetXp; + totalXp = xp.TotalXp; + level = xp.Level == 0 ? 1 : xp.Level; + } + // result_code 3502 ("battle already finished") is the idempotent-retry tolerance per // FinishTaskBase.IsEffectiveErrorCode. Server emits standard data; the translation // middleware sets the wire result_code via data_headers from the response envelope. - return Ok(new ColosseumBattleFinishResponseDto { BattleResult = req.BattleResult }); + return Ok(new ColosseumBattleFinishResponseDto + { + BattleResult = req.BattleResult, + GetClassExperience = gainXp, + ClassExperience = totalXp, + ClassLevel = level, + }); } private static List ParseIntList(string json) => diff --git a/SVSim.EmulatedEntrypoint/Models/Dtos/ArenaColosseum/BattleFinishRequestDto.cs b/SVSim.EmulatedEntrypoint/Models/Dtos/ArenaColosseum/BattleFinishRequestDto.cs index 32c766e6..7d80d892 100644 --- a/SVSim.EmulatedEntrypoint/Models/Dtos/ArenaColosseum/BattleFinishRequestDto.cs +++ b/SVSim.EmulatedEntrypoint/Models/Dtos/ArenaColosseum/BattleFinishRequestDto.cs @@ -46,4 +46,13 @@ public sealed class ColosseumBattleFinishResponseDto { [JsonPropertyName("battle_result")] [Key("battle_result")] public int BattleResult { get; set; } + + [JsonPropertyName("get_class_experience")] [Key("get_class_experience")] + public int GetClassExperience { get; set; } + + [JsonPropertyName("class_experience")] [Key("class_experience")] + public int ClassExperience { get; set; } + + [JsonPropertyName("class_level")] [Key("class_level")] + public int ClassLevel { get; set; } = 1; } diff --git a/SVSim.UnitTests/Controllers/ArenaColosseumBattleControllerTests.cs b/SVSim.UnitTests/Controllers/ArenaColosseumBattleControllerTests.cs index 07cc21a7..1ee59532 100644 --- a/SVSim.UnitTests/Controllers/ArenaColosseumBattleControllerTests.cs +++ b/SVSim.UnitTests/Controllers/ArenaColosseumBattleControllerTests.cs @@ -118,6 +118,12 @@ public class ArenaColosseumBattleControllerTests var resp = await client.PostAsync("/colosseum_battle/finish", JsonContent.Create(req)); Assert.That(resp.StatusCode, Is.EqualTo(HttpStatusCode.OK)); + // XP wire assertions + using var doc = JsonDocument.Parse(await resp.Content.ReadAsStringAsync()); + Assert.That(doc.RootElement.GetProperty("get_class_experience").GetInt32(), Is.EqualTo(100)); + Assert.That(doc.RootElement.GetProperty("class_experience").GetInt32(), Is.EqualTo(50)); + Assert.That(doc.RootElement.GetProperty("class_level").GetInt32(), Is.EqualTo(2)); + using var scope = factory.Services.CreateScope(); var db = scope.ServiceProvider.GetRequiredService(); var run = await db.ViewerArenaColosseumRuns.FirstAsync(r => r.ViewerId == vid); @@ -125,6 +131,13 @@ public class ArenaColosseumBattleControllerTests Assert.That(run.LossCount, Is.EqualTo(0)); Assert.That(run.BattleCountThisRound, Is.EqualTo(1)); Assert.That(run.ResultListJson, Is.EqualTo("[1]")); + + // Persisted class XP on viewer + var v = await db.Viewers.Include(x => x.Classes).ThenInclude(c => c.Class) + .FirstAsync(x => x.Id == vid); + var cls1 = v.Classes.Single(c => c.Class.Id == 1); + Assert.That(cls1.Level, Is.EqualTo(2)); + Assert.That(cls1.Exp, Is.EqualTo(50)); } [Test]