forked from tmokmss/com.unity.multiplayer.samples.coop
-
Notifications
You must be signed in to change notification settings - Fork 0
/
HostingState.cs
165 lines (146 loc) · 8.34 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
165
using System;
using System.Collections;
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()
{
SceneLoaderWrapper.Instance.AddOnSceneEventCallback();
//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)
{
m_ConnectionEventPublisher.Publish(new ConnectionEventMessage() { ConnectStatus = ConnectStatus.Success, PlayerName = SessionManager<SessionPlayerData>.Instance.GetPlayerData(clientId)?.PlayerName });
}
public override void OnClientDisconnect(ulong clientId)
{
if (clientId == m_ConnectionManager.NetworkManager.LocalClientId)
{
m_ConnectionManager.ChangeState(m_ConnectionManager.m_Offline);
}
else
{
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()
{
m_ConnectionManager.SendServerToAllClientsSetDisconnectReason(ConnectStatus.HostEndedSession);
// Wait before shutting down to make sure clients receive that message before they are disconnected
m_ConnectionManager.StartCoroutine(WaitToShutdown());
}
/// <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;
}
// In order for clients to not just get disconnected with no feedback, the server needs to tell the client why it disconnected it.
// This could happen after an auth check on a service or because of gameplay reasons (server full, wrong build version, etc)
// Since network objects haven't synced yet (still in the approval process), we need to send a custom message to clients, wait for
// UTP to update a frame and flush that message, then give our response to NetworkManager's connection approval process, with a denied approval.
IEnumerator WaitToDenyApproval()
{
response.Pending = true; // give some time for server to send connection status message to clients
response.Approved = false;
m_ConnectionManager.SendServerToClientSetDisconnectReason(clientId, gameReturnStatus);
yield return null; // wait a frame so UTP can flush it's messages on next update
response.Pending = false; // connection approval process can be finished.
}
m_ConnectionManager.SendServerToClientSetDisconnectReason(clientId, gameReturnStatus);
m_ConnectionManager.StartCoroutine(WaitToDenyApproval());
if (m_LobbyServiceFacade.CurrentUnityLobby != null)
{
m_LobbyServiceFacade.RemovePlayerFromLobbyAsync(connectionPayload.playerId, m_LobbyServiceFacade.CurrentUnityLobby.Id);
}
}
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;
}
IEnumerator WaitToShutdown()
{
yield return null;
m_ConnectionManager.ChangeState(m_ConnectionManager.m_Offline);
}
}
}