using Epic.OnlineServices.Logging; using Mirror; using Mirror.FizzySteam; using OWML.Common; using OWML.Utils; using QSB.Anglerfish.TransformSync; using QSB.ClientServerStateSync; using QSB.DeathSync; using QSB.EchoesOfTheEye.AirlockSync.VariableSync; using QSB.EchoesOfTheEye.EclipseDoors.VariableSync; using QSB.EchoesOfTheEye.EclipseElevators.VariableSync; using QSB.EchoesOfTheEye.RaftSync.TransformSync; using QSB.JellyfishSync.TransformSync; using QSB.Menus; using QSB.Messaging; using QSB.ModelShip; using QSB.ModelShip.TransformSync; using QSB.OrbSync.Messages; using QSB.OrbSync.TransformSync; using QSB.OrbSync.WorldObjects; using QSB.OwnershipSync; using QSB.Patches; using QSB.Player; using QSB.Player.Messages; using QSB.Player.TransformSync; using QSB.SaveSync; using QSB.ShipSync; using QSB.ShipSync.TransformSync; using QSB.Syncs.Occasional; using QSB.TimeSync; using QSB.Tools.ProbeLauncherTool.VariableSync; using QSB.Tools.ProbeTool.TransformSync; using QSB.Utility; using QSB.Utility.VariableSync; using QSB.WorldSync; using System; using System.Linq; using System.Text.RegularExpressions; using QSB.API; using UnityEngine; namespace QSB; public class QSBNetworkManager : NetworkManager, IAddComponentOnStart { public new static QSBNetworkManager singleton => (QSBNetworkManager)NetworkManager.singleton; public event Action OnClientConnected; public event Action OnClientDisconnected; public GameObject OrbPrefab { get; private set; } public GameObject ShipPrefab { get; private set; } public GameObject AnglerPrefab { get; private set; } public GameObject JellyfishPrefab { get; private set; } public GameObject OccasionalPrefab { get; private set; } public GameObject RaftPrefab { get; private set; } public GameObject DoorPrefab { get; private set; } public GameObject ElevatorPrefab { get; private set; } public GameObject AirlockPrefab { get; private set; } public GameObject ShipModulePrefab { get; private set; } public GameObject ShipLegPrefab { get; private set; } public GameObject ModelShipPrefab { get; private set; } public GameObject StationaryProbeLauncherPrefab { get; private set; } private string PlayerName { get; set; } private GameObject _probePrefab; private bool _everConnected; private (TransportError error, string reason) _lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh"); private static LatencySimulation _latencyTransport; private static kcp2k.KcpTransport _kcpTransport; private static FizzySteamworks _steamTransport; public override void Awake() { gameObject.SetActive(false); { _kcpTransport = gameObject.AddComponent(); // KCP uses milliseconds _kcpTransport.Timeout = QSBCore.DebugSettings.Timeout * 1000; _kcpTransport.Port = QSBCore.KcpPort; } { _steamTransport = gameObject.AddComponent(); // Steam uses seconds _steamTransport.Timeout = QSBCore.DebugSettings.Timeout; } { _latencyTransport = gameObject.AddComponent(); _latencyTransport.reliableLatency = _latencyTransport.unreliableLatency = QSBCore.DebugSettings.LatencySimulation; _latencyTransport.wrap = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport; } transport = QSBCore.DebugSettings.LatencySimulation > 0 ? _latencyTransport : QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport; gameObject.SetActive(true); base.Awake(); InitPlayerName(); QSBCore.ProfileManager.OnProfileSignInComplete += _ => InitPlayerName(); playerPrefab = QSBCore.NetworkAssetBundle.LoadAsset("Assets/Prefabs/NETWORK_Player_Body.prefab"); playerPrefab.GetRequiredComponent().SetValue("_assetId", (uint)1); ShipPrefab = MakeNewNetworkObject("NetworkShip", typeof(ShipTransformSync)); var shipVector3Sync = ShipPrefab.AddComponent(); var shipThrustSync = ShipPrefab.AddComponent(); shipThrustSync.AccelerationSyncer = shipVector3Sync; spawnPrefabs.Add(ShipPrefab); _probePrefab = MakeNewNetworkObject("NetworkProbe", typeof(PlayerProbeSync)); spawnPrefabs.Add(_probePrefab); OrbPrefab = MakeNewNetworkObject("NetworkOrb", typeof(NomaiOrbTransformSync)); spawnPrefabs.Add(OrbPrefab); AnglerPrefab = MakeNewNetworkObject("NetworkAngler", typeof(AnglerTransformSync)); spawnPrefabs.Add(AnglerPrefab); JellyfishPrefab = MakeNewNetworkObject("NetworkJellyfish", typeof(JellyfishTransformSync)); spawnPrefabs.Add(JellyfishPrefab); OccasionalPrefab = MakeNewNetworkObject("NetworkOccasional", typeof(OccasionalTransformSync)); spawnPrefabs.Add(OccasionalPrefab); RaftPrefab = MakeNewNetworkObject("NetworkRaft", typeof(RaftTransformSync)); spawnPrefabs.Add(RaftPrefab); DoorPrefab = MakeNewNetworkObject("NetworkEclipseDoor", typeof(EclipseDoorVariableSyncer)); spawnPrefabs.Add(DoorPrefab); ElevatorPrefab = MakeNewNetworkObject("NetworkEclipseElevator", typeof(EclipseElevatorVariableSyncer)); spawnPrefabs.Add(ElevatorPrefab); AirlockPrefab = MakeNewNetworkObject("NetworkGhostAirlock", typeof(AirlockVariableSyncer)); spawnPrefabs.Add(AirlockPrefab); ShipModulePrefab = MakeNewNetworkObject("NetworkShipModule", typeof(ShipModuleTransformSync)); spawnPrefabs.Add(ShipModulePrefab); ShipLegPrefab = MakeNewNetworkObject("NetworkShipLeg", typeof(ShipLegTransformSync)); spawnPrefabs.Add(ShipLegPrefab); ModelShipPrefab = MakeNewNetworkObject("NetworkModelShip", typeof(ModelShipTransformSync)); var modelShipVector3Syncer = ModelShipPrefab.AddComponent(); var modelShipThrusterVariableSyncer = ModelShipPrefab.AddComponent(); modelShipThrusterVariableSyncer.AccelerationSyncer = modelShipVector3Syncer; spawnPrefabs.Add(ModelShipPrefab); StationaryProbeLauncherPrefab = MakeNewNetworkObject("NetworkStationaryProbeLauncher", typeof(StationaryProbeLauncherVariableSyncer)); spawnPrefabs.Add(StationaryProbeLauncherPrefab); ConfigureNetworkManager(); } public static void UpdateTransport() { if (_kcpTransport == null) { return; } _kcpTransport.Port = QSBCore.KcpPort; if (QSBCore.IsInMultiplayer) { return; } if (singleton != null) { if (QSBCore.DebugSettings.LatencySimulation > 0) { _latencyTransport.wrap = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport; singleton.transport = Transport.active = _latencyTransport; } else { singleton.transport = Transport.active = QSBCore.UseKcpTransport ? _kcpTransport : _steamTransport; } } if (MenuManager.Instance != null) { MenuManager.Instance.OnLanguageChanged(); // hack to update text } } private void InitPlayerName() => Delay.RunWhen(PlayerData.IsLoaded, () => { try { if (!QSBCore.IsStandalone) { PlayerName = QSBMSStoreProfileManager.SharedInstance.userDisplayName; } else { var currentProfile = QSBStandaloneProfileManager.SharedInstance.currentProfile; if (currentProfile == null) { // probably havent created a profile yet Delay.RunWhen(() => QSBStandaloneProfileManager.SharedInstance.currentProfile != null, () => InitPlayerName()); return; } PlayerName = currentProfile.profileName; } } catch (Exception ex) { DebugLog.ToConsole($"Error - Exception when getting player name : {ex}", MessageType.Error); PlayerName = "Player"; } }); private static uint _assetId = 2; // 1 is the player /// create a new network prefab from the network object prefab template. /// this works by calling Unload(false) and then reloading the AssetBundle, /// which makes LoadAsset give you a new resource. /// see https://docs.unity3d.com/Manual/AssetBundles-Native.html. public static GameObject MakeNewNetworkObject(string name, Type networkBehaviourType) { var bundle = QSBCore.Helper.Assets.LoadBundle("AssetBundles/qsb_empty"); if (bundle == null) { DebugLog.ToConsole($"FATAL - An assetbundle is missing! Re-install mod or contact devs.", MessageType.Fatal); return null; } var template = bundle.LoadAsset("Assets/Prefabs/Empty.prefab"); bundle.Unload(false); template.name = name; template.AddComponent().SetValue("_assetId", _assetId); template.AddComponent(networkBehaviourType); _assetId++; return template; } private void ConfigureNetworkManager() { networkAddress = QSBCore.DefaultServerIP; { kcp2k.Log.Info = s => { DebugLog.DebugWrite("[KCP] " + s); // hack if (s == "KcpPeer: received disconnect message") { OnClientError(TransportError.ConnectionClosed, s); } }; kcp2k.Log.Warning = s => DebugLog.DebugWrite("[KCP] " + s, MessageType.Warning); kcp2k.Log.Error = s => DebugLog.DebugWrite("[KCP] " + s, MessageType.Error); } QSBSceneManager.OnPostSceneLoad += (_, loadScene) => { if (QSBCore.IsInMultiplayer && loadScene == OWScene.TitleScreen) { StopHost(); } }; DebugLog.DebugWrite("Network Manager ready.", MessageType.Success); } public override void OnServerAddPlayer(NetworkConnectionToClient connection) // Called on the server when a client joins { DebugLog.DebugWrite("OnServerAddPlayer", MessageType.Info); base.OnServerAddPlayer(connection); NetworkServer.Spawn(Instantiate(_probePrefab), connection); } public override void OnStartClient() { QSBCore.DefaultServerIP = networkAddress; var config = QSBCore.Helper.Config; config.SetSettingsValue("defaultServerIP", networkAddress); QSBCore.Helper.Storage.Save(config, Constants.ModConfigFileName); } public override void OnClientConnect() // Called on the client when connecting to a server { DebugLog.DebugWrite("OnClientConnect", MessageType.Info); base.OnClientConnect(); OnClientConnected?.SafeInvoke(); QSBMessageManager.Init(); gameObject.AddComponent(); gameObject.AddComponent(); gameObject.AddComponent(); if (QSBSceneManager.IsInUniverse) { QSBWorldSync.BuildWorldObjects(QSBSceneManager.CurrentScene).Forget(); } var specificType = QSBCore.IsHost ? QSBPatchTypes.OnServerClientConnect : QSBPatchTypes.OnNonServerClientConnect; QSBPatchManager.DoPatchType(specificType); QSBPatchManager.DoPatchType(QSBPatchTypes.OnClientConnect); Delay.RunWhen(() => PlayerTransformSync.LocalInstance, () => new PlayerJoinMessage(PlayerName).Send()); if (!QSBCore.IsHost) { Delay.RunWhen(() => PlayerTransformSync.LocalInstance, () => new RequestStateResyncMessage().Send()); } _everConnected = true; } public override void OnStopClient() // Called on the client when closing connection { DebugLog.DebugWrite("OnStopClient", MessageType.Info); DebugLog.ToConsole("Disconnecting from server...", MessageType.Info); Destroy(GetComponent()); Destroy(GetComponent()); Destroy(GetComponent()); QSBPlayerManager.PlayerList.ForEach(player => { player.HudMarker?.Remove(); player.MapMarker?.Remove(); }); QSBWorldSync.RemoveWorldObjects(); if (WakeUpSync.LocalInstance != null) { WakeUpSync.LocalInstance.OnDisconnect(); } if (_everConnected) { var specificType = QSBCore.IsHost ? QSBPatchTypes.OnServerClientConnect : QSBPatchTypes.OnNonServerClientConnect; QSBPatchManager.DoUnpatchType(specificType); QSBPatchManager.DoUnpatchType(QSBPatchTypes.OnClientConnect); } _everConnected = false; } public override void OnClientDisconnect() { DebugLog.DebugWrite("OnClientDisconnect"); base.OnClientDisconnect(); OnClientDisconnected?.SafeInvoke(_lastTransportError.error, _lastTransportError.reason); _lastTransportError = (TransportError.Unexpected, "transport did not give an error. uh oh"); } public override void OnServerDisconnect(NetworkConnectionToClient conn) // Called on the server when any client disconnects { DebugLog.DebugWrite("OnServerDisconnect", MessageType.Info); // local conn = we are host, so skip if (conn is not LocalConnectionToClient) { // revert ownership from ship if (ShipTransformSync.LocalInstance != null) { var identity = ShipTransformSync.LocalInstance.netIdentity; if (identity != null && identity.connectionToClient == conn) { identity.SetOwner(QSBPlayerManager.LocalPlayerId); } } // revert ownership from model ship if (ModelShipTransformSync.LocalInstance != null) { var identity = ModelShipTransformSync.LocalInstance.netIdentity; if (identity != null && identity.connectionToClient == conn) { identity.SetOwner(QSBPlayerManager.LocalPlayerId); } } // stop dragging for the orbs this player was dragging // i THINK this is here because orb ownership is in network behavior, which may not work properly in OnPlayerLeave foreach (var qsbOrb in QSBWorldSync.GetWorldObjects()) { if (qsbOrb.NetworkBehaviour == null) { DebugLog.ToConsole($"{qsbOrb} TransformSync == null??????????", MessageType.Warning); continue; } var identity = qsbOrb.NetworkBehaviour.netIdentity; if (identity.connectionToClient == conn) { qsbOrb.SetDragging(false); qsbOrb.SendMessage(new OrbDragMessage(false)); } } OwnershipManager.OnDisconnect(conn); } base.OnServerDisconnect(conn); } public override void OnStopServer() { DebugLog.DebugWrite("OnStopServer", MessageType.Info); Destroy(GetComponent()); DebugLog.ToConsole("Server stopped!", MessageType.Info); QSBPlayerManager.PlayerList.ForEach(player => { player.HudMarker?.Remove(); player.MapMarker?.Remove(); }); base.OnStopServer(); } public override void OnServerError(NetworkConnectionToClient conn, TransportError error, string reason) { DebugLog.DebugWrite($"OnServerError({conn}, {error}, {reason})", MessageType.Error); _lastTransportError = (error, reason); } public override void OnClientError(TransportError error, string reason) { DebugLog.DebugWrite($"OnClientError({error}, {reason})", MessageType.Error); _lastTransportError = (error, reason); } }