diff --git a/Messages/ClientMessages.cs b/Messages/ClientMessages.cs index c375cc4..475efdb 100644 --- a/Messages/ClientMessages.cs +++ b/Messages/ClientMessages.cs @@ -1,6 +1,7 @@ using ProtoBuf; using Sandbox.Game.World; using Sandbox.ModAPI; +using SeamlessClientPlugin.SeamlessTransfer; using SeamlessClientPlugin.Utilities; using System; using System.Collections.Generic; @@ -31,15 +32,13 @@ namespace SeamlessClientPlugin.ClientMessages [ProtoMember(5)] public string PluginVersion = "0"; - - public ClientMessage(ClientMessageType Type) { MessageType = Type; if (MyAPIGateway.Multiplayer != null && !MyAPIGateway.Multiplayer.IsServer) { - if(MyAPIGateway.Session.LocalHumanPlayer == null) + if (MyAPIGateway.Session.LocalHumanPlayer == null) { return; } @@ -57,12 +56,15 @@ namespace SeamlessClientPlugin.ClientMessages MessageData = Utility.Serialize(Data); } - public T DeserializeData() + + public Transfer GetTransferData() { if (MessageData == null) - return default(T); + return default(Transfer); + + return Utility.Deserialize(MessageData); - return Utility.Deserialize(MessageData); } + } } diff --git a/Messages/Transfer.cs b/Messages/Transfer.cs new file mode 100644 index 0000000..58e72e9 --- /dev/null +++ b/Messages/Transfer.cs @@ -0,0 +1,67 @@ +using ProtoBuf; +using Sandbox; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Entities; +using Sandbox.Game.Gui; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Sandbox.Graphics.GUI; +using Sandbox.ModAPI; +using SeamlessClientPlugin.ClientMessages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Net; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading.Tasks; +using VRage; +using VRage.Game; +using VRage.GameServices; +using VRage.Network; +using VRage.Steam; +using VRage.Utils; +using VRageMath; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + + [ProtoContract] + public class Transfer + { + [ProtoMember(1)] + public ulong TargetServerID; + [ProtoMember(2)] + public string IPAdress; + [ProtoMember(6)] + public WorldRequest WorldRequest; + [ProtoMember(7)] + public string PlayerName; + + [ProtoMember(8)] + public List PlayerGPSCoords; + + [ProtoMember(9)] + public MyObjectBuilder_Toolbar PlayerToolbar; + + public List PlayerBuildSlots; + + public Transfer(ulong ServerID, string IPAdress) + { + /* This is only called serverside + */ + + this.IPAdress = IPAdress; + TargetServerID = ServerID; + } + + public Transfer() { } + + + + + + } +} diff --git a/SeamlessClient.cs b/SeamlessClient.cs index 604e480..b03818f 100644 --- a/SeamlessClient.cs +++ b/SeamlessClient.cs @@ -108,56 +108,23 @@ namespace SeamlessClientPlugin public static string Version = "1.2.12"; public static bool Debug = false; + private static bool Initilized = false; + - private bool Initilized = false; - private bool SentPingResponse = false; public const ushort SeamlessClientNetID = 2936; - private System.Timers.Timer PingTimer = new System.Timers.Timer(3000); + private static System.Timers.Timer PingTimer = new System.Timers.Timer(3000); - public static LoadServer Server = new LoadServer(); public static bool IsSwitching = false; public static bool RanJoin = false; - public void Dispose() - { -#pragma warning disable CS0618 // Type or member is obsolete - MyAPIGateway.Multiplayer?.UnregisterMessageHandler(SeamlessClientNetID, MessageHandler); -#pragma warning restore CS0618 // Type or member is obsolete - Initilized = false; - SentPingResponse = false; - PingTimer.Stop(); - //throw new NotImplementedException(); - } public void Init(object gameInstance) { TryShow("Running Seamless Client Plugin v[" + Version + "]"); - - // Reload = new ReloadPatch(); - //Patching goes here - - PingTimer.Elapsed += PingTimer_Elapsed; PingTimer.Start(); - //throw new NotImplementedException(); - } - - - - private void PingTimer_Elapsed(object sender, ElapsedEventArgs e) - { - //TryShow("Sending PluginVersion to Server!"); - try - { - ClientMessage PingServer = new ClientMessage(ClientMessageType.FirstJoin); - MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer)); - } - catch (Exception ex) - { - TryShow(ex.ToString()); - } } @@ -170,8 +137,8 @@ namespace SeamlessClientPlugin { TryShow("Initilizing Communications!"); RunInitilizations(); - Initilized = true; - + + } //OnNewPlayerRequest //throw new NotImplementedException(); @@ -179,35 +146,15 @@ namespace SeamlessClientPlugin - public static void RunInitilizations() - { -#pragma warning disable CS0618 // Type or member is obsolete - MyAPIGateway.Multiplayer.RegisterMessageHandler(SeamlessClientNetID, MessageHandler); -#pragma warning restore CS0618 // Type or member is obsolete - //We need to initiate ping request - } - - public static void DisposeInitilizations() - { - MyAPIGateway.Multiplayer.UnregisterMessageHandler(SeamlessClientNetID, MessageHandler); - } - private static void MessageHandler(byte[] bytes) + private void PingTimer_Elapsed(object sender, ElapsedEventArgs e) { + // Terrible way to make sure server knows we are running seamless client try { - ClientMessage Recieved = Utilities.Utility.Deserialize(bytes); - if (Recieved.MessageType == ClientMessageType.TransferServer) - { - Transfer TransferMessage = Recieved.DeserializeData(); - IsSwitching = true; - TransferMessage.PingServerAndBeginRedirect(); - RanJoin = false; - MySession.Static.SetCameraController(VRage.Game.MyCameraControllerEnum.SpectatorFixed); - - //DisposeInitilizations(); - } + ClientMessage PingServer = new ClientMessage(ClientMessageType.FirstJoin); + MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer)); } catch (Exception ex) { @@ -216,6 +163,47 @@ namespace SeamlessClientPlugin } + + + + + + + + + public static void RunInitilizations() + { + TryShow("Initilizing Communications!"); + MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(SeamlessClientNetID, MessageHandler); + Initilized = true; + } + + public static void DisposeInitilizations() + { + PingTimer.Stop(); + MyAPIGateway.Multiplayer.UnregisterSecureMessageHandler(SeamlessClientNetID, MessageHandler); + Initilized = false; + + } + + + private static void MessageHandler(ushort obj1, byte[] obj2, ulong obj3, bool obj4) + { + try + { + ClientMessage Recieved = Utilities.Utility.Deserialize(obj2); + if (Recieved.MessageType == ClientMessageType.TransferServer) + { + Transfer TransferMessage = Recieved.GetTransferData(); + + ServerPing.StartServerPing(TransferMessage); + } + } + catch (Exception ex) + { + TryShow(ex.ToString()); + } + } public static void TryShow(string message) @@ -226,30 +214,9 @@ namespace SeamlessClientPlugin MyLog.Default?.WriteLineAndConsole($"SeamlessClient: {message}"); } - - public static void RestartClientAfterUpdate() + public void Dispose() { - try - { - TryShow("Restarting Client!"); - string exe = Assembly.GetEntryAssembly().Location; - Process currentProcess = Process.GetCurrentProcess(); - - string[] CommandArgs = Environment.GetCommandLineArgs(); - string NewCommandLine = ""; - for(int i = 1; i < CommandArgs.Length; i++) - { - NewCommandLine += " "+ CommandArgs[i]; - } - - TryShow(NewCommandLine); - Process.Start(exe, NewCommandLine); - currentProcess.Kill(); - } - catch (Exception ex) - { - TryShow("Restarting Client error!"); - } + DisposeInitilizations(); } } } diff --git a/SeamlessClientPlugin.csproj b/SeamlessClientPlugin.csproj index 023050c..b84a8a6 100644 --- a/SeamlessClientPlugin.csproj +++ b/SeamlessClientPlugin.csproj @@ -162,9 +162,10 @@ - - - + + + + diff --git a/SeamlessTransfer/LoadServer.cs b/SeamlessTransfer/LoadServer.cs deleted file mode 100644 index dc8d289..0000000 --- a/SeamlessTransfer/LoadServer.cs +++ /dev/null @@ -1,723 +0,0 @@ -using HarmonyLib; -using Sandbox; -using Sandbox.Definitions; -using Sandbox.Engine; -using Sandbox.Engine.Analytics; -using Sandbox.Engine.Multiplayer; -using Sandbox.Engine.Networking; -using Sandbox.Engine.Physics; -using Sandbox.Engine.Utils; -using Sandbox.Engine.Voxels; -using Sandbox.Game; -using Sandbox.Game.Entities; -using Sandbox.Game.Gui; -using Sandbox.Game.GUI; -using Sandbox.Game.Multiplayer; -using Sandbox.Game.Screens.Helpers; -using Sandbox.Game.SessionComponents; -using Sandbox.Game.World; -using Sandbox.Graphics.GUI; -using Sandbox.ModAPI; -using SeamlessClientPlugin.Utilities; -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Threading.Tasks; -using System.Timers; -using VRage; -using VRage.FileSystem; -using VRage.Game; -using VRage.Game.ModAPI; -using VRage.Game.SessionComponents; -using VRage.Game.Voxels; -using VRage.GameServices; -using VRage.Network; -using VRage.Serialization; -using VRage.Utils; -using VRageMath; -using VRageRender; -using VRageRender.Messages; - -namespace SeamlessClientPlugin.SeamlessTransfer -{ - public class LoadServer - { - //Protected or internal class types - private static readonly Type ClientType = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClient, Sandbox.Game"); - private static readonly Type SyncLayerType = Type.GetType("Sandbox.Game.Multiplayer.MySyncLayer, Sandbox.Game"); - private static readonly Type MyTransportLayerType = Type.GetType("Sandbox.Engine.Multiplayer.MyTransportLayer, Sandbox.Game"); - private static readonly Type MySessionType = Type.GetType("Sandbox.Game.World.MySession, Sandbox.Game"); - private static readonly Type VirtualClientsType = Type.GetType("Sandbox.Engine.Multiplayer.MyVirtualClients, Sandbox.Game"); - private static readonly Type GUIScreenChat = Type.GetType("Sandbox.Game.Gui.MyGuiScreenChat, Sandbox.Game"); - private static readonly Type MyMultiplayerClientBase = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClientBase, Sandbox.Game"); - - - - private static Harmony Patcher = new Harmony("SeamlessClientReUnload"); - private static MyGameServerItem Server; - private static MyObjectBuilder_World World; - - public static object MyMulitplayerClient; - - - - public static ConstructorInfo ClientConstructor; - public static ConstructorInfo SyncLayerConstructor; - public static ConstructorInfo TransportLayerConstructor; - public static ConstructorInfo MySessionConstructor; - public static ConstructorInfo MyMultiplayerClientBaseConstructor; - - //Reflected Methods - public static FieldInfo VirtualClients; - public static FieldInfo AdminSettings; - - public static FieldInfo RemoteAdminSettings; - public static FieldInfo MPlayerGPSCollection; - public static MethodInfo RemovePlayerFromDictionary; - public static MethodInfo InitVirtualClients; - public static MethodInfo LoadPlayerInternal; - public static MethodInfo LoadMembersFromWorld; - - public static FieldInfo DisconnectedClients; - public static FieldInfo MClients; - public static PropertyInfo MySessionLayer; - - public static MethodInfo LoadMultiplayer; - - private static ulong LocalSteamID; - - - public LoadServer() - { - InitiatePatches(); - } - - - - public void InitiatePatches() - { - //Patch the on connection event - MethodInfo OnJoin = ClientType.GetMethod("OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance); - Patcher.Patch(OnJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined)))); - - - ClientConstructor = ClientType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MyGameServerItem), SyncLayerType }, null); - SyncLayerConstructor = SyncLayerType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[1] { MyTransportLayerType }, null); - TransportLayerConstructor = MyTransportLayerType?.GetConstructor(BindingFlags.Instance | BindingFlags.Public, null, new Type[1] { typeof(int) }, null); - MySessionConstructor = MySessionType?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MySyncLayer), typeof(bool) }, null); - MyMultiplayerClientBaseConstructor = MyMultiplayerClientBase?.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[] { typeof(MySyncLayer) }, null); - - - - MethodInfo ClientJoined = ClientType.GetMethod("OnClientConnected", BindingFlags.Instance | BindingFlags.NonPublic); - MethodInfo ClientLeft = SyncLayerType?.GetMethod("OnClientLeft", BindingFlags.Instance | BindingFlags.NonPublic); - - Timer ClientChecker = new Timer(5000); - ClientChecker.Elapsed += ClientChecker_Elapsed; - ClientChecker.Start(); - - - - //Patcher.Patch(ClientJoined, prefix: new HarmonyMethod(GetPatchMethod(nameof(OnClientJoined)))); - Patcher.Patch(ClientLeft, prefix: new HarmonyMethod(GetPatchMethod(nameof(OnClientLeft)))); - - - if (ClientConstructor == null) - { - throw new InvalidOperationException("Couldn't find ClientConstructor"); - } - - if (SyncLayerConstructor == null) - { - throw new InvalidOperationException("Couldn't find SyncLayerConstructor"); - } - - if (TransportLayerConstructor == null) - { - throw new InvalidOperationException("Couldn't find TransportLayerConstructor"); - } - - if (MySessionConstructor == null) - { - throw new InvalidOperationException("Couldn't find MySessionConstructor"); - } - - RemovePlayerFromDictionary = typeof(MyPlayerCollection).GetMethod("RemovePlayerFromDictionary", BindingFlags.Instance | BindingFlags.NonPublic); - - - DisconnectedClients = typeof(MyClientCollection).GetField("m_disconnectedClients", BindingFlags.Instance | BindingFlags.NonPublic); - MClients = typeof(MyClientCollection).GetField("m_clients", BindingFlags.Instance | BindingFlags.NonPublic); - - - MySessionLayer = typeof(MySession).GetProperty("SyncLayer", BindingFlags.Instance | BindingFlags.Public); - - - VirtualClients = typeof(MySession).GetField("VirtualClients", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - InitVirtualClients = VirtualClientsType.GetMethod("Init", BindingFlags.Instance | BindingFlags.Public); - LoadPlayerInternal = typeof(MyPlayerCollection).GetMethod("LoadPlayerInternal", BindingFlags.Instance | BindingFlags.NonPublic); - LoadMembersFromWorld = typeof(MySession).GetMethod("LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance); - AdminSettings = typeof(MySession).GetField("m_adminSettings", BindingFlags.Instance | BindingFlags.NonPublic); - RemoteAdminSettings = typeof(MySession).GetField("m_remoteAdminSettings", BindingFlags.Instance | BindingFlags.NonPublic); - MPlayerGPSCollection = typeof(MyPlayerCollection).GetField("m_players", BindingFlags.Instance | BindingFlags.NonPublic); - LoadMultiplayer = typeof(MySession).GetMethod("LoadMultiplayer", BindingFlags.Static | BindingFlags.NonPublic); - - - - - MethodInfo LoadingAction = typeof(MySessionLoader).GetMethod("LoadMultiplayerSession", BindingFlags.Public | BindingFlags.Static); - Patcher.Patch(LoadingAction, prefix: new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession)))); - } - - - private static MethodInfo GetPatchMethod(string v) - { - return typeof(LoadServer).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); - } - - - - private static bool GetCustomLoadingScreenPath(List Mods, out string File) - { - File = null; - string WorkshopDir = MyFileSystem.ModsPath; - SeamlessClient.TryShow(WorkshopDir); - try - { - SeamlessClient.TryShow("Installed Mods: " + Mods); - foreach (var Mod in Mods) - { - string SearchDir = Mod.GetPath(); - - if (!Directory.Exists(SearchDir)) - continue; - - var files = Directory.GetFiles(SearchDir, "*.dds", SearchOption.TopDirectoryOnly); - foreach (var file in files) - { - if (Path.GetFileNameWithoutExtension(file) == "CustomLoadingBackground") - { - SeamlessClient.TryShow(Mod.FriendlyName + " contains a custom loading background!"); - File = file; - return true; - } - } - } - - } - catch (Exception ex) - { - SeamlessClient.TryShow(ex.ToString()); - } - - SeamlessClient.TryShow("No installed custom loading screen!"); - return false; - } - - - - private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) - { - - - MyLog.Default.WriteLine("LoadSession() - Start"); - if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, allowLocalMods: false)) - { - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer))); - MyLog.Default.WriteLine("LoadSession() - End"); - return false; - } - MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, delegate (bool success) - { - if (success) - { - MyScreenManager.CloseAllScreensNowExcept(null); - MyGuiSandbox.Update(16); - if (MySession.Static != null) - { - MySession.Static.Unload(); - MySession.Static = null; - } - - string CustomBackgroundImage = null; - GetCustomLoadingScreenPath(world.Checkpoint.Mods, out CustomBackgroundImage); - - MySessionLoader.StartLoading(delegate - { - - LoadMultiplayer.Invoke(null, new object[] { world, multiplayerSession }); - //MySession.LoadMultiplayer(world, multiplayerSession); - }, null, CustomBackgroundImage, null); - } - else - { - multiplayerSession.Dispose(); - MySessionLoader.UnloadAndExitToMenu(); - if (MyGameService.IsOnline) - { - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed))); - } - else - { - MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: new StringBuilder(string.Format(MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline), MySession.GameServiceName)))); - } - } - MyLog.Default.WriteLine("LoadSession() - End"); - }, delegate - { - multiplayerSession.Dispose(); - MySessionLoader.UnloadAndExitToMenu(); - }); - - return false; - } - - - - - - private static void OnUserJoined(ref JoinResultMsg msg) - { - if (SeamlessClient.IsSwitching && msg.JoinResult == JoinResult.OK) - { - SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString()); - - - if(MySessionLayer == null) - { - SeamlessClient.TryShow("Null SessionLayer FieldInfo"); - } - - - if (MySession.Static == null) - { - SeamlessClient.TryShow("Null MySession.Static"); - } - - if (MyMultiplayer.Static.SyncLayer == null) - { - SeamlessClient.TryShow("Null MyMultiplayer.Static.SyncLayer"); - } - - MySessionLayer.SetValue(MySession.Static , MyMultiplayer.Static.SyncLayer); - - - - - - - - ForceClientConnection(); - MyMultiplayer.Static.StartProcessingClientMessages(); - - } - else if (SeamlessClient.IsSwitching && msg.JoinResult != JoinResult.OK) - { - SeamlessClient.TryShow("Failed to join server! Reason: " + msg.JoinResult.ToString()); - MySession.Static?.Unload(); - } - } - - - private static void OnClientJoined(MyPacket packet) - { - ConnectedClientDataMsg msg = MySerializer.CreateAndRead(packet.BitStream); - SeamlessClient.TryShow($"Client: {msg.ClientId} Name: {msg.Name} PacketSender: {packet.Sender.Id.Value}"); - } - - private static void OnClientLeft(ulong steamUserId, MyChatMemberStateChangeEnum leaveReason) - { - SeamlessClient.TryShow($"Client {steamUserId} left!"); - } - - - private static void ClientChecker_Elapsed(object sender, ElapsedEventArgs e) - { - - - var Clients = Sync.Clients.GetClients(); - SeamlessClient.TryShow("---------------------"); - foreach (var client in Clients) - { - SeamlessClient.TryShow($"{client.DisplayName}:{client.SteamUserId}"); - } - SeamlessClient.TryShow("---------------------"); - } - - public static void LoadWorldData(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld) - { - Server = TargetServer; - World = TargetWorld; - } - - - public static void ResetMPClient() - { - try - { - - //m_memberData - - - MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(World.Checkpoint, World.Sector); - var LayerInstance = TransportLayerConstructor.Invoke(new object[] { 2 }); - var SyncInstance = SyncLayerConstructor.Invoke(new object[] { LayerInstance }); - var instance = ClientConstructor.Invoke(new object[] { Server, SyncInstance }); - MyMulitplayerClient = instance; - - MyMultiplayer.Static = Utility.CastToReflected(instance, ClientType); - MyMultiplayer.Static.ExperimentalMode = true; - - - - - SeamlessClient.TryShow("Successfully set MyMultiplayer.Static"); - //var m = ClientType.GetMethod("SendPlayerData", BindingFlags.Public | BindingFlags.Instance); - //m.Invoke(MyMultiplayer.Static, new object[] { MyGameService.UserName }); - Server.GetGameTagByPrefix("gamemode"); - //typeof(MySession).GetMethod("LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance).Invoke(MySession.Static, new object[] { LoadServer.TargetWorld, MyMultiplayer.Static }); - - //MyMultiplayer.Static.ClientJoined += Static_ClientJoined; - - //MyScreenManager.CloseScreen(GUIScreenChat); - MyHud.Chat.RegisterChat(MyMultiplayer.Static); - //MySession.SetSpectatorPositionFromServer(SeamlessClient.PreviousPosition ?? Vector3D.Zero); - MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed); - - - Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName); - Sync.Players.RegisterEvents(); - - - } - catch (Exception ex) - { - SeamlessClient.TryShow("Error! " + ex.ToString()); - } - } - - - public static void LoadMP(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) - { - SeamlessClient.TryShow("Starting LoadMP!"); - - - //var MySessionConstructor = MySessionType.GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[2] { typeof(MySyncLayer), typeof(bool) }, null); - //MySession.Static = (MySession)MySessionConstructor.Invoke(new object[] { MyMultiplayer.Static.SyncLayer, true }); - MySession.Static.Mods = World.Checkpoint.Mods; - MySession.Static.Settings = World.Checkpoint.Settings; - MySession.Static.CurrentPath = MyLocalCache.GetSessionSavesPath(MyUtils.StripInvalidChars(world.Checkpoint.SessionName), contentFolder: false, createIfNotExists: false); - MySession.Static.WorldBoundaries = world.Checkpoint.WorldBoundaries; - MySession.Static.InGameTime = MyObjectBuilder_Checkpoint.DEFAULT_DATE; - - - - // MySession.Static.Players.LoadConnectedPlayers(world.Checkpoint); - - //typeof(MySession).GetMethod("PrepareBaseSession", BindingFlags.NonPublic | BindingFlags.Instance, null, new Type[] { typeof(MyObjectBuilder_Checkpoint), typeof(MyObjectBuilder_Sector) }, null).Invoke(MySession.Static, new object[] { world.Checkpoint, world.Sector }); - - if (MyFakes.MP_SYNC_CLUSTERTREE) - { - SeamlessClient.TryShow("Deserializing Clusters!"); - //MyPhysics.DeserializeClusters(world.Clusters); - } - - - - //_ = world.Checkpoint.ControlledObject; - //world.Checkpoint.ControlledObject = -1L; - LoadOnlinePlayers(world.Checkpoint); - LoadWorld(world.Checkpoint, world.Sector); - SeamlessClient.TryShow("Loading World Complete!"); - } - - - - private static void LoadWorld(MyObjectBuilder_Checkpoint checkpoint, MyObjectBuilder_Sector sector) - { - - Dictionary AdminSettingsList = (Dictionary)RemoteAdminSettings.GetValue(MySession.Static); - AdminSettingsList.Clear(); - - MySession.Static.PromotedUsers.Clear(); - MySession.Static.CreativeTools.Clear(); - - MyEntities.MemoryLimitAddFailureReset(); - MySession.Static.ElapsedGameTime = new TimeSpan(checkpoint.ElapsedGameTime); - MySession.Static.InGameTime = checkpoint.InGameTime; - MySession.Static.Name = MyStatControlText.SubstituteTexts(checkpoint.SessionName); - MySession.Static.Description = checkpoint.Description; - - - if (checkpoint.PromotedUsers != null) - { - MySession.Static.PromotedUsers = checkpoint.PromotedUsers.Dictionary; - } - else - { - MySession.Static.PromotedUsers = new Dictionary(); - } - - - - - foreach (KeyValuePair item in checkpoint.AllPlayersData.Dictionary) - { - ulong clientId = item.Key.GetClientId(); - AdminSettingsEnum adminSettingsEnum = (AdminSettingsEnum)item.Value.RemoteAdminSettings; - if (checkpoint.RemoteAdminSettings != null && checkpoint.RemoteAdminSettings.Dictionary.TryGetValue(clientId, out var value)) - { - adminSettingsEnum = (AdminSettingsEnum)value; - } - if (!MyPlatformGameSettings.IsIgnorePcuAllowed) - { - adminSettingsEnum &= ~AdminSettingsEnum.IgnorePcu; - adminSettingsEnum &= ~AdminSettingsEnum.KeepOriginalOwnershipOnPaste; - } - - - AdminSettingsList[clientId] = adminSettingsEnum; - if (!Sync.IsDedicated && clientId == Sync.MyId) - { - AdminSettings.SetValue(MySession.Static, adminSettingsEnum); - - //m_adminSettings = adminSettingsEnum; - } - - - - if (!MySession.Static.PromotedUsers.TryGetValue(clientId, out var value2)) - { - value2 = 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); - } - } - - - //MySession.Static.WorkshopId = checkpoint.WorkshopId; - MySession.Static.Password = checkpoint.Password; - MySession.Static.PreviousEnvironmentHostility = checkpoint.PreviousEnvironmentHostility; - MySession.Static.RequiresDX = checkpoint.RequiresDX; - MySession.Static.CustomLoadingScreenImage = checkpoint.CustomLoadingScreenImage; - MySession.Static.CustomLoadingScreenText = checkpoint.CustomLoadingScreenText; - MySession.Static.CustomSkybox = checkpoint.CustomSkybox; - //FixIncorrectSettings(Settings); - // MySession.Static.AppVersionFromSave = checkpoint.AppVersion; - //MyToolbarComponent.InitCharacterToolbar(checkpoint.CharacterToolbar); - //LoadCameraControllerSettings(checkpoint); - - - - - - SeamlessClient.TryShow("LocalPlayerID: " + MySession.Static.LocalPlayerId); - //checkpoint.Gps.Dictionary.TryGetValue(MySession.Static.LocalPlayerId, out MyObjectBuilder_Gps GPSCollection); - //SeamlessClient.TryShow("You have " + GPSCollection.Entries.Count + " gps points!"); - - - - MySession.Static.Gpss = new MyGpsCollection(); - MySession.Static.Gpss.LoadGpss(checkpoint); - MyRenderProxy.RebuildCullingStructure(); - //MySession.Static.Toolbars.LoadToolbars(checkpoint); - - Sync.Players.RespawnComponent.InitFromCheckpoint(checkpoint); - } - - - private static void ForceClientConnection() - { - SeamlessClient.IsSwitching = false; - - try - { - - try - { - if (MyMultiplayer.Static == null) - SeamlessClient.TryShow("MyMultiplayer.Static is null"); - - if (World == null) - SeamlessClient.TryShow("TargetWorld is null"); - - LoadClients(); - - LoadMP(World, MyMultiplayer.Static); - - - } - catch (Exception ex) - { - SeamlessClient.TryShow(ex.ToString()); - } - - - SeamlessClient.TryShow("Requesting Player From Server"); - Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, realPlayer: true, initialPlayer: true); - if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Sandbox.Engine.Platform.Game.IsDedicated) - { - SeamlessClient.TryShow("C"); - MyLog.Default.WriteLine("ControlledObject was null, respawning character"); - //m_cameraAwaitingEntity = true; - MyPlayerCollection.RequestLocalRespawn(); - } - - //typeof(MyGuiScreenTerminal).GetMethod("CreateTabs") - MyMultiplayer.Static.OnSessionReady(); - 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!"); - - MyMultiplayer.Static.OnSessionReady(); - //Recreate all controls... Will fix weird gui/paint/crap - MyGuiScreenHudSpace.Static.RecreateControls(true); - - } - catch (Exception Ex) - { - SeamlessClient.TryShow(Ex.ToString()); - } - - - - - - - - } - - - - private static void LoadClients() - { - - - try - { - - //Reset Disconnected Clients - foreach (var Client in Sync.Clients.GetClients().ToList()) - { - if (Client.SteamUserId == Sync.MyId) - { - break; - } - - Sync.Clients.RemoveClient(Client.SteamUserId); - } - - - - //Remove all old players - //Sync.Clients.Clear(); - - //Add server client - Sync.Clients.AddClient(Sync.ServerId, "Good.bot"); - //Sync.Clients.AddClient(Sync.MyId, Sync.MyName); - - object VirtualClientsValue = VirtualClients.GetValue(MySession.Static); - - //Re-Initilize Virtual clients - SeamlessClient.TryShow("Initilizing Virtual Clients!"); - InitVirtualClients.Invoke(VirtualClientsValue, null); - - - //Load Members from world - SeamlessClient.TryShow("Loading Members From World!"); - //LoadMembersFromWorld.Invoke(MySession.Static, new object[] { World, MyMulitplayerClient }); - foreach (var Client in World.Checkpoint.Clients) - { - SeamlessClient.TryShow("Adding New Client: " + Client.Name); - Sync.Clients.AddClient(Client.SteamId, Client.Name); - } - - - - - } - catch (Exception ex) - { - SeamlessClient.TryShow(ex.ToString()); - } - - - } - - private static void LoadOnlinePlayers(MyObjectBuilder_Checkpoint checkpoint) - { - //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.ToString()); - - - SeamlessClient.TryShow("Checkpoint.AllPlayers: " + checkpoint.AllPlayers.Count); - //These both are null/empty. Server doesnt need to send them to the client - //SeamlessClient.TryShow("Checkpoint.ConnectedPlayers: " + checkpoint.ConnectedPlayers.Dictionary.Count); - //SeamlessClient.TryShow("Checkpoint.DisconnectedPlayers: " + checkpoint.DisconnectedPlayers.Dictionary.Count); - SeamlessClient.TryShow("Checkpoint.AllPlayersData: " + checkpoint.AllPlayersData.Dictionary.Count); - - - foreach (KeyValuePair item3 in 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); - } - - LoadPlayerInternal.Invoke(MySession.Static.Players, new object[] { playerId5, item3.Value, false }); - ConcurrentDictionary Players = (ConcurrentDictionary)MPlayerGPSCollection.GetValue(MySession.Static.Players); - //LoadPlayerInternal(ref playerId5, item3.Value); - if (Players.TryGetValue(playerId5, out MyPlayer myPlayer)) - { - List value2 = null; - if (checkpoint.AllPlayersColors != null && checkpoint.AllPlayersColors.Dictionary.TryGetValue(item3.Key, out value2)) - { - myPlayer.SetBuildColorSlots(value2); - } - else if (checkpoint.CharacterToolbar != null && checkpoint.CharacterToolbar.ColorMaskHSVList != null && checkpoint.CharacterToolbar.ColorMaskHSVList.Count > 0) - { - myPlayer.SetBuildColorSlots(checkpoint.CharacterToolbar.ColorMaskHSVList); - } - } - } - } - - - private static void UpdatePlayerData() - { - - } - - - } - - - - - -} diff --git a/SeamlessTransfer/Patches.cs b/SeamlessTransfer/Patches.cs new file mode 100644 index 0000000..5767e60 --- /dev/null +++ b/SeamlessTransfer/Patches.cs @@ -0,0 +1,293 @@ +using HarmonyLib; +using Sandbox.Engine.Multiplayer; +using Sandbox.Engine.Networking; +using Sandbox.Game.Multiplayer; +using Sandbox.Game.World; +using Sandbox.Graphics.GUI; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Threading.Tasks; +using VRage; +using VRage.FileSystem; +using VRage.Game; +using VRage.GameServices; +using VRage.Network; +using VRage.Utils; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + public static class Patches + { + /* All internal classes Types */ + public static readonly Type ClientType = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClient, Sandbox.Game"); + public static readonly Type SyncLayerType = Type.GetType("Sandbox.Game.Multiplayer.MySyncLayer, Sandbox.Game"); + public static readonly Type MyTransportLayerType = Type.GetType("Sandbox.Engine.Multiplayer.MyTransportLayer, Sandbox.Game"); + public static readonly Type MySessionType = Type.GetType("Sandbox.Game.World.MySession, Sandbox.Game"); + public static readonly Type VirtualClientsType = Type.GetType("Sandbox.Engine.Multiplayer.MyVirtualClients, Sandbox.Game"); + public static readonly Type GUIScreenChat = Type.GetType("Sandbox.Game.Gui.MyGuiScreenChat, Sandbox.Game"); + public static readonly Type MyMultiplayerClientBase = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClientBase, Sandbox.Game"); + + /* Harmony Patcher */ + private static Harmony Patcher = new Harmony("SeamlessClientPatcher"); + + + /* Static Contructors */ + public static ConstructorInfo ClientConstructor { get; private set; } + public static ConstructorInfo SyncLayerConstructor { get; private set; } + public static ConstructorInfo TransportLayerConstructor { get; private set; } + public static ConstructorInfo MySessionConstructor { get; private set; } + public static ConstructorInfo MyMultiplayerClientBaseConstructor { get; private set; } + + + + /* Static FieldInfos */ + public static FieldInfo MySessionLayer { get; private set; } + public static FieldInfo VirtualClients { get; private set; } + public static FieldInfo AdminSettings { get; private set; } + public static FieldInfo RemoteAdminSettings { get; private set; } + public static FieldInfo MPlayerGPSCollection { get; private set; } + + + /* Static MethodInfos */ + public static MethodInfo InitVirtualClients { get; private set; } + public static MethodInfo LoadPlayerInternal { get; private set; } + public static MethodInfo LoadMembersFromWorld { get; private set; } + public static MethodInfo LoadMultiplayer { get; private set; } + + + public static event EventHandler OnJoinEvent; + + + + + + public static void GetPatches() + { + //Get reflected values and store them + + + + + /* Get Constructors */ + ClientConstructor = GetConstructor(ClientType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[2] { typeof(MyGameServerItem), SyncLayerType }); + SyncLayerConstructor = GetConstructor(SyncLayerType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[1] { MyTransportLayerType }); + TransportLayerConstructor = GetConstructor(MyTransportLayerType, BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(int) }); + MySessionConstructor = GetConstructor(MySessionType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[2] { typeof(MySyncLayer), typeof(bool) }); + MyMultiplayerClientBaseConstructor = GetConstructor(MyMultiplayerClientBase, BindingFlags.Instance | BindingFlags.NonPublic, new Type[] { typeof(MySyncLayer) }); + + + /* Get Fields */ + MySessionLayer = GetField(typeof(MySession), "SyncLayer", BindingFlags.Instance | BindingFlags.Public); + VirtualClients = GetField(typeof(MySession), "VirtualClients", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + AdminSettings = GetField(typeof(MySession), "m_adminSettings", BindingFlags.Instance | BindingFlags.NonPublic); + RemoteAdminSettings = GetField(typeof(MySession), "m_remoteAdminSettings", BindingFlags.Instance | BindingFlags.NonPublic); + MPlayerGPSCollection = GetField(typeof(MyPlayerCollection), "m_players", BindingFlags.Instance | BindingFlags.NonPublic); + + + + + /* Get Methods */ + MethodInfo OnJoin = GetMethod(ClientType, "OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance); + MethodInfo LoadingAction = GetMethod(typeof(MySessionLoader),"LoadMultiplayerSession", BindingFlags.Public | BindingFlags.Static); + InitVirtualClients = GetMethod(VirtualClientsType, "Init", BindingFlags.Instance | BindingFlags.Public); + LoadPlayerInternal = GetMethod(typeof(MyPlayerCollection), "LoadPlayerInternal", BindingFlags.Instance | BindingFlags.NonPublic); + LoadMembersFromWorld = GetMethod(typeof(MySession), "LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance); + LoadMultiplayer = GetMethod(typeof(MySession), "LoadMultiplayer", BindingFlags.Static | BindingFlags.NonPublic); + + + + + + + + + + Patcher.Patch(OnJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined)))); + Patcher.Patch(LoadingAction, prefix: new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession)))); + } + + private static MethodInfo GetPatchMethod(string v) + { + return typeof(Patches).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); + } + + #region LoadingScreen + /* Loading Screen Stuff */ + private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession) + { + MyLog.Default.WriteLine("LoadSession() - Start"); + if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, allowLocalMods: false)) + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer))); + MyLog.Default.WriteLine("LoadSession() - End"); + return false; + } + MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, delegate (bool success) + { + if (success) + { + MyScreenManager.CloseAllScreensNowExcept(null); + MyGuiSandbox.Update(16); + if (MySession.Static != null) + { + MySession.Static.Unload(); + MySession.Static = null; + } + + string CustomBackgroundImage = null; + GetCustomLoadingScreenPath(world.Checkpoint.Mods, out CustomBackgroundImage); + + MySessionLoader.StartLoading(delegate + { + + LoadMultiplayer.Invoke(null, new object[] { world, multiplayerSession }); + //MySession.LoadMultiplayer(world, multiplayerSession); + }, null, CustomBackgroundImage, null); + } + else + { + multiplayerSession.Dispose(); + MySessionLoader.UnloadAndExitToMenu(); + if (MyGameService.IsOnline) + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed))); + } + else + { + MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: new StringBuilder(string.Format(MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline), MySession.GameServiceName)))); + } + } + MyLog.Default.WriteLine("LoadSession() - End"); + }, delegate + { + multiplayerSession.Dispose(); + MySessionLoader.UnloadAndExitToMenu(); + }); + + return false; + } + + private static bool GetCustomLoadingScreenPath(List Mods, out string File) + { + File = null; + string WorkshopDir = MyFileSystem.ModsPath; + SeamlessClient.TryShow(WorkshopDir); + try + { + SeamlessClient.TryShow("Installed Mods: " + Mods); + foreach (var Mod in Mods) + { + string SearchDir = Mod.GetPath(); + + if (!Directory.Exists(SearchDir)) + continue; + + var files = Directory.GetFiles(SearchDir, "*.dds", SearchOption.TopDirectoryOnly); + foreach (var file in files) + { + if (Path.GetFileNameWithoutExtension(file) == "CustomLoadingBackground") + { + SeamlessClient.TryShow(Mod.FriendlyName + " contains a custom loading background!"); + File = file; + return true; + } + } + } + + } + catch (Exception ex) + { + SeamlessClient.TryShow(ex.ToString()); + } + + SeamlessClient.TryShow("No installed custom loading screen!"); + return false; + } + + #endregion + + + private static void OnUserJoined(ref JoinResultMsg msg) + { + if (msg.JoinResult == JoinResult.OK) + { + //SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString()); + + //Invoke the switch event + OnJoinEvent?.Invoke(null, msg); + } + } + + + + + + private static MethodInfo GetMethod(Type type, string MethodName, BindingFlags Flags) + { + try + { + MethodInfo FoundMethod = type.GetMethod(MethodName, Flags); + + if (FoundMethod == null) + throw new NullReferenceException($"Method for {MethodName} is null!"); + + + return FoundMethod; + + } + catch(Exception Ex) + { + throw Ex; + } + + } + + private static FieldInfo GetField(Type type, string FieldName, BindingFlags Flags) + { + try + { + FieldInfo FoundField = type.GetField(FieldName, Flags); + + if (FoundField == null) + throw new NullReferenceException($"Field for {FieldName} is null!"); + + + return FoundField; + + } + catch (Exception Ex) + { + throw Ex; + } + + } + + private static ConstructorInfo GetConstructor(Type type, BindingFlags Flags, Type[] Types) + { + + try + { + ConstructorInfo FoundConstructor = type.GetConstructor(Flags, null, Types, null); + + if (FoundConstructor == null) + throw new NullReferenceException($"Contructor for {type.Name} is null!"); + + + return FoundConstructor; + + } + catch (Exception Ex) + { + throw Ex; + } + + + } + + + } +} diff --git a/SeamlessTransfer/PingServer.cs b/SeamlessTransfer/PingServer.cs new file mode 100644 index 0000000..d07f32f --- /dev/null +++ b/SeamlessTransfer/PingServer.cs @@ -0,0 +1,72 @@ +using Sandbox.Engine.Networking; +using Sandbox.Game.Multiplayer; +using SeamlessClientPlugin.ClientMessages; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.GameServices; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + public class ServerPing + { + + private static WorldRequest Request; + private static Transfer Transfer; + + + public static void StartServerPing(Transfer ClientTransfer) + { + // We need to first ping the server to make sure its running and so we can get a connection + + if (Transfer.TargetServerID == 0) + { + SeamlessClient.TryShow("This is not a valid server!"); + return; + } + + Transfer = ClientTransfer; + Request = Transfer.WorldRequest; + + + SeamlessClient.TryShow("Beginning Redirect to server: " + Transfer.TargetServerID); + MyGameService.OnPingServerResponded += PingResponded; + MyGameService.OnPingServerFailedToRespond += FailedToRespond; + + MyGameService.PingServer(Transfer.IPAdress); + + + + } + + private static void PingResponded(object sender, MyGameServerItem e) + { + //If server ping was successful we need to begin the switching proccess + UnRegisterEvents(); + + SeamlessClient.TryShow($"{e.Name} was successfully pinged!"); + SwitchServers Switcher = new SwitchServers(e, Request.DeserializeWorldData()); + Switcher.BeginSwitch(); + // LoadServer.LoadWorldData(e, Request.DeserializeWorldData()); + } + + + private static void FailedToRespond(object sender, EventArgs e) + { + // If the target server failed to respond, we need to exit/return to menu + + UnRegisterEvents(); + + } + + + private static void UnRegisterEvents() + { + //Un-register ping events + MyGameService.OnPingServerResponded -= PingResponded; + MyGameService.OnPingServerFailedToRespond -= FailedToRespond; + } + } +} diff --git a/SeamlessTransfer/SwitchServers.cs b/SeamlessTransfer/SwitchServers.cs new file mode 100644 index 0000000..ec11521 --- /dev/null +++ b/SeamlessTransfer/SwitchServers.cs @@ -0,0 +1,328 @@ +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.World; +using SeamlessClientPlugin.Utilities; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using VRage.Game; +using VRage.Game.ModAPI; +using VRage.GameServices; +using VRage.Utils; +using VRageMath; +using VRageRender; + +namespace SeamlessClientPlugin.SeamlessTransfer +{ + public class SwitchServers + { + public MyGameServerItem TargetServer { get; } + public MyObjectBuilder_World TargetWorld { get; } + + + public SwitchServers(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld) + { + this.TargetServer = TargetServer; + this.TargetWorld = TargetWorld; + } + + + public void BeginSwitch() + { + MySandboxGame.Static.Invoke(delegate + { + //Set camera controller to fixed spectator + MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed); + UnloadCurrentServer(); + SetNewMultiplayerClient(); + + + + }, "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 = Patches.TransportLayerConstructor.Invoke(new object[] { 2 }); + var SyncInstance = Patches.SyncLayerConstructor.Invoke(new object[] { LayerInstance }); + var instance = Patches.ClientConstructor.Invoke(new object[] { TargetServer, SyncInstance }); + + + MyMultiplayer.Static = Utility.CastToReflected(instance, Patches.ClientType); + MyMultiplayer.Static.ExperimentalMode = true; + + SeamlessClient.TryShow("Successfully set MyMultiplayer.Static"); + + MyHud.Chat.RegisterChat(MyMultiplayer.Static); + Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName); + Sync.Players.RegisterEvents(); + } + + + + private void OnJoinEvent(object sender, VRage.Network.JoinResultMsg e) + { + ForceClientConnection(); + + // Un-register the event + Patches.OnJoinEvent -= OnJoinEvent; + } + + + + private void ForceClientConnection() + { + // Set the new SyncLayer to the MySession.Static.SyncLayer + Patches.MySessionLayer.SetValue(MySession.Static, MyMultiplayer.Static.SyncLayer); + + LoadConnectedClients(); + LoadOnlinePlayers(); + SetWorldSettings(); + RemoveOldEntities(); + StartEntitySync(); + + + // Allow the game to start proccessing incoming messages in the buffer + MyMultiplayer.Static.StartProcessingClientMessages(); + } + + + 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.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(); + Dictionary AdminSettingsList = (Dictionary)Patches.RemoteAdminSettings.GetValue(MySession.Static); + AdminSettingsList.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), contentFolder: false, createIfNotExists: false); + MySession.Static.WorldBoundaries = TargetWorld.Checkpoint.WorldBoundaries; + MySession.Static.InGameTime = MyObjectBuilder_Checkpoint.DEFAULT_DATE; + MySession.Static.ElapsedGameTime = new TimeSpan(TargetWorld.Checkpoint.ElapsedGameTime); + + + + 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; + + + MySession.Static.Gpss = new MyGpsCollection(); + MySession.Static.Gpss.LoadGpss(TargetWorld.Checkpoint); + 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 (KeyValuePair item in TargetWorld.Checkpoint.AllPlayersData.Dictionary) + { + ulong clientId = item.Key.GetClientId(); + AdminSettingsEnum 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; + } + + + AdminSettingsList[clientId] = adminSettingsEnum; + if (!Sync.IsDedicated && clientId == Sync.MyId) + { + Patches.AdminSettings.SetValue(MySession.Static, adminSettingsEnum); + + //m_adminSettings = adminSettingsEnum; + } + + + + if (!MySession.Static.PromotedUsers.TryGetValue(clientId, out var value2)) + { + value2 = 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() + { + //Re-Initilize Virtual clients + object VirtualClientsValue = Patches.VirtualClients.GetValue(MySession.Static); + Patches.InitVirtualClients.Invoke(VirtualClientsValue, null); + + SeamlessClient.TryShow("Loading exsisting Members From World!"); + foreach (var Client in TargetWorld.Checkpoint.Clients) + { + SeamlessClient.TryShow("Adding New Client: " + Client.Name); + Sync.Clients.AddClient(Client.SteamId, Client.Name); + } + } + + private void StartEntitySync() + { + SeamlessClient.TryShow("Requesting Player From Server"); + Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, realPlayer: true, initialPlayer: true); + if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Sandbox.Engine.Platform.Game.IsDedicated) + { + MyLog.Default.WriteLine("ControlledObject was null, respawning character"); + //m_cameraAwaitingEntity = true; + MyPlayerCollection.RequestLocalRespawn(); + } + + //typeof(MyGuiScreenTerminal).GetMethod("CreateTabs") + MyMultiplayer.Static.OnSessionReady(); + 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!"); + + MyMultiplayer.Static.OnSessionReady(); + //Recreate all controls... Will fix weird gui/paint/crap + MyGuiScreenHudSpace.Static.RecreateControls(true); + } + + + + + + + private void UnloadCurrentServer() + { + //Unload current session on game thread + if (MyMultiplayer.Static == null) + throw new Exception("MyMultiplayer.Static is null on unloading? dafuq?"); + + + //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; + } + + private void RemoveOldEntities() + { + foreach (var ent in MyEntities.GetEntities()) + { + if (ent is MyPlanet) + continue; + + ent.Close(); + } + } + + } +} diff --git a/SeamlessTransfer/Transfer.cs b/SeamlessTransfer/Transfer.cs deleted file mode 100644 index 2b617a5..0000000 --- a/SeamlessTransfer/Transfer.cs +++ /dev/null @@ -1,184 +0,0 @@ -using ProtoBuf; -using Sandbox; -using Sandbox.Engine.Multiplayer; -using Sandbox.Engine.Networking; -using Sandbox.Game.Entities; -using Sandbox.Game.Gui; -using Sandbox.Game.Multiplayer; -using Sandbox.Game.World; -using Sandbox.Graphics.GUI; -using Sandbox.ModAPI; -using SeamlessClientPlugin.ClientMessages; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Net; -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading.Tasks; -using VRage; -using VRage.Game; -using VRage.GameServices; -using VRage.Network; -using VRage.Steam; -using VRage.Utils; -using VRageMath; - -namespace SeamlessClientPlugin.SeamlessTransfer -{ - - [ProtoContract] - public class Transfer - { - [ProtoMember(1)] - public ulong TargetServerID; - [ProtoMember(2)] - public string IPAdress; - [ProtoMember(6)] - public WorldRequest WorldRequest; - [ProtoMember(7)] - public string PlayerName; - - [ProtoMember(8)] - public List PlayerGPSCoords; - - [ProtoMember(9)] - public MyObjectBuilder_Toolbar PlayerToolbar; - - public List PlayerBuildSlots; - - public Transfer(ulong ServerID, string IPAdress) - { - /* This is only called serverside - */ - - this.IPAdress = IPAdress; - TargetServerID = ServerID; - } - - public Transfer() { } - - - - public void PingServerAndBeginRedirect() - { - if (TargetServerID == 0) - { - SeamlessClient.TryShow("This is not a valid server!"); - return; - } - - SeamlessClient.TryShow("SyncMyID: " + Sync.MyId.ToString()); - SeamlessClient.TryShow("Beginning Redirect to server: " + TargetServerID); - MyGameService.OnPingServerResponded += MyGameService_OnPingServerResponded; - MyGameService.OnPingServerFailedToRespond += MyGameService_OnPingServerFailedToRespond; - - MyGameService.PingServer(IPAdress); - } - - private void MyGameService_OnPingServerFailedToRespond(object sender, EventArgs e) - { - MyGameService.OnPingServerResponded -= MyGameService_OnPingServerResponded; - MyGameService.OnPingServerFailedToRespond -= MyGameService_OnPingServerFailedToRespond; - SeamlessClient.TryShow("ServerPing failed!"); - - } - - private void MyGameService_OnPingServerResponded(object sender, MyGameServerItem e) - { - - - MyGameService.OnPingServerResponded -= MyGameService_OnPingServerResponded; - MyGameService.OnPingServerFailedToRespond -= MyGameService_OnPingServerFailedToRespond; - SeamlessClient.TryShow("ServerPing Successful! Attempting to connect to lobby: " + e.GameID); - - - LoadServer.LoadWorldData(e, WorldRequest.DeserializeWorldData()); - - - MySandboxGame.Static.Invoke(delegate - { - StringBuilder Builder = new StringBuilder(); - Builder.AppendLine("Please be patient! Some users can spend a minute switching servers... others may be able to swtich faster. Lots of factors to consider."); - Builder.AppendLine(); - Builder.AppendLine("Sitting in spectator is perfectly normal for a few seconds!"); - - MyAPIGateway.Utilities.ShowMissionScreen("Switching Servers!",null, null, Builder.ToString(), null, "Ok!"); - //MySessionLoader.UnloadAndExitToMenu(); - UnloadCurrentServer(); - - //MyJoinGameHelper.JoinGame(e, true); - LoadServer.ResetMPClient(); - - ClearEntities(); - //ReloadPatch.SeamlessSwitch = false; - }, "SeamlessClient"); - return; - - - - - } - - - - - - private void UnloadCurrentServer() - { - if (MyMultiplayer.Static != null) - { - Sync.Clients.Clear(); - Sync.Players.ClearPlayers(); - - MyHud.Chat.UnregisterChat(MyMultiplayer.Static); - //OnPlayerCreated - //OnConnectedClient - - try - { - //Could throw error when there are no gps points - var PCollection = MySession.Static.Gpss[MySession.Static.LocalPlayerId]; - PCollection?.Clear(); - SeamlessClient.TryShow(PCollection.Count + "registered GPS points"); - } - catch(Exception ex) - { - SeamlessClient.TryShow(ex.ToString()); - } - - - MyHud.GpsMarkers.Clear(); - - - MyMultiplayer.Static.ReplicationLayer.Disconnect(); - MyMultiplayer.Static.ReplicationLayer.Dispose(); - - MyMultiplayer.Static.Dispose(); - MyMultiplayer.Static = null; - - //Sync.Clients.Clear(); - - - - - - // MyGuiSandbox.UnloadContent(); - } - } - - - private void ClearEntities() - { - foreach (var ent in MyEntities.GetEntities()) - { - if (ent is MyPlanet) - continue; - - ent.Close(); - } - } - - } -} diff --git a/Utilities/Utility.cs b/Utilities/Utility.cs index 9c1b9e7..c79a751 100644 --- a/Utilities/Utility.cs +++ b/Utilities/Utility.cs @@ -25,6 +25,7 @@ namespace SeamlessClientPlugin.Utilities } } + public static T Deserialize(byte[] data) { if (data == null) @@ -36,7 +37,6 @@ namespace SeamlessClientPlugin.Utilities } } - public static dynamic CastToReflected(this object o, Type type) { return Convert.ChangeType(o, type);