using Sandbox; using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Networking; using Sandbox.Game; using Sandbox.Game.Entities; using Sandbox.Game.Gui; using Sandbox.Game.GUI; using Sandbox.Game.Multiplayer; using Sandbox.Game.SessionComponents; using Sandbox.Game.World; using Sandbox.Game.World.Generator; using Sandbox.ModAPI; using SeamlessClientPlugin.Utilities; using SpaceEngineers.Game.GUI; using VRage.Game; using VRage.Game.ModAPI; using VRage.GameServices; using VRage.Network; using VRage.Utils; using VRageRender; using VRageRender.Messages; using Game = Sandbox.Engine.Platform.Game; namespace SeamlessClientPlugin.SeamlessTransfer; public class SwitchServers { public SwitchServers(MyGameServerItem targetServer, MyObjectBuilder_World targetWorld) { this.TargetServer = targetServer; this.TargetWorld = targetWorld; //ModLoader.DownloadNewMods(TargetWorld.Checkpoint.Mods); } public MyGameServerItem TargetServer { get; } public MyObjectBuilder_World TargetWorld { get; } private string OldArmorSkin { get; set; } = string.Empty; public void BeginSwitch() { OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin; MySandboxGame.Static.Invoke(delegate { //Set camera controller to fixed spectator MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed); UnloadCurrentServer(); SetNewMultiplayerClient(); SeamlessClient.IsSwitching = false; }, "SeamlessClient"); } private void SetNewMultiplayerClient() { // Following is called when the multiplayer is set successfully to target server Patches.OnJoinEvent += OnJoinEvent; MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(TargetWorld.Checkpoint, TargetWorld.Sector); // Create constructors var layerInstance = new MyTransportLayer(MyMultiplayer.GAME_EVENT_CHANNEL); var syncInstance = new MySyncLayer(layerInstance); var instance = new MyMultiplayerClient(TargetServer, syncInstance); MyMultiplayer.Static = instance; MyMultiplayer.Static.ExperimentalMode = true; // Set the new SyncLayer to the MySession.Static.SyncLayer MySession.Static.SyncLayer = MyMultiplayer.Static.SyncLayer; SeamlessClient.TryShow("Successfully set MyMultiplayer.Static"); Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName); Sync.Players.RegisterEvents(); } private void OnJoinEvent(object sender, JoinResultMsg e) { ForceClientConnection(); // Un-register the event Patches.OnJoinEvent -= OnJoinEvent; } private void ForceClientConnection() { //Set World Settings SetWorldSettings(); //Load force load any connected players LoadConnectedClients(); MySector.InitEnvironmentSettings(TargetWorld.Sector.Environment); var text = !string.IsNullOrEmpty(TargetWorld.Checkpoint.CustomSkybox) ? TargetWorld.Checkpoint.CustomSkybox : MySector.EnvironmentDefinition.EnvironmentTexture; MyRenderProxy.PreloadTextures([text], TextureType.CubeMap); MyModAPIHelper.Initialize(); MySession.Static.LoadDataComponents(); //MySession.Static.LoadObjectBuildersComponents(TargetWorld.Checkpoint.SessionComponents); MyModAPIHelper.Initialize(); // MySession.Static.LoadObjectBuildersComponents(TargetWorld.Checkpoint.SessionComponents); //MethodInfo A = typeof(MySession).GetMethod("LoadGameDefinition", BindingFlags.Instance | BindingFlags.NonPublic); // A.Invoke(MySession.Static, new object[] { TargetWorld.Checkpoint }); MyMultiplayer.Static.OnSessionReady(); UpdateWorldGenerator(); StartEntitySync(); MyHud.Chat.RegisterChat(MyMultiplayer.Static); MySession.Static.Gpss.RegisterChat(MyMultiplayer.Static); // Allow the game to start proccessing incoming messages in the buffer MyMultiplayer.Static.StartProcessingClientMessages(); //Recreate all controls... Will fix weird gui/paint/crap MyGuiScreenHudSpace.Static.RecreateControls(true); //MySession.Static.LocalHumanPlayer.BuildArmorSkin = OldArmorSkin; } private void LoadOnlinePlayers() { //Get This players ID MyPlayer.PlayerId? savingPlayerId = new MyPlayer.PlayerId(Sync.MyId); if (!savingPlayerId.HasValue) { SeamlessClient.TryShow("SavingPlayerID is null! Creating Default!"); savingPlayerId = new MyPlayer.PlayerId(Sync.MyId); } SeamlessClient.TryShow($"Saving PlayerID: {savingPlayerId}"); Sync.Players.LoadConnectedPlayers(TargetWorld.Checkpoint, savingPlayerId); Sync.Players.LoadControlledEntities(TargetWorld.Checkpoint.ControlledEntities, TargetWorld.Checkpoint.ControlledObject, savingPlayerId); /* SeamlessClient.TryShow("Saving PlayerID: " + savingPlayerId.ToString()); foreach (KeyValuePair item3 in TargetWorld.Checkpoint.AllPlayersData.Dictionary) { MyPlayer.PlayerId playerId5 = new MyPlayer.PlayerId(item3.Key.GetClientId(), item3.Key.SerialId); SeamlessClient.TryShow($"ConnectedPlayer: {playerId5.ToString()}"); if (savingPlayerId.HasValue && playerId5.SteamId == savingPlayerId.Value.SteamId) { playerId5 = new MyPlayer.PlayerId(Sync.MyId, playerId5.SerialId); } Patches.LoadPlayerInternal.Invoke(MySession.Static.Players, new object[] { playerId5, item3.Value, false }); ConcurrentDictionary Players = (ConcurrentDictionary)Patches.MPlayerGPSCollection.GetValue(MySession.Static.Players); //LoadPlayerInternal(ref playerId5, item3.Value); if (Players.TryGetValue(playerId5, out MyPlayer myPlayer)) { List value2 = null; if (TargetWorld.Checkpoint.AllPlayersColors != null && TargetWorld.Checkpoint.AllPlayersColors.Dictionary.TryGetValue(item3.Key, out value2)) { myPlayer.SetBuildColorSlots(value2); } else if (TargetWorld.Checkpoint.CharacterToolbar != null && TargetWorld.Checkpoint.CharacterToolbar.ColorMaskHSVList != null && TargetWorld.Checkpoint.CharacterToolbar.ColorMaskHSVList.Count > 0) { myPlayer.SetBuildColorSlots(TargetWorld.Checkpoint.CharacterToolbar.ColorMaskHSVList); } } } */ } private void SetWorldSettings() { //MyEntities.MemoryLimitAddFailureReset(); //Clear old list MySession.Static.PromotedUsers.Clear(); MySession.Static.CreativeTools.Clear(); MySession.Static.RemoteAdminSettings.Clear(); // Set new world settings MySession.Static.Name = MyStatControlText.SubstituteTexts(TargetWorld.Checkpoint.SessionName); MySession.Static.Description = TargetWorld.Checkpoint.Description; MySession.Static.Mods = TargetWorld.Checkpoint.Mods; MySession.Static.Settings = TargetWorld.Checkpoint.Settings; MySession.Static.CurrentPath = MyLocalCache.GetSessionSavesPath(MyUtils.StripInvalidChars(TargetWorld.Checkpoint.SessionName), false, false); MySession.Static.WorldBoundaries = TargetWorld.Checkpoint.WorldBoundaries; MySession.Static.InGameTime = MyObjectBuilder_Checkpoint.DEFAULT_DATE; MySession.Static.ElapsedGameTime = new TimeSpan(TargetWorld.Checkpoint.ElapsedGameTime); MySession.Static.Settings.EnableSpectator = false; MySession.Static.Password = TargetWorld.Checkpoint.Password; MySession.Static.PreviousEnvironmentHostility = TargetWorld.Checkpoint.PreviousEnvironmentHostility; MySession.Static.RequiresDX = TargetWorld.Checkpoint.RequiresDX; MySession.Static.CustomLoadingScreenImage = TargetWorld.Checkpoint.CustomLoadingScreenImage; MySession.Static.CustomLoadingScreenText = TargetWorld.Checkpoint.CustomLoadingScreenText; MySession.Static.CustomSkybox = TargetWorld.Checkpoint.CustomSkybox; try { MySession.Static.Gpss = new MyGpsCollection(); MySession.Static.Gpss.LoadGpss(TargetWorld.Checkpoint); } catch (Exception ex) { SeamlessClient.TryShow( $"An error occured while loading GPS points! You will have an empty gps list! \n {ex}"); } MyRenderProxy.RebuildCullingStructure(); //MySession.Static.Toolbars.LoadToolbars(checkpoint); Sync.Players.RespawnComponent.InitFromCheckpoint(TargetWorld.Checkpoint); // Set new admin settings if (TargetWorld.Checkpoint.PromotedUsers != null) MySession.Static.PromotedUsers = TargetWorld.Checkpoint.PromotedUsers.Dictionary; else MySession.Static.PromotedUsers = new Dictionary(); foreach (var item in TargetWorld.Checkpoint.AllPlayersData.Dictionary) { var clientId = item.Key.GetClientId(); var adminSettingsEnum = (AdminSettingsEnum)item.Value.RemoteAdminSettings; if (TargetWorld.Checkpoint.RemoteAdminSettings != null && TargetWorld.Checkpoint.RemoteAdminSettings.Dictionary.TryGetValue(clientId, out var value)) adminSettingsEnum = (AdminSettingsEnum)value; if (!MyPlatformGameSettings.IsIgnorePcuAllowed) { adminSettingsEnum &= ~AdminSettingsEnum.IgnorePcu; adminSettingsEnum &= ~AdminSettingsEnum.KeepOriginalOwnershipOnPaste; } MySession.Static.RemoteAdminSettings[clientId] = adminSettingsEnum; if (!Sync.IsDedicated && clientId == Sync.MyId) MySession.Static.AdminSettings = adminSettingsEnum; var value2 = MySession.Static.PromotedUsers.GetValueOrDefault(clientId, MyPromoteLevel.None); if (item.Value.PromoteLevel > value2) MySession.Static.PromotedUsers[clientId] = item.Value.PromoteLevel; if (!MySession.Static.CreativeTools.Contains(clientId) && item.Value.CreativeToolsEnabled) MySession.Static.CreativeTools.Add(clientId); } } private void LoadConnectedClients() { MySession.Static.LoadMembersFromWorld(TargetWorld, MyMultiplayer.Static); //Re-Initilize Virtual clients MySession.Static.VirtualClients.Init(); //load online players LoadOnlinePlayers(); } private void StartEntitySync() { SeamlessClient.TryShow("Requesting Player From Server"); Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, true, true); if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Game.IsDedicated) { MyLog.Default.WriteLine("ControlledObject was null, respawning character"); //m_cameraAwaitingEntity = true; MyPlayerCollection.RequestLocalRespawn(); } //Request client state batch ((MyMultiplayerClientBase)MyMultiplayer.Static).RequestBatchConfirmation(); MyMultiplayer.Static.PendingReplicablesDone += MyMultiplayer_PendingReplicablesDone; //typeof(MyGuiScreenTerminal).GetMethod("CreateTabs") MySession.Static.LoadDataComponents(); //MyGuiSandbox.LoadData(false); //MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.HUDScreen)); MyRenderProxy.RebuildCullingStructure(); MyRenderProxy.CollectGarbage(); SeamlessClient.TryShow($"OnlinePlayers: {MySession.Static.Players.GetOnlinePlayers().Count}"); SeamlessClient.TryShow("Loading Complete!"); } private void MyMultiplayer_PendingReplicablesDone() { if (MySession.Static.VoxelMaps.Instances.Count > 0) MySandboxGame.AreClipmapsReady = false; MyMultiplayer.Static.PendingReplicablesDone -= MyMultiplayer_PendingReplicablesDone; } private void UpdateWorldGenerator() { //This will re-init the MyProceduralWorldGenerator. (Not doing this will result in asteroids not rendering in properly) //This shoud never be null var generator = MySession.Static.GetComponent(); //Force component to unload Patches.UnloadProceduralWorldGenerator.Invoke(generator, null); //Re-call the generator init var generatorSettings = TargetWorld.Checkpoint.SessionComponents.OfType().FirstOrDefault(); if (generatorSettings != null) //Re-initilized this component (forces to update asteroid areas like not in planets etc) generator.Init(generatorSettings); //Force component to reload, re-syncing settings and seeds to the destination server generator.LoadData(); //We need to go in and force planets to be empty areas in the generator. This is originially done on planet init. foreach (var planet in MyEntities.GetEntities().OfType()) { generator.MarkEmptyArea(planet.PositionComp.GetPosition(), planet.m_planetInitValues.MaxRadius); } } private void UnloadCurrentServer() { //Unload current session on game thread if (MyMultiplayer.Static == null) throw new Exception("MyMultiplayer.Static is null on unloading? dafuq?"); RemoveOldEntities(); //Try and close the quest log var component = MySession.Static.GetComponent(); component?.TryCancelObjective(); //Clear all old players and clients. Sync.Clients.Clear(); Sync.Players.ClearPlayers(); MyHud.Chat.UnregisterChat(MyMultiplayer.Static); MySession.Static.Gpss.RemovePlayerGpss(MySession.Static.LocalPlayerId); MyHud.GpsMarkers.Clear(); MyMultiplayer.Static.ReplicationLayer.Disconnect(); MyMultiplayer.Static.ReplicationLayer.Dispose(); MyMultiplayer.Static.Dispose(); MyMultiplayer.Static = null; //Close any respawn screens that are open MyGuiScreenMedicals.Close(); //MySession.Static.UnloadDataComponents(); } private void RemoveOldEntities() { foreach (var ent in MyEntities.GetEntities()) { if (ent is MyPlanet) continue; ent.Close(); } } }