234 lines
9.3 KiB
C#
234 lines
9.3 KiB
C#
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());
|
|
}
|
|
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
}
|