Files
soccar2d/Assets/Cupid/Scripts/CupidHelpers.cs
T
2026-09-06 21:36:26 +05:30

318 lines
11 KiB
C#

using System;
using System.Collections;
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;
/// <summary>True once <see cref="Init"/> has built a matchmaker base URI and stored a bearer token (required for <c>/settings</c>).</summary>
public static bool IsEndpointReady =>
!string.IsNullOrEmpty(cupidBaseUri) && !string.IsNullOrEmpty(bearerToken);
public static int RoomPort = -1;
/// <summary>Clears auth/matchmaker session fields. Server address is left so the next login can <see cref="Init"/> again.</summary>
public static void ClearSession()
{
bearerToken = "";
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;
}
ApplySettingsJsonBody(body, logSuccess: true);
}
catch (Exception e)
{
Logger.Log("Error retreiving settings from server " + e.Message);
Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : "");
settings = null;
}
}
}
/// <summary>
/// Re-fetches GET <c>/settings</c> (e.g. each time Main Menu is shown). Safe to call from a coroutine; no-op if not <see cref="IsEndpointReady"/>.
/// </summary>
public static IEnumerator CoRefreshSettings()
{
if (!IsEndpointReady)
yield break;
string settingsUrl = CupidURI + "/settings?password=" + UnityWebRequest.EscapeURL(settingsPassword ?? "");
using (UnityWebRequest www = UnityWebRequest.Get(settingsUrl))
{
www.SetRequestHeader("Authorization", "Bearer " + bearerToken);
yield return www.SendWebRequest();
if (www.result != UnityWebRequest.Result.Success)
{
Logger.Log("Cupid settings refresh failed: " + www.error);
Logger.Log(www.downloadHandler != null ? www.downloadHandler.text : "");
yield break;
}
string body = www.downloadHandler != null ? www.downloadHandler.text : "";
ApplySettingsJsonBody(body, logSuccess: true);
}
}
static void ApplySettingsJsonBody(string body, bool logSuccess)
{
settings = null;
if (string.IsNullOrWhiteSpace(body))
return;
if (body.Trim() == "403 Unauthorized")
{
Logger.Log("Cupid settings: wrong or missing password query (server rejected /settings).");
return;
}
try
{
var parsed = JsonUtility.FromJson<CupidSettings>(body);
if (parsed == null)
throw new NullReferenceException(nameof(CupidSettings));
settings = parsed;
if (logSuccess)
Logger.Log("Cupid settings updated");
}
catch (Exception e)
{
Logger.Log("Cupid settings JSON error: " + e.Message + " body=" + body);
settings = null;
}
}
/// <summary>
/// Plain host/IP + HTTP port, or an absolute URL (https://host with no port uses 443).
/// Sets <paramref name="hostOnly"/> for game client transport; <paramref name="baseUri"/> has no trailing slash.
/// </summary>
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;
}
}
/// <summary>Matchmaker GET / body: <c>"0"</c> while waiting; otherwise a JSON room object.</summary>
public static CupidRoom? ParseRoom(string data) => ParseRoom(data, 0, null);
/// <inheritdoc cref="ParseRoom(string)"/>
/// <param name="currentUserId">Used to pick <c>for_red</c> vs <c>for_blue</c> when both list the same players.</param>
/// <param name="teamDisambiguation">Optional hint (e.g. <see cref="GameManager.MyTeam"/>) when the roster is duplicated on both sides.</param>
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<MatchmadeResponse>(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<CupidRoom>(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;
public int port_range_min;
public int port_range_max;
public CupidGameEntry[] games;
/// <summary>RC stake per player for the default match (<c>/settings</c>), in coin units (same as matchmaker <c>entry_fee</c>).</summary>
public int entry_fee;
/// <summary>Optional side-bet or table minimum from matchmaker settings; same units as <see cref="entry_fee"/>.</summary>
public int bet_fee;
/// <summary>Winner net RC prize from settings, coin units (same as room <c>rc_prize</c>).</summary>
public int rc_prize;
public CupidTableSettings table_settings;
}
[System.Serializable]
public class CupidGameEntry
{
public string name;
public string exe;
}
[System.Serializable]
public class CupidTableSettings
{
public string entry_fee;
public string bet_fee;
/// <summary>Minimum client version (e.g. <c>0.99</c>, <c>1.21</c>). Compared to <see cref="Application.version"/>.</summary>
public string version;
public string error_msg;
public string warning_msg;
}
[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;
}