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); } }