You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
165 lines
7.8 KiB
C#
165 lines
7.8 KiB
C#
3 weeks ago
|
using System;
|
||
|
using Unity.BossRoom.Infrastructure;
|
||
|
using Unity.BossRoom.UnityServices.Lobbies;
|
||
|
using Unity.Multiplayer.Samples.BossRoom;
|
||
|
using Unity.Multiplayer.Samples.Utilities;
|
||
|
using Unity.Netcode;
|
||
|
using UnityEngine;
|
||
|
using VContainer;
|
||
|
|
||
|
namespace Unity.BossRoom.ConnectionManagement
|
||
|
{
|
||
|
/// <summary>
|
||
|
/// Connection state corresponding to a listening host. Handles incoming client connections. When shutting down or
|
||
|
/// being timed out, transitions to the Offline state.
|
||
|
/// </summary>
|
||
|
class HostingState : OnlineState
|
||
|
{
|
||
|
[Inject]
|
||
|
LobbyServiceFacade m_LobbyServiceFacade;
|
||
|
[Inject]
|
||
|
IPublisher<ConnectionEventMessage> m_ConnectionEventPublisher;
|
||
|
|
||
|
// used in ApprovalCheck. This is intended as a bit of light protection against DOS attacks that rely on sending silly big buffers of garbage.
|
||
|
const int k_MaxConnectPayload = 1024;
|
||
|
|
||
|
public override void Enter()
|
||
|
{
|
||
|
//The "BossRoom" server always advances to CharSelect immediately on start. Different games
|
||
|
//may do this differently.
|
||
|
SceneLoaderWrapper.Instance.LoadScene("CharSelect", useNetworkSceneManager: true);
|
||
|
|
||
|
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
|
||
|
{
|
||
|
m_LobbyServiceFacade.BeginTracking();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void Exit()
|
||
|
{
|
||
|
SessionManager<SessionPlayerData>.Instance.OnServerEnded();
|
||
|
}
|
||
|
|
||
|
public override void OnClientConnected(ulong clientId)
|
||
|
{
|
||
|
var playerData = SessionManager<SessionPlayerData>.Instance.GetPlayerData(clientId);
|
||
|
if (playerData != null)
|
||
|
{
|
||
|
m_ConnectionEventPublisher.Publish(new ConnectionEventMessage() { ConnectStatus = ConnectStatus.Success, PlayerName = playerData.Value.PlayerName });
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
// This should not happen since player data is assigned during connection approval
|
||
|
Debug.LogError($"No player data associated with client {clientId}");
|
||
|
var reason = JsonUtility.ToJson(ConnectStatus.GenericDisconnect);
|
||
|
m_ConnectionManager.NetworkManager.DisconnectClient(clientId, reason);
|
||
|
}
|
||
|
|
||
|
}
|
||
|
|
||
|
public override void OnClientDisconnect(ulong clientId)
|
||
|
{
|
||
|
if (clientId != m_ConnectionManager.NetworkManager.LocalClientId)
|
||
|
{
|
||
|
var playerId = SessionManager<SessionPlayerData>.Instance.GetPlayerId(clientId);
|
||
|
if (playerId != null)
|
||
|
{
|
||
|
var sessionData = SessionManager<SessionPlayerData>.Instance.GetPlayerData(playerId);
|
||
|
if (sessionData.HasValue)
|
||
|
{
|
||
|
m_ConnectionEventPublisher.Publish(new ConnectionEventMessage() { ConnectStatus = ConnectStatus.GenericDisconnect, PlayerName = sessionData.Value.PlayerName });
|
||
|
}
|
||
|
SessionManager<SessionPlayerData>.Instance.DisconnectClient(clientId);
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
public override void OnUserRequestedShutdown()
|
||
|
{
|
||
|
var reason = JsonUtility.ToJson(ConnectStatus.HostEndedSession);
|
||
|
for (var i = m_ConnectionManager.NetworkManager.ConnectedClientsIds.Count - 1; i >= 0; i--)
|
||
|
{
|
||
|
var id = m_ConnectionManager.NetworkManager.ConnectedClientsIds[i];
|
||
|
if (id != m_ConnectionManager.NetworkManager.LocalClientId)
|
||
|
{
|
||
|
m_ConnectionManager.NetworkManager.DisconnectClient(id, reason);
|
||
|
}
|
||
|
}
|
||
|
m_ConnectionManager.ChangeState(m_ConnectionManager.m_Offline);
|
||
|
}
|
||
|
|
||
|
public override void OnServerStopped()
|
||
|
{
|
||
|
m_ConnectStatusPublisher.Publish(ConnectStatus.GenericDisconnect);
|
||
|
m_ConnectionManager.ChangeState(m_ConnectionManager.m_Offline);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// This logic plugs into the "ConnectionApprovalResponse" exposed by Netcode.NetworkManager. It is run every time a client connects to us.
|
||
|
/// The complementary logic that runs when the client starts its connection can be found in ClientConnectingState.
|
||
|
/// </summary>
|
||
|
/// <remarks>
|
||
|
/// Multiple things can be done here, some asynchronously. For example, it could authenticate your user against an auth service like UGS' auth service. It can
|
||
|
/// also send custom messages to connecting users before they receive their connection result (this is useful to set status messages client side
|
||
|
/// when connection is refused, for example).
|
||
|
/// Note on authentication: It's usually harder to justify having authentication in a client hosted game's connection approval. Since the host can't be trusted,
|
||
|
/// clients shouldn't send it private authentication tokens you'd usually send to a dedicated server.
|
||
|
/// </remarks>
|
||
|
/// <param name="request"> The initial request contains, among other things, binary data passed into StartClient. In our case, this is the client's GUID,
|
||
|
/// which is a unique identifier for their install of the game that persists across app restarts.
|
||
|
/// <param name="response"> Our response to the approval process. In case of connection refusal with custom return message, we delay using the Pending field.
|
||
|
public override void ApprovalCheck(NetworkManager.ConnectionApprovalRequest request, NetworkManager.ConnectionApprovalResponse response)
|
||
|
{
|
||
|
var connectionData = request.Payload;
|
||
|
var clientId = request.ClientNetworkId;
|
||
|
if (connectionData.Length > k_MaxConnectPayload)
|
||
|
{
|
||
|
// If connectionData too high, deny immediately to avoid wasting time on the server. This is intended as
|
||
|
// a bit of light protection against DOS attacks that rely on sending silly big buffers of garbage.
|
||
|
response.Approved = false;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
var payload = System.Text.Encoding.UTF8.GetString(connectionData);
|
||
|
var connectionPayload = JsonUtility.FromJson<ConnectionPayload>(payload); // https://docs.unity3d.com/2020.2/Documentation/Manual/JSONSerialization.html
|
||
|
var gameReturnStatus = GetConnectStatus(connectionPayload);
|
||
|
|
||
|
if (gameReturnStatus == ConnectStatus.Success)
|
||
|
{
|
||
|
SessionManager<SessionPlayerData>.Instance.SetupConnectingPlayerSessionData(clientId, connectionPayload.playerId,
|
||
|
new SessionPlayerData(clientId, connectionPayload.playerName, new NetworkGuid(), 0, true));
|
||
|
|
||
|
// connection approval will create a player object for you
|
||
|
response.Approved = true;
|
||
|
response.CreatePlayerObject = true;
|
||
|
response.Position = Vector3.zero;
|
||
|
response.Rotation = Quaternion.identity;
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
response.Approved = false;
|
||
|
response.Reason = JsonUtility.ToJson(gameReturnStatus);
|
||
|
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
|
||
|
{
|
||
|
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(connectionPayload.playerId);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
ConnectStatus GetConnectStatus(ConnectionPayload connectionPayload)
|
||
|
{
|
||
|
if (m_ConnectionManager.NetworkManager.ConnectedClientsIds.Count >= m_ConnectionManager.MaxConnectedPlayers)
|
||
|
{
|
||
|
return ConnectStatus.ServerFull;
|
||
|
}
|
||
|
|
||
|
if (connectionPayload.isDebug != Debug.isDebugBuild)
|
||
|
{
|
||
|
return ConnectStatus.IncompatibleBuildType;
|
||
|
}
|
||
|
|
||
|
return SessionManager<SessionPlayerData>.Instance.IsDuplicateConnection(connectionPayload.playerId) ?
|
||
|
ConnectStatus.LoggedInAgain : ConnectStatus.Success;
|
||
|
}
|
||
|
}
|
||
|
}
|