246 lines
8.7 KiB
C#
246 lines
8.7 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.IO;
|
|
using UnityEngine;
|
|
using XWorld.Framework;
|
|
using XWorld.Framework.Protocol;
|
|
|
|
namespace XGame.MiniGame
|
|
{
|
|
public sealed class PcLobbySmokeLauncher : MonoBehaviour
|
|
{
|
|
public string GatewayUrl = "ws://127.0.0.1:5005/ws?pid=1";
|
|
public string CdnBase = "";
|
|
public bool AutoStart;
|
|
|
|
private readonly List<GameInfoMsg> _games = new List<GameInfoMsg>();
|
|
private string _status = "idle";
|
|
private XWebSocket _socket;
|
|
private MiniGameNetChannel _lobbyNet;
|
|
private MiniGameHost _gameHost;
|
|
private bool _lobbyActive;
|
|
private bool _matching;
|
|
private float _matchElapsed;
|
|
|
|
private void Start()
|
|
{
|
|
#if XW_DEVTEST
|
|
if (GetComponent<DevServerSwitchUI>() == null) gameObject.AddComponent<DevServerSwitchUI>();
|
|
#endif
|
|
if (AutoStart) ConnectLobby();
|
|
}
|
|
|
|
private void OnDestroy()
|
|
{
|
|
_socket?.close();
|
|
_socket = null;
|
|
}
|
|
|
|
private void Update()
|
|
{
|
|
if (_lobbyActive) _lobbyNet?.Pump();
|
|
if (_matching) _matchElapsed += Time.deltaTime;
|
|
}
|
|
|
|
private void OnGUI()
|
|
{
|
|
if (!_lobbyActive && _matching) return;
|
|
|
|
GUILayout.BeginArea(new Rect(16, 16, 640, 260), GUI.skin.box);
|
|
GUILayout.Label("PC C# Lobby");
|
|
GUILayout.Label("Status: " + _status);
|
|
GUILayout.Label("Gateway URL");
|
|
GatewayUrl = GUILayout.TextField(GatewayUrl);
|
|
GUILayout.Label("CDN Base");
|
|
CdnBase = GUILayout.TextField(string.IsNullOrEmpty(CdnBase) ? DefaultCdnBase() : CdnBase);
|
|
|
|
GUILayout.BeginHorizontal();
|
|
if (GUILayout.Button(_socket == null ? "Enter Lobby" : "Refresh Games", GUILayout.Width(140)))
|
|
{
|
|
if (_socket == null) ConnectLobby();
|
|
else RequestGameList();
|
|
}
|
|
GUI.enabled = _socket != null && !_matching && _games.Count > 0;
|
|
if (GUILayout.Button("Random Match", GUILayout.Width(140))) RandomMatch();
|
|
GUI.enabled = true;
|
|
GUILayout.EndHorizontal();
|
|
|
|
if (_matching) GUILayout.Label("Matching: " + _matchElapsed.ToString("0.0") + "s / AI fill after server timeout");
|
|
GUILayout.Label("Discovered games:");
|
|
if (_games.Count == 0) GUILayout.Label("(none)");
|
|
for (int i = 0; i < _games.Count; i++)
|
|
{
|
|
var g = _games[i];
|
|
GUILayout.Label("- " + g.GameId + " v" + g.Version + " players " + g.MinPlayers + "-" + g.MaxPlayers);
|
|
}
|
|
GUILayout.EndArea();
|
|
}
|
|
|
|
public void ConnectLobby()
|
|
{
|
|
string url = ResolvedGatewayUrl();
|
|
_status = "connecting " + url;
|
|
_socket?.close();
|
|
_socket = new XWebSocket();
|
|
_socket.Connect(url, (sock, err) =>
|
|
{
|
|
if (!string.IsNullOrEmpty(err) || sock == null)
|
|
{
|
|
_status = "connect failed: " + err;
|
|
Debug.LogError("[PcLobby] " + _status);
|
|
return;
|
|
}
|
|
|
|
_status = "lobby connected";
|
|
_lobbyNet = new MiniGameNetChannel(sock);
|
|
_lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
|
|
_lobbyActive = true;
|
|
_matching = false;
|
|
RequestGameList();
|
|
});
|
|
}
|
|
|
|
private void RequestGameList()
|
|
{
|
|
_status = "requesting game list";
|
|
_lobbyNet?.SendFramework(FrameworkOpcode.GameListRequest, Array.Empty<byte>());
|
|
}
|
|
|
|
private void RandomMatch()
|
|
{
|
|
_status = "matching random game";
|
|
_matching = true;
|
|
_matchElapsed = 0f;
|
|
_lobbyNet?.SendFramework(FrameworkOpcode.RandomMatchRequest, Array.Empty<byte>());
|
|
}
|
|
|
|
private void OnLobbyFrameworkFrame(Frame frame)
|
|
{
|
|
switch ((FrameworkOpcode)frame.Opcode)
|
|
{
|
|
case FrameworkOpcode.GameListResponse:
|
|
var list = GameListResponseMsg.Decode(frame.Payload);
|
|
_games.Clear();
|
|
_games.AddRange(list.Games);
|
|
_status = "games discovered: " + _games.Count;
|
|
break;
|
|
case FrameworkOpcode.MatchAssigned:
|
|
var assigned = MatchAssignedMsg.Decode(frame.Payload);
|
|
_status = "assigned " + assigned.GameId + " v" + assigned.Version + ", loading hot DLL";
|
|
EnterAssignedGame(assigned.GameId, assigned.Version);
|
|
break;
|
|
case FrameworkOpcode.Error:
|
|
var err = ErrorMsg.Decode(frame.Payload);
|
|
_status = "server error " + err.Code + ": " + err.Message;
|
|
_matching = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
private void EnterAssignedGame(string gameId, int version)
|
|
{
|
|
_lobbyActive = false;
|
|
_lobbyNet = null;
|
|
_gameHost = gameObject.GetComponent<MiniGameHost>();
|
|
if (_gameHost == null) _gameHost = gameObject.AddComponent<MiniGameHost>();
|
|
_gameHost.OnGameExited = () =>
|
|
{
|
|
_status = "returned to lobby";
|
|
_matching = false;
|
|
_lobbyNet = new MiniGameNetChannel(_socket);
|
|
_lobbyNet.OnFrameworkFrame = OnLobbyFrameworkFrame;
|
|
_lobbyActive = true;
|
|
RequestGameList();
|
|
};
|
|
_gameHost.EnterGame(ResolvedCdnBase(), gameId, version, _socket, false);
|
|
}
|
|
|
|
private string ResolvedCdnBase()
|
|
{
|
|
#if XW_DEVTEST
|
|
if (MiniGameTestOverride.IsActive && !string.IsNullOrEmpty(MiniGameTestOverride.CdnBase))
|
|
return MiniGameTestOverride.CdnBase;
|
|
#endif
|
|
return string.IsNullOrEmpty(CdnBase) ? DefaultCdnBase() : CdnBase;
|
|
}
|
|
|
|
private string ResolvedGatewayUrl()
|
|
{
|
|
#if XW_DEVTEST
|
|
if (MiniGameTestOverride.IsActive && !string.IsNullOrEmpty(MiniGameTestOverride.GatewayUrl))
|
|
{
|
|
string g = MiniGameTestOverride.GatewayUrl;
|
|
return WithLocalPid(g);
|
|
}
|
|
#endif
|
|
return WithLocalPid(GatewayUrl);
|
|
}
|
|
|
|
private static string WithLocalPid(string gatewayUrl)
|
|
{
|
|
if (string.IsNullOrWhiteSpace(gatewayUrl))
|
|
{
|
|
return gatewayUrl;
|
|
}
|
|
int pid = GetLocalPlayerId();
|
|
int queryStart = gatewayUrl.IndexOf('?');
|
|
if (queryStart < 0)
|
|
{
|
|
return gatewayUrl + "?pid=" + pid;
|
|
}
|
|
|
|
string head = gatewayUrl.Substring(0, queryStart);
|
|
string query = gatewayUrl.Substring(queryStart + 1);
|
|
string[] parts = query.Split('&');
|
|
bool replaced = false;
|
|
for (int i = 0; i < parts.Length; i++)
|
|
{
|
|
if (parts[i].StartsWith("pid=", StringComparison.OrdinalIgnoreCase))
|
|
{
|
|
parts[i] = "pid=" + pid;
|
|
replaced = true;
|
|
break;
|
|
}
|
|
}
|
|
return head + "?" + (replaced ? string.Join("&", parts) : query + "&pid=" + pid);
|
|
}
|
|
|
|
private static int GetLocalPlayerId()
|
|
{
|
|
const string key = "LocalClientId";
|
|
string id = XData.GetString(key, string.Empty);
|
|
if (string.IsNullOrEmpty(id))
|
|
{
|
|
id = SystemInfo.deviceUniqueIdentifier;
|
|
if (string.IsNullOrEmpty(id) || id == "unsupported")
|
|
{
|
|
id = Guid.NewGuid().ToString("N");
|
|
}
|
|
XData.SetString(key, id);
|
|
XData.Save();
|
|
}
|
|
|
|
unchecked
|
|
{
|
|
uint hash = 2166136261u;
|
|
for (int i = 0; i < id.Length; i++)
|
|
{
|
|
hash ^= id[i];
|
|
hash *= 16777619u;
|
|
}
|
|
return (int)(hash % 2000000000u) + 1;
|
|
}
|
|
}
|
|
|
|
private static string DefaultCdnBase()
|
|
{
|
|
// Editor: <repo>/Client/Assets -> <repo>/CDN/minigame/(由 XWorld/生成小游戏更新 菜单产出)
|
|
var assets = new DirectoryInfo(Application.dataPath);
|
|
var client = assets.Parent;
|
|
var repo = client?.Parent;
|
|
string root = repo != null ? repo.FullName : Directory.GetCurrentDirectory();
|
|
return "file:///" + root.Replace('\\', '/') + "/CDN/minigame/";
|
|
}
|
|
}
|
|
}
|