using SVSim.BattleNode.Bridge;
using SVSim.BattleNode.Sessions;
namespace SVSim.EmulatedEntrypoint.Matching;
///
public sealed class MatchingResolver : IMatchingResolver
{
private readonly IMatchingBridge _bridge;
private readonly IMatchingPairUpService _pairUp;
private readonly BattleNodeOptions _options;
public MatchingResolver(
IMatchingBridge bridge,
IMatchingPairUpService pairUp,
BattleNodeOptions options)
{
_bridge = bridge;
_pairUp = pairUp;
_options = options;
}
public Task ResolveAsync(
string mode,
BattlePlayer player,
bool scriptedOptIn,
CancellationToken ct)
{
// Dev-affordance short-circuit. Either a per-request flag (e.g. ?scripted=1) or the
// process-wide BattleNodeOptions.SoloDefaultsToScripted toggle puts us here.
// Registers a Scripted match (server-side scripted opponent in BattleSession) and
// returns matching_state=3004 SUCCEEDED so the client opens the WS and proceeds.
if (scriptedOptIn || _options.SoloDefaultsToScripted)
{
var m = _bridge.RegisterBattle(player, p2: null, BattleType.Scripted);
return Task.FromResult(new MatchingResolution(3004, m.BattleId, m.NodeServerUrl));
}
return ResolveViaPairUpAsync(mode, player, ct);
}
private async Task ResolveViaPairUpAsync(string mode, BattlePlayer player, CancellationToken ct)
{
var paired = await _pairUp.TryPairAsync(mode, player, ct);
if (paired is null)
{
// Parked. matching_state 3002 RETRY. node_server_url MUST be present as empty
// string (the client unguarded-.ToString()s it before consulting matching_state).
return new MatchingResolution(3002, BattleId: null, "");
}
// 3011 = AI_BATTLE_MATCHING_SUCCEEDED (PvpFirstThenAiFallback policy's threshold fired)
// 3007 = RC_BATTLE_MATCHING_SUCCEEDED_OWNER (first arriver, cache pickup)
// 3004 = RC_BATTLE_MATCHING_SUCCEEDED (joiner — triggered the pair)
var state = paired switch
{
{ IsAiFallback: true } => 3011,
{ IsOwner: true } => 3007,
_ => 3004,
};
return new MatchingResolution(state, paired.Match.BattleId, paired.Match.NodeServerUrl);
}
}