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; using SVSim.EmulatedEntrypoint.Services; namespace SVSim.EmulatedEntrypoint.Controllers; [Route("arena_two_pick_battle")] 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, IBattleContextStore battleContextStore, IBattleHistoryWriter historyWriter, IPlayedTogetherWriter playedTogetherWriter) { _svc = svc; _matchContextBuilder = matchContextBuilder; _resolver = resolver; _battleContextStore = battleContextStore; _historyWriter = historyWriter; _playedTogetherWriter = playedTogetherWriter; } [HttpPost("do_matching")] public async Task DoMatching( [FromBody] DoMatchingRequest req, CancellationToken ct = default) { if (!TryGetViewerId(out var vid)) return Unauthorized(); try { 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, BattleId = r.BattleId, NodeServerUrl = r.NodeServerUrl, }); } catch (ArenaTwoPickException ex) { return BadRequest(new { error_code = ex.ErrorCode }); } } [HttpPost("finish")] public async Task Finish([FromBody] BattleFinishRequest req, CancellationToken ct = default) { if (!TryGetViewerId(out var vid)) return Unauthorized(); try { 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, GetClassExperience = result.GetClassExperience, ClassExperience = result.ClassExperience, ClassLevel = result.ClassLevel, SpotPointInfo = new SpotPointInfoDto { BeforeSpotPoint = result.BeforeSpotPoint, AddSpotPoint = result.AddSpotPoint, AfterSpotPoint = result.AfterSpotPoint, }, }); } catch (ArenaTwoPickException ex) { return BadRequest(new { error_code = ex.ErrorCode }); } } }