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#

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;
}
}
}