Compare commits
6 Commits
NexusV3-Su
...
actual-v3
Author | SHA1 | Date | |
---|---|---|---|
7b5a895246 | |||
d84f37bea7 | |||
c8c0255edd | |||
95d4b1ba27 | |||
7db71ab013 | |||
4e728d823e |
44
.github/workflows/build.yml
vendored
Normal file
44
.github/workflows/build.yml
vendored
Normal 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
119
Components/ModAPI.cs
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -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");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
@@ -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)
|
||||
{
|
||||
|
@@ -55,7 +55,7 @@ namespace SeamlessClient.Components
|
||||
|
||||
|
||||
|
||||
if(t <= 0)
|
||||
if (t <= 0)
|
||||
return;
|
||||
|
||||
stringBuilder.AppendLine($" [T-{FormatDuration(t)}]");
|
||||
|
@@ -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);
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
|
||||
|
||||
|
@@ -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?
|
||||
*/
|
||||
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
847
Components/ServerSwitcherComponent.cs
Normal file
847
Components/ServerSwitcherComponent.cs
Normal 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}");
|
||||
}
|
||||
}
|
||||
}
|
609
Components/ServerSwitcherComponentOLD.cs
Normal file
609
Components/ServerSwitcherComponentOLD.cs
Normal 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>());
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
}
|
||||
}
|
@@ -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)
|
||||
{
|
||||
|
@@ -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;
|
||||
|
@@ -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()
|
||||
{
|
||||
|
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -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)
|
||||
|
@@ -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")]
|
||||
|
18
README.md
18
README.md
@@ -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.
|
||||
|
||||
|
||||
|
66
Seamless.cs
66
Seamless.cs
@@ -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)
|
||||
|
@@ -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')" />
|
||||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<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>
|
||||
|
||||
|
||||
<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>
|
||||
<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>
|
||||
<OutputFiles Include="$(OutputPath)\*"/>
|
||||
</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" />
|
||||
<Copy SourceFiles="@(OutputFiles)" DestinationFolder="$(AppdataPath)\CringeLauncher\plugins\$(ProjectName)" OverwriteReadOnlyFiles="true"/>
|
||||
</Target>
|
||||
|
||||
|
||||
</Project>
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
7
global.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"sdk": {
|
||||
"version": "9.0.0",
|
||||
"rollForward": "latestFeature",
|
||||
"allowPrerelease": false
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user