6 Commits

Author SHA1 Message Date
7b5a895246 feature: pve (bump version)
All checks were successful
Build / Compute Version (push) Successful in 5s
Build / Build Nuget package (push) Successful in 37s
2025-05-11 16:19:20 -04:00
d84f37bea7 Update for latest version of seamless
Some checks failed
Build / Compute Version (push) Successful in 5s
Build / Build Nuget package (push) Failing after 35s
Notable feature: ModAPI
2025-05-11 16:14:22 -04:00
c8c0255edd Update for latest version of seamless
All checks were successful
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (push) Successful in 36s
Notable feature: V3 support (allegedly)
2025-02-04 19:17:52 -05:00
95d4b1ba27 pve cringe
All checks were successful
Build / Compute Version (push) Successful in 5s
Build / Build Nuget package (push) Successful in 15m32s
2024-12-12 19:55:39 -05:00
7db71ab013 fix compatibility with CringeLauncher
Some checks failed
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (push) Failing after 6s
2024-12-12 19:44:27 -05:00
4e728d823e rollback to stable version 2024-12-12 19:34:17 -05:00
26 changed files with 1790 additions and 1170 deletions

44
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,44 @@
name: Build
on:
push:
branches: [master]
jobs:
compute-version:
name: Compute Version
runs-on: ubuntu-latest
outputs:
version: ${{ steps.version.outputs.version }}
steps:
- uses: actions/checkout@master
with:
ref: ${{ github.head_ref }}
fetch-depth: 0
- id: version
uses: paulhatch/semantic-version@v5.3.0
with:
tag_prefix: ''
major_pattern: 'Add project files'
minor_pattern: 'feature:'
bump_each_commit: true
enable_prerelease_mode: false
build-nuget:
name: Build Nuget package
runs-on: ubuntu-latest
needs: [compute-version]
steps:
- uses: actions/checkout@master
- uses: actions/setup-dotnet@v4
- run: dotnet restore ./SeamlessClient.csproj --locked-mode
name: Restore Project
- run: dotnet pack -c Release -o ./pub ./SeamlessClient.csproj --no-restore -p:Version="${{ needs.compute-version.outputs.version }}"
name: Pack Project
- name: Push Project
run: dotnet nuget push -s https://ng.zznty.ru/v3/index.json -k ${{ secrets.NUGET_API_KEY }} ./pub/*.nupkg

119
Components/ModAPI.cs Normal file
View File

@@ -0,0 +1,119 @@
using HarmonyLib;
using Sandbox.Game.World;
using SeamlessClient.Utilities;
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;
using VRage.Game.Components;
using VRage.Utils;
namespace SeamlessClient.Components
{
/// <summary>
/// ModAPI so that mods can register seamless events
/// </summary>
public class ModAPI : ComponentBase
{
private static FieldInfo SessionComponents;
private static List<LoadedMod> LoadedMods = new List<LoadedMod>();
public class LoadedMod
{
public MethodInfo SeamlessServerUnload;
public MethodInfo SeamlessServerLoad;
public MySessionComponentBase ModSession;
}
public override void Patch(Harmony patcher)
{
var AddModAssembly = PatchUtils.GetMethod(typeof(MySession), "TryRegisterSessionComponent");
patcher.Patch(AddModAssembly, postfix: new HarmonyMethod(Get(typeof(ModAPI), nameof(AddModAssembly))));
SessionComponents = PatchUtils.GetField(typeof(MySession), "m_sessionComponents");
base.Patch(patcher);
}
public static void ClearCache()
{
LoadedMods.Clear();
}
public static void StartModSwitching()
{
Seamless.TryShow($"Invoking SeamlessUnload API on {LoadedMods.Count} mods!");
foreach (var mod in LoadedMods)
{
try
{
mod.SeamlessServerUnload?.Invoke(mod.ModSession, null);
}
catch (Exception ex)
{
Seamless.TryShow(ex, $"Error during modAPI unloading! {mod.SeamlessServerUnload.Name}");
}
}
}
public static void ServerSwitched()
{
Seamless.TryShow($"Invoking SeamlessServerLoad API on {LoadedMods.Count} mods!");
foreach (var mod in LoadedMods)
{
try
{
mod.SeamlessServerLoad?.Invoke(mod.ModSession, null);
}
catch (Exception ex)
{
Seamless.TryShow(ex, $"Error during modAPI loading! {mod.SeamlessServerLoad.Name}");
}
}
}
public static void AddModAssembly(Type type, bool modAssembly, MyModContext context)
{
if (!modAssembly || context == null)
return;
CachingDictionary<Type, MySessionComponentBase> dict = (CachingDictionary<Type, MySessionComponentBase>)SessionComponents.GetValue(MySession.Static);
dict.TryGetValue(type, out MySessionComponentBase component);
Seamless.TryShow($"Loading Mod Assembly: {component.ComponentType.FullName}");
MethodInfo Load = AccessTools.Method(component.ComponentType, "SeamlessServerLoaded");
MethodInfo Unload = AccessTools.Method(component.ComponentType, "SeamlessServerUnloaded");
if(Load != null || Unload != null)
{
LoadedMod newMod = new LoadedMod();
newMod.SeamlessServerLoad = Load;
newMod.SeamlessServerUnload = Unload;
newMod.ModSession = component;
Seamless.TryShow($"Mod Assembly: {component.ComponentType.FullName} has SeamlessServerLoaded/SeamlessServerUnloaded methods!");
LoadedMods.Add(newMod);
return;
}
}
}
}

