using System; using System.Threading.Tasks; using UnityEngine; using UnityEngine.Networking; public static class Cupid { private static string serverAddress; public static string ServerAddress => serverAddress; private static string cupidBaseUri; public static string CupidURI => cupidBaseUri; private static int port; public static int Port => port; private static string bearerToken; public static string BearerToken => bearerToken; private static string settingsPassword = ""; public static string SettingsPassword => settingsPassword; private static CupidSettings settings = null; public static CupidSettings Settings => settings; public static bool isInitialized => settings != null; public static int RoomPort = -1; public static async Task Init(string _serverAddress, int _port, string _bearerToken, string _settingsPassword) { if (!TryBuildEndpoints(_serverAddress, _port, out serverAddress, out cupidBaseUri, out port)) { Logger.Log("Cupid: invalid or empty server address."); bearerToken = _bearerToken ?? ""; settings = null; return; } bearerToken = _bearerToken ?? ""; if (string.IsNullOrEmpty(bearerToken)) { Logger.Log("Cupid: no bearer token; configure auth before matchmaking."); settings = null; return; } settingsPassword = _settingsPassword ?? ""; string settingsUrl = CupidURI + "/settings?password=" + UnityWebRequest.EscapeURL(settingsPassword); using (UnityWebRequest www = UnityWebRequest.Get(settingsUrl)) { www.SetRequestHeader("Authorization", "Bearer " + bearerToken); var operation = www.SendWebRequest(); while (!operation.isDone) { await Task.Yield(); } try { if (www.result != UnityWebRequest.Result.Success) { Logger.Log("Cupid settings request failed: " + www.error); Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : ""); settings = null; return; } string body = www.downloadHandler != null ? www.downloadHandler.text : ""; if (string.IsNullOrWhiteSpace(body)) { settings = null; return; } if (body.Trim() == "403 Unauthorized") { Logger.Log("Cupid settings: wrong or missing password query (server rejected /settings)."); settings = null; return; } settings = JsonUtility.FromJson(body); if (settings == null) { throw new NullReferenceException(); } Logger.Log("Cupid init success"); } catch (Exception e) { Logger.Log("Error retreiving settings from server " + e.Message); Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : ""); settings = null; } } } /// /// Plain host/IP + HTTP port, or an absolute URL (https://host with no port uses 443). /// Sets for game client transport; has no trailing slash. /// static bool TryBuildEndpoints(string input, int fallbackHttpPort, out string hostOnly, out string baseUri, out int resolvedPort) { hostOnly = ""; baseUri = ""; resolvedPort = fallbackHttpPort; input = (input ?? "").Trim(); if (string.IsNullOrEmpty(input)) return false; if (input.IndexOf("://", StringComparison.Ordinal) >= 0 && Uri.TryCreate(input, UriKind.Absolute, out Uri uri) && (uri.Scheme == Uri.UriSchemeHttp || uri.Scheme == Uri.UriSchemeHttps)) { hostOnly = uri.Host; string authority = uri.GetLeftPart(UriPartial.Authority); string path = uri.AbsolutePath; if (!string.IsNullOrEmpty(path) && path != "/") baseUri = authority.TrimEnd('/') + path.TrimEnd('/'); else baseUri = authority; resolvedPort = uri.Port; return true; } hostOnly = input; baseUri = "http://" + input + ":" + fallbackHttpPort; resolvedPort = fallbackHttpPort; return true; } public static string RandomUsername{ get{ string pool="ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; string output =""; for(int i=0; i < 5; i++){ output += pool.ToCharArray()[UnityEngine.Random.Range(0,pool.Length)]; } return output; } } /// Matchmaker GET / body: "0" while waiting; otherwise a JSON room object. public static CupidRoom? ParseRoom(string data) => ParseRoom(data, 0, null); /// /// Used to pick for_red vs for_blue when both list the same players. /// Optional hint (e.g. ) when the roster is duplicated on both sides. public static CupidRoom? ParseRoom(string data, int currentUserId, Team? teamDisambiguation) { if (string.IsNullOrWhiteSpace(data)) return null; string t = data.Trim(); if (t == "0") return null; if (t.IndexOf("\"for_red\"", StringComparison.Ordinal) >= 0) { MatchmadeResponse env = JsonUtility.FromJson(t); if (env == null || !env.ok || !MatchmadeResponse.IsMatchReady(env)) return null; MatchmadeTeamPayload branch = MatchmadeResponse.SelectTeamPayload(env, currentUserId, teamDisambiguation); if (branch == null || branch.Port <= 0) return null; return ToCupidRoom(branch); } try { CupidRoom room = JsonUtility.FromJson(t); if (room.Port <= 0) return null; return room; } catch { return null; } } static CupidRoom ToCupidRoom(MatchmadeTeamPayload p) { var room = new CupidRoom { GameName = p.GameName ?? "", Port = p.Port, InitTime = p.InitTime, match_id = p.match_id, your_team = p.your_team ?? "" }; if (p.Players != null) { room.Players = new CupidQueueEntry[p.Players.Length]; for (int i = 0; i < p.Players.Length; i++) { room.Players[i] = new CupidQueueEntry { Name = p.Players[i].Name, LastSeen = p.Players[i].LastSeen, UserId = p.Players[i].UserId }; } } return room; } } [System.Serializable] public class CupidSettings { public int minimum_players; public int maximum_players; public int waiting_time; } [System.Serializable] public struct CupidRoom { public CupidQueueEntry[] Players; public string GameName; public int Port; public long InitTime; public int match_id; public string your_team; } [System.Serializable] public struct CupidQueueEntry { public string Name; public long LastSeen; public int UserId; }