7 Commits

Author SHA1 Message Date
Garrett
3d0d9160e2 Seamless Version 2024-12-02 21:19:20 -06:00
Garrett
b19ff123d7 mod reloader 2024-12-01 16:53:59 -06:00
Garrett
0fc7fee73f Some changes to support mod reloading. (ModAPI messages are still not being recieved properly in nexus mod) this could be a serverside issue but unknown currently 2024-11-09 23:38:17 -06:00
Garrett
f631d2b9ff Its working... but at what cost? 2024-10-29 14:01:58 -05:00
Garrett
af95b422d5 commit 2024-10-29 10:45:54 -05:00
Garrett
7b4a2aa205 Works with just character 2024-10-05 17:25:11 -05:00
Garrett
f62fc4e32b Revert 2024-09-10 12:47:54 -05:00
15 changed files with 963 additions and 1378 deletions

140
Components/ModReloader.cs Normal file
View File

@@ -0,0 +1,140 @@
using Sandbox.Game.World;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Collections;
using VRage.Game.Components;
using VRage.Game;
using static Sandbox.Game.World.MySession;
using System.IO;
using VRage.Scripting;
using Microsoft.CodeAnalysis.CSharp;
using System.Collections.Immutable;
using Microsoft.CodeAnalysis;
using HarmonyLib;
using SeamlessClient.Models;
using VRage;
namespace SeamlessClient.Components
{
/* Keen compiles mods into assemblies. Unfortunetly, we cannot trust these assemblies after calling unload due to changed variables as the ASSEMBLY level. aka default types
* To fix this we need to go further and actually attempt to call the assembly to be unloaded, then reload the assembly and get the update type to be initiated.
*
* This might be a time consuming process? We will just have to see. Ideally would like to keep the whole mod script reloading down to like less than a second or two for N amount of mods. Might
* be able to speed up loading compared to keens way too
*
*
*/
public class ModReloader
{
Dictionary<Type, MyModContext> sessionsBeingRemoved = new Dictionary<Type, MyModContext>();
private static ModCache modCache = new ModCache();
public void Patch(Harmony patcher)
{
MethodInfo assemblyLoad = AccessTools.Method(typeof(Assembly), "Load", new Type[] { typeof(byte[]) });
var patchAsmLoad = PatchUtils.GetMethod(this.GetType(), "AssemblyLoad");
patcher.Patch(assemblyLoad, postfix: patchAsmLoad);
}
private static void AssemblyLoad(Assembly __result, byte[] rawAssembly)
{
//This will get all of the mods being loading into the game. Worry about saving/loading later
modCache.AddModToCache(__result, rawAssembly);
}
public void UnloadModSessionComponents()
{
CachingDictionary<Type, MySessionComponentBase> sessionComponents = (CachingDictionary<Type, MySessionComponentBase>)typeof(MySession).GetField("m_sessionComponents", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static);
List<MySessionComponentBase> m_loadOrder = (List<MySessionComponentBase>)typeof(MySession).GetField("m_loadOrder", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static);
List<MySessionComponentBase> m_sessionComponentForDraw = (List<MySessionComponentBase>)typeof(MySession).GetField("m_sessionComponentForDraw", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static);
Dictionary<int, SortedSet<MySessionComponentBase>> m_sessionComponentsForUpdate = (Dictionary<int, SortedSet<MySessionComponentBase>>)typeof(MySession).GetField("m_sessionComponentsForUpdate", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static);
foreach (var component in sessionComponents)
{
if (component.Value.ModContext != null && !component.Value.ModContext.IsBaseGame)
{
Seamless.TryShow($"{component.Key.FullName}");
sessionsBeingRemoved.Add(component.Key, component.Value.ModContext as MyModContext);
//Calls the component to be unloaded
component.Value.UnloadDataConditional();
m_loadOrder.Remove(component.Value);
}
}
/* Remove all */
//Remove from session components
foreach (var item in sessionsBeingRemoved)
sessionComponents.Remove(item.Key, true);
//Remove from draw
m_sessionComponentForDraw.RemoveAll(x => x.ModContext != null && !x.ModContext.IsBaseGame);
//Remove from update
foreach (var kvp in m_sessionComponentsForUpdate)
{
kvp.Value.RemoveWhere(x => sessionsBeingRemoved.ContainsKey(x.ComponentType));
}
}
public void AddModComponents()
{
List<MySessionComponentBase> m_sessionComponentForDraw = (List<MySessionComponentBase>)typeof(MySession).GetField("m_sessionComponentForDraw", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(MySession.Static);
List<MySessionComponentBase> newSessions = new List<MySessionComponentBase>();
int newLoadedmods = 0;
foreach (var item in sessionsBeingRemoved)
{
//If it fails, skip the mod? Or try to use old type? (May Fail to load)
if (!modCache.TryGetModAssembly(item.Value.ModItem.PublishedFileId, out Assembly newModAssembly))
continue;
Type newType = newModAssembly.GetType(item.Key.FullName);
MySessionComponentBase mySessionComponentBase = (MySessionComponentBase)Activator.CreateInstance(newType);
mySessionComponentBase.ModContext = item.Value;
MySession.Static.RegisterComponent(mySessionComponentBase, mySessionComponentBase.UpdateOrder, mySessionComponentBase.Priority);
newSessions.Add(mySessionComponentBase);
m_sessionComponentForDraw.Add(mySessionComponentBase);
newLoadedmods++;
}
//Will check toi see if session is actually loaded before calling load
MySession.Static.LoadDataComponents();
sessionsBeingRemoved.Clear();
typeof(MySession).GetMethod("InitDataComponents", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(MySession.Static, null);
//Call before start
foreach (var session in newSessions)
session.BeforeStart();
Seamless.TryShow($"Loaded {newLoadedmods} mods");
}
}
}

View File

@@ -16,8 +16,8 @@ namespace SeamlessClient.OnlinePlayersWindow
public class PlayersWindowComponent : ComponentBase
{
public static List<OnlineClientServer> allServers = new List<OnlineClientServer>();
public static OnlineClientServer onlineServer;
//public static List<OnlineClientServer> allServers = new List<OnlineClientServer>();
//public static OnlineClientServer onlineServer;
public override void Patch(Harmony patcher)
@@ -38,6 +38,7 @@ namespace SeamlessClient.OnlinePlayersWindow
}
}
/*
public static void ApplyRecievedPlayers(List<OnlineClientServer> servers, int CurrentServer)
{
//Seamless.TryShow($"Recieved {CurrentServer} - {servers.Count}");
@@ -56,6 +57,7 @@ namespace SeamlessClient.OnlinePlayersWindow
allServers.Add(server);
}
}
*/

View File

@@ -0,0 +1,65 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game;
using VRage.Utils;
namespace SeamlessClient.Components
{
internal class SeamlessScriptManager
{
/* Sandbox.Game.World.MyScriptManager
*
* ScriptManager handles all mod script compiling and loading. Stores all compilations as various dictionaries like below:
* Looks like TryAddEntityScripts(,) can be called whenever to pass in the mod context and the compiled assembly on the fly
*
* Possibly to dynamically load/unload mods?
*/
/* Dictionary<MyModContext, HashSet<MyStringId>> ScriptsPerMod ->
*
* Use in Sandbox.Game.World.MySession.RegisterComponentsFromAssembly
* Primarily MySessionComponentDescriptor/Session Component
*/
/* Dictionary<MyStringId, Assembly> Scripts ->
*
* Use in Sandbox.Game.World.MySession.RegisterComponentsFromAssembly
* Stores actual assemblies for all modded scripts
*/
/* Dictionary<Type, HashSet<Type>> EntityScripts ->
*
* Use in Sandbox.Game.Entities.MyEntityFactory.AddScriptGameLogic
* List of Type entity Scripts. Activator.CreatInstance called on entity creation
*/
/* Dictionary<Tuple<Type, string>, HashSet<Type>> SubEntityScripts ->
*
* Use in Sandbox.Game.Entities.MyEntityFactory.AddScriptGameLogic
* List of Type && SubType entity Scripts. Activator.CreatInstance called on entity creation
*/
/* Dictionary<string, Type> StatScripts ->
*
* Use in Sandbox.Game.Components.MyEntityStatComponent
* No idea what this is but prob need to deal with it
*/
/* Dictionary<MyStringId, Type> InGameScripts -> */
/* Dictionary<MyStringId, StringBuilder> InGameScriptsCode -> */
/* Dictionary<Type, MyModContext> TypeToModMap ->
*
* Use in Sandbox.Game.Entities.MyEntityFactory.AddScriptGameLogic
* Some sort of type to mod logic component?
*/
}
}

View File

@@ -0,0 +1,525 @@
using HarmonyLib;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Sandbox.Game.Entities.Character;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.SessionComponents;
using Sandbox.Game.World;
using Sandbox.Graphics.GUI;
using SeamlessClient.Utilities;
using SpaceEngineers.Game.GUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Game;
using VRage.GameServices;
using VRage.Library.Utils;
using VRage.Network;
using VRageRender;
using EmptyKeys.UserInterface.Generated.StoreBlockView_Bindings;
using System.Security.Cryptography;
using VRage.Groups;
using VRage.Game.ModAPI;
using VRage;
using static Sandbox.ModAPI.MyModAPIHelper;
using NLog.Targets;
using MyMultiplayer = Sandbox.Engine.Multiplayer.MyMultiplayer;
using static Sandbox.Engine.Networking.MyNetworkWriter;
using System.Collections.Concurrent;
using VRage.Game.Entity;
using VRageRender.Effects;
using VRage.Scripting;
using VRage.Utils;
using SpaceEngineers.Game.World;
using VRage.Collections;
using VRage.Game.Components;
using System.CodeDom;
using System.Diagnostics;
namespace SeamlessClient.Components
{
public class SeamlessSwitcher : ComponentBase
{
private static bool isSwitchingServer = false;
private PropertyInfo _MyGameServerItemProperty;
private PropertyInfo _MultiplayerServerID;
private static MethodInfo PauseClient;
private static MethodInfo _SendPlayerData;
private MethodInfo _SendFlush;
private MethodInfo _ClearTransportLayer;
private FieldInfo _NetworkWriteQueue;
private FieldInfo _TransportLayer;
public static SeamlessSwitcher Instance;
private static List<MyCubeGrid> allGrids = new List<MyCubeGrid>();
private long _OriginalCharacterEntity = -1;
private long _OriginalGridEntity = -1;
private static ulong TargetServerID;
private static bool PreventRPC = false;
private static bool StartPacketCheck = false;
private bool keepGrid = false;
private static ModReloader modReload;
private static Stopwatch switchTime = new Stopwatch();
public SeamlessSwitcher()
{
Instance = this;
modReload = new ModReloader();
}
public override void Patch(Harmony patcher)
{
_MyGameServerItemProperty = PatchUtils.GetProperty(PatchUtils.ClientType, "Server");
_MultiplayerServerID = PatchUtils.GetProperty(typeof(MyMultiplayerBase), "ServerId");
PauseClient = PatchUtils.GetMethod(PatchUtils.MyMultiplayerClientBase, "PauseClient");
_SendPlayerData = PatchUtils.GetMethod(PatchUtils.ClientType, "SendPlayerData");
_ClearTransportLayer = PatchUtils.GetMethod(PatchUtils.MyTransportLayerType, "Clear");
List<string> st = AccessTools.GetMethodNames(PatchUtils.MyMultiplayerClientBase);
var _SendRPC = PatchUtils.GetMethod(PatchUtils.MyMultiplayerClientBase, "VRage.Replication.IReplicationClientCallback.SendEvent");
var preSendRPC = PatchUtils.GetMethod(this.GetType(), "PreventEvents");
_SendFlush = PatchUtils.GetMethod(PatchUtils.MyTransportLayerType, "SendFlush");
_NetworkWriteQueue = PatchUtils.GetField(typeof(MyNetworkWriter), "m_packetsToSend");
_TransportLayer = PatchUtils.GetField(typeof(MySyncLayer), "TransportLayer");
var method = AccessTools.Method(PatchUtils.MyTransportLayerType, "SendMessage", new Type[] { typeof(MyMessageId), typeof(IPacketData), typeof(bool), typeof(EndpointId), typeof(byte) });
var patchSend = PatchUtils.GetMethod(this.GetType(), "SendMessage_Patch");
var OnUserJoin = AccessTools.Method(PatchUtils.ClientType, "OnUserJoined");
var LocalOnUserJoin = PatchUtils.GetMethod(this.GetType(), nameof(OnUserJoined_Patch));
patcher.Patch(method, prefix: patchSend);
patcher.Patch(_SendRPC, prefix: preSendRPC);
patcher.Patch(OnUserJoin, postfix: LocalOnUserJoin);
modReload.Patch(patcher);
base.Patch(patcher);
}
//This method is when we have fully joined the server. It is a patched method
private static void OnUserJoined_Patch(JoinResultMsg msg)
{
if (!isSwitchingServer)
return;
if (msg.JoinResult != JoinResult.OK)
return;
Seamless.TryShow($"OnUserJoin! Result: {msg.JoinResult}");
LoadDestinationServer();
isSwitchingServer = false;
switchTime.Stop();
Seamless.TryShow($"Total Seamless time: {switchTime.ElapsedMilliseconds}");
}
public static void SendMessage_Patch(MyMessageId id, IPacketData data, bool reliable, EndpointId endpoint, byte index = 0)
{
if (!StartPacketCheck)
return;
//MyLog.Default?.WriteLineAndConsole($"{System.Environment.StackTrace}");
//Seamless.TryShow($"Id:{id} -> {endpoint}");
}
public void StartSwitch(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld)
{
switchTime.Restart();
isSwitchingServer = true;
TargetServerID = TargetServer.GameID;
MyReplicationClient localMPClient = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
_OriginalCharacterEntity = MySession.Static.LocalCharacter?.EntityId ?? 0;
if (MySession.Static.LocalCharacter?.Parent != null && MySession.Static.LocalCharacter.Parent is MyCockpit cockpit)
{
_OriginalGridEntity = cockpit.CubeGrid.EntityId;
}
UnloadCurrentServer(localMPClient);
/* Fix any weird compatibilities */
MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(TargetWorld.Checkpoint, TargetWorld.Sector);
/* Set New Multiplayer Stuff */
_MyGameServerItemProperty.SetValue(MyMultiplayer.Static, TargetServer);
_MultiplayerServerID.SetValue(MyMultiplayer.Static, TargetServer.SteamID);
/* Connect To Server */
MyGameService.ConnectToServer(TargetServer, delegate (JoinResult joinResult)
{
MySandboxGame.Static.Invoke(delegate
{
Seamless.TryShow("Connected to server!");
//_OnConnectToServer.Invoke(MyMultiplayer.Static, new object[] { joinResult });
_SendPlayerData.Invoke(MyMultiplayer.Static, new object[] { MyGameService.OnlineName });
modReload.UnloadModSessionComponents();
}, "OnConnectToServer");
});
}
private void UnloadCurrentServer(MyReplicationClient localMPClient)
{
//Close and Cancel any screens (Medical or QuestLog)
MySessionComponentIngameHelp component = MySession.Static.GetComponent<MySessionComponentIngameHelp>();
component?.TryCancelObjective();
MyGuiScreenMedicals.Close();
MethodInfo removeClient = PatchUtils.GetMethod(PatchUtils.ClientType, "MyMultiplayerClient_ClientLeft");
foreach (var connectedClient in Sync.Clients.GetClients())
{
if (connectedClient.SteamUserId == Sync.MyId || connectedClient.SteamUserId == Sync.ServerId)
continue;
removeClient.Invoke(MyMultiplayer.Static, new object[] { connectedClient.SteamUserId, MyChatMemberStateChangeEnum.Left });
}
/* Sends disconnect message to outbound server */
MyControlDisconnectedMsg myControlDisconnectedMsg = default(MyControlDisconnectedMsg);
myControlDisconnectedMsg.Client = Sync.MyId;
typeof(MyMultiplayerBase).GetMethod("SendControlMessage", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(MyControlDisconnectedMsg)).Invoke(MyMultiplayer.Static, new object[] { Sync.ServerId, myControlDisconnectedMsg, true });
MyGameService.Peer2Peer.CloseSession(Sync.ServerId);
MyGameService.DisconnectFromServer();
MyGameService.ClearCache();
//Remove old signals
MyHud.GpsMarkers.Clear();
MyHud.LocationMarkers.Clear();
MyHud.HackingMarkers.Clear();
Seamless.TryShow($"2 Streaming: {localMPClient.HasPendingStreamingReplicables} - LastMessage: {localMPClient.LastMessageFromServer}");
Seamless.TryShow($"2 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
UnloadOldEntities();
ResetReplicationTime(false);
//MyMultiplayer.Static.ReplicationLayer.Disconnect();
//MyMultiplayer.Static.ReplicationLayer.Dispose();
//MyMultiplayer.Static.Dispose();
}
private static bool PreventEvents(IPacketData data, bool reliable)
{
return !PreventRPC;
}
private void UnloadOldEntities()
{
foreach (var ent in MyEntities.GetEntities().ToList())
{
if (ent is MyPlanet || ent is MyCubeGrid)
continue;
if((ent.EntityId == _OriginalCharacterEntity && keepGrid) && ent is MyCharacter character)
{
continue;
}
ent.Close();
}
List<IMyGridGroupData> grids = new List<IMyGridGroupData>();
foreach (var gridgroup in MyCubeGridGroups.GetGridGroups(VRage.Game.ModAPI.GridLinkTypeEnum.Physical, grids))
{
List<IMyCubeGrid> localGrids = new List<IMyCubeGrid>();
localGrids = gridgroup.GetGrids(localGrids);
if (localGrids.Count == 0)
continue;
if (localGrids.Any(x => x.EntityId == _OriginalGridEntity) && keepGrid)
{
foreach(var grid in localGrids)
{
MyEntity ent = grid as MyEntity;
allGrids.Add((MyCubeGrid)grid);
//grid.Synchronized = false;
//ent.SyncFlag = false;
//ent.Save = false;
}
continue;
}
//Delete
foreach (var grid in localGrids)
{
grid.Close();
}
}
//Following event doesnt clear networked replicables
MyMultiplayer.Static.ReplicationLayer.Dispose();
ClearClientReplicables();
PreventRPC = true;
StartPacketCheck = true;
//_ClearTransportLayer.Invoke(_TransportLayer.GetValue(MyMultiplayer.Static.SyncLayer), null);
}
private void ClearClientReplicables()
{
MyReplicationClient replicationClient = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
Dictionary<NetworkId,IMyNetObject> networkedobjs = (Dictionary<NetworkId, IMyNetObject>)typeof(MyReplicationLayer).GetField("m_networkIDToObject", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(replicationClient);
MethodInfo destroyreplicable = typeof(MyReplicationClient).GetMethod("ReplicableDestroy", BindingFlags.Instance | BindingFlags.NonPublic);
int i = 0;
foreach(var obj in networkedobjs)
{
destroyreplicable.Invoke(replicationClient, new object[] { obj.Value, true });
i++;
}
Seamless.TryShow($"Cleared {i} replicables");
}
private static void LoadDestinationServer()
{
MyReplicationClient clienta = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
Seamless.TryShow($"5 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
Seamless.TryShow($"5 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
Seamless.TryShow("Starting new MP Client!");
/* On Server Successfull Join
*
*
*/
List<ulong> clients = new List<ulong>();
foreach (var client in Sync.Clients.GetClients())
{
clients.Add(client.SteamUserId);
Seamless.TryShow($"ADDING {client.SteamUserId} - {Sync.MyId}");
}
foreach (var client in clients)
{
if (client == TargetServerID || client == Sync.MyId)
continue;
Seamless.TryShow($"REMOVING {client}");
Sync.Clients.RemoveClient(client);
}
typeof(MySandboxGame).GetField("m_pauseStackCount", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, 0);
Seamless.TryShow($"6 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
Seamless.TryShow($"6 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
modReload.AddModComponents();
SendClientReady();
ResetReplicationTime(true);
//MyPlayerCollection.ChangePlayerCharacter(MySession.Static.LocalHumanPlayer, MySession.Static.LocalCharacter, MySession.Static.LocalCharacter);
// Allow the game to start proccessing incoming messages in the buffer
//MyMultiplayer.Static.StartProcessingClientMessages();
//Send Client Ready
PreventRPC = false;
//_ClearTransportLayer.Invoke(_TransportLayer.GetValue(MyMultiplayer.Static.SyncLayer), null);
StartEntitySync();
PauseClient.Invoke(MyMultiplayer.Static, new object[] { false });
MySandboxGame.PausePop();
}
private static void SendClientReady()
{
MyReplicationClient clienta = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
ClientReadyDataMsg clientReadyDataMsg = default(ClientReadyDataMsg);
clientReadyDataMsg.ForcePlayoutDelayBuffer = MyFakes.ForcePlayoutDelayBuffer;
clientReadyDataMsg.UsePlayoutDelayBufferForCharacter = true;
clientReadyDataMsg.UsePlayoutDelayBufferForJetpack = true;
clientReadyDataMsg.UsePlayoutDelayBufferForGrids = true;
ClientReadyDataMsg msg = clientReadyDataMsg;
clienta.SendClientReady(ref msg);
Seamless.SendSeamlessVersion();
}
private static void StartEntitySync()
{
Seamless.TryShow("Requesting Player From Server");
//Request client state batch
(MyMultiplayer.Static as MyMultiplayerClientBase).RequestBatchConfirmation();
MyMultiplayer.Static.PendingReplicablesDone += Static_PendingReplicablesDone;
//typeof(MyGuiScreenTerminal).GetMethod("CreateTabs")
//MySession.Static.LocalHumanPlayer.Controller.TakeControl(originalControlledEntity);
MyGuiSandbox.UnloadContent();
MyGuiSandbox.LoadContent();
//MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.HUDScreen);
MyRenderProxy.RebuildCullingStructure();
MyRenderProxy.CollectGarbage();
Seamless.TryShow("OnlinePlayers: " + MySession.Static.Players.GetOnlinePlayers().Count);
Seamless.TryShow("Loading Complete!");
}
private static void Static_PendingReplicablesDone()
{
if (MySession.Static.VoxelMaps.Instances.Count > 0)
{
MySandboxGame.AreClipmapsReady = false;
}
MyMultiplayer.Static.PendingReplicablesDone -= Static_PendingReplicablesDone;
Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, true, true);
if (!Sandbox.Engine.Platform.Game.IsDedicated && MySession.Static.LocalHumanPlayer == null)
{
Seamless.TryShow("RequestNewPlayer");
}
else if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Sandbox.Engine.Platform.Game.IsDedicated)
{
Seamless.TryShow("ControlledObject was null, respawning character");
//m_cameraAwaitingEntity = true;
MyPlayerCollection.RequestLocalRespawn();
}
//Try find existing seat:
//bool found = (bool)typeof(MySpaceRespawnComponent).GetMethod("TryFindExistingCharacter", BindingFlags.Static | BindingFlags.NonPublic).Invoke(null, new object[] { MySession.Static.LocalHumanPlayer });
MyGuiScreenHudSpace.Static?.RecreateControls(true);
}
public static void ResetReplicationTime(bool ClientReady)
{
if (!ClientReady)
{
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTimestamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTimeStampReceivedTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_clientStartTimeStamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastClientTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastClientTimestamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastStateSyncPacketId").SetValue(MyMultiplayer.Static.ReplicationLayer,(byte)0);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastStreamingPacketId").SetValue(MyMultiplayer.Static.ReplicationLayer, (byte)0);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastStreamingPacketId").SetValue(MyMultiplayer.Static.ReplicationLayer, (byte)0);
//PatchUtils.GetField(typeof(MyReplicationClient), "m_acks").SetValue(MyMultiplayer.Static.ReplicationLayer, new List<byte>());
}
PatchUtils.GetField(typeof(MyReplicationClient), "m_clientReady").SetValue(MyMultiplayer.Static.ReplicationLayer, ClientReady);
}
}
}

View File

@@ -1,845 +0,0 @@
using HarmonyLib;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Sandbox.Engine.Utils;
using Sandbox.Game;
using Sandbox.Game.Entities;
using Sandbox.Game.Entities.Character;
using Sandbox.Game.Gui;
using Sandbox.Game.GUI;
using Sandbox.Game.GUI.HudViewers;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.Replication;
using Sandbox.Game.SessionComponents;
using Sandbox.Game.World;
using Sandbox.Game.World.Generator;
using Sandbox.Graphics.GUI;
using Sandbox.ModAPI;
using SeamlessClient.Components;
using SeamlessClient.OnlinePlayersWindow;
using SeamlessClient.Utilities;
using SpaceEngineers.Game.GUI;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using VRage;
using VRage.Game;
using VRage.Game.Entity;
using VRage.Game.ModAPI;
using VRage.GameServices;
using VRage.Library.Utils;
using VRage.Network;
using VRage.Utils;
using VRageRender;
using VRageRender.Messages;
namespace SeamlessClient.ServerSwitching
{
public class ServerSwitcherComponent : ComponentBase
{
public static ConstructorInfo ClientConstructor;
public static ConstructorInfo SyncLayerConstructor;
public static ConstructorInfo TransportLayerConstructor;
public static PropertyInfo MySessionLayer;
private static FieldInfo RemoteAdminSettings;
private static FieldInfo AdminSettings;
private static MethodInfo UnloadProceduralWorldGenerator;
private static MethodInfo GpsRegisterChat;
private static MethodInfo LoadMembersFromWorld;
private static MethodInfo InitVirtualClients;
private static MethodInfo CreateNewPlayerInternal;
private static MethodInfo PauseClient;
public static MethodInfo SendPlayerData;
private static FieldInfo VirtualClients;
public static ServerSwitcherComponent Instance { get; private set; }
private MyGameServerItem TargetServer { get; set; }
private MyObjectBuilder_World TargetWorld { get; set; }
private string OldArmorSkin { get; set; } = string.Empty;
private bool needsEntityUnload = true;
public static event EventHandler<JoinResultMsg> OnJoinEvent;
private MyCharacter originalLocalCharacter;
private IMyControllableEntity originalControlledEntity;
private static bool isSwitch = false;
private MyObjectBuilder_Player player { get; set; }
private static Timer pauseResetTimer = new Timer(1000);
public ServerSwitcherComponent()
{
pauseResetTimer.Elapsed += PauseResetTimer_Elapsed;
Instance = this;
}
private void PauseResetTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if(MySandboxGame.IsPaused)
{
Seamless.TryShow("Game is still paused... Attempting to unpause!");
MySandboxGame.PausePop();
}
else
{
pauseResetTimer.Stop();
}
}
public override void Patch(Harmony patcher)
{
MySessionLayer = PatchUtils.GetProperty(typeof(MySession), "SyncLayer");
PauseClient = PatchUtils.GetMethod(PatchUtils.MyMultiplayerClientBase, "PauseClient");
ClientConstructor = PatchUtils.GetConstructor(PatchUtils.ClientType, new[] { typeof(MyGameServerItem), PatchUtils.SyncLayerType });
SyncLayerConstructor = PatchUtils.GetConstructor(PatchUtils.SyncLayerType, new[] { PatchUtils.MyTransportLayerType });
TransportLayerConstructor = PatchUtils.GetConstructor(PatchUtils.MyTransportLayerType, new[] { typeof(int) });
RemoteAdminSettings = PatchUtils.GetField(typeof(MySession), "m_remoteAdminSettings");
AdminSettings = PatchUtils.GetField(typeof(MySession), "m_adminSettings");
VirtualClients = PatchUtils.GetField(typeof(MySession), "VirtualClients");
UnloadProceduralWorldGenerator = PatchUtils.GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData");
GpsRegisterChat = PatchUtils.GetMethod(typeof(MyGpsCollection), "RegisterChat");
LoadMembersFromWorld = PatchUtils.GetMethod(typeof(MySession), "LoadMembersFromWorld");
InitVirtualClients = PatchUtils.GetMethod(PatchUtils.VirtualClientsType, "Init");
SendPlayerData = PatchUtils.GetMethod(PatchUtils.ClientType, "SendPlayerData");
CreateNewPlayerInternal = PatchUtils.GetMethod(typeof(MyPlayerCollection), "CreateNewPlayerInternal");
MethodInfo onAllmembersData = PatchUtils.ClientType.GetMethod("OnAllMembersData", BindingFlags.Instance | BindingFlags.NonPublic);
var onClientRemoved = PatchUtils.GetMethod(typeof(MyClientCollection), "RemoveClient");
var onDisconnectedClient = PatchUtils.GetMethod(typeof(MyMultiplayerBase), "OnDisconnectedClient");
var onClientConnected = PatchUtils.GetMethod(PatchUtils.ClientType, "OnClientConnected");
var processAllMembersData = PatchUtils.GetMethod(typeof(MyMultiplayerBase), "ProcessAllMembersData");
var RemovePlayer = PatchUtils.GetMethod(typeof(MyPlayerCollection), "RemovePlayerFromDictionary");
var LoadClient = PatchUtils.GetMethod(PatchUtils.ClientType, "LoadMembersFromWorld");
Seamless.TryShow("Patched!");
patcher.Patch(LoadClient, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(LoadClientsFromWorld))));
patcher.Patch(RemovePlayer, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(RemovePlayerFromDict))));
patcher.Patch(processAllMembersData, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(ProcessAllMembersData))));
patcher.Patch(onDisconnectedClient, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(OnDisconnectedClient))));
patcher.Patch(onClientRemoved, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(RemoveClient))));
patcher.Patch(onAllmembersData, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(OnAllMembersData))));
patcher.Patch(onClientConnected, prefix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(OnClientConnected))));
base.Patch(patcher);
}
public static void OnUserJoined(JoinResult msg)
{
Seamless.TryShow($"OnUserJoin! Result: {msg}");
if (msg == JoinResult.OK)
{
//SendPlayerData
//Invoke the switch event
if (isSwitch)
Instance.StartSwitch();
if (MySandboxGame.IsPaused)
{
pauseResetTimer.Start();
MyHud.Notifications.Remove(MyNotificationSingletons.ConnectionProblem);
MySandboxGame.PausePop();
}
SendPlayerData.Invoke(MyMultiplayer.Static, new object[] { MyGameService.OnlineName });
isSwitch = false;
}
}
public static bool LoadClientsFromWorld(ref List<MyObjectBuilder_Client> clients)
{
if(!isSwitch || clients == null || clients.Count == 0)
return true;
//Dictionary<ulong, MyConnectedClientData>
IDictionary m_memberData = (IDictionary)PatchUtils.GetField(PatchUtils.ClientType, "m_memberData").GetValue(MyMultiplayer.Static);
Seamless.TryShow($"{m_memberData.Count} members from clients");
var keys = m_memberData.Keys.Cast<ulong>();
for(int i = clients.Count - 1; i >= 0; i-- )
{
Seamless.TryShow($"Client {clients[i].SteamId}");
if (keys.Contains(clients[i].SteamId))
{
Seamless.TryShow($"Remove {clients[i].SteamId}");
clients.RemoveAt(i);
}
}
return false;
}
private static bool ProcessAllMembersData(ref AllMembersDataMsg msg)
{
if(!isSwitch)
return true;
Sync.Players.ClearIdentities();
if (msg.Identities != null)
{
Sync.Players.LoadIdentities(msg.Identities);
}
Seamless.TryShow($"Clearing Players! \n {Environment.StackTrace.ToString()} ");
//Sync.Players.ClearPlayers();
if (msg.Players != null)
{
Sync.Players.LoadPlayers(msg.Players);
}
//MySession.Static.Factions.LoadFactions(msg.Factions);
return false;
}
private static bool RemovePlayerFromDict(MyPlayer.PlayerId playerId)
{
//Seamless.TryShow($"Removing player {playerId.SteamId} from dictionariy! \n {Environment.StackTrace.ToString()} - Sender: {MyEventContext.Current.Sender}");
return true;
}
public override void Initilized()
{
base.Initilized();
}
public static bool OnClientConnected(MyPacket packet)
{
Seamless.TryShow("OnClientConnected");
return true;
}
public static void RemoveClient(ulong steamId)
{
if (steamId != Sync.MyId)
return;
//Seamless.TryShow(Environment.StackTrace.ToString());
}
public static void OnAllMembersData(ref AllMembersDataMsg msg)
{
Seamless.TryShow("Recieved all members data!");
}
public static bool OnDisconnectedClient(ref MyControlDisconnectedMsg data, ulong sender)
{
Seamless.TryShow($"OnDisconnectedClient {data.Client} - Sender {sender}");
if (data.Client == Sync.MyId)
return false;
return true;
}
public void StartBackendSwitch(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld)
{
this.TargetServer = TargetServer;
this.TargetWorld = TargetWorld;
OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin;
Seamless.TryShow($"1 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
if (Seamless.NexusVersion.Major >= 2)
needsEntityUnload = false;
isSwitch = true;
originalLocalCharacter = MySession.Static.LocalCharacter;
//originalControlledEntity = MySession.Static.ControlledEntity;
player = MySession.Static.LocalHumanPlayer.GetObjectBuilder();
player.Connected = false;
AsyncInvoke.InvokeAsync(() =>
{
Seamless.TryShow($"Needs entity Unload: {needsEntityUnload}");
if (needsEntityUnload)
MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed);
try
{
UnloadServer();
SetNewMultiplayerClient();
}catch(Exception ex)
{
Seamless.TryShow(ex.ToString());
}
});
}
public void ResetReplicationTime(bool ClientReady)
{
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTimestamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTimeStampReceivedTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_clientStartTimeStamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastClientTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastServerTime").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_lastClientTimestamp").SetValue(MyMultiplayer.Static.ReplicationLayer, MyTimeSpan.Zero);
PatchUtils.GetField(typeof(MyReplicationClient), "m_clientReady").SetValue(MyMultiplayer.Static.ReplicationLayer, ClientReady);
}
private void UnloadServer()
{
if (MyMultiplayer.Static == null)
throw new Exception("MyMultiplayer.Static is null on unloading? dafuq?");
UnloadOldEntities();
//Close and Cancel any screens (Medical or QuestLog)
MySessionComponentIngameHelp component = MySession.Static.GetComponent<MySessionComponentIngameHelp>();
component?.TryCancelObjective();
MyGuiScreenMedicals.Close();
//Clear all old players and clients.
//Sync.Clients.Clear();
//Sync.Players.ClearPlayers();
//Unregister Chat
MyHud.Chat.UnregisterChat(MyMultiplayer.Static);
MethodInfo removeClient = PatchUtils.GetMethod(PatchUtils.ClientType, "MyMultiplayerClient_ClientLeft");
foreach (var connectedClient in Sync.Clients.GetClients())
{
if (connectedClient.SteamUserId == Sync.MyId || connectedClient.SteamUserId == Sync.ServerId)
continue;
removeClient.Invoke(MyMultiplayer.Static, new object[] { connectedClient.SteamUserId, MyChatMemberStateChangeEnum.Left });
}
//Clear all local GPS Points
MyReplicationClient client = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
client.Dispose();
//client.Disconnect();
MyGameService.Peer2Peer.CloseSession(Sync.ServerId);
MyGameService.DisconnectFromServer();
MyControlDisconnectedMsg myControlDisconnectedMsg = default(MyControlDisconnectedMsg);
myControlDisconnectedMsg.Client = Sync.MyId;
typeof(MyMultiplayerBase).GetMethod("SendControlMessage", BindingFlags.Instance | BindingFlags.NonPublic).MakeGenericMethod(typeof(MyControlDisconnectedMsg)).Invoke(MyMultiplayer.Static, new object[] { Sync.ServerId, myControlDisconnectedMsg, true });
//DisconnectReplication
//MyMultiplayer.Static.ReplicationLayer.Disconnect();
//MyMultiplayer.Static.ReplicationLayer.Dispose();
//MyMultiplayer.Static.Dispose();
//MyMultiplayer.Static = null;
MyReplicationClient clienta = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
ResetReplicationTime(false);
//Remove old signals
MyHud.GpsMarkers.Clear();
MyHud.LocationMarkers.Clear();
MyHud.HackingMarkers.Clear();
Seamless.TryShow($"2 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
Seamless.TryShow($"2 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
//MyMultiplayer.Static.ReplicationLayer.Disconnect();
//MyMultiplayer.Static.ReplicationLayer.Dispose();
//MyMultiplayer.Static.Dispose();
}
private void UnloadOldEntities()
{
foreach (var ent in MyEntities.GetEntities())
{
if (ent is MyPlanet)
continue;
if (ent is MyCharacter)
continue;
ent.Close();
if (needsEntityUnload)
{
ent.Close();
}
else
{
//
}
}
}
private void SetNewMultiplayerClient()
{
MyReplicationClient clienta = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
Seamless.TryShow($"3 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
Seamless.TryShow($"3 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(TargetWorld.Checkpoint, TargetWorld.Sector);
PatchUtils.ClientType.GetProperty("Server", BindingFlags.Public | BindingFlags.Instance).SetValue(MyMultiplayer.Static, TargetServer);
typeof(MyMultiplayerBase).GetProperty("ServerId", BindingFlags.Public | BindingFlags.Instance).SetValue(MyMultiplayer.Static, TargetServer.SteamID);
MyGameService.ConnectToServer(TargetServer, delegate (JoinResult joinResult)
{
MySandboxGame.Static.Invoke(delegate
{
Seamless.TryShow("Connected to server!");
PatchUtils.ClientType.GetMethod("OnConnectToServer", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(MyMultiplayer.Static, new object[] { joinResult });
OnUserJoined(joinResult);
}, "OnConnectToServer");
});
Seamless.TryShow($"4 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
Seamless.TryShow($"4 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
return;
// Create constructors
var LayerInstance = TransportLayerConstructor.Invoke(new object[] { 2 });
var SyncInstance = SyncLayerConstructor.Invoke(new object[] { LayerInstance });
var instance = ClientConstructor.Invoke(new object[] { TargetServer, SyncInstance });
MyMultiplayer.Static = UtilExtensions.CastToReflected(instance, PatchUtils.ClientType);
MyMultiplayer.Static.ExperimentalMode = true;
// Set the new SyncLayer to the MySession.Static.SyncLayer
MySessionLayer.SetValue(MySession.Static, MyMultiplayer.Static.SyncLayer);
Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName);
return;
Seamless.TryShow("Successfully set MyMultiplayer.Static");
Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName);
Sync.Players.RegisterEvents();
return;
}
private void StartSwitch()
{
MyReplicationClient clienta = (MyReplicationClient)MyMultiplayer.Static.ReplicationLayer;
Seamless.TryShow($"5 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
Seamless.TryShow($"5 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
Seamless.TryShow("Starting new MP Client!");
/* On Server Successfull Join
*
*
*/
List<ulong> clients = new List<ulong>();
foreach(var client in Sync.Clients.GetClients())
{
clients.Add(client.SteamUserId);
Seamless.TryShow($"ADDING {client.SteamUserId} - {Sync.MyId}");
}
foreach(var client in clients)
{
if (client == TargetServer.SteamID || client == Sync.MyId)
continue;
Seamless.TryShow($"REMOVING {client}");
Sync.Clients.RemoveClient(client);
}
LoadConnectedClients();
//Sync.Clients.SetLocalSteamId(Sync.MyId, !Sync.Clients.HasClient(Sync.MyId), MyGameService.UserName);
Seamless.TryShow("Applying World Settings...");
SetWorldSettings();
Seamless.TryShow("Starting Components...");
InitComponents();
Seamless.TryShow("Starting Entity Sync...");
StartEntitySync();
//MyGuiSandbox.RemoveScreen(MyGuiScreenHudSpace.Static);
typeof(MySandboxGame).GetField("m_pauseStackCount", BindingFlags.Static | BindingFlags.NonPublic).SetValue(null, 0);
MyHud.Chat.RegisterChat(MyMultiplayer.Static);
//GpsRegisterChat.Invoke(MySession.Static.Gpss, new object[] { MyMultiplayer.Static });
//Recreate all controls... Will fix weird gui/paint/crap
//MyGuiScreenHudSpace.Static?.RecreateControls(true);
Seamless.TryShow($"6 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
Seamless.TryShow($"6 Streaming: {clienta.HasPendingStreamingReplicables} - LastMessage: {clienta.LastMessageFromServer}");
originalLocalCharacter?.Close();
ResetReplicationTime(true);
// Allow the game to start proccessing incoming messages in the buffer
MyMultiplayer.Static.StartProcessingClientMessages();
//Send Client Ready
ClientReadyDataMsg clientReadyDataMsg = default(ClientReadyDataMsg);
clientReadyDataMsg.ForcePlayoutDelayBuffer = MyFakes.ForcePlayoutDelayBuffer;
clientReadyDataMsg.UsePlayoutDelayBufferForCharacter = true;
clientReadyDataMsg.UsePlayoutDelayBufferForJetpack = true;
clientReadyDataMsg.UsePlayoutDelayBufferForGrids = true;
ClientReadyDataMsg msg = clientReadyDataMsg;
clienta.SendClientReady(ref msg);
Seamless.SendSeamlessVersion();
FieldInfo hudPoints = typeof(MyHudMarkerRender).GetField("m_pointsOfInterest", BindingFlags.Instance | BindingFlags.NonPublic);
IList points = (IList)hudPoints.GetValue(MyGuiScreenHudSpace.Static.MarkerRender);
MySandboxGame.PausePop();
//Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, true, true);
PauseClient.Invoke(MyMultiplayer.Static, new object[] { false });
MySandboxGame.PausePop();
MyHud.Notifications.Remove(MyNotificationSingletons.ConnectionProblem);
}
private void SetWorldSettings()
{
//Clear old list
MySession.Static.PromotedUsers.Clear();
MySession.Static.CreativeTools.Clear();
Dictionary<ulong, AdminSettingsEnum> AdminSettingsList = (Dictionary<ulong, AdminSettingsEnum>)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.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();
}
catch (Exception ex)
{
Seamless.TryShow($"An error occured while loading GPS points! You will have an empty gps list! \n {ex.ToString()}");
}
//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<ulong, MyPromoteLevel>();
}
foreach (KeyValuePair<MyObjectBuilder_Checkpoint.PlayerId, MyObjectBuilder_Player> 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)
{
AdminSettings.SetValue(MySession.Static, 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);
}
}
MySector.InitEnvironmentSettings(TargetWorld.Sector.Environment);
string text = ((!string.IsNullOrEmpty(TargetWorld.Checkpoint.CustomSkybox)) ? TargetWorld.Checkpoint.CustomSkybox : MySector.EnvironmentDefinition.EnvironmentTexture);
MyRenderProxy.PreloadTextures(new string[1] { text }, TextureType.CubeMap);
}
private void InitComponents()
{
UpdateWorldGenerator();
}
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<MyProceduralWorldGenerator>();
//Force component to unload
UnloadProceduralWorldGenerator.Invoke(Generator, null);
//Re-call the generator init
MyObjectBuilder_WorldGenerator GeneratorSettings = (MyObjectBuilder_WorldGenerator)TargetWorld.Checkpoint.SessionComponents.FirstOrDefault(x => x.GetType() == typeof(MyObjectBuilder_WorldGenerator));
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.
FieldInfo PlanetInitArgs = typeof(MyPlanet).GetField("m_planetInitValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var Planet in MyEntities.GetEntities().OfType<MyPlanet>())
{
MyPlanetInitArguments args = (MyPlanetInitArguments)PlanetInitArgs.GetValue(Planet);
float MaxRadius = args.MaxRadius;
Generator.MarkEmptyArea(Planet.PositionComp.GetPosition(), MaxRadius);
}
}
private void StartEntitySync()
{
Seamless.TryShow("Requesting Player From Server");
Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, true, true);
if (!Sandbox.Engine.Platform.Game.IsDedicated && MySession.Static.LocalHumanPlayer == null)
{
Seamless.TryShow("RequestNewPlayer");
}
else if (MySession.Static.ControlledEntity == null && Sync.IsServer && !Sandbox.Engine.Platform.Game.IsDedicated)
{
Seamless.TryShow("ControlledObject was null, respawning character");
//m_cameraAwaitingEntity = true;
MyPlayerCollection.RequestLocalRespawn();
}
//Request client state batch
(MyMultiplayer.Static as MyMultiplayerClientBase).RequestBatchConfirmation();
MyMultiplayer.Static.PendingReplicablesDone += Static_PendingReplicablesDone;
//typeof(MyGuiScreenTerminal).GetMethod("CreateTabs")
//MySession.Static.LocalHumanPlayer.Controller.TakeControl(originalControlledEntity);
MyGuiSandbox.UnloadContent();
MyGuiSandbox.LoadContent();
//MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.HUDScreen);
MyRenderProxy.RebuildCullingStructure();
MyRenderProxy.CollectGarbage();
Seamless.TryShow("OnlinePlayers: " + MySession.Static.Players.GetOnlinePlayers().Count);
Seamless.TryShow("Loading Complete!");
}
private void Static_PendingReplicablesDone()
{
if (MySession.Static.VoxelMaps.Instances.Count > 0)
{
MySandboxGame.AreClipmapsReady = false;
}
MyMultiplayer.Static.PendingReplicablesDone -= Static_PendingReplicablesDone;
}
private void LoadConnectedClients()
{
Seamless.TryShow($"BEFORE {MySession.Static.LocalHumanPlayer == null} - {MySession.Static.LocalCharacter == null}");
MyPlayer.PlayerId? savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
if (!savingPlayerId.HasValue)
{
Seamless.TryShow("SavingPlayerID is null! Creating Default!");
savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
}
LoadMembersFromWorld.Invoke(MySession.Static, new object[] { TargetWorld, MyMultiplayer.Static });
player.IsWildlifeAgent = true;
CreateNewPlayerInternal.Invoke(MySession.Static.Players, new object[] { Sync.Clients.LocalClient, savingPlayerId.Value, player });
typeof(MyPlayerCollection).GetMethod("LoadPlayerInternal", BindingFlags.Instance | BindingFlags.NonPublic).Invoke(MySession.Static.Players, new object[] { savingPlayerId.Value, player, false });
Seamless.TryShow("Saving PlayerID: " + savingPlayerId.ToString());
Sync.Players.LoadConnectedPlayers(TargetWorld.Checkpoint, savingPlayerId);
Sync.Players.LoadControlledEntities(TargetWorld.Checkpoint.ControlledEntities, TargetWorld.Checkpoint.ControlledObject, savingPlayerId);
Seamless.TryShow($"AFTER {MySession.Static.LocalHumanPlayer == null} - {MySession.Static.LocalCharacter == null}");
}
}
}

View File

@@ -1,493 +0,0 @@
using HarmonyLib;
using Sandbox.Game.World;
using Sandbox;
using SeamlessClient.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.Game;
using VRage.GameServices;
using Sandbox.Game.Gui;
using Sandbox.Game.SessionComponents;
using SpaceEngineers.Game.GUI;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.Entities;
using Sandbox.Engine.Networking;
using System.Reflection;
using VRage.Network;
using Sandbox.ModAPI;
using VRageRender.Messages;
using VRageRender;
using Sandbox.Game.GUI;
using Sandbox.Game.World.Generator;
using Sandbox.Game;
using VRage.Game.ModAPI;
using VRage.Utils;
using SeamlessClient.ServerSwitching;
namespace SeamlessClient.Components
{
public class ServerSwitcherComponentOLD : ComponentBase
{
private static bool isSeamlessSwitching = false;
private static ConstructorInfo TransportLayerConstructor;
private static ConstructorInfo SyncLayerConstructor;
private static ConstructorInfo ClientConstructor;
private static MethodInfo UnloadProceduralWorldGenerator;
private static MethodInfo GpsRegisterChat;
private static MethodInfo LoadMembersFromWorld;
private static MethodInfo InitVirtualClients;
private static FieldInfo AdminSettings;
private static FieldInfo RemoteAdminSettings;
private static FieldInfo VirtualClients;
private static PropertyInfo MySessionLayer;
public static MyGameServerItem TargetServer { get; private set; }
public static MyObjectBuilder_World TargetWorld { get; private set; }
public static ServerSwitcherComponentOLD Instance { get; private set; }
private string OldArmorSkin { get; set; } = string.Empty;
public ServerSwitcherComponentOLD() { Instance = this; }
public override void Patch(Harmony patcher)
{
TransportLayerConstructor = PatchUtils.GetConstructor(PatchUtils.MyTransportLayerType, new[] { typeof(int) });
SyncLayerConstructor = PatchUtils.GetConstructor(PatchUtils.SyncLayerType, new[] { PatchUtils.MyTransportLayerType });
ClientConstructor = PatchUtils.GetConstructor(PatchUtils.ClientType, new[] { typeof(MyGameServerItem), PatchUtils.SyncLayerType });
MySessionLayer = PatchUtils.GetProperty(typeof(MySession), "SyncLayer");
var onJoin = PatchUtils.GetMethod(PatchUtils.ClientType, "OnUserJoined");
UnloadProceduralWorldGenerator = PatchUtils.GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData");
GpsRegisterChat = PatchUtils.GetMethod(typeof(MyGpsCollection), "RegisterChat");
AdminSettings = PatchUtils.GetField(typeof(MySession), "m_adminSettings");
RemoteAdminSettings = PatchUtils.GetField(typeof(MySession), "m_remoteAdminSettings");
LoadMembersFromWorld = PatchUtils.GetMethod(typeof(MySession), "LoadMembersFromWorld");
InitVirtualClients = PatchUtils.GetMethod(PatchUtils.VirtualClientsType, "Init");
VirtualClients = PatchUtils.GetField(typeof(MySession), "VirtualClients");
patcher.Patch(onJoin, postfix: new HarmonyMethod(Get(typeof(ServerSwitcherComponentOLD), nameof(OnUserJoined))));
base.Patch(patcher);
}
private static void OnUserJoined(ref JoinResultMsg msg)
{
if (msg.JoinResult == JoinResult.OK && isSeamlessSwitching)
{
//SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString());
//Invoke the switch event
ForceClientConnection();
isSeamlessSwitching = false;
}
}
public void StartBackendSwitch(MyGameServerItem _TargetServer, MyObjectBuilder_World _TargetWorld)
{
isSeamlessSwitching = true;
OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin;
TargetServer = _TargetServer;
TargetWorld = _TargetWorld;
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
MySandboxGame.Static.SessionCompatHelper.FixSessionComponentObjectBuilders(TargetWorld.Checkpoint, TargetWorld.Sector);
// Create constructors
var LayerInstance = TransportLayerConstructor.Invoke(new object[] { 2 });
var SyncInstance = SyncLayerConstructor.Invoke(new object[] { LayerInstance });
var instance = ClientConstructor.Invoke(new object[] { TargetServer, SyncInstance });
MyMultiplayer.Static = UtilExtensions.CastToReflected(instance, PatchUtils.ClientType);
MyMultiplayer.Static.ExperimentalMode = true;
// Set the new SyncLayer to the MySession.Static.SyncLayer
MySessionLayer.SetValue(MySession.Static, MyMultiplayer.Static.SyncLayer);
Seamless.TryShow("Successfully set MyMultiplayer.Static");
Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName);
Sync.Players.RegisterEvents();
}
private static void ForceClientConnection()
{
//Set World Settings
SetWorldSettings();
//Load force load any connected players
LoadConnectedClients();
MySector.InitEnvironmentSettings(TargetWorld.Sector.Environment);
string text = ((!string.IsNullOrEmpty(TargetWorld.Checkpoint.CustomSkybox)) ? TargetWorld.Checkpoint.CustomSkybox : MySector.EnvironmentDefinition.EnvironmentTexture);
MyRenderProxy.PreloadTextures(new string[1] { 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);
GpsRegisterChat.Invoke(MySession.Static.Gpss, new object[] { 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 static void LoadOnlinePlayers()
{
//Get This players ID
MyPlayer.PlayerId? savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
if (!savingPlayerId.HasValue)
{
Seamless.TryShow("SavingPlayerID is null! Creating Default!");
savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
}
Seamless.TryShow("Saving PlayerID: " + savingPlayerId.ToString());
Sync.Players.LoadConnectedPlayers(TargetWorld.Checkpoint, savingPlayerId);
Sync.Players.LoadControlledEntities(TargetWorld.Checkpoint.ControlledEntities, TargetWorld.Checkpoint.ControlledObject, savingPlayerId);
/*
SeamlessClient.TryShow("Saving PlayerID: " + savingPlayerId.ToString());
foreach (KeyValuePair<MyObjectBuilder_Checkpoint.PlayerId, MyObjectBuilder_Player> 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<MyPlayer.PlayerId, MyPlayer> Players = (ConcurrentDictionary<MyPlayer.PlayerId, MyPlayer>)Patches.MPlayerGPSCollection.GetValue(MySession.Static.Players);
//LoadPlayerInternal(ref playerId5, item3.Value);
if (Players.TryGetValue(playerId5, out MyPlayer myPlayer))
{
List<Vector3> 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 static void SetWorldSettings()
{
//MyEntities.MemoryLimitAddFailureReset();
//Clear old list
MySession.Static.PromotedUsers.Clear();
MySession.Static.CreativeTools.Clear();
Dictionary<ulong, AdminSettingsEnum> AdminSettingsList = (Dictionary<ulong, AdminSettingsEnum>)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.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)
{
Seamless.TryShow($"An error occured while loading GPS points! You will have an empty gps list! \n {ex.ToString()}");
}
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<ulong, MyPromoteLevel>();
}
foreach (KeyValuePair<MyObjectBuilder_Checkpoint.PlayerId, MyObjectBuilder_Player> 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)
{
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 static void LoadConnectedClients()
{
LoadMembersFromWorld.Invoke(MySession.Static, new object[] { TargetWorld, MyMultiplayer.Static });
//Re-Initilize Virtual clients
object VirtualClientsValue = VirtualClients.GetValue(MySession.Static);
InitVirtualClients.Invoke(VirtualClientsValue, null);
//load online players
LoadOnlinePlayers();
}
private static void StartEntitySync()
{
Seamless.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();
}
//Request client state batch
(MyMultiplayer.Static as MyMultiplayerClientBase).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();
Seamless.TryShow("OnlinePlayers: " + MySession.Static.Players.GetOnlinePlayers().Count);
Seamless.TryShow("Loading Complete!");
}
private static void MyMultiplayer_PendingReplicablesDone()
{
if (MySession.Static.VoxelMaps.Instances.Count > 0)
{
MySandboxGame.AreClipmapsReady = false;
}
MyMultiplayer.Static.PendingReplicablesDone -= MyMultiplayer_PendingReplicablesDone;
}
private static 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<MyProceduralWorldGenerator>();
//Force component to unload
UnloadProceduralWorldGenerator.Invoke(Generator, null);
//Re-call the generator init
MyObjectBuilder_WorldGenerator GeneratorSettings = (MyObjectBuilder_WorldGenerator)TargetWorld.Checkpoint.SessionComponents.FirstOrDefault(x => x.GetType() == typeof(MyObjectBuilder_WorldGenerator));
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.
FieldInfo PlanetInitArgs = typeof(MyPlanet).GetField("m_planetInitValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var Planet in MyEntities.GetEntities().OfType<MyPlanet>())
{
MyPlanetInitArguments args = (MyPlanetInitArguments)PlanetInitArgs.GetValue(Planet);
float MaxRadius = args.MaxRadius;
Generator.MarkEmptyArea(Planet.PositionComp.GetPosition(), 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
MySessionComponentIngameHelp component = MySession.Static.GetComponent<MySessionComponentIngameHelp>();
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();
}
}
}
}

View File

@@ -10,26 +10,11 @@ namespace SeamlessClient.Messages
[ProtoContract]
public class OnlinePlayerData
{
[ProtoMember(10)] public List<OnlineClientServer> OnlineServers = new List<OnlineClientServer>();
[ProtoMember(10)] public List<OnlinePlayer> OnlinePlayers = new List<OnlinePlayer>();
[ProtoMember(12)] public int currentServerID;
}
[ProtoContract]
public class OnlineClientServer
{
[ProtoMember(2)] public List<OnlinePlayer> Players = new List<OnlinePlayer>();
[ProtoMember(3)] public bool ServerRunning = false;
[ProtoMember(10)] public int ServerID;
[ProtoMember(11)] public string ServerName;
public OnlineClientServer()
{
}
}
[ProtoContract]
public class OnlinePlayer
@@ -42,9 +27,6 @@ namespace SeamlessClient.Messages
[ProtoMember(4)] public int OnServer;
public string ServerName;
public OnlinePlayer(string PlayerName, ulong SteamID, long IdentityID, int OnServer)
{
this.PlayerName = PlayerName;

28
Models/ModByte.cs Normal file
View File

@@ -0,0 +1,28 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClient.Models
{
[ProtoContract]
public class ModByte
{
[ProtoMember(1)]
public ulong ModID { get; set; }
[ProtoMember(2)]
public byte[] AssemblyBytes { get; set; }
public Assembly GetNewAssembly()
{
return Assembly.Load(AssemblyBytes);
}
}
}

83
Models/ModCache.cs Normal file
View File

@@ -0,0 +1,83 @@
using ProtoBuf;
using SeamlessClient.Utilities;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using VRage.Serialization;
namespace SeamlessClient.Models
{
[ProtoContract]
public class ModCache
{
[ProtoMember(1)]
public List<ModByte> CachedMods { get; set; } = new List<ModByte>();
public void AddModToCache(Assembly asm, byte[] raw)
{
//Get the modID from the loaded assembly name
ulong? modid = GetLeadingNumber(asm.FullName);
if (!modid.HasValue || modid.Value == 0)
return;
//Check to see if the loading mod is already in our cache
if(CachedMods.Any(x => x.ModID == modid.Value))
return;
ModByte mod = new ModByte();
mod.ModID = modid.Value;
mod.AssemblyBytes = raw;
CachedMods.Add(mod);
}
public static void SaveToFile(ModCache cache)
{
byte[] data = NetUtils.Serialize(cache);
File.WriteAllBytes("", data);
}
public ModCache LoadFromFile()
{
byte[] data = File.ReadAllBytes("");
return NetUtils.Deserialize<ModCache>(data);
}
public bool TryGetModAssembly(ulong modid, out Assembly asm)
{
asm = null;
if (modid == 0)
return false;
ModByte mod = CachedMods.FirstOrDefault(x => x.ModID == modid);
if(mod == null)
return false;
//Compiles new assembly
try
{
asm = mod.GetNewAssembly();
return true;
}
catch (Exception ex)
{
return false;
}
}
static ulong? GetLeadingNumber(string assemblyName)
{
Match match = Regex.Match(assemblyName, @"^\d+");
return match.Success ? ulong.Parse(match.Value) : (ulong?)null;
}
}
}

View File

@@ -385,7 +385,8 @@ namespace SeamlessClient.OnlinePlayersWindow
Controls.Add(m_playersTable);
string servername = PlayersWindowComponent.onlineServer?.ServerName ?? "thisServer";
//string servername = PlayersWindowComponent.onlineServer?.ServerName ?? "thisServer";
string servername = "thisserver";
foreach (MyPlayer onlinePlayer in Sync.Players.GetOnlinePlayers())
{
@@ -408,13 +409,14 @@ namespace SeamlessClient.OnlinePlayersWindow
AddPlayer(onlinePlayer.Id.SteamId, servername);
}
/*
foreach(var server in PlayersWindowComponent.allServers)
{
foreach(var player in server.Players)
AddPlayerFromOtherServer(player, server.ServerName);
}
*/
m_lobbyTypeCombo.ItemSelected += lobbyTypeCombo_OnSelect;
@@ -796,7 +798,7 @@ namespace SeamlessClient.OnlinePlayersWindow
protected void Multiplayer_PlayerJoined(ulong userId, string userName)
{
AddPlayer(userId, PlayersWindowComponent.onlineServer.ServerName);
//AddPlayer(userId, PlayersWindowComponent.onlineServer.ServerName);
}
protected void Multiplayer_PlayerLeft(ulong userId, MyChatMemberStateChangeEnum arg2)

View File

@@ -33,5 +33,5 @@ using System.Runtime.InteropServices;
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("2.0.0.6")] //Set these both to the same
[assembly: AssemblyFileVersion("2.0.0.6")]
[assembly: AssemblyVersion("3.0.0.0")] //Set these both to the same
[assembly: AssemblyFileVersion("3.0.0.0")]

View File

@@ -1,13 +1,14 @@
using HarmonyLib;
using NLog.Fluent;
using Sandbox;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui;
using Sandbox.Game.Localization;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using SeamlessClient.Components;
using SeamlessClient.Messages;
using SeamlessClient.OnlinePlayersWindow;
using SeamlessClient.ServerSwitching;
using SeamlessClient.Utilities;
using System;
using System.Collections.Generic;
@@ -33,11 +34,12 @@ namespace SeamlessClient
public static ushort SeamlessClientNetId = 2936;
private List<ComponentBase> allComps = new List<ComponentBase>();
private Assembly thisAssembly => typeof(Seamless).Assembly;
private bool Initilized = false;
public static bool isSeamlessServer { get; private set; } = false;
public static bool isDebug = false;
public static bool UseNewVersion = true;
public static bool isDebug = true;
public static bool UseNewVersion = false;
@@ -111,6 +113,10 @@ namespace SeamlessClient
}
}
private static void MessageHandler2(ushort packetID, byte[] data, ulong sender, bool fromServer)
{
Seamless.TryShow("Recieved visuals");
}
private static void MessageHandler(ushort packetID, byte[] data, ulong sender, bool fromServer)
{
@@ -130,7 +136,7 @@ namespace SeamlessClient
switch (msg.MessageType)
{
case ClientMessageType.FirstJoin:
Seamless.TryShow("Sending First Join!");
//Seamless.TryShow("Sending First Join!");
SendSeamlessVersion();
break;
@@ -141,7 +147,7 @@ namespace SeamlessClient
case ClientMessageType.OnlinePlayers:
//Not implemented yet
var playerData = msg.GetOnlinePlayers();
PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
//PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
break;
}
}
@@ -150,7 +156,7 @@ namespace SeamlessClient
{
ClientMessage response = new ClientMessage(SeamlessVersion.ToString());
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetId, MessageUtils.Serialize(response));
Seamless.TryShow("Sending Seamless request...");
//Seamless.TryShow("Sending Seamless request...");
}
@@ -172,6 +178,7 @@ namespace SeamlessClient
if (!Initilized)
{
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(SeamlessClientNetId, MessageHandler);
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(2938, MessageHandler2);
InitilizeComponents();
Initilized = true;
@@ -179,8 +186,6 @@ namespace SeamlessClient
IMyGameServer server = MyServiceManager.Instance.GetService<IMyGameServer>();
MySandboxGame.PausePop();
}
@@ -204,11 +209,11 @@ namespace SeamlessClient
Seamless.TryShow($"Beginning Redirect to server: {targetServer.TargetServerId}");
var world = targetServer.WorldRequest.DeserializeWorldData();
SeamlessSwitcher.Instance.StartSwitch(server, world);
//Temp fix till im not lazy enough to fix new version
if (UseNewVersion)
ServerSwitcherComponent.Instance.StartBackendSwitch(server, world);
else
ServerSwitcherComponentOLD.Instance.StartBackendSwitch(server, world);
}

View File

@@ -34,6 +34,14 @@
<Reference Include="0Harmony, Version=2.3.3.0, Culture=neutral, processorArchitecture=MSIL">
<HintPath>packages\Lib.Harmony.2.3.3\lib\net48\0Harmony.dll</HintPath>
</Reference>
<Reference Include="Microsoft.CodeAnalysis">
<HintPath>GameBinaries\Microsoft.CodeAnalysis.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.CodeAnalysis.CSharp">
<HintPath>GameBinaries\Microsoft.CodeAnalysis.CSharp.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="NLog">
<HintPath>GameBinaries\NLog.dll</HintPath>
<Private>False</Private>
@@ -68,6 +76,10 @@
<Private>False</Private>
</Reference>
<Reference Include="System" />
<Reference Include="System.Collections.Immutable, Version=8.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>GameBinaries\System.Collections.Immutable.dll</HintPath>
</Reference>
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
@@ -99,15 +111,25 @@
<HintPath>GameBinaries\VRage.Render.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Scripting">
<HintPath>GameBinaries\VRage.Scripting.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Components\EntityPerformanceImprovements.cs" />
<Compile Include="Components\LoadingScreenComponent.cs" />
<Compile Include="Components\ModReloader.cs" />
<Compile Include="Components\MyGUIScreenMedicalsPatch.cs" />
<Compile Include="Components\MyHudTimeTracker.cs" />
<Compile Include="Components\ServerSwitcherComponentOLD.cs" />
<Compile Include="Components\SeamlessScriptManager.cs" />
<Compile Include="Components\SeamlessSwitcher.cs" />
<Compile Include="GUI\Screens\GUILoadingScreen.cs" />
<Compile Include="Messages\OnlinePlayerData.cs" />
<Compile Include="Models\ModCache.cs" />
<Compile Include="Models\ModByte.cs" />
<Compile Include="Utilities\GameEvents.cs" />
<Compile Include="Utilities\NetUtils.cs" />
<Compile Include="Utilities\UtilExtensions.cs" />
<Compile Include="Utilities\PatchUtils.cs" />
<Compile Include="Messages\ClientMessage.cs" />
@@ -117,7 +139,6 @@
<Compile Include="Components\PlayersWindowComponent.cs" />
<Compile Include="Seamless.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Components\ServerSwitcherComponent.cs" />
<Compile Include="Utilities\AsyncInvoke.cs" />
<Compile Include="Utilities\MessageUtils.cs" />
<Compile Include="Utilities\ComponentBase.cs" />

34
Utilities/GameEvents.cs Normal file
View File

@@ -0,0 +1,34 @@
using Sandbox.Engine.Multiplayer;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Network;
using VRageMath;
namespace SeamlessClient.Utilities
{
public class GameEvents
{
private static Dictionary<MethodInfo, Delegate> _delegateCache = new Dictionary<MethodInfo, Delegate>();
private static Func<T, TA> GetDelegate<T, TA>(MethodInfo method) where TA : class
{
if (!_delegateCache.TryGetValue(method, out var del))
{
del = (Func<T, TA>)(x => Delegate.CreateDelegate(typeof(TA), x, method) as TA);
_delegateCache[method] = del;
}
return (Func<T, TA>)del;
}
public static void RaiseStaticEvent<T1>(MethodInfo method, T1 arg1, EndpointId target = default, Vector3D? position = null)
{
var del = GetDelegate<IMyEventOwner, Action<T1>>(method);
MyMultiplayer.RaiseStaticEvent(del, arg1, target, position);
}
}
}

36
Utilities/NetUtils.cs Normal file
View File

@@ -0,0 +1,36 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClient.Utilities
{
public class NetUtils
{
public static byte[] Serialize<T>(T instance)
{
if (instance == null)
return null;
using (var m = new MemoryStream())
{
Serializer.Serialize(m, instance);
return m.ToArray();
}
}
public static T Deserialize<T>(byte[] data)
{
if (data == null)
return default(T);
using (var m = new MemoryStream(data))
{
return Serializer.Deserialize<T>(m);
}
}
}
}