View File

@@ -1,140 +0,0 @@
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

@@ -39,6 +39,10 @@ namespace SeamlessClient.Components
return;
MyGuiControlTable myGuiControlTable = (MyGuiControlTable)typeof(MyGuiScreenMedicals).GetField("m_respawnsTable", System.Reflection.BindingFlags.Instance | System.Reflection.BindingFlags.NonPublic).GetValue(__instance);
if (myGuiControlTable == null)
return;
string s = MyTexts.GetString(MySpaceTexts.SpawnInSpaceSuit);
foreach (var item in myGuiControlTable.Rows)
{

View File

@@ -55,7 +55,7 @@ namespace SeamlessClient.Components
if(t <= 0)
if (t <= 0)
return;
stringBuilder.AppendLine($" [T-{FormatDuration(t)}]");

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,7 +38,6 @@ namespace SeamlessClient.OnlinePlayersWindow
}
}
/*
public static void ApplyRecievedPlayers(List<OnlineClientServer> servers, int CurrentServer)
{
//Seamless.TryShow($"Recieved {CurrentServer} - {servers.Count}");
@@ -48,7 +47,7 @@ namespace SeamlessClient.OnlinePlayersWindow
foreach (OnlineClientServer server in servers)
{
if(server.ServerID == CurrentServer)
if (server.ServerID == CurrentServer)
{
onlineServer = server;
continue;
@@ -57,7 +56,6 @@ namespace SeamlessClient.OnlinePlayersWindow
allServers.Add(server);
}
}
*/

View File

@@ -1,65 +0,0 @@
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

@@ -1,525 +0,0 @@
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

@@ -0,0 +1,847 @@
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();
}
ModAPI.ServerSwitched();
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}");
ModAPI.StartModSwitching();
//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

@@ -0,0 +1,609 @@
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;
using Sandbox.Game.Entities.Character;
using VRage.Game.Utils;
using VRage;
using Sandbox.Game.GameSystems.CoordinateSystem;
namespace SeamlessClient.Components
{
public class ServerSwitcherComponentOLD : ComponentBase
{
private static bool isSeamlessSwitching { get; set; } = false;
private static bool WaitingForClientCheck { get; set; } = 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 static string SwitchingText = string.Empty;
public override void Update()
{
//Toggle waiting for client check
if (WaitingForClientCheck == false && isSeamlessSwitching)
WaitingForClientCheck = true;
if (WaitingForClientCheck && MySession.Static.LocalHumanPlayer != null)
WaitingForClientCheck = false;
if (isSeamlessSwitching || WaitingForClientCheck)
{
//SeamlessClient.TryShow("Switching Servers!");
MyRenderProxy.DebugDrawText2D(new VRageMath.Vector2(MySandboxGame.ScreenViewport.Width / 2, MySandboxGame.ScreenViewport.Height - 150), SwitchingText, VRageMath.Color.AliceBlue, 1f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER);
MyRenderProxy.DebugDrawText2D(new VRageMath.Vector2(MySandboxGame.ScreenViewport.Width / 2, MySandboxGame.ScreenViewport.Height - 200), $"Transferring to {TargetServer.Name}", VRageMath.Color.Yellow, 1.5f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER);
MyRenderProxy.DebugDrawLine2D(new VRageMath.Vector2((MySandboxGame.ScreenViewport.Width / 2) - 250, MySandboxGame.ScreenViewport.Height - 170), new VRageMath.Vector2((MySandboxGame.ScreenViewport.Width / 2) + 250, MySandboxGame.ScreenViewport.Height - 170), VRageMath.Color.Blue, VRageMath.Color.Green);
}
}
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))));
}
public override void Initilized()
{
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
}
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
{
if (!messageText.StartsWith("/nexus"))
return;
string[] cmd = messageText.ToLowerInvariant().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (cmd[1] == "refreshcharacter")
{
if (MySession.Static.LocalHumanPlayer == null)
{
MyAPIGateway.Utilities?.ShowMessage("Seamless", "LocalHumanPlayer Null!");
return;
}
if (MySession.Static.LocalHumanPlayer.Character == null)
{
MyAPIGateway.Utilities?.ShowMessage("Seamless", "LocalHumanPlayerCharacter Null!");
return;
}
//None of this shit works.... 5/3/2025
MySession.Static.LocalHumanPlayer.SpawnIntoCharacter(MySession.Static.LocalHumanPlayer.Character);
MySession.Static.LocalHumanPlayer.Controller.TakeControl(MySession.Static.LocalHumanPlayer.Character);
MySession.Static.LocalHumanPlayer.Character.GetOffLadder();
MySession.Static.LocalHumanPlayer.Character.Stand();
MySession.Static.LocalHumanPlayer.Character.ResetControls();
MySession.Static.LocalHumanPlayer.Character.UpdateCharacterPhysics(true);
MyAPIGateway.Utilities?.ShowMessage("Seamless", "Character Controls Reset!");
}
}
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
SwitchingText = "Server Responded! Removing Old Entities and forcing client connection!";
RemoveOldEntities();
ForceClientConnection();
ModAPI.ServerSwitched();
MySession.Static.LocalHumanPlayer?.Character?.Stand();
isSeamlessSwitching = false;
}
}
public void StartBackendSwitch(MyGameServerItem _TargetServer, MyObjectBuilder_World _TargetWorld)
{
if (MySession.Static.LocalCharacter != null)
{
var viewMatrix = MySession.Static.LocalCharacter.GetViewMatrix();
MySpectator.Static.SetViewMatrix(viewMatrix);
}
SwitchingText = "Starting Seamless Switch... Please wait!";
isSeamlessSwitching = true;
OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin;
TargetServer = _TargetServer;
TargetWorld = _TargetWorld;
MySandboxGame.Static.Invoke(delegate
{
//Pause the game/update thread while we load
MySandboxGame.IsPaused = true;
//Set camera controller to fixed spectator
MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed);
UnloadCurrentServer();
SetNewMultiplayerClient();
ModAPI.StartModSwitching();
//SeamlessClient.IsSwitching = false;
SwitchingText = "Waiting for server response...";
}, "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);
SwitchingText = "New Multiplayer Session Set";
Seamless.TryShow("Successfully set MyMultiplayer.Static");
MySandboxGame.IsPaused = true;
Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName);
Sync.Players.RegisterEvents();
SwitchingText = "Registering Player Events";
}
private static void ForceClientConnection()
{
//Set World Settings
SetWorldSettings();
//Load force load any connected players
LoadConnectedClients();
SwitchingText = "Loaded Connected Clients";
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();
MyModAPIHelper.Initialize();
//MethodInfo A = typeof(MySession).GetMethod("LoadGameDefinition", BindingFlags.Instance | BindingFlags.NonPublic);
// A.Invoke(MySession.Static, new object[] { TargetWorld.Checkpoint });
MyMultiplayer.Static.OnSessionReady();
SwitchingText = "Session Ready";
UpdateWorldGenerator();
StartEntitySync();
//Resume the game/update thread
MySandboxGame.IsPaused = false;
MyHud.Chat.RegisterChat(MyMultiplayer.Static);
GpsRegisterChat.Invoke(MySession.Static.Gpss, new object[] { MyMultiplayer.Static });
SwitchingText = "Registered Chat";
// 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);
SwitchingText = "Client Registered. Waiting for entities from server...";
Seamless.TryShow($"LocalHumanPlayer = {MySession.Static.LocalHumanPlayer == null}");
//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()
{
//TargetWorld.Checkpoint.AllPlayers.Count
Seamless.TryShow($"Loading members from world... {TargetWorld.Checkpoint.AllPlayers.Count}");
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?");
SwitchingText = "Unloading Local Server Components";
//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;
SwitchingText = "Local Multiplayer Disposed";
//Clear grid coord systems
ResetCoordinateSystems();
//Close any respawn screens that are open
MyGuiScreenMedicals.Close();
//Unload any lingering updates queued
MyEntities.Orchestrator.Unload();
}
private static void RemoveOldEntities()
{
foreach (var ent in MyEntities.GetEntities())
{
if (ent is MyPlanet)
{
//Re-Add planet updates
MyEntities.RegisterForUpdate(ent);
continue;
}
ent.Close();
}
}
private static void ResetCoordinateSystems()
{
AccessTools.Field(typeof(MyCoordinateSystem), "m_lastCoordSysId").SetValue(MyCoordinateSystem.Static, 1L);
AccessTools.Field(typeof(MyCoordinateSystem), "m_localCoordSystems").SetValue(MyCoordinateSystem.Static, new Dictionary<long, MyLocalCoordSys>());
}
}
}

View File

@@ -113,7 +113,7 @@ namespace SeamlessClient.GUI.Screens
m_isTopMostScreen = true;
object instance = PatchUtils.GetProperty(PatchUtils.MyLoadingPerformance, "Instance").GetValue(null);
if(instance != null)
if (instance != null)
PatchUtils.GetMethod(PatchUtils.MyLoadingPerformance, "StartTiming").Invoke(instance, null);
//MyLoadingPerformance.Instance.StartTiming();
@@ -531,9 +531,9 @@ namespace SeamlessClient.GUI.Screens
MyGuiManager.GetSafeHeightFullScreenPictureSize(MyGuiConstants.LOADING_BACKGROUND_TEXTURE_REAL_SIZE, out outRect);
bool isCustom = false;
if(LoadingScreenComponent.CustomLoadingTextures.Count != 0)
if (LoadingScreenComponent.CustomLoadingTextures.Count != 0)
{
if(LoadingScreenComponent.CustomLoadingTextures.Count > 1)
if (LoadingScreenComponent.CustomLoadingTextures.Count > 1)
{
if (!backgroundTimer.Enabled)
{

View File

@@ -10,11 +10,26 @@ namespace SeamlessClient.Messages
[ProtoContract]
public class OnlinePlayerData
{
[ProtoMember(10)] public List<OnlinePlayer> OnlinePlayers = new List<OnlinePlayer>();
[ProtoMember(10)] public List<OnlineClientServer> OnlineServers = new List<OnlineClientServer>();
[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
@@ -27,6 +42,9 @@ namespace SeamlessClient.Messages
[ProtoMember(4)] public int OnServer;
public string ServerName;
public OnlinePlayer(string PlayerName, ulong SteamID, long IdentityID, int OnServer)
{
this.PlayerName = PlayerName;

View File

@@ -24,7 +24,7 @@ namespace SeamlessClient.Messages
[ProtoMember(3)] public string PlayerName;
[ProtoMember(4)] public byte[] WorldData;
[ProtoMember(5)] public MyObjectBuilder_Gps GpsCollection;
[ProtoMember(5)] public MyObjectBuilder_Gps GpsCollection { get; set; }
public WorldRequest()
{

View File

@@ -1,28 +0,0 @@
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);
}
}
}

View File

@@ -1,83 +0,0 @@
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,9 +385,6 @@ namespace SeamlessClient.OnlinePlayersWindow
Controls.Add(m_playersTable);
//string servername = PlayersWindowComponent.onlineServer?.ServerName ?? "thisServer";
string servername = "thisserver";
foreach (MyPlayer onlinePlayer in Sync.Players.GetOnlinePlayers())
{
if (onlinePlayer.Id.SerialId != 0)
@@ -404,19 +401,16 @@ namespace SeamlessClient.OnlinePlayersWindow
}
}
AddPlayer(onlinePlayer.Id.SteamId, servername);
AddPlayer(onlinePlayer.Id.SteamId, $"{MyMultiplayer.Static.HostName ?? "thisServer"}*");
}
/*
foreach(var server in PlayersWindowComponent.allServers)
foreach (var server in PlayersWindowComponent.allServers)
{
foreach(var player in server.Players)
foreach (var player in server.Players)
AddPlayerFromOtherServer(player, server.ServerName);
}
*/
m_lobbyTypeCombo.ItemSelected += lobbyTypeCombo_OnSelect;
@@ -798,7 +792,7 @@ namespace SeamlessClient.OnlinePlayersWindow
protected void Multiplayer_PlayerJoined(ulong userId, string userName)
{
//AddPlayer(userId, PlayersWindowComponent.onlineServer.ServerName);
AddPlayer(userId, PlayersWindowComponent.onlineServer?.ServerName ?? "Unknown");
}
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("3.0.0.0")] //Set these both to the same
[assembly: AssemblyFileVersion("3.0.0.0")]
[assembly: AssemblyVersion("3.0.0.10")] //Set these both to the same
[assembly: AssemblyFileVersion("3.0.0.10")]

View File

@@ -13,9 +13,21 @@ With Nexus servers, all data is shared between servers. (Factions, Identities, P
## How to install
Simply install the plguin loader, and check this plugins box to be added to the plugin loaders' active plugin list. (SE will need to be restarted afterwards)
Simply install the plugin loader, and check this plugin's box to be added to the plugin loaders' active plugin list. (SE will need to be restarted afterwards)
## Known issues
Obviously this is not an issue free-system. Currently since im doing no mod unloading or loading there could be issues if your servers dont have the exact same mods, or the mods dont properly work right. Please do not swarm mod authors with faults if seamless doesnt play nice with it. ***Its not their fault*** its ***mine***. I will be trying to implement mod unloading and loading switching between servers, just no ETA.
Obviously this is not an issue-free system. Currently, since im doing no mod unloading or loading, there could be issues if your servers don't have the same mods, or the mods don't work right. Please do not swarm mod authors with faults if seamless doesn't play nice with it. ***Its not their fault*** its ***mine***. I will be trying to implement mod unloading and loading switching between servers, just no ETA.
## ModAPI
I attempted to avoid implementing any modAPI in seamless, but unfortunately, Space Engineers doesn't handle unloading and reloading the same mod without compiling easily. Either I compile the mod every time you switch servers, eventually running into memory issues, or I attempt to unload the mod manually and restart it.
In both scenarios, unloading static variables are often up to the mod author and sometimes are set to null. On mod load, these variables are not re-instantiated with the default values resulting in many issues. It is way easier for mod authors when needed implement seamless unload and load logic appropriately.
There are two methods you can add to your mods (In the main mod session component class)
private void SeamlessServerLoaded(){}
private void SeamlessServerUnloaded(){}
Unloaded happens when seamless starts switching, Loaded when seamless is done switching. Seamless patches these methods on mod compilation.

View File

@@ -1,14 +1,15 @@
using HarmonyLib;
using CringePlugins.Utils;
using HarmonyLib;
using NLog.Fluent;
using Sandbox;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui;
using Sandbox.Engine.Multiplayer;
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;
@@ -34,36 +35,37 @@ 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 = true;
public static bool isDebug = false;
public static bool UseNewVersion = false;
public void Init(object gameInstance)
{
TryShow($"Running Seamless Client Plugin v[{SeamlessVersion}]");
SeamlessPatcher = new Harmony("SeamlessClientPatcher");
GetComponents();
PatchComponents(SeamlessPatcher);
MySession.LoadingStep += SessionLoaded;
}
private void SessionLoaded(LoadingProgress progress)
{
if (progress >= LoadingProgress.PROGRESS_STEP8)
SendSeamlessVersion();
}
private void GetComponents()
{
int failedCount = 0;
foreach (Type type in thisAssembly.GetTypes())
foreach (Type type in IntrospectionContext.Global.CollectDerivedTypes<ComponentBase>(typeof(Seamless).Module))
{
if (type.BaseType != typeof(ComponentBase))
continue;
try
{
ComponentBase s = (ComponentBase)Activator.CreateInstance(type);
@@ -97,27 +99,25 @@ namespace SeamlessClient
}
}
private void InitilizeComponents()
{
foreach(ComponentBase component in allComps)
foreach (ComponentBase component in allComps)
{
try
{
component.Initilized();
TryShow($"Initilized {component.GetType()}");
}catch(Exception ex)
}
catch (Exception ex)
{
TryShow(ex, $"Failed to initialize {component.GetType()}");
}
}
}
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)
{
//Ignore anything except dedicated server
@@ -135,28 +135,25 @@ namespace SeamlessClient
isSeamlessServer = true;
switch (msg.MessageType)
{
case ClientMessageType.FirstJoin:
//Seamless.TryShow("Sending First Join!");
SendSeamlessVersion();
break;
case ClientMessageType.TransferServer:
StartSwitch(msg.GetTransferData());
break;
case ClientMessageType.OnlinePlayers:
//Not implemented yet
var playerData = msg.GetOnlinePlayers();
//PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
break;
}
}
public static void SendSeamlessVersion()
{
ClientMessage response = new ClientMessage(SeamlessVersion.ToString());
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetId, MessageUtils.Serialize(response));
//Seamless.TryShow("Sending Seamless request...");
Seamless.TryShow("Sending Seamless request...");
}
@@ -167,6 +164,10 @@ namespace SeamlessClient
}
public void Update()
{
allComps.ForEach(x => x.Update());
if (MyAPIGateway.Multiplayer == null)
@@ -178,7 +179,6 @@ namespace SeamlessClient
if (!Initilized)
{
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(SeamlessClientNetId, MessageHandler);
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(2938, MessageHandler2);
InitilizeComponents();
Initilized = true;
@@ -186,6 +186,8 @@ namespace SeamlessClient
IMyGameServer server = MyServiceManager.Instance.GetService<IMyGameServer>();
MySandboxGame.PausePop();
}
@@ -209,11 +211,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);
}
@@ -221,8 +223,6 @@ namespace SeamlessClient
public static void TryShow(string message)
{
if (MySession.Static?.LocalHumanPlayer != null && isDebug)

View File

@@ -1,162 +1,41 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{883B492D-823E-4CC3-BD35-07FEF06F8359}</ProjectGuid>
<OutputType>Library</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>SeamlessClient</RootNamespace>
<AssemblyName>SeamlessClient</AssemblyName>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<Deterministic>true</Deterministic>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<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>
</Reference>
<Reference Include="ProtoBuf.Net">
<HintPath>GameBinaries\ProtoBuf.Net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ProtoBuf.Net.Core, Version=1.0.0.0, Culture=neutral, processorArchitecture=MSIL">
<SpecificVersion>False</SpecificVersion>
<HintPath>GameBinaries\ProtoBuf.Net.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Common">
<HintPath>GameBinaries\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game">
<HintPath>GameBinaries\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game.XmlSerializers">
<HintPath>GameBinaries\Sandbox.Game.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics">
<HintPath>GameBinaries\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.Game">
<HintPath>GameBinaries\SpaceEngineers.Game.dll</HintPath>
<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" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
<Reference Include="VRage">
<HintPath>GameBinaries\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game">
<HintPath>GameBinaries\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Input">
<HintPath>GameBinaries\VRage.Input.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Library">
<HintPath>GameBinaries\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math">
<HintPath>GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render">
<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\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" />
<Compile Include="Messages\TransferData.cs" />
<Compile Include="Messages\WorldRequestData.cs" />
<Compile Include="OnlinePlayersWindow\OnlineNexusPlayersWindow.cs" />
<Compile Include="Components\PlayersWindowComponent.cs" />
<Compile Include="Seamless.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Utilities\AsyncInvoke.cs" />
<Compile Include="Utilities\MessageUtils.cs" />
<Compile Include="Utilities\ComponentBase.cs" />
<Compile Include="Utilities\Types.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
<None Include="SetupSolution.bat">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<Folder Include="ServerSwitching\" />
</ItemGroup>
<ItemGroup>
<Content Include="SeamlessClient.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</Content>
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework>
<RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Nullable>disable</Nullable>
<EnableWindowsTargeting>true</EnableWindowsTargeting>
<PackageType>CringePlugin</PackageType>
<RestoreAdditionalProjectSources>https://ng.zznty.ru/v3/index.json</RestoreAdditionalProjectSources>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<Authors>Casimir</Authors>
<PackageId>Plugin.Casimir255.SeamlessClient</PackageId>
<AssemblyName>Plugin.Casimir255.SeamlessClient</AssemblyName>
<Title>Nexus Seamless Switcher</Title>
<Description>This plugin allows seamless transfers between Nexus enabled servers. Some mods or plugins may not play nice with switching between servers. Be cautious when using!</Description>
<PackageProjectUrl>https://git.zznty.ru/PvE/SeamlessClient</PackageProjectUrl>
<RepositoryUrl>https://git.zznty.ru/PvE/SeamlessClient</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CringePlugins" Version="*" ExcludeAssets="runtime; native"/>
<PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" ExcludeAssets="runtime; native" PrivateAssets="all"/>
<PackageReference Include="Steamworks.NET" Version="20.1.0" ExcludeAssets="runtime; native" PrivateAssets="all"/>
</ItemGroup>
<Target Name="CopyFiles" AfterTargets="PostBuildEvent">
<PropertyGroup>
<AppdataPath>$([System.Environment]::GetFolderPath(SpecialFolder.ApplicationData))</AppdataPath>
</PropertyGroup>
<ItemGroup>
<OutputFiles Include="$(OutputPath)\*"/>
</ItemGroup>
<Copy SourceFiles="@(OutputFiles)" DestinationFolder="$(AppdataPath)\CringeLauncher\plugins\$(ProjectName)" OverwriteReadOnlyFiles="true"/>
</Target>
</Project>

View File

@@ -1,23 +1,23 @@
<?xml version="1.0"?>
<PluginData xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:type="GitHubPlugin">
<!-- Place your github repository name here. One repository can only store one plugin. This one is from https://github.com/austinvaness/ToolSwitcherPlugin -->
<Id>Casimir255/SeamlessClient</Id>
<!-- Place your github repository name here. One repository can only store one plugin. This one is from https://github.com/austinvaness/ToolSwitcherPlugin -->
<Id>Casimir255/SeamlessClient</Id>
<!-- Optional tag that specifies the group this plugin belongs to. Only one plugin from a given group can be enabled. -->
<GroupId>NexusSeamless</GroupId>
<!-- Optional tag that specifies the group this plugin belongs to. Only one plugin from a given group can be enabled. -->
<GroupId>NexusSeamless</GroupId>
<!-- The name of your plugin that will appear in the list. -->
<FriendlyName>SeamlessClient</FriendlyName>
<!-- The name of your plugin that will appear in the list. -->
<FriendlyName>SeamlessClient</FriendlyName>
<!-- The author name that you want to appear in the list. -->
<Author>Casimir</Author>
<!-- The author name that you want to appear in the list. -->
<Author>Casimir</Author>
<!-- Optional tag that adds a tooltip to the plugin in-game. -->
<Tooltip>Allows transferring between Nexus enabled servers seamlessly</Tooltip>
<!-- Optional tag that adds a tooltip to the plugin in-game. -->
<Tooltip>Allows transferring between Nexus enabled servers seamlessly</Tooltip>
<!-- Optional tag that adds a plugin description. If omitted, this will be the same as the Tooltip. 1000 characters max. -->
<Description>This plugin allows seamless transfers between Nexus enabled servers. Some mods or plugins may not play nice with switching between servers. Be cautious when using!</Description>
<!-- Optional tag that adds a plugin description. If omitted, this will be the same as the Tooltip. 1000 characters max. -->
<Description>This plugin allows seamless transfers between Nexus enabled servers. Some mods or plugins may not play nice with switching between servers. Be cautious when using!</Description>
<!-- The commit id. You can find this in the commits list: https://github.com/austinvaness/ToolSwitcherPlugin/commits/main -->
<Commit>7a5ff795fba108309810a4cde7f414c1ffb1ba73</Commit>
<!-- The commit id. You can find this in the commits list: https://github.com/austinvaness/ToolSwitcherPlugin/commits/main -->
<Commit>7a5ff795fba108309810a4cde7f414c1ffb1ba73</Commit>
</PluginData>

View File

@@ -1,34 +0,0 @@
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);
}
}
}

View File

@@ -1,36 +0,0 @@
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);
}
}
}
}

View File

@@ -97,7 +97,7 @@ Type.GetType("Sandbox.Game.Screens.Helpers.MyLoadingScreenText, Sandbox.Game");
//MethodInfo ConnectToServer = GetMethod(typeof(MyGameService), "ConnectToServer", BindingFlags.Static | BindingFlags.Public);
base.Patch(patcher);
}

7
global.json Normal file
View File

@@ -0,0 +1,7 @@
{
"sdk": {
"version": "9.0.0",
"rollForward": "latestFeature",
"allowPrerelease": false
}
}