feat(replay): wire arena two-pick finish hook
Same pattern as rank-battle: DoMatching stashes context; Finish takes it and records history + played-together. Opponent identity is left as placeholder fields until the resolver carries it through. Test seeds an active ViewerArenaTwoPickRun so RecordBattleResultAsync does not throw no_active_run during the e2e flow. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,9 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SVSim.BattleNode.Bridge;
|
||||
using SVSim.Database.Enums;
|
||||
using SVSim.Database.Services.Friend;
|
||||
using SVSim.Database.Services.Replay;
|
||||
using SVSim.EmulatedEntrypoint.Extensions;
|
||||
using SVSim.EmulatedEntrypoint.Matching;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.ArenaTwoPick;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.ArenaTwoPick;
|
||||
@@ -13,15 +17,24 @@ public class ArenaTwoPickBattleController : SVSimController
|
||||
private readonly IArenaTwoPickService _svc;
|
||||
private readonly IMatchContextBuilder _matchContextBuilder;
|
||||
private readonly IMatchingResolver _resolver;
|
||||
private readonly IBattleContextStore _battleContextStore;
|
||||
private readonly IBattleHistoryWriter _historyWriter;
|
||||
private readonly IPlayedTogetherWriter _playedTogetherWriter;
|
||||
|
||||
public ArenaTwoPickBattleController(
|
||||
IArenaTwoPickService svc,
|
||||
IMatchContextBuilder matchContextBuilder,
|
||||
IMatchingResolver resolver)
|
||||
IMatchingResolver resolver,
|
||||
IBattleContextStore battleContextStore,
|
||||
IBattleHistoryWriter historyWriter,
|
||||
IPlayedTogetherWriter playedTogetherWriter)
|
||||
{
|
||||
_svc = svc;
|
||||
_matchContextBuilder = matchContextBuilder;
|
||||
_resolver = resolver;
|
||||
_battleContextStore = battleContextStore;
|
||||
_historyWriter = historyWriter;
|
||||
_playedTogetherWriter = playedTogetherWriter;
|
||||
}
|
||||
|
||||
[HttpPost("do_matching")]
|
||||
@@ -34,6 +47,38 @@ public class ArenaTwoPickBattleController : SVSimController
|
||||
{
|
||||
var ctx = await _matchContextBuilder.BuildForTwoPickAsync(vid);
|
||||
var r = await _resolver.ResolveAsync("arena_two_pick_battle", new BattlePlayer(vid, ctx), ct);
|
||||
|
||||
if (r.BattleId is not null && long.TryParse(r.BattleId, out var battleIdLong))
|
||||
{
|
||||
_battleContextStore.Set(vid, new BattleContext(
|
||||
BattleId: battleIdLong,
|
||||
// Two-pick wire battle_type — see docs/api-spec/common/types.ts.md
|
||||
// #battle-types. Captured prod frames use 4 for both private match
|
||||
// AND arena two-pick contexts; if a future capture disagrees, refine.
|
||||
BattleType: 4,
|
||||
DeckFormat: Format.TwoPick.ToApi(), // wire-int 10
|
||||
TwoPickType: 0, // captured "0"; refine once tracked on MatchContext
|
||||
SelfClassId: (int)ctx.ClassId, // CardClass enum
|
||||
SelfSubClassId: 0,
|
||||
SelfCharaId: int.TryParse(ctx.CharaId, out var ch) ? ch : 0,
|
||||
SelfRotationId: "0",
|
||||
// MatchContext (SVSim.BattleNode/Bridge/MatchContext.cs) does NOT carry
|
||||
// opponent identity — the resolver returns only the BattleId. Leave
|
||||
// opponent placeholders; when the two-pick matchmaking flow plumbs the
|
||||
// second player's MatchContext through to the resolver result, fill
|
||||
// these from there (and stash for both players).
|
||||
OpponentViewerId: 0,
|
||||
OpponentName: "",
|
||||
OpponentClassId: 0,
|
||||
OpponentSubClassId: 0,
|
||||
OpponentCharaId: 0,
|
||||
OpponentCountryCode: "",
|
||||
OpponentEmblemId: 0,
|
||||
OpponentDegreeId: 0,
|
||||
OpponentRotationId: "0",
|
||||
BattleStartTime: DateTime.UtcNow));
|
||||
}
|
||||
|
||||
return Ok(new DoMatchingResponseDto
|
||||
{
|
||||
MatchingState = r.MatchingState,
|
||||
@@ -48,12 +93,30 @@ public class ArenaTwoPickBattleController : SVSimController
|
||||
}
|
||||
|
||||
[HttpPost("finish")]
|
||||
public async Task<IActionResult> Finish([FromBody] BattleFinishRequest req)
|
||||
public async Task<IActionResult> Finish([FromBody] BattleFinishRequest req, CancellationToken ct = default)
|
||||
{
|
||||
if (!TryGetViewerId(out var vid)) return Unauthorized();
|
||||
try
|
||||
{
|
||||
var result = await _svc.RecordBattleResultAsync(vid, req.BattleResult == 1);
|
||||
var battleCtx = _battleContextStore.TakeFor(vid);
|
||||
bool isWin = req.BattleResult == 1;
|
||||
|
||||
await _historyWriter.RecordAsync(vid, battleCtx, isWin, ct);
|
||||
|
||||
if (battleCtx is { OpponentViewerId: > 0 })
|
||||
{
|
||||
await _playedTogetherWriter.RecordAsync(
|
||||
vid,
|
||||
battleCtx.OpponentViewerId,
|
||||
new BattleParticipationContext(
|
||||
PlayedMode: 0,
|
||||
BattleType: battleCtx.BattleType,
|
||||
DeckFormat: battleCtx.DeckFormat,
|
||||
TwoPickType: battleCtx.TwoPickType),
|
||||
ct);
|
||||
}
|
||||
|
||||
var result = await _svc.RecordBattleResultAsync(vid, isWin);
|
||||
return Ok(new BattleFinishResponseDto
|
||||
{
|
||||
BattleResult = result.BattleResult,
|
||||
|
||||
Reference in New Issue
Block a user