MultiplayerSession.Lobbies.cs
using Sandbox;
using Sandbox.Network;
using System;
using System.Threading;
using System.Threading.Tasks;
namespace PaintballBaddies;

public sealed partial class MultiplayerSession
{
    public bool Searching {get;private set;}
    public bool QuickPlaying {get;private set;}
    public bool CanQuickPlay=>!Online || (Networking.IsHost && RoundId==0 && Players.Count()<=1);
    public int LobbyRevision {get;private set;}
    private CancellationTokenSource lobbySearch;
    private int searchVersion;
    private int playVersion;

    public static bool CompatibleLobby(LobbyInformation lobby)=>lobby.Get("pb_protocol","")==Protocol;
    public static string LobbyLabel(LobbyInformation lobby)
    {
        string mode=lobby.Get("pb_mode","");
        string title=mode=="overdrive" ? "SAUSAGE SURVIVAL" : mode=="pop_riot" ? "POP RIOT" : "PAINT RUSH";
        string length=mode=="overdrive" ? "3 WAVES" : $"{lobby.Get("pb_minutes","3")} MIN";
        return $"{lobby.Name} / {title} / {lobby.Members}/{lobby.MaxMembers} PEOPLE / {length}"
            +(lobby.IsFull ? " / FULL" : !CompatibleLobby(lobby) ? " / DIFFERENT BUILD" : "");
    }

    public async void RefreshLobbies()=>await RefreshLobbiesAsync();
    public async Task<bool> RefreshLobbiesAsync()
    {
        if(Searching || !CanQuickPlay || Networking.IsConnecting)return false;
        int version=++searchVersion;
        Searching=true;LobbyRevision++;Notice="Looking for games...";
        using var search=new CancellationTokenSource(TimeSpan.FromSeconds(12));
        lobbySearch=search;
        try
        {
            var found=await Networking.QueryLobbies(search.Token);
            await GameTask.MainThread();
            if(!IsValid || search.IsCancellationRequested || version!=searchVersion)return false;
            Lobbies=found.Where(x=>!x.IsHidden && x.OwnerId!=Connection.Local.SteamId.ValueUnsigned)
                .OrderBy(x=>x.IsFull || !CompatibleLobby(x)).ThenByDescending(x=>x.Members).ToList();
            Notice=Lobbies.Count==0 ? "No matches found. Play starts one with AI, ready for people to join."
                : "Choose a game. Full lobbies and different builds cannot be joined.";
            return true;
        }
        catch(OperationCanceledException){await GameTask.MainThread();if(IsValid && version==searchVersion)Notice="Search timed out. Check Steam and try Play again.";}
        catch(Exception){await GameTask.MainThread();if(IsValid && version==searchVersion)Notice="Cannot reach matches. Check Steam and try Play again.";}
        finally {if(IsValid && version==searchVersion){Searching=false;LobbyRevision++;lobbySearch=null;}}
        return false;
    }
    // Use the engine's lobby directory and connection transport, with game-specific
    // compatibility, public-access and self-host filters before joining.
    public static IEnumerable<LobbyInformation> PlayCandidates(IEnumerable<LobbyInformation> lobbies,ulong localOwner)
        =>lobbies.Where(x=>!x.IsHidden && !x.IsFull && x.Members>0 && CompatibleLobby(x)
            && x.Get("pb_access","")=="public" && x.OwnerId!=localOwner && x.LobbyId!=0)
            .OrderByDescending(x=>x.Members).ThenBy(x=>x.LobbyId);
    public async void QuickPlay()
    {
        if(QuickPlaying || Searching || Networking.IsConnecting)return;
        var controls=Scene.GetAllComponents<PaintballControls>().FirstOrDefault();
        if(!CanQuickPlay){controls?.ResumeGame();return;}
        if(controls.IsValid() && !controls.Open)controls.Toggle();
        int request=++playVersion;
        QuickPlaying=true;
        bool success=await RefreshLobbiesAsync();
        await GameTask.MainThread();
        if(!IsValid || !QuickPlaying || request!=playVersion)return;
        QuickPlaying=false;LobbyRevision++;
        if(!success)return;
        // A person may have joined our platform-created lobby while searching.
        // Keep their match rather than moving its host to a different lobby.
        if(!CanQuickPlay){controls?.ResumeGame();return;}
        var candidates=PlayCandidates(Lobbies,Connection.Local.SteamId.ValueUnsigned)
            .Where(x=>x.Get("pb_mode","paint_rush")==ModeKey(Mode) && x.Get("pb_arena",ArenaSelection.Foundry)==ArenaSelection.Current(Scene)).ToArray();
        if(candidates.Length>0)JoinLobby(candidates[0]);
        else if(Online)controls?.ResumeGame();
        else Host();
    }
    public void CancelPlaySearch()
    {
        lobbySearch?.Cancel();lobbySearch=null;searchVersion++;playVersion++;
        Searching=false;QuickPlaying=false;LobbyRevision++;Notice="Search cancelled.";
    }
    public void CancelMenuConnection()
    {
        CancelPlaySearch();
        if(!Networking.IsConnecting)return;
        Networking.Disconnect();
        Notice="Connection cancelled. Choose another match or start your own.";
    }
    public void JoinLobby(LobbyInformation lobby)
    {
        if(lobby.IsFull){Notice="That lobby is full. Refresh to find another game.";return;}
        if(!CompatibleLobby(lobby)){Notice="That lobby uses a different build. Update the game and refresh.";return;}
        Join(lobby.LobbyId);
    }
    protected override void OnDestroy()=>CancelPlaySearch();
}