-
Notifications
You must be signed in to change notification settings - Fork 503
/
HostingState.cs
164 lines (146 loc) · 7.78 KB
/
HostingState.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
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;
}
}
}