This commit is contained in:
2025-06-16 15:14:23 +02:00
commit 074e590073
3174 changed files with 428263 additions and 0 deletions

View File

@ -0,0 +1,345 @@
using System;
using System.Collections;
using System.Threading;
using Mirror;
using UnityEngine;
using Random = UnityEngine.Random;
namespace Edgegap
{
[HelpURL("https://mirror-networking.gitbook.io/docs/manual/transports/edgegap-transports/edgegap-relay")]
public class EdgegapLobbyKcpTransport : EdgegapKcpTransport
{
[Header("Lobby Settings")]
[Tooltip("URL to the Edgegap lobby service, automatically filled in after completing the creation process via button below (or enter manually)")]
public string lobbyUrl;
[Tooltip("How long to wait for the relay to be assigned after starting a lobby")]
public float lobbyWaitTimeout = 60;
public LobbyApi Api;
private LobbyCreateRequest? _request;
private string _lobbyId;
private string _playerId;
private TransportStatus _status = TransportStatus.Offline;
public enum TransportStatus
{
Offline,
CreatingLobby,
StartingLobby,
JoiningLobby,
WaitingRelay,
Connecting,
Connected,
Error,
}
public TransportStatus Status
{
get
{
if (!NetworkClient.active && !NetworkServer.active)
{
return TransportStatus.Offline;
}
if (_status == TransportStatus.Connecting)
{
if (NetworkServer.active)
{
switch (((EdgegapKcpServer)this.server).state)
{
case ConnectionState.Valid:
return TransportStatus.Connected;
case ConnectionState.Invalid:
case ConnectionState.SessionTimeout:
case ConnectionState.Error:
return TransportStatus.Error;
}
}
else if (NetworkClient.active)
{
switch (((EdgegapKcpClient)this.client).connectionState)
{
case ConnectionState.Valid:
return TransportStatus.Connected;
case ConnectionState.Invalid:
case ConnectionState.SessionTimeout:
case ConnectionState.Error:
return TransportStatus.Error;
}
}
}
return _status;
}
}
protected override void Awake()
{
base.Awake();
Api = new LobbyApi(lobbyUrl);
}
private void Reset()
{
this.relayGUI = false;
}
public override void ServerStart()
{
if (!_request.HasValue)
{
throw new Exception("No lobby request set. Call SetServerLobbyParams");
}
_status = TransportStatus.CreatingLobby;
Api.CreateLobby(_request.Value, lobby =>
{
_lobbyId = lobby.lobby_id;
_status = TransportStatus.StartingLobby;
Api.StartLobby(new LobbyIdRequest(_lobbyId), () =>
{
StartCoroutine(WaitForLobbyRelay(_lobbyId, true));
}, error =>
{
_status = TransportStatus.Error;
string errorMsg = $"Could not start lobby: {error}";
Debug.LogError(errorMsg);
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
});
},
error =>
{
_status = TransportStatus.Error;
string errorMsg = $"Couldn't create lobby: {error}";
Debug.LogError(errorMsg);
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
});
}
public override void ServerStop()
{
base.ServerStop();
Api.DeleteLobby(_lobbyId, () =>
{
// yay
}, error =>
{
OnServerError?.Invoke(0, TransportError.Unexpected, $"Failed to delete lobby: {error}");
});
}
public override void ClientDisconnect()
{
base.ClientDisconnect();
// this gets called for host mode as well
if (!NetworkServer.active)
{
Api.LeaveLobby(new LobbyJoinOrLeaveRequest
{
player = new LobbyJoinOrLeaveRequest.Player
{
id = _playerId
},
lobby_id = _lobbyId
}, () =>
{
// yay
}, error =>
{
string errorMsg = $"Failed to leave lobby: {error}";
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
Debug.LogError(errorMsg);
});
}
}
public override void ClientConnect(string address)
{
_lobbyId = address;
_playerId = RandomPlayerId();
_status = TransportStatus.JoiningLobby;
Api.JoinLobby(new LobbyJoinOrLeaveRequest
{
player = new LobbyJoinOrLeaveRequest.Player
{
id = _playerId,
},
lobby_id = address
}, () =>
{
StartCoroutine(WaitForLobbyRelay(_lobbyId, false));
}, error =>
{
_status = TransportStatus.Offline;
string errorMsg = $"Failed to join lobby: {error}";
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
Debug.LogError(errorMsg);
OnClientDisconnected?.Invoke();
});
}
private IEnumerator WaitForLobbyRelay(string lobbyId, bool forServer)
{
_status = TransportStatus.WaitingRelay;
double startTime = NetworkTime.localTime;
bool running = true;
while (running)
{
if (NetworkTime.localTime - startTime >= lobbyWaitTimeout)
{
_status = TransportStatus.Error;
string errorMsg = "Timed out waiting for lobby.";
Debug.LogError(errorMsg);
if (forServer)
{
_status = TransportStatus.Error;
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
_status = TransportStatus.Error;
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
yield break;
}
bool waitingForResponse = true;
Api.GetLobby(lobbyId, lobby =>
{
waitingForResponse = false;
if (string.IsNullOrEmpty(lobby.assignment.ip))
{
// no lobby deployed yet, have the outer loop retry
return;
}
relayAddress = lobby.assignment.ip;
foreach (Lobby.Port aport in lobby.assignment.ports)
{
if (aport.protocol == "UDP")
{
if (aport.name == "server")
{
relayGameServerPort = (ushort)aport.port;
}
else if (aport.name == "client")
{
relayGameClientPort = (ushort)aport.port;
}
}
}
bool found = false;
foreach (Lobby.Player player in lobby.players)
{
if (player.id == _playerId)
{
userId = player.authorization_token;
sessionId = lobby.assignment.authorization_token;
found = true;
break;
}
}
running = false;
if (!found)
{
string errorMsg = $"Couldn't find my player ({_playerId})";
Debug.LogError(errorMsg);
if (forServer)
{
_status = TransportStatus.Error;
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
_status = TransportStatus.Error;
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
return;
}
_status = TransportStatus.Connecting;
if (forServer)
{
base.ServerStart();
}
else
{
base.ClientConnect("");
}
}, error =>
{
running = false;
waitingForResponse = false;
_status = TransportStatus.Error;
string errorMsg = $"Failed to get lobby info: {error}";
Debug.LogError(errorMsg);
if (forServer)
{
OnServerError?.Invoke(0, TransportError.Unexpected, errorMsg);
ServerStop();
}
else
{
OnClientError?.Invoke(TransportError.Unexpected, errorMsg);
ClientDisconnect();
}
});
while (waitingForResponse)
{
yield return null;
}
yield return new WaitForSeconds(0.2f);
}
}
private static string RandomPlayerId()
{
return $"mirror-player-{Random.Range(1, int.MaxValue)}";
}
public void SetServerLobbyParams(string lobbyName, int capacity)
{
SetServerLobbyParams(new LobbyCreateRequest
{
player = new LobbyCreateRequest.Player
{
id = RandomPlayerId(),
},
annotations = new LobbyCreateRequest.Annotation[]
{
},
capacity = capacity,
is_joinable = true,
name = lobbyName,
tags = new string[]
{
}
});
}
public void SetServerLobbyParams(LobbyCreateRequest request)
{
_playerId = request.player.id;
_request = request;
}
private void OnDestroy()
{
// attempt to clean up lobbies, if active
if (NetworkServer.active)
{
ServerStop();
// Absolutely make sure there's time for the network request to hit edgegap servers.
// sorry. this can go once the lobby service can timeout lobbies itself
Thread.Sleep(300);
}
else if (NetworkClient.active)
{
ClientDisconnect();
// Absolutely make sure there's time for the network request to hit edgegap servers.
// sorry. this can go once the lobby service can timeout lobbies itself
Thread.Sleep(300);
}
}
}
}