-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Expand file tree
/
Copy pathMultiplayer.cs
More file actions
235 lines (200 loc) · 9.35 KB
/
Multiplayer.cs
File metadata and controls
235 lines (200 loc) · 9.35 KB
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
using System;
using System.Collections;
using System.Collections.Generic;
using NitroxClient.Communication;
using NitroxClient.Communication.Abstract;
using NitroxClient.Communication.MultiplayerSession;
using NitroxClient.Communication.Packets.Processors.Abstract;
using NitroxClient.GameLogic;
using NitroxClient.GameLogic.Bases;
using NitroxClient.GameLogic.ChatUI;
using NitroxClient.GameLogic.PlayerLogic.PlayerModel.Abstract;
using NitroxClient.GameLogic.PlayerLogic.PlayerModel.ColorSwap;
using NitroxClient.MonoBehaviours.Cyclops;
using NitroxClient.MonoBehaviours.Discord;
using NitroxClient.MonoBehaviours.Gui.MainMenu;
using NitroxClient.MonoBehaviours.Gui.MainMenu.ServerJoin;
using NitroxModel.Core;
using NitroxModel.Packets;
using NitroxModel.Packets.Processors.Abstract;
using UnityEngine;
using UnityEngine.SceneManagement;
using UWE;
namespace NitroxClient.MonoBehaviours
{
public class Multiplayer : MonoBehaviour
{
public static Multiplayer Main;
private readonly Dictionary<Type, PacketProcessor> packetProcessorCache = new();
private IClient client;
private IMultiplayerSession multiplayerSession;
private PacketReceiver packetReceiver;
private ThrottledPacketSender throttledPacketSender;
private GameLogic.Terrain terrain;
public bool InitialSyncCompleted { get; set; }
/// <summary>
/// True if multiplayer is loaded and client is connected to a server.
/// </summary>
public static bool Active => Main && Main.multiplayerSession.Client.IsConnected;
/// <summary>
/// True if multiplayer is loaded and player has successfully joined a server.
/// </summary>
public static bool Joined => Main && Main.multiplayerSession.CurrentState.CurrentStage == MultiplayerSessionConnectionStage.SESSION_JOINED;
public void Awake()
{
NitroxServiceLocator.LifetimeScopeEnded += (_, _) => packetProcessorCache.Clear();
client = NitroxServiceLocator.LocateService<IClient>();
multiplayerSession = NitroxServiceLocator.LocateService<IMultiplayerSession>();
packetReceiver = NitroxServiceLocator.LocateService<PacketReceiver>();
throttledPacketSender = NitroxServiceLocator.LocateService<ThrottledPacketSender>();
terrain = NitroxServiceLocator.LocateService<GameLogic.Terrain>();
Main = this;
DontDestroyOnLoad(gameObject);
Log.Info("Multiplayer client loaded…");
Log.InGame(Language.main.Get("Nitrox_MultiplayerLoaded"));
}
public void Update()
{
client.PollEvents();
if (multiplayerSession.CurrentState.CurrentStage != MultiplayerSessionConnectionStage.DISCONNECTED)
{
ProcessPackets();
throttledPacketSender.Update();
// Loading up shouldn't be bothered by entities spawning in the surroundings
if (multiplayerSession.CurrentState.CurrentStage == MultiplayerSessionConnectionStage.SESSION_JOINED &&
InitialSyncCompleted)
{
terrain.UpdateVisibility();
}
}
}
public static event Action OnLoadingComplete;
public static event Action OnBeforeMultiplayerStart;
public static event Action OnAfterMultiplayerEnd;
public static void SubnauticaLoadingStarted()
{
OnBeforeMultiplayerStart?.Invoke();
}
public static void SubnauticaLoadingCompleted()
{
if (Active)
{
Main.InitialSyncCompleted = false;
Main.StartCoroutine(LoadAsync());
}
else
{
SetLoadingComplete();
OnLoadingComplete?.Invoke();
}
}
public static IEnumerator LoadAsync()
{
WaitScreen.ManualWaitItem worldSettleItem = WaitScreen.Add(Language.main.Get("Nitrox_WorldSettling"));
yield return new WaitUntil(() => LargeWorldStreamer.main != null &&
LargeWorldStreamer.main.land != null &&
LargeWorldStreamer.main.IsReady() &&
LargeWorldStreamer.main.IsWorldSettled());
WaitScreen.Remove(worldSettleItem);
WaitScreen.ManualWaitItem item = WaitScreen.Add(Language.main.Get("Nitrox_JoiningSession"));
yield return Main.StartCoroutine(Main.StartSession());
WaitScreen.Remove(item);
yield return new WaitUntil(() => Main.InitialSyncCompleted);
SetLoadingComplete();
OnLoadingComplete?.Invoke();
}
public void ProcessPackets()
{
static PacketProcessor ResolveProcessor(Packet packet, Dictionary<Type, PacketProcessor> processorCache)
{
Type packetType = packet.GetType();
if (processorCache.TryGetValue(packetType, out PacketProcessor processor))
{
return processor;
}
try
{
Type packetProcessorType = typeof(ClientPacketProcessor<>).MakeGenericType(packetType);
return processorCache[packetType] = (PacketProcessor)NitroxServiceLocator.LocateService(packetProcessorType);
}
catch (Exception ex)
{
Log.Error(ex, $"Failed to find packet processor for packet {packet}");
}
return null;
}
packetReceiver.ConsumePackets(static (packet, processorCache) =>
{
try
{
ResolveProcessor(packet, processorCache)?.ProcessPacket(packet, null);
}
catch (Exception ex)
{
Log.Error(ex, $"Error while processing packet {packet}");
}
}, packetProcessorCache);
}
public IEnumerator StartSession()
{
yield return StartCoroutine(InitializeLocalPlayerState());
multiplayerSession.JoinSession();
InitMonoBehaviours();
Utils.SetContinueMode(true);
SceneManager.sceneLoaded += SceneManager_sceneLoaded;
}
public void InitMonoBehaviours()
{
// Gameplay.
gameObject.AddComponent<AnimationSender>();
gameObject.AddComponent<PlayerMovementBroadcaster>();
gameObject.AddComponent<PlayerDeathBroadcaster>();
gameObject.AddComponent<PlayerStatsBroadcaster>();
gameObject.AddComponent<EntityPositionBroadcaster>();
gameObject.AddComponent<BuildingHandler>();
gameObject.AddComponent<MovementBroadcaster>();
VirtualCyclops.Initialize();
}
public void StopCurrentSession()
{
SceneManager.sceneLoaded -= SceneManager_sceneLoaded;
OnAfterMultiplayerEnd?.Invoke();
}
private static void SetLoadingComplete()
{
WaitScreen.main.isWaiting = false;
WaitScreen.main.stageProgress.Clear();
FreezeTime.End(FreezeTime.Id.WaitScreen);
WaitScreen.main.items.Clear();
PlayerManager remotePlayerManager = NitroxServiceLocator.LocateService<PlayerManager>();
LoadingScreenVersionText.DisableWarningText();
DiscordClient.InitializeRPInGame(Main.multiplayerSession.AuthenticationContext.Username, remotePlayerManager.GetTotalPlayerCount(), Main.multiplayerSession.SessionPolicy.MaxConnections);
CoroutineHost.StartCoroutine(NitroxServiceLocator.LocateService<PlayerChatManager>().LoadChatKeyHint());
GameLogic.Terrain.WaitForEntities();
}
private IEnumerator InitializeLocalPlayerState()
{
ILocalNitroxPlayer localPlayer = NitroxServiceLocator.LocateService<ILocalNitroxPlayer>();
IEnumerable<IColorSwapManager> colorSwapManagers = NitroxServiceLocator.LocateService<IEnumerable<IColorSwapManager>>();
// This is used to init the lazy GameObject in order to create a real default Body Prototype for other players
GameObject body = localPlayer.BodyPrototype;
Log.Info($"Init body prototype {body.name}");
ColorSwapAsyncOperation swapOperation = new ColorSwapAsyncOperation(localPlayer, colorSwapManagers).BeginColorSwap();
yield return new WaitUntil(() => swapOperation.IsColorSwapComplete());
swapOperation.ApplySwappedColors();
// UWE developers added noisy logging for non-whitelisted components during serialization.
// We add NitroxEntiy in here to avoid a large amount of log spam.
ProtobufSerializer.componentWhitelist.Add(nameof(NitroxEntity));
}
private void SceneManager_sceneLoaded(Scene scene, LoadSceneMode loadMode)
{
if (scene.name == "XMenu")
{
// If we just disconnected from a multiplayer session, then we need to kill the connection here.
// Maybe a better place for this, but here works in a pinch.
JoinServerBackend.StopMultiplayerClient();
SceneCleaner.Open();
}
}
}
}