feat(arena-tk2): PvP pair-up trigger via /do_matching, ?scripted=1 opt-in
Solo pollers park (3001 RETRY); two concurrent pollers pair and both receive 3004 + same BattleId. Cache hits on the first arriver's next poll. ?scripted=1 retains today's solo Scripted path for dev work. Response DTO's BattleId/NodeServerUrl become nullable so 3001 omits them on the wire (WhenWritingNull policy drops them). ASP.NET's default bool binder rejects "1" as a value, so the scripted opt-in is bound as string? and parsed permissively (accepts "1" and "true"/"True"/etc.) rather than relying on built-in bool binding.
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using SVSim.BattleNode.Bridge;
|
||||
using SVSim.EmulatedEntrypoint.Matching;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Requests.ArenaTwoPick;
|
||||
using SVSim.EmulatedEntrypoint.Models.Dtos.Responses.ArenaTwoPick;
|
||||
using SVSim.EmulatedEntrypoint.Services;
|
||||
@@ -12,33 +13,67 @@ public class ArenaTwoPickBattleController : SVSimController
|
||||
private readonly IArenaTwoPickService _svc;
|
||||
private readonly IMatchingBridge _matching;
|
||||
private readonly IMatchContextBuilder _matchContextBuilder;
|
||||
private readonly IMatchingPairUpService _pairUp;
|
||||
|
||||
public ArenaTwoPickBattleController(
|
||||
IArenaTwoPickService svc,
|
||||
IMatchingBridge matching,
|
||||
IMatchContextBuilder matchContextBuilder)
|
||||
IMatchContextBuilder matchContextBuilder,
|
||||
IMatchingPairUpService pairUp)
|
||||
{
|
||||
_svc = svc;
|
||||
_matching = matching;
|
||||
_matchContextBuilder = matchContextBuilder;
|
||||
_pairUp = pairUp;
|
||||
}
|
||||
|
||||
[HttpPost("do_matching")]
|
||||
public async Task<IActionResult> DoMatching([FromBody] DoMatchingRequest req)
|
||||
public async Task<IActionResult> DoMatching(
|
||||
[FromBody] DoMatchingRequest req,
|
||||
[FromQuery(Name = "scripted")] string? scripted = null,
|
||||
CancellationToken ct = default)
|
||||
{
|
||||
if (!TryGetViewerId(out var vid)) return Unauthorized();
|
||||
// Accept "1" or "true" (case-insensitive) as opt-in for the legacy Scripted path.
|
||||
// ASP.NET's default bool binder rejects "1", so do a permissive parse here.
|
||||
var useScripted = scripted is not null
|
||||
&& (scripted == "1" || string.Equals(scripted, "true", StringComparison.OrdinalIgnoreCase));
|
||||
try
|
||||
{
|
||||
var ctx = await _matchContextBuilder.BuildForTwoPickAsync(vid);
|
||||
var match = _matching.RegisterBattle(
|
||||
|
||||
if (useScripted)
|
||||
{
|
||||
var scriptedMatch = _matching.RegisterBattle(
|
||||
new SVSim.BattleNode.Bridge.BattlePlayer(vid, ctx),
|
||||
p2: null,
|
||||
SVSim.BattleNode.Sessions.BattleType.Scripted);
|
||||
return Ok(new DoMatchingResponseDto
|
||||
{
|
||||
MatchingState = 3004,
|
||||
BattleId = scriptedMatch.BattleId,
|
||||
NodeServerUrl = scriptedMatch.NodeServerUrl,
|
||||
});
|
||||
}
|
||||
|
||||
var paired = await _pairUp.TryPairAsync(
|
||||
"arena_two_pick_battle",
|
||||
new SVSim.BattleNode.Bridge.BattlePlayer(vid, ctx),
|
||||
p2: null,
|
||||
SVSim.BattleNode.Sessions.BattleType.Scripted);
|
||||
ct);
|
||||
if (paired is null)
|
||||
{
|
||||
return Ok(new DoMatchingResponseDto
|
||||
{
|
||||
MatchingState = 3001,
|
||||
// BattleId / NodeServerUrl null — client polls again.
|
||||
});
|
||||
}
|
||||
|
||||
return Ok(new DoMatchingResponseDto
|
||||
{
|
||||
MatchingState = 3004,
|
||||
BattleId = match.BattleId,
|
||||
NodeServerUrl = match.NodeServerUrl,
|
||||
BattleId = paired.BattleId,
|
||||
NodeServerUrl = paired.NodeServerUrl,
|
||||
});
|
||||
}
|
||||
catch (ArenaTwoPickException ex)
|
||||
|
||||
Reference in New Issue
Block a user