fixes for nexus v3
Some checks failed
Build / Compute Version (push) Successful in 6s
Build / Build Nuget package (push) Failing after 6s

This commit is contained in:
zznty
2025-07-28 03:58:25 +07:00
parent 32ae6569a8
commit aa323f1ff6
17 changed files with 1766 additions and 2392 deletions

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

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

View File

@@ -1,77 +1,67 @@
using ProtoBuf; using ProtoBuf;
using Sandbox.Engine.Networking;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using SeamlessClientPlugin.SeamlessTransfer;
using SeamlessClientPlugin.Utilities; using SeamlessClientPlugin.Utilities;
using System.Collections.Generic;
namespace SeamlessClientPlugin.Messages namespace SeamlessClientPlugin.Messages;
public enum ClientMessageType
{ {
public enum ClientMessageType FirstJoin,
{ TransferServer,
FirstJoin, OnlinePlayers
TransferServer,
OnlinePlayers,
}
[ProtoContract]
public class ClientMessage
{
[ProtoMember(1)]
public ClientMessageType MessageType;
[ProtoMember(2)]
public byte[] MessageData;
[ProtoMember(3)]
public long IdentityID;
[ProtoMember(4)]
public ulong SteamID;
[ProtoMember(5)]
public string PluginVersion = "0";
public ClientMessage(ClientMessageType Type)
{
MessageType = Type;
if (MyAPIGateway.Multiplayer != null && !MyAPIGateway.Multiplayer.IsServer)
{
if (MyAPIGateway.Session.LocalHumanPlayer == null)
{
return;
}
IdentityID = MySession.Static?.LocalHumanPlayer?.Identity?.IdentityId ?? 0;
SteamID = MySession.Static?.LocalHumanPlayer?.Id.SteamId ?? 0;
PluginVersion = SeamlessClient.Version;
}
}
public ClientMessage() { }
public void SerializeData<T>(T Data)
{
MessageData = Utility.Serialize(Data);
}
public Transfer GetTransferData()
{
if (MessageData == null)
return default(Transfer);
return Utility.Deserialize<Transfer>(MessageData);
}
public OnlinePlayersMessage GetOnlinePlayers()
{
if (MessageData == null)
return default(OnlinePlayersMessage);
OnlinePlayersMessage msg = Utility.Deserialize<OnlinePlayersMessage>(MessageData);
return msg;
}
}
} }
[ProtoContract]
public class ClientMessage
{
[ProtoMember(1)] public ClientMessageType MessageType;
[ProtoMember(2)] public byte[] MessageData;
[ProtoMember(3)] public long IdentityId;
[ProtoMember(4)] public ulong SteamId;
[ProtoMember(5)] public string PluginVersion = "0";
[ProtoMember(6)] public string NexusVersion;
public ClientMessage(ClientMessageType type)
{
MessageType = type;
IdentityId = MySession.Static?.LocalHumanPlayer?.Identity?.IdentityId ?? 0;
SteamId = MyGameService.UserId;
PluginVersion = SeamlessClient.Version;
}
public ClientMessage()
{
}
public void SerializeData<T>(T data)
{
MessageData = Utility.Serialize(data);
}
public Transfer GetTransferData()
{
if (MessageData == null)
return null;
return Utility.Deserialize<Transfer>(MessageData);
}
public OnlinePlayersMessage GetOnlinePlayers()
{
if (MessageData == null)
return null;
var msg = Utility.Deserialize<OnlinePlayersMessage>(MessageData);
return msg;
}
}

View File

@@ -1,73 +1,48 @@
using ProtoBuf; using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClientPlugin.Messages namespace SeamlessClientPlugin.Messages;
[ProtoContract]
public class OnlinePlayersMessage
{ {
[ProtoMember(12)] public int CurrentServerId;
[ProtoContract] [ProtoMember(10)] public List<OnlineServer> OnlineServers = [];
public class OnlinePlayersMessage
{
[ProtoMember(10)]
public List<OnlineServer> OnlineServers = new List<OnlineServer>();
[ProtoMember(12)]
public int currentServerID;
}
[ProtoContract]
public class OnlineServer
{
[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 OnlineServer() { }
}
[ProtoContract]
public class OnlinePlayer
{
[ProtoMember(1)]
public string PlayerName;
[ProtoMember(2)]
public ulong SteamID;
[ProtoMember(3)]
public long IdentityID;
[ProtoMember(4)]
public int OnServer;
public OnlinePlayer(string PlayerName, ulong SteamID, long IdentityID, int OnServer)
{
this.PlayerName = PlayerName;
this.SteamID = SteamID;
this.IdentityID = IdentityID;
this.OnServer = OnServer;
}
public OnlinePlayer() { }
}
} }
[ProtoContract]
public class OnlineServer
{
[ProtoMember(2)] public List<OnlinePlayer> Players = [];
[ProtoMember(10)] public int ServerId;
[ProtoMember(11)] public string ServerName;
[ProtoMember(3)] public bool ServerRunning = false;
}
[ProtoContract]
public class OnlinePlayer
{
[ProtoMember(3)] public long IdentityId;
[ProtoMember(4)] public int OnServer;
[ProtoMember(1)] public string PlayerName;
[ProtoMember(2)] public ulong SteamId;
public OnlinePlayer(string playerName, ulong steamId, long identityId, int onServer)
{
this.PlayerName = playerName;
this.SteamId = steamId;
this.IdentityId = identityId;
this.OnServer = onServer;
}
public OnlinePlayer()
{
}
}

View File

@@ -1,63 +1,33 @@
using ProtoBuf; using ProtoBuf;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World;
using Sandbox.Graphics.GUI;
using Sandbox.ModAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
using VRage;
using VRage.Game; using VRage.Game;
using VRage.GameServices;
using VRage.Network;
using VRage.Steam;
using VRage.Utils;
using VRageMath;
namespace SeamlessClientPlugin.Messages namespace SeamlessClientPlugin.Messages;
[ProtoContract]
public class Transfer
{ {
[ProtoMember(2)] public string IpAdress;
[ProtoContract] [ProtoMember(7)] public string PlayerName;
public class Transfer
[ProtoMember(9)] public MyObjectBuilder_Toolbar PlayerToolbar;
[ProtoMember(10)] public string ServerName;
[ProtoMember(1)] public ulong TargetServerId;
[ProtoMember(6)] public WorldRequest WorldRequest;
public Transfer(ulong serverId, string ipAdress)
{ {
[ProtoMember(1)] /* This is only called serverside
public ulong TargetServerID; */
[ProtoMember(2)]
public string IPAdress;
[ProtoMember(6)]
public WorldRequest WorldRequest;
[ProtoMember(7)]
public string PlayerName;
[ProtoMember(9)]
public MyObjectBuilder_Toolbar PlayerToolbar;
[ProtoMember(10)]
public string ServerName;
public Transfer(ulong ServerID, string IPAdress)
{
/* This is only called serverside
*/
this.IPAdress = IPAdress;
TargetServerID = ServerID;
}
public Transfer() { }
this.IpAdress = ipAdress;
TargetServerId = serverId;
} }
}
public Transfer()
{
}
}

View File

@@ -1,72 +1,55 @@
using NLog; using System.Reflection;
using NLog;
using ProtoBuf; using ProtoBuf;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Game; using VRage.Game;
using VRage.ObjectBuilders; using VRage.ObjectBuilders.Private;
namespace SeamlessClientPlugin.Messages namespace SeamlessClientPlugin.Messages;
[ProtoContract]
public class WorldRequest
{ {
[ProtoContract] private static readonly Logger Log = LogManager.GetCurrentClassLogger();
public class WorldRequest
[ProtoMember(5)] public MyObjectBuilder_Gps GpsCollection;
[ProtoMember(2)] public long IdentityId;
[ProtoMember(1)] public ulong PlayerId;
[ProtoMember(3)] public string PlayerName;
[ProtoMember(4)] public byte[] WorldData;
public WorldRequest(ulong playerId, long playerIdentity, string name)
{ {
private static readonly Logger Log = LogManager.GetCurrentClassLogger(); this.PlayerId = playerId;
PlayerName = name;
[ProtoMember(1)] IdentityId = playerIdentity;
public ulong PlayerID;
[ProtoMember(2)]
public long IdentityID;
[ProtoMember(3)]
public string PlayerName;
[ProtoMember(4)]
public byte[] WorldData;
[ProtoMember(5)]
public MyObjectBuilder_Gps gpsCollection;
public WorldRequest(ulong PlayerID,long PlayerIdentity, string Name)
{
this.PlayerID = PlayerID;
this.PlayerName = Name;
this.IdentityID = PlayerIdentity;
}
public WorldRequest() { }
public void SerializeWorldData(MyObjectBuilder_World WorldData)
{
MethodInfo CleanupData = typeof(MyMultiplayerServerBase).GetMethod("CleanUpData", BindingFlags.Static | BindingFlags.NonPublic, null, new Type[3]
{
typeof(MyObjectBuilder_World),
typeof(ulong),
typeof(long),
}, null);
object[] Data = new object[] { WorldData, PlayerID, IdentityID };
CleanupData.Invoke(null, Data);
WorldData = (MyObjectBuilder_World)Data[0];
using (MemoryStream memoryStream = new MemoryStream())
{
MyObjectBuilderSerializer.SerializeXML(memoryStream, WorldData, MyObjectBuilderSerializer.XmlCompression.Gzip);
this.WorldData = memoryStream.ToArray();
Log.Warn("Successfully Converted World");
}
}
public MyObjectBuilder_World DeserializeWorldData()
{
MyObjectBuilderSerializer.DeserializeGZippedXML<MyObjectBuilder_World>(new MemoryStream(WorldData), out var objectBuilder);
objectBuilder.Checkpoint.Gps.Dictionary.Add(IdentityID, gpsCollection);
return objectBuilder;
}
} }
}
public WorldRequest()
{
}
public void SerializeWorldData(MyObjectBuilder_World worldData)
{
MyMultiplayerServerBase.CleanUpData(worldData, PlayerId, IdentityId);
using var memoryStream = new MemoryStream();
MyObjectBuilderSerializerKeen.SerializeXML(memoryStream, worldData,
MyObjectBuilderSerializerKeen.XmlCompression.Gzip);
WorldData = memoryStream.ToArray();
Log.Warn("Successfully Converted World");
}
public MyObjectBuilder_World DeserializeWorldData()
{
MyObjectBuilderSerializerKeen.DeserializeGZippedXML<MyObjectBuilder_World>(new MemoryStream(WorldData),
out var objectBuilder);
objectBuilder.Checkpoint.Gps.Dictionary.Add(IdentityId, GpsCollection);
return objectBuilder;
}
}

View File

@@ -1,38 +0,0 @@
using System.Resources;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
// General Information about an assembly is controlled through the following
// set of attributes. Change these attribute values to modify the information
// associated with an assembly.
[assembly: AssemblyTitle("SeamlessClientPlugin")]
[assembly: AssemblyDescription("A seamless client plugin for Nexus compatible servers")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("Casimir")]
[assembly: AssemblyProduct("SeamlessClientPlugin")]
[assembly: AssemblyCopyright("Copyright © 2020")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]
// Setting ComVisible to false makes the types in this assembly not visible
// to COM components. If you need to access a type in this assembly from
// COM, set the ComVisible attribute to true on that type.
[assembly: ComVisible(false)]
// The following GUID is for the ID of the typelib if this project is exposed to COM
[assembly: Guid("102a3d80-b588-43ba-b686-000fa8ff1a0c")]
// Version information for an assembly consists of the following four values:
//
// Major Version
// Minor Version
// Build Number
// Revision
//
// You can specify all the values or you can default the Build and Revision Numbers
// by using the '*' as shown below:
// [assembly: AssemblyVersion("1.0.*")]
[assembly: AssemblyVersion("1.4.02.0")]
[assembly: AssemblyFileVersion("1.4.02.0")]
[assembly: NeutralResourcesLanguage("en")]

View File

@@ -1,21 +1,33 @@
# SeamlessClientPlugin # SeamlessClientPlugin
The seamless client plugin lets you switch between SE servers without a loading screen. This currently *only* works with Nexus compatible servers as all data is shared and synced between them.
The main load time is the time it takes for your client to ping the destination server, and the client to recieve the go-ahead. Any extra time is contributed to entities syncing to the client similar to if you were respawning at the grid. (sometimes it takes forever). I will be looking into pre-loading synced entities to the client in the near future, but atm this was the easier solution. The seamless client plugin lets you switch between SE servers without a loading screen. This currently *only* works with
Nexus compatible servers as all data is shared and synced between them.
This has taken countless hours of testing and debugging to get right. Not to mention the countless hours implementing the server plugin Nexus. If you enjoy this kind of work, please donate [here](https://se-nexus.net/en/Contribute) to help keep this project alive.
The main load time is the time it takes for your client to ping the destination server, and the client to recieve the
go-ahead. Any extra time is contributed to entities syncing to the client similar to if you were respawning at the
grid. (sometimes it takes forever). I will be looking into pre-loading synced entities to the client in the near future,
but atm this was the easier solution.
This has taken countless hours of testing and debugging to get right. Not to mention the countless hours implementing
the server plugin Nexus. If you enjoy this kind of work, please donate [here](https://se-nexus.net/en/Contribute) to
help keep this project alive.
## How it works ## How it works
With Nexus servers, all data is shared between servers. (Factions, Identities, Players, Econ etc) This is a huge benefit as we dont have to go in and reload all identities and factions etc. The next thing that happens is that the server tells the client to switch to the proper server. It then goes in and just re-applies the MyMultiplayerClient to the target server. Of course there is a few other things that must happen to fix any errors or bugs, but that is the main rundown.
With Nexus servers, all data is shared between servers. (Factions, Identities, Players, Econ etc) This is a huge benefit
as we dont have to go in and reload all identities and factions etc. The next thing that happens is that the server
tells the client to switch to the proper server. It then goes in and just re-applies the MyMultiplayerClient to the
target server. Of course there is a few other things that must happen to fix any errors or bugs, but that is the main
rundown.
## How to install ## 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 plguin loader, and check this plugins box to be added to the plugin loaders' active plugin list. (SE
will need to be restarted afterwards)
## Known issues ## 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 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.

View File

@@ -1,218 +1,203 @@
using Sandbox.Engine.Multiplayer; using System.Runtime.CompilerServices;
using Sandbox.Engine.Networking; using CringePlugins.Ui;
using Sandbox.Game;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.Graphics.GUI;
using Sandbox.ModAPI; using Sandbox.ModAPI;
using SeamlessClientPlugin.Messages; using SeamlessClientPlugin.Messages;
using SeamlessClientPlugin.SeamlessTransfer; using SeamlessClientPlugin.SeamlessTransfer;
using SeamlessClientPlugin.Utilities; using SeamlessClientPlugin.Utilities;
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Timers;
using System.Windows.Forms;
using VRage.Game.ModAPI;
using VRage.Input;
using VRage.Plugins; using VRage.Plugins;
using VRage.Utils; using VRage.Utils;
using VRageMath;
using VRageRender;
[assembly: IgnoresAccessChecksTo("Sandbox.Game")]
[assembly: IgnoresAccessChecksTo("Sandbox.Graphics")]
namespace SeamlessClientPlugin;
namespace SeamlessClientPlugin //SendAllMembersDataToClient
public class SeamlessClient : IPlugin
{ {
public const ushort SeamlessClientNetId = 2936;
/* First Step. How does a player join a game?
* First JoinGameInternal is called with the ServersLobbyID.
* In this method, MySessionLoader.UnloadAndExitToMenu() is called. Ultimatley we want to prevent this as we dont want to unload the entire game. Just the basic neccessities.
* Then JoinLobby is called. This looks to be simply a check to see if the client gets a result from the server. If it does, it initilizes a new STATIC/Multiplayer base by: [Static = new MyMultiplayerLobbyClient(lobby, new MySyncLayer(new MyTransportLayer(2)))));]
* Once this above method is done and join is done, we begin the OnJoin()
*
* On join begins by checking join result. Success downloads world and failed sends you back to menu. (May need to use this to send players to menu)
* Download world Requires the multiplayerbase and MyGUIScreenProgress. Which is essentially checking that the user hasnt cliecked left or closed.
*
* StringBuilder text = MyTexts.Get(MyCommonTexts.DialogTextJoiningWorld);
* MyGuiScreenProgress progress = new MyGuiScreenProgress(text, MyCommonTexts.Cancel);
*
* This just looks to be like what happens still with the little GUI popupscreen before load starts
*
* DownloadWorld also contains a method to get the serversessionstate too. We will need to check this before load. Ultimatley once everything has passed join checks Downloadworld is called. (MyMultiplayerClientBase.DownloadWorld)
*
*
* MyMultiplayerClientBase.DownloadWorld simply rasies a static event to the server for world request. [return MyMultiplayerServerBase.WorldRequest;]
*
*
*
*
* MyMultiplayerServerBase.WorldRequest (WorldRequest) This is near the start of where the magic happens. THIS IS ALL RAN SERVERSIDE
* Checks to see if the client has been kicked or banned to prevent world requests. Not sure we really need to worry about this.
*
* Server gets world clears non important data such as player gps. Our player gps gets added on join so we can yeet this.
* Also theres a sendfluch via transport layer. Might need to keep this in mind and use this for our testings
* Theres a CleanUpData that gets called with world and playerid/identity ID. This is probably to limit things and only send whats neccessary
* CLEANUPDATA shows me how to send allplayers data synced on the server.
*
*
* Once we have everythings we use a MS to serialize everything to byte[] and via replication layer we send world to client.
*
*
* BACK TO CLIENT:
* RecieveWorld is called and packet is deserialized and then MyJoinGameHelper.WorldReceived(...,...) is called
*
* WorldReceived does some checks for extra version mismatches and to make sure the CheckPointObjectBuilder isnt null etc.
* Once it passes all these checks, CheckDx11AndJoin(world, MyMutliplayerBase) is called
*
* CheckDx11AndJoin just checks if its a scenario world or not. Forget about this. We can figure that out all later. Then it runs: MySessionLoader.LoadMultiplayerSession(world, multiplayer);
*
*
* MySessionLoader.LoadMultiplayerSession looks to be the start of the join code. It also checks for mod mismatches. (And Downloads them). However once it passes this, MySession.LoadMultiplayer is called.
*
*
*
*
*
*
*
* MySession.LoadMultiplayer (The most important step in world loading)
* Creates new MySession.
* Does settings stuff.
* LoadMembersFromWorld (Loads Clients)
* FixSessionComponentObjectBuilders
*
*
*
* PrepareBaseSession is something we need. Looks like it does some weird stuff to init fonts, Sector Enviroment settings, Loading datacomponents from world, and re-initilizes modAPI stuff
* DeserializeClusters
* Loads planets.
* RegistersChat
*
* LOADWORLD -----------
* Static.BeforeStartComponents
*
*
*
*
* -plugin "../Plugins/SeamlessClientPlugin.dll"
*/
//SendAllMembersDataToClient
public class SeamlessClient : IPlugin // pretend to be the latest one
public static string Version = "3.0.0.10";
public static bool Debug = true;
private static bool _initilized;
public static bool IsSwitching = false;
public static bool RanJoin = false;
public void Init(object gameInstance)
{ {
/* First Step. How does a player join a game? TryShow($"Running Seamless Client Plugin v[{Version}]");
* First JoinGameInternal is called with the ServersLobbyID. MySession.LoadingStep += LoadingProgressStep;
* In this method, MySessionLoader.UnloadAndExitToMenu() is called. Ultimatley we want to prevent this as we dont want to unload the entire game. Just the basic neccessities. }
* Then JoinLobby is called. This looks to be simply a check to see if the client gets a result from the server. If it does, it initilizes a new STATIC/Multiplayer base by: [Static = new MyMultiplayerLobbyClient(lobby, new MySyncLayer(new MyTransportLayer(2)))));]
* Once this above method is done and join is done, we begin the OnJoin() private static void LoadingProgressStep(LoadingProgress step)
* {
* On join begins by checking join result. Success downloads world and failed sends you back to menu. (May need to use this to send players to menu) // i copied it from the new one no guarantee it works properly
* Download world Requires the multiplayerbase and MyGUIScreenProgress. Which is essentially checking that the user hasnt cliecked left or closed. if (step >= LoadingProgress.PROGRESS_STEP8)
* SendFirstJoin();
* StringBuilder text = MyTexts.Get(MyCommonTexts.DialogTextJoiningWorld); }
* MyGuiScreenProgress progress = new MyGuiScreenProgress(text, MyCommonTexts.Cancel);
*
* This just looks to be like what happens still with the little GUI popupscreen before load starts
*
* DownloadWorld also contains a method to get the serversessionstate too. We will need to check this before load. Ultimatley once everything has passed join checks Downloadworld is called. (MyMultiplayerClientBase.DownloadWorld)
*
*
* MyMultiplayerClientBase.DownloadWorld simply rasies a static event to the server for world request. [return MyMultiplayerServerBase.WorldRequest;]
*
*
*
*
* MyMultiplayerServerBase.WorldRequest (WorldRequest) This is near the start of where the magic happens. THIS IS ALL RAN SERVERSIDE
* Checks to see if the client has been kicked or banned to prevent world requests. Not sure we really need to worry about this.
*
* Server gets world clears non important data such as player gps. Our player gps gets added on join so we can yeet this.
* Also theres a sendfluch via transport layer. Might need to keep this in mind and use this for our testings
* Theres a CleanUpData that gets called with world and playerid/identity ID. This is probably to limit things and only send whats neccessary
* CLEANUPDATA shows me how to send allplayers data synced on the server.
*
*
* Once we have everythings we use a MS to serialize everything to byte[] and via replication layer we send world to client.
*
*
* BACK TO CLIENT:
* RecieveWorld is called and packet is deserialized and then MyJoinGameHelper.WorldReceived(...,...) is called
*
* WorldReceived does some checks for extra version mismatches and to make sure the CheckPointObjectBuilder isnt null etc.
* Once it passes all these checks, CheckDx11AndJoin(world, MyMutliplayerBase) is called
*
* CheckDx11AndJoin just checks if its a scenario world or not. Forget about this. We can figure that out all later. Then it runs: MySessionLoader.LoadMultiplayerSession(world, multiplayer);
*
*
* MySessionLoader.LoadMultiplayerSession looks to be the start of the join code. It also checks for mod mismatches. (And Downloads them). However once it passes this, MySession.LoadMultiplayer is called.
*
*
*
*
*
*
*
* MySession.LoadMultiplayer (The most important step in world loading)
* Creates new MySession.
* Does settings stuff.
* LoadMembersFromWorld (Loads Clients)
* FixSessionComponentObjectBuilders
*
*
*
* PrepareBaseSession is something we need. Looks like it does some weird stuff to init fonts, Sector Enviroment settings, Loading datacomponents from world, and re-initilizes modAPI stuff
* DeserializeClusters
* Loads planets.
* RegistersChat
*
* LOADWORLD -----------
* Static.BeforeStartComponents
*
*
*
*
* -plugin "../Plugins/SeamlessClientPlugin.dll"
*/
public void Update()
public static string Version = "1.4.01"; {
public static bool Debug = true; if (MyAPIGateway.Multiplayer == null)
private static bool Initilized = false; return;
if (!_initilized)
public const ushort SeamlessClientNetID = 2936;
public static bool IsSwitching = false;
public static bool RanJoin = false;
public void Init(object gameInstance)
{ {
Patches.GetPatches();
TryShow("Running Seamless Client Plugin v[" + Version + "]"); OnlinePlayers.Patch();
TryShow("Initilizing Communications!");
RunInitilizations();
}
public void Update()
{
if (MyAPIGateway.Multiplayer == null)
return;
if (!Initilized)
{
Patches.GetPatches();
OnlinePlayers.Patch();
TryShow("Initilizing Communications!");
RunInitilizations();
}
}
public static void RunInitilizations()
{
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(SeamlessClientNetID, MessageHandler);
Initilized = true;
}
public static void DisposeInitilizations()
{
MyAPIGateway.Multiplayer?.UnregisterSecureMessageHandler(SeamlessClientNetID, MessageHandler);
Initilized = false;
}
private static void MessageHandler(ushort obj1, byte[] obj2, ulong obj3, bool obj4)
{
try
{
ClientMessage Recieved = Utilities.Utility.Deserialize<ClientMessage>(obj2);
if(Recieved.MessageType == ClientMessageType.FirstJoin)
{
//Server sent a first join message! Send a reply back so the server knows what version we are on
ClientMessage PingServer = new ClientMessage(ClientMessageType.FirstJoin);
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetID, Utilities.Utility.Serialize(PingServer));
}
else if (Recieved.MessageType == ClientMessageType.TransferServer)
{
//Server sent a transfer message! Begin transfer via seamless
Transfer TransferMessage = Recieved.GetTransferData();
ServerPing.StartServerPing(TransferMessage);
}else if(Recieved.MessageType == ClientMessageType.OnlinePlayers)
{
var p = Recieved.GetOnlinePlayers();
OnlinePlayers.AllServers = p.OnlineServers;
OnlinePlayers.currentServer = p.currentServerID;
//TryShow("Recieved Players! "+OnlinePlayers.AllServers.Count);
}
}
catch (Exception ex)
{
TryShow(ex.ToString());
}
}
public static void TryShow(string message)
{
if (MySession.Static?.LocalHumanPlayer != null && Debug)
MyAPIGateway.Utilities?.ShowMessage("NetworkClient", message);
MyLog.Default?.WriteLineAndConsole($"SeamlessClient: {message}");
}
public void Dispose()
{
DisposeInitilizations();
} }
} }
}
public void Dispose()
{
DisposeInitilizations();
}
public static void RunInitilizations()
{
MyAPIGateway.Multiplayer.RegisterSecureMessageHandler(SeamlessClientNetId, MessageHandler);
_initilized = true;
}
public static void DisposeInitilizations()
{
MyAPIGateway.Multiplayer?.UnregisterSecureMessageHandler(SeamlessClientNetId, MessageHandler);
_initilized = false;
}
private static void MessageHandler(ushort obj1, byte[] obj2, ulong obj3, bool obj4)
{
try
{
var recieved = Utility.Deserialize<ClientMessage>(obj2);
switch (recieved.MessageType)
{
// was removed in newer versions, but we leave it here for backwards compatability
case ClientMessageType.FirstJoin:
{
SendFirstJoin();
break;
}
case ClientMessageType.TransferServer:
{
//Server sent a transfer message! Begin transfer via seamless
var transferMessage = recieved.GetTransferData();
ServerPing.StartServerPing(transferMessage);
break;
}
case ClientMessageType.OnlinePlayers:
{
var p = recieved.GetOnlinePlayers();
OnlinePlayers.AllServers = p.OnlineServers;
OnlinePlayers.CurrentServer = p.CurrentServerId;
//TryShow("Recieved Players! "+OnlinePlayers.AllServers.Count);
break;
}
}
}
catch (Exception ex)
{
TryShow(ex.ToString());
}
}
private static void SendFirstJoin()
{
var pingServer = new ClientMessage(ClientMessageType.FirstJoin);
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetId, Utility.Serialize(pingServer));
}
public static void TryShow(string message)
{
NotificationsComponent.SpawnNotification(5f, message);
MyLog.Default?.WriteLineAndConsole($"SeamlessClient: {message}");
}
}

View File

@@ -1,162 +1,49 @@
<?xml version="1.0" encoding="utf-8"?> <Project Sdk="Microsoft.NET.Sdk">
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" /> <PropertyGroup>
<PropertyGroup> <TargetFramework>net9.0-windows</TargetFramework>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration> <RuntimeIdentifier>win-x64</RuntimeIdentifier>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform> <ImplicitUsings>enable</ImplicitUsings>
<ProjectGuid>{102A3D80-B588-43BA-B686-000FA8FF1A0C}</ProjectGuid> <Nullable>disable</Nullable>
<OutputType>Library</OutputType> <EnableWindowsTargeting>true</EnableWindowsTargeting>
<AppDesignerFolder>Properties</AppDesignerFolder> <PackageType>CringePlugin</PackageType>
<RootNamespace>SeamlessClientPlugin</RootNamespace> <RestoreAdditionalProjectSources>https://ng.zznty.ru/v3/index.json</RestoreAdditionalProjectSources>
<AssemblyName>SeamlessClientPlugin</AssemblyName> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<TargetFrameworkVersion>v4.8</TargetFrameworkVersion> <Authors>Casimir</Authors>
<FileAlignment>512</FileAlignment> <PackageId>Plugin.Casimir255.SeamlessClient</PackageId>
<Deterministic>true</Deterministic> <AssemblyName>Plugin.Casimir255.SeamlessClient</AssemblyName>
<TargetFrameworkProfile /> <Title>Nexus Seamless Switcher</Title>
<NuGetPackageImportStamp> <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>
</NuGetPackageImportStamp> <PackageProjectUrl>https://git.zznty.ru/PvE/SeamlessClient</PackageProjectUrl>
</PropertyGroup> <RepositoryUrl>https://git.zznty.ru/PvE/SeamlessClient</RepositoryUrl>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' "> <RepositoryType>git</RepositoryType>
<DebugSymbols>true</DebugSymbols> <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<DebugType>full</DebugType> </PropertyGroup>
<Optimize>false</Optimize>
<OutputPath>F:\SteamLibrary\steamapps\common\SpaceEngineers\Plugins\</OutputPath> <ItemGroup>
<DefineConstants>DEBUG;TRACE</DefineConstants> <Publicize Include="Sandbox.Game;Sandbox.Graphics" IncludeVirtualMembers="false" IncludeCompilerGeneratedMembers="false" />
<ErrorReport>prompt</ErrorReport> </ItemGroup>
<WarningLevel>4</WarningLevel>
<PlatformTarget>x64</PlatformTarget> <ItemGroup>
</PropertyGroup> <PackageReference Include="CringePlugins" Version="*" ExcludeAssets="runtime; native"/>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' "> <PackageReference Include="Krafs.Publicizer" Version="2.3.0">
<DebugType>pdbonly</DebugType> <PrivateAssets>all</PrivateAssets>
<Optimize>true</Optimize> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<OutputPath>bin\Release\</OutputPath> </PackageReference>
<DefineConstants>TRACE</DefineConstants> <PackageReference Include="Microsoft.CodeAnalysis.CSharp" Version="4.11.0" ExcludeAssets="runtime; native" PrivateAssets="all"/>
<ErrorReport>prompt</ErrorReport> <PackageReference Include="Steamworks.NET" Version="20.1.0" ExcludeAssets="runtime; native" PrivateAssets="all"/>
<WarningLevel>4</WarningLevel> </ItemGroup>
</PropertyGroup>
<PropertyGroup>
<AutoGenerateBindingRedirects>false</AutoGenerateBindingRedirects> <Target Name="CopyFiles" AfterTargets="PostBuildEvent">
</PropertyGroup> <PropertyGroup>
<ItemGroup> <AppdataPath>$([System.Environment]::GetFolderPath(SpecialFolder.ApplicationData))</AppdataPath>
<Reference Include="0Harmony, Version=2.2.1.0, Culture=neutral, processorArchitecture=MSIL"> </PropertyGroup>
<HintPath>..\Nexus\packages\Lib.Harmony.2.2.1\lib\net48\0Harmony.dll</HintPath> <ItemGroup>
</Reference> <OutputFiles Include="$(OutputPath)\*"/>
<Reference Include="NLog"> </ItemGroup>
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\NLog.dll</HintPath> <Copy SourceFiles="@(OutputFiles)" DestinationFolder="$(AppdataPath)\CringeLauncher\plugins\$(ProjectName)" OverwriteReadOnlyFiles="true"/>
<Private>False</Private> </Target>
</Reference>
<Reference Include="ProtoBuf.Net">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\ProtoBuf.Net.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="ProtoBuf.Net.Core">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\ProtoBuf.Net.Core.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Common">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\Sandbox.Common.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Game">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\Sandbox.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="Sandbox.Graphics">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\Sandbox.Graphics.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.Game">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\SpaceEngineers.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="SpaceEngineers.ObjectBuilders.XmlSerializers">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\SpaceEngineers.ObjectBuilders.XmlSerializers.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="System">
<Private>False</Private>
</Reference>
<Reference Include="System.Core">
<Private>False</Private>
</Reference>
<Reference Include="System.IO.Compression, Version=4.1.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\Nexus\packages\System.IO.Compression.4.3.0\lib\net46\System.IO.Compression.dll</HintPath>
<Private>True</Private>
<Private>False</Private>
</Reference>
<Reference Include="System.IO.Compression.FileSystem" />
<Reference Include="System.IO.Compression.ZipFile, Version=4.0.2.0, Culture=neutral, PublicKeyToken=b77a5c561934e089, processorArchitecture=MSIL">
<HintPath>..\Nexus\packages\System.IO.Compression.ZipFile.4.3.0\lib\net46\System.IO.Compression.ZipFile.dll</HintPath>
<Private>True</Private>
<Private>False</Private>
</Reference>
<Reference Include="System.Runtime.InteropServices.RuntimeInformation">
<Private>False</Private>
</Reference>
<Reference Include="System.Runtime.Serialization" />
<Reference Include="System.Runtime.Serialization.Json" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xaml" />
<Reference Include="System.Xml.Linq">
<Private>False</Private>
</Reference>
<Reference Include="System.Data.DataSetExtensions">
<Private>False</Private>
</Reference>
<Reference Include="Microsoft.CSharp">
<Private>False</Private>
</Reference>
<Reference Include="System.Data">
<Private>False</Private>
</Reference>
<Reference Include="System.Net.Http">
<Private>False</Private>
</Reference>
<Reference Include="System.Xml">
<Private>False</Private>
</Reference>
<Reference Include="VRage">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Game">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.Game.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Library">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.Library.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Math">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.Render.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Steam">
<HintPath>..\..\..\Desktop\TorchServers\torch-server1\DedicatedServer64\VRage.Steam.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Messages\ClientMessages.cs" />
<Compile Include="Messages\OnlinePlayersMessage.cs" />
<Compile Include="Messages\WorldRequest.cs" />
<Compile Include="SeamlessClient.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SeamlessTransfer\ModLoader.cs" />
<Compile Include="SeamlessTransfer\MyScriptManagerLoader.cs" />
<Compile Include="SeamlessTransfer\PingServer.cs" />
<Compile Include="Messages\Transfer.cs" />
<Compile Include="Utilities\OnlinePlayers.cs" />
<Compile Include="Utilities\Patches.cs" />
<Compile Include="SeamlessTransfer\SwitchServers.cs" />
<Compile Include="Utilities\Utility.cs" />
</ItemGroup>
<ItemGroup>
<None Include="packages.config" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
</Project> </Project>

View File

@@ -1,173 +1,136 @@
using Sandbox.Definitions; using Sandbox.Engine.Networking;
using Sandbox.Engine.Networking;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.ModAPI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using VRage.Game; using VRage.Game;
using VRage.Game.GUI; using VRage.Game.GUI;
using VRage.GameServices;
namespace SeamlessClientPlugin.SeamlessTransfer namespace SeamlessClientPlugin.SeamlessTransfer;
public static class ModLoader
{ {
public static class ModLoader /* Mod loader should download and load missing mods for target server, and unload ones that arent needed
*
* Sandbox.Game.World.MyScriptManager.LoadData() is where modded scripts get loaded and added
* Sandbox.Game.World.MySession() calls MyDefinitionManager.Static.LoadData(mods); which loads mod data files
*
*
* Need to be called in the following order:
* ScriptManager.Init(checkpoint.ScriptManagerData);
* MyDefinitionManager.Static.LoadData(checkpoint.Mods);
* PreloadModels(sector);
*
*
*/
//Mods that are currently loaded in this instance.
private static List<MyObjectBuilder_Checkpoint.ModItem> _currentLoadedMods = [];
//Mods that we need to Load
private static readonly List<MyObjectBuilder_Checkpoint.ModItem> TargetLoadMods = [];
//Mods that we need to UnLoad
private static readonly List<MyObjectBuilder_Checkpoint.ModItem> TargetUnLoadMods = [];
private static bool _finishedDownloadingMods;
private static bool _downloadSuccess;
private static DateTime _downloadTimeout;
public static void DownloadNewMods(List<MyObjectBuilder_Checkpoint.ModItem> target)
{ {
/* Mod loader should download and load missing mods for target server, and unload ones that arent needed _currentLoadedMods = MySession.Static.Mods;
*
* Sandbox.Game.World.MyScriptManager.LoadData() is where modded scripts get loaded and added
* Sandbox.Game.World.MySession() calls MyDefinitionManager.Static.LoadData(mods); which loads mod data files
*
*
* Need to be called in the following order:
* ScriptManager.Init(checkpoint.ScriptManagerData);
* MyDefinitionManager.Static.LoadData(checkpoint.Mods);
* PreloadModels(sector);
*
*
*/
//Mods that are currently loaded in this instance. //Loop through our current mods
private static List<MyObjectBuilder_Checkpoint.ModItem> CurrentLoadedMods = new List<MyObjectBuilder_Checkpoint.ModItem>(); foreach (var mod in _currentLoadedMods)
if (!target.Contains(mod))
TargetUnLoadMods.Add(mod);
//Mods that we need to Load
private static List<MyObjectBuilder_Checkpoint.ModItem> TargetLoadMods = new List<MyObjectBuilder_Checkpoint.ModItem>();
//Mods that we need to UnLoad
private static List<MyObjectBuilder_Checkpoint.ModItem> TargetUnLoadMods = new List<MyObjectBuilder_Checkpoint.ModItem>();
private static bool FinishedDownloadingMods = false;
private static bool DownloadSuccess = false;
private static DateTime DownloadTimeout;
private static MethodInfo PrepareBaseSession = typeof(MySession).GetMethod("PreloadModels", BindingFlags.Static | BindingFlags.NonPublic);
private static FieldInfo ScriptManager = typeof(MySession).GetField("ScriptManager", BindingFlags.Instance | BindingFlags.Public);
public static void DownloadNewMods(List<MyObjectBuilder_Checkpoint.ModItem> Target)
{
CurrentLoadedMods = MySession.Static.Mods;
//Loop through our current mods
foreach(var mod in CurrentLoadedMods)
{
if (!Target.Contains(mod))
TargetUnLoadMods.Add(mod);
}
//Loop through our TargetMods
foreach(var mod in Target)
{
if (!CurrentLoadedMods.Contains(mod))
TargetLoadMods.Add(mod);
}
DownloadTimeout = DateTime.Now + TimeSpan.FromMinutes(5);
SeamlessClient.TryShow("Downloading New Mods");
MyWorkshop.DownloadModsAsync(TargetLoadMods, ModDownloadingFinished);
}
private static void ModDownloadingFinished(bool Success)
{
if (Success)
{
SeamlessClient.TryShow("Mod Downloading Finished!");
FinishedDownloadingMods = true;
DownloadSuccess = true;
//May need to wait seamless loading if mods have yet to finish downloading
}
else
{
DownloadSuccess = false;
FinishedDownloadingMods = true;
}
}
public static void ReadyModSwitch(MyObjectBuilder_Checkpoint checkpoint, MyObjectBuilder_Sector sector)
{
while (!FinishedDownloadingMods)
{
//Break out of loop
if (DownloadTimeout < DateTime.Now)
break;
Thread.Sleep(20);
}
FinishedDownloadingMods = false;
//Skip mod switch
if (!DownloadSuccess)
return;
//Create new script manager?
ScriptManager.SetValue(MySession.Static, new MyScriptManager());
MyGuiTextures.Static.Unload();
MySession.Static.ScriptManager.Init(checkpoint.ScriptManagerData);
//MyDefinitionManager.Static.LoadData(TargetServerMods);
PrepareBaseSession.Invoke(null, new object[] { sector });
MyLocalCache.PreloadLocalInventoryConfig();
//SeamlessClient.TryShow("Finished transfering!");
// PrepareBaseSession.Invoke(MySession.Static, new object[] { TargetServerMods, null });
}
private static void UnloadOldScripts()
{
// MySandboxGame.Log.WriteLine(string.Format("Script loaded: {0}", value.FullName));
int amount = 0;
foreach (var mod in TargetUnLoadMods)
{
var val = MySession.Static.ScriptManager.Scripts.FirstOrDefault(x => x.Value.FullName.Contains(mod.PublishedFileId.ToString()));
MySession.Static.ScriptManager.Scripts.Remove(val.Key);
amount++;
}
SeamlessClient.TryShow($"Removed {amount} old scripts!");
}
//Loop through our TargetMods
foreach (var mod in target)
if (!_currentLoadedMods.Contains(mod))
TargetLoadMods.Add(mod);
_downloadTimeout = DateTime.Now + TimeSpan.FromMinutes(5);
SeamlessClient.TryShow("Downloading New Mods");
MyWorkshop.DownloadModsAsync(TargetLoadMods, ModDownloadingFinished);
} }
}
private static void ModDownloadingFinished(MyGameServiceCallResult result)
{
if (result == MyGameServiceCallResult.OK)
{
SeamlessClient.TryShow("Mod Downloading Finished!");
_finishedDownloadingMods = true;
_downloadSuccess = true;
//May need to wait seamless loading if mods have yet to finish downloading
}
else
{
_downloadSuccess = false;
_finishedDownloadingMods = true;
}
}
public static void ReadyModSwitch(MyObjectBuilder_Checkpoint checkpoint, MyObjectBuilder_Sector sector)
{
while (!_finishedDownloadingMods)
{
//Break out of loop
if (_downloadTimeout < DateTime.Now)
break;
Thread.Sleep(20);
}
_finishedDownloadingMods = false;
//Skip mod switch
if (!_downloadSuccess)
return;
//Create new script manager?
MySession.Static.ScriptManager = new();
MyGuiTextures.Static.Unload();
MySession.Static.ScriptManager.Init(checkpoint.ScriptManagerData);
//MyDefinitionManager.Static.LoadData(TargetServerMods);
MySession.PreloadModels(sector);
MyLocalCache.PreloadLocalInventoryConfig();
//SeamlessClient.TryShow("Finished transfering!");
// PrepareBaseSession.Invoke(MySession.Static, new object[] { TargetServerMods, null });
}
private static void UnloadOldScripts()
{
// MySandboxGame.Log.WriteLine(string.Format("Script loaded: {0}", value.FullName));
var amount = 0;
foreach (var mod in TargetUnLoadMods)
{
var val = MySession.Static.ScriptManager.Scripts.FirstOrDefault(x =>
x.Value.FullName.Contains(mod.PublishedFileId.ToString()));
MySession.Static.ScriptManager.Scripts.Remove(val.Key);
amount++;
}
SeamlessClient.TryShow($"Removed {amount} old scripts!");
}
}

View File

@@ -1,83 +1,73 @@
using Sandbox; namespace SeamlessClientPlugin.SeamlessTransfer;
using Sandbox.Game.World;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClientPlugin.SeamlessTransfer public class MyScriptManagerLoader
{ {
public class MyScriptManagerLoader /*
public void LoadData(MyScriptManager __instance)
{ {
/* MySandboxGame.Log.WriteLine("MyScriptManager.LoadData() - START");
public void LoadData(MyScriptManager __instance) MySandboxGame.Log.IncreaseIndent();
{ MyScriptManager.Static = __instance;
MySandboxGame.Log.WriteLine("MyScriptManager.LoadData() - START"); __instance.Scripts.Clear();
MySandboxGame.Log.IncreaseIndent(); __instance.EntityScripts.Clear();
MyScriptManager.Static = __instance; __instance.SubEntityScripts.Clear();
__instance.Scripts.Clear();
__instance.EntityScripts.Clear();
__instance.SubEntityScripts.Clear();
TryAddEntityScripts(MyModContext.BaseGame, MyPlugins.SandboxAssembly); TryAddEntityScripts(MyModContext.BaseGame, MyPlugins.SandboxAssembly);
TryAddEntityScripts(MyModContext.BaseGame, MyPlugins.SandboxGameAssembly); TryAddEntityScripts(MyModContext.BaseGame, MyPlugins.SandboxGameAssembly);
if (MySession.Static.CurrentPath != null) if (MySession.Static.CurrentPath != null)
{ {
LoadScripts(MySession.Static.CurrentPath, MyModContext.BaseGame); LoadScripts(MySession.Static.CurrentPath, MyModContext.BaseGame);
} }
if (MySession.Static.Mods != null) if (MySession.Static.Mods != null)
{ {
bool isServer = Sync.IsServer; bool isServer = Sync.IsServer;
foreach (MyObjectBuilder_Checkpoint.ModItem mod in MySession.Static.Mods) foreach (MyObjectBuilder_Checkpoint.ModItem mod in MySession.Static.Mods)
{ {
bool flag = false; bool flag = false;
if (mod.IsModData()) if (mod.IsModData())
{ {
ListReader<string> tags = mod.GetModData().Tags; ListReader<string> tags = mod.GetModData().Tags;
if (tags.Contains(MySteamConstants.TAG_SERVER_SCRIPTS) && !isServer) if (tags.Contains(MySteamConstants.TAG_SERVER_SCRIPTS) && !isServer)
{ {
continue; continue;
} }
flag = tags.Contains(MySteamConstants.TAG_NO_SCRIPTS); flag = tags.Contains(MySteamConstants.TAG_NO_SCRIPTS);
} }
MyModContext myModContext = (MyModContext)mod.GetModContext(); MyModContext myModContext = (MyModContext)mod.GetModContext();
try try
{ {
LoadScripts(mod.GetPath(), myModContext); LoadScripts(mod.GetPath(), myModContext);
} }
catch (MyLoadingRuntimeCompilationNotSupportedException) catch (MyLoadingRuntimeCompilationNotSupportedException)
{ {
if (flag) if (flag)
{ {
MyVRage.Platform.Scripting.ReportIncorrectBehaviour(MyCommonTexts.ModRuleViolation_RuntimeScripts); MyVRage.Platform.Scripting.ReportIncorrectBehaviour(MyCommonTexts.ModRuleViolation_RuntimeScripts);
continue; continue;
} }
throw; throw;
} }
catch (Exception ex2) catch (Exception ex2)
{ {
MyLog.Default.WriteLine(string.Format("Fatal error compiling {0}:{1} - {2}. This item is likely not a mod and should be removed from the mod list.", myModContext.ModServiceName, myModContext.ModId, myModContext.ModName)); MyLog.Default.WriteLine(string.Format("Fatal error compiling {0}:{1} - {2}. This item is likely not a mod and should be removed from the mod list.", myModContext.ModServiceName, myModContext.ModId, myModContext.ModName));
MyLog.Default.WriteLine(ex2); MyLog.Default.WriteLine(ex2);
throw; throw;
} }
} }
} }
foreach (Assembly value in Scripts.Values) foreach (Assembly value in Scripts.Values)
{ {
if (MyFakes.ENABLE_TYPES_FROM_MODS) if (MyFakes.ENABLE_TYPES_FROM_MODS)
{ {
MyGlobalTypeMetadata.Static.RegisterAssembly(value); MyGlobalTypeMetadata.Static.RegisterAssembly(value);
} }
MySandboxGame.Log.WriteLine(string.Format("Script loaded: {0}", value.FullName)); MySandboxGame.Log.WriteLine(string.Format("Script loaded: {0}", value.FullName));
} }
MyTextSurfaceScriptFactory.LoadScripts(); MyTextSurfaceScriptFactory.LoadScripts();
MyUseObjectFactory.RegisterAssemblyTypes(Scripts.Values.ToArray()); MyUseObjectFactory.RegisterAssemblyTypes(Scripts.Values.ToArray());
MySandboxGame.Log.DecreaseIndent(); MySandboxGame.Log.DecreaseIndent();
MySandboxGame.Log.WriteLine("MyScriptManager.LoadData() - END"); MySandboxGame.Log.WriteLine("MyScriptManager.LoadData() - END");
} }
*/ */
}
}
}

View File

@@ -1,51 +1,41 @@
using Sandbox.Engine.Networking; using SeamlessClientPlugin.Messages;
using Sandbox.Game.Multiplayer;
using SeamlessClientPlugin.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using VRage.GameServices; using VRage.GameServices;
namespace SeamlessClientPlugin.SeamlessTransfer namespace SeamlessClientPlugin.SeamlessTransfer;
public class ServerPing
{ {
public class ServerPing private static Transfer _transfer;
private static WorldRequest Request => _transfer.WorldRequest;
public static void StartServerPing(Transfer clientTransfer)
{ {
// We need to first ping the server to make sure its running and so we can get a connection
private static WorldRequest Request { get { return Transfer.WorldRequest; } } _transfer = clientTransfer;
private static Transfer Transfer;
public static void StartServerPing(Transfer ClientTransfer) if (_transfer.TargetServerId == 0)
{ {
// We need to first ping the server to make sure its running and so we can get a connection SeamlessClient.TryShow("This is not a valid server!");
Transfer = ClientTransfer; return;
if (Transfer.TargetServerID == 0)
{
SeamlessClient.TryShow("This is not a valid server!");
return;
}
MyGameServerItem E = new MyGameServerItem();
E.ConnectionString = Transfer.IPAdress;
E.SteamID = Transfer.TargetServerID;
E.Name = Transfer.ServerName;
SeamlessClient.TryShow("Beginning Redirect to server: " + Transfer.TargetServerID);
var world = Request.DeserializeWorldData();
SwitchServers Switcher = new SwitchServers(E, world);
Switcher.BeginSwitch();
} }
var e = new MyGameServerItem();
e.ConnectionString = _transfer.IpAdress;
e.SteamID = _transfer.TargetServerId;
e.Name = _transfer.ServerName;
SeamlessClient.TryShow($"Beginning Redirect to server: {_transfer.TargetServerId}");
var world = Request.DeserializeWorldData();
var switcher = new SwitchServers(e, world);
switcher.BeginSwitch();
} }
} }

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -1,439 +1,297 @@
using HarmonyLib; using System.Reflection;
using Sandbox.Engine.Analytics; using System.Text;
using HarmonyLib;
using Sandbox.Engine.Multiplayer; using Sandbox.Engine.Multiplayer;
using Sandbox.Engine.Networking; using Sandbox.Engine.Networking;
using Sandbox.Game;
using Sandbox.Game.Entities;
using Sandbox.Game.Gui; using Sandbox.Game.Gui;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World; using Sandbox.Game.World;
using Sandbox.Game.World.Generator; using Sandbox.Game.World.Generator;
using Sandbox.Graphics; using Sandbox.Graphics;
using Sandbox.Graphics.GUI; using Sandbox.Graphics.GUI;
using Sandbox.ModAPI;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage; using VRage;
using VRage.Collections;
using VRage.FileSystem; using VRage.FileSystem;
using VRage.Game; using VRage.Game;
using VRage.Game.Entity;
using VRage.GameServices; using VRage.GameServices;
using VRage.Network; using VRage.Network;
using VRage.Utils; using VRage.Utils;
using VRageMath; using VRageMath;
namespace SeamlessClientPlugin.SeamlessTransfer namespace SeamlessClientPlugin.Utilities;
public static class Patches
{ {
public static class Patches /* Harmony Patcher */
private static readonly Harmony Patcher = new("SeamlessClientPatcher");
/* WorldGenerator */
public static MethodInfo UnloadProceduralWorldGenerator { get; private set; }
public static event EventHandler<JoinResultMsg> OnJoinEvent;
public static void GetPatches()
{ {
/* All internal classes Types */ //Get reflected values and store them
public static readonly Type ClientType = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClient, Sandbox.Game");
public static readonly Type SyncLayerType = Type.GetType("Sandbox.Game.Multiplayer.MySyncLayer, Sandbox.Game");
public static readonly Type MyTransportLayerType = Type.GetType("Sandbox.Engine.Multiplayer.MyTransportLayer, Sandbox.Game");
public static readonly Type MySessionType = Type.GetType("Sandbox.Game.World.MySession, Sandbox.Game");
public static readonly Type VirtualClientsType = Type.GetType("Sandbox.Engine.Multiplayer.MyVirtualClients, Sandbox.Game");
public static readonly Type GUIScreenChat = Type.GetType("Sandbox.Game.Gui.MyGuiScreenChat, Sandbox.Game");
public static readonly Type MyMultiplayerClientBase = Type.GetType("Sandbox.Engine.Multiplayer.MyMultiplayerClientBase, Sandbox.Game");
public static readonly Type MySteamServerDiscovery = Type.GetType("VRage.Steam.MySteamServerDiscovery, Vrage.Steam");
/* Harmony Patcher */ /* Get Methods */
private static Harmony Patcher = new Harmony("SeamlessClientPatcher"); var onJoin = GetMethod(typeof(MyMultiplayerClient), "OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance);
var loadingAction = GetMethod(typeof(MySessionLoader), "LoadMultiplayerSession",
BindingFlags.Public | BindingFlags.Static);
UnloadProceduralWorldGenerator = GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData",
BindingFlags.Instance | BindingFlags.NonPublic);
/* Static Contructors */ //MethodInfo ConnectToServer = GetMethod(typeof(MyGameService), "ConnectToServer", BindingFlags.Static | BindingFlags.Public);
public static ConstructorInfo ClientConstructor { get; private set; } var loadingScreenDraw = GetMethod(typeof(MyGuiScreenLoading), "DrawInternal",
public static ConstructorInfo SyncLayerConstructor { get; private set; } BindingFlags.Instance | BindingFlags.NonPublic);
public static ConstructorInfo TransportLayerConstructor { get; private set; }
public static ConstructorInfo MySessionConstructor { get; private set; }
public static ConstructorInfo MyMultiplayerClientBaseConstructor { get; private set; }
//Test patches
/* Static FieldInfos and PropertyInfos */ //MethodInfo SetPlayerDed = GetMethod(typeof(MyPlayerCollection), "SetPlayerDeadInternal", BindingFlags.Instance | BindingFlags.NonPublic);
public static PropertyInfo MySessionLayer { get; private set; }
public static FieldInfo VirtualClients { get; private set; }
public static FieldInfo AdminSettings { get; private set; }
public static FieldInfo RemoteAdminSettings { get; private set; }
public static FieldInfo MPlayerGPSCollection { get; private set; }
/* Static MethodInfos */ Patcher.Patch(loadingScreenDraw, new HarmonyMethod(GetPatchMethod(nameof(DrawInternal))));
public static MethodInfo InitVirtualClients { get; private set; } Patcher.Patch(onJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined))));
public static MethodInfo LoadPlayerInternal { get; private set; } Patcher.Patch(loadingAction, new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession))));
public static MethodInfo LoadMembersFromWorld { get; private set; } //Patcher.Patch(SetPlayerDed, prefix: new HarmonyMethod(GetPatchMethod(nameof(SetPlayerDeadInternal))));
public static MethodInfo LoadMultiplayer { get; private set; } }
public static MethodInfo GPSRegisterChat { get; private set; }
public static MethodInfo SendPlayerData;
public static event EventHandler<JoinResultMsg> OnJoinEvent; private static MethodInfo GetPatchMethod(string v)
{
return typeof(Patches).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}
private static void OnUserJoined(ref JoinResultMsg msg)
/* WorldGenerator */ {
public static MethodInfo UnloadProceduralWorldGenerator; if (msg.JoinResult == JoinResult.OK)
//SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString());
//Invoke the switch event
OnJoinEvent?.Invoke(null, msg);
}
private static bool OnConnectToServer(MyGameServerItem server, Action<JoinResult> onDone)
{
if (SeamlessClient.IsSwitching)
return false;
private static FieldInfo MBuffer;
public static void GetPatches() return true;
}
/* Patch Utils */
private static MethodInfo GetMethod(Type type, string methodName, BindingFlags flags)
{
var foundMethod = type.GetMethod(methodName, flags);
if (foundMethod == null)
throw new NullReferenceException($"Method for {methodName} is null!");
return foundMethod;
}
private static FieldInfo GetField(Type type, string fieldName, BindingFlags flags)
{
var foundField = type.GetField(fieldName, flags);
if (foundField == null)
throw new NullReferenceException($"Field for {fieldName} is null!");
return foundField;
}
private static PropertyInfo GetProperty(Type type, string propertyName, BindingFlags flags)
{
var foundProperty = type.GetProperty(propertyName, flags);
if (foundProperty == null)
throw new NullReferenceException($"Property for {propertyName} is null!");
return foundProperty;
}
private static ConstructorInfo GetConstructor(Type type, BindingFlags flags, Type[] types)
{
var foundConstructor = type.GetConstructor(flags, null, types, null);
if (foundConstructor == null)
throw new NullReferenceException($"Contructor for {type.Name} is null!");
return foundConstructor;
}
#region LoadingScreen
/* Loading Screen Stuff */
private static string _loadingScreenTexture;
private static string _serverName;
private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession)
{
//
MyLog.Default.WriteLine("LoadSession() - Start");
if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, false))
{ {
//Get reflected values and store them MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(
messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError),
messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer)));
MyLog.Default.WriteLine("LoadSession() - End");
return false;
/* Get Constructors */
ClientConstructor = GetConstructor(ClientType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[2] { typeof(MyGameServerItem), SyncLayerType });
SyncLayerConstructor = GetConstructor(SyncLayerType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[1] { MyTransportLayerType });
TransportLayerConstructor = GetConstructor(MyTransportLayerType, BindingFlags.Instance | BindingFlags.Public, new Type[1] { typeof(int) });
MySessionConstructor = GetConstructor(MySessionType, BindingFlags.Instance | BindingFlags.NonPublic, new Type[2] { typeof(MySyncLayer), typeof(bool) });
MyMultiplayerClientBaseConstructor = GetConstructor(MyMultiplayerClientBase, BindingFlags.Instance | BindingFlags.NonPublic, new Type[] { typeof(MySyncLayer) });
/* Get Fields and Properties */
MySessionLayer = GetProperty(typeof(MySession), "SyncLayer", BindingFlags.Instance | BindingFlags.Public);
VirtualClients = GetField(typeof(MySession), "VirtualClients", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
AdminSettings = GetField(typeof(MySession), "m_adminSettings", BindingFlags.Instance | BindingFlags.NonPublic);
RemoteAdminSettings = GetField(typeof(MySession), "m_remoteAdminSettings", BindingFlags.Instance | BindingFlags.NonPublic);
MPlayerGPSCollection = GetField(typeof(MyPlayerCollection), "m_players", BindingFlags.Instance | BindingFlags.NonPublic);
MBuffer = GetField(MyTransportLayerType, "m_buffer", BindingFlags.Instance | BindingFlags.NonPublic);
/* Get Methods */
MethodInfo OnJoin = GetMethod(ClientType, "OnUserJoined", BindingFlags.NonPublic | BindingFlags.Instance);
MethodInfo LoadingAction = GetMethod(typeof(MySessionLoader), "LoadMultiplayerSession", BindingFlags.Public | BindingFlags.Static);
InitVirtualClients = GetMethod(VirtualClientsType, "Init", BindingFlags.Instance | BindingFlags.Public);
LoadPlayerInternal = GetMethod(typeof(MyPlayerCollection), "LoadPlayerInternal", BindingFlags.Instance | BindingFlags.NonPublic);
LoadMembersFromWorld = GetMethod(typeof(MySession), "LoadMembersFromWorld", BindingFlags.NonPublic | BindingFlags.Instance);
LoadMultiplayer = GetMethod(typeof(MySession), "LoadMultiplayer", BindingFlags.Static | BindingFlags.NonPublic);
SendPlayerData = GetMethod(ClientType, "SendPlayerData", BindingFlags.Instance | BindingFlags.NonPublic);
UnloadProceduralWorldGenerator = GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData", BindingFlags.Instance | BindingFlags.NonPublic);
GPSRegisterChat = GetMethod(typeof(MyGpsCollection), "RegisterChat", BindingFlags.Instance | BindingFlags.NonPublic);
//MethodInfo ConnectToServer = GetMethod(typeof(MyGameService), "ConnectToServer", BindingFlags.Static | BindingFlags.Public);
MethodInfo LoadingScreenDraw = GetMethod(typeof(MyGuiScreenLoading), "DrawInternal", BindingFlags.Instance | BindingFlags.NonPublic);
//Test patches
//MethodInfo SetPlayerDed = GetMethod(typeof(MyPlayerCollection), "SetPlayerDeadInternal", BindingFlags.Instance | BindingFlags.NonPublic);
Patcher.Patch(LoadingScreenDraw, prefix: new HarmonyMethod(GetPatchMethod(nameof(DrawInternal))));
Patcher.Patch(OnJoin, postfix: new HarmonyMethod(GetPatchMethod(nameof(OnUserJoined))));
Patcher.Patch(LoadingAction, prefix: new HarmonyMethod(GetPatchMethod(nameof(LoadMultiplayerSession))));
//Patcher.Patch(SetPlayerDed, prefix: new HarmonyMethod(GetPatchMethod(nameof(SetPlayerDeadInternal))));
} }
MyLog.Default.WriteLine("Seamless Downloading mods!");
MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, result =>
private static MethodInfo GetPatchMethod(string v)
{ {
return typeof(Patches).GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic); if (result == MyGameServiceCallResult.OK)
}
#region LoadingScreen
/* Loading Screen Stuff */
private static string LoadingScreenTexture = null;
private static string ServerName;
private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession)
{
//
MyLog.Default.WriteLine("LoadSession() - Start");
if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, allowLocalMods: false))
{ {
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer))); MyScreenManager.CloseAllScreensNowExcept(null);
MyLog.Default.WriteLine("LoadSession() - End"); MyGuiSandbox.Update(16);
return false; if (MySession.Static != null)
{
MySession.Static.Unload();
MySession.Static = null;
}
_serverName = multiplayerSession.HostName;
GetCustomLoadingScreenPath(world.Checkpoint.Mods, out _loadingScreenTexture);
MySessionLoader.StartLoading(delegate
{
MySession.LoadMultiplayer(world, multiplayerSession);
});
} }
else
MyLog.Default.WriteLine("Seamless Downloading mods!");
MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, delegate (bool success)
{
if (success)
{
MyScreenManager.CloseAllScreensNowExcept(null);
MyGuiSandbox.Update(16);
if (MySession.Static != null)
{
MySession.Static.Unload();
MySession.Static = null;
}
ServerName = multiplayerSession.HostName;
GetCustomLoadingScreenPath(world.Checkpoint.Mods, out LoadingScreenTexture);
MySessionLoader.StartLoading(delegate
{
LoadMultiplayer.Invoke(null, new object[] { world, multiplayerSession });
//MySession.LoadMultiplayer(world, multiplayerSession);
}, null, null, null);
}
else
{
multiplayerSession.Dispose();
MySessionLoader.UnloadAndExitToMenu();
if (MyGameService.IsOnline)
{
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed)));
}
else
{
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: new StringBuilder(string.Format(MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline), MySession.GameServiceName))));
}
}
MyLog.Default.WriteLine("LoadSession() - End");
}, delegate
{ {
multiplayerSession.Dispose(); multiplayerSession.Dispose();
MySessionLoader.UnloadAndExitToMenu(); MySessionLoader.UnloadAndExitToMenu();
}); if (MyGameService.IsOnline)
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(
return false; messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError),
} messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed)));
else
private static bool DrawInternal(MyGuiScreenLoading __instance) MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(
{ messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError),
messageText: new StringBuilder(string.Format(
//If we dont have a custom loading screen texture, do not do the special crap below MyTexts.GetString(MyCommonTexts.DialogTextDownloadModsFailedSteamOffline),
if (string.IsNullOrEmpty(LoadingScreenTexture)) MySession.GameServiceName))));
return true;
float m_transitionAlpha = (float)typeof(MyGuiScreenBase).GetField("m_transitionAlpha", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance);
string m_font = "LoadingScreen";
//MyGuiControlMultilineText m_multiTextControl = (MyGuiControlMultilineText)typeof(MyGuiScreenLoading).GetField("m_multiTextControl", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance);
Color color = new Color(255, 255, 255, 250);
color.A = (byte)((float)(int)color.A * m_transitionAlpha);
Rectangle fullscreenRectangle = MyGuiManager.GetFullscreenRectangle();
MyGuiManager.DrawSpriteBatch("Textures\\GUI\\Blank.dds", fullscreenRectangle, Color.Black, false, true);
Rectangle outRect;
MyGuiManager.GetSafeHeightFullScreenPictureSize(MyGuiConstants.LOADING_BACKGROUND_TEXTURE_REAL_SIZE, out outRect);
MyGuiManager.DrawSpriteBatch(LoadingScreenTexture, outRect, new Color(new Vector4(1f, 1f, 1f, m_transitionAlpha)), true, true);
MyGuiManager.DrawSpriteBatch("Textures\\Gui\\Screens\\screen_background_fade.dds", outRect, new Color(new Vector4(1f, 1f, 1f, m_transitionAlpha)), true, true);
//MyGuiSandbox.DrawGameLogoHandler(m_transitionAlpha, MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, 44, 68));
string LoadScreen = $"Loading into {ServerName}! Please wait!";
MyGuiManager.DrawString(m_font, LoadScreen, new Vector2(0.5f, 0.95f), MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f, new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * m_transitionAlpha), MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM);
MyGuiManager.DrawString(m_font, "Nexus & SeamlessClient Made by: Casimir", new Vector2(0.95f, 0.95f), MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f, new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * m_transitionAlpha), MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM);
/*
if (string.IsNullOrEmpty(m_customTextFromConstructor))
{
string font = m_font;
Vector2 positionAbsoluteBottomLeft = m_multiTextControl.GetPositionAbsoluteBottomLeft();
Vector2 textSize = m_multiTextControl.TextSize;
Vector2 normalizedCoord = positionAbsoluteBottomLeft + new Vector2((m_multiTextControl.Size.X - textSize.X) * 0.5f + 0.025f, 0.025f);
MyGuiManager.DrawString(font, m_authorWithDash.ToString(), normalizedCoord, MyGuiSandbox.GetDefaultTextScaleWithLanguage());
} }
*/
MyLog.Default.WriteLine("LoadSession() - End");
//m_multiTextControl.Draw(1f, 1f); }, () =>
return false;
}
private static bool GetCustomLoadingScreenPath(List<MyObjectBuilder_Checkpoint.ModItem> Mods, out string File)
{ {
File = null; multiplayerSession.Dispose();
string WorkshopDir = MyFileSystem.ModsPath; MySessionLoader.UnloadAndExitToMenu();
List<string> backgrounds = new List<string>(); });
Random r = new Random(DateTime.Now.Millisecond);
SeamlessClient.TryShow(WorkshopDir); return false;
try }
private static bool DrawInternal(MyGuiScreenLoading __instance)
{
//If we dont have a custom loading screen texture, do not do the special crap below
if (string.IsNullOrEmpty(_loadingScreenTexture))
return true;
//MyGuiControlMultilineText m_multiTextControl = (MyGuiControlMultilineText)typeof(MyGuiScreenLoading).GetField("m_multiTextControl", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance);
var color = new Color(255, 255, 255, 250);
color.A = (byte)(color.A * __instance.m_transitionAlpha);
var fullscreenRectangle = MyGuiManager.GetFullscreenRectangle();
MyGuiManager.DrawSpriteBatch(@"Textures\GUI\Blank.dds", fullscreenRectangle, Color.Black, false, true);
MyGuiManager.GetSafeHeightFullScreenPictureSize(MyGuiConstants.LOADING_BACKGROUND_TEXTURE_REAL_SIZE,
out var outRect);
MyGuiManager.DrawSpriteBatch(_loadingScreenTexture, outRect,
new Color(new Vector4(1f, 1f, 1f, __instance.m_transitionAlpha)), true, true);
MyGuiManager.DrawSpriteBatch(@"Textures\Gui\Screens\screen_background_fade.dds", outRect,
new Color(new Vector4(1f, 1f, 1f, __instance.m_transitionAlpha)), true, true);
//MyGuiSandbox.DrawGameLogoHandler(m_transitionAlpha, MyGuiManager.ComputeFullscreenGuiCoordinate(MyGuiDrawAlignEnum.HORISONTAL_LEFT_AND_VERTICAL_TOP, 44, 68));
var loadScreen = $"Loading into {_serverName}! Please wait!";
MyGuiManager.DrawString(MyFontEnum.LoadingScreen, loadScreen, new Vector2(0.5f, 0.95f),
MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f,
new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * __instance.m_transitionAlpha),
MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM);
MyGuiManager.DrawString(MyFontEnum.LoadingScreen, "Nexus & SeamlessClient Made by: Casimir", new Vector2(0.95f, 0.95f),
MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f,
new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * __instance.m_transitionAlpha),
MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM);
/*
if (string.IsNullOrEmpty(m_customTextFromConstructor))
{
string font = m_font;
Vector2 positionAbsoluteBottomLeft = m_multiTextControl.GetPositionAbsoluteBottomLeft();
Vector2 textSize = m_multiTextControl.TextSize;
Vector2 normalizedCoord = positionAbsoluteBottomLeft + new Vector2((m_multiTextControl.Size.X - textSize.X) * 0.5f + 0.025f, 0.025f);
MyGuiManager.DrawString(font, m_authorWithDash.ToString(), normalizedCoord, MyGuiSandbox.GetDefaultTextScaleWithLanguage());
}
*/
//m_multiTextControl.Draw(1f, 1f);
return false;
}
private static bool GetCustomLoadingScreenPath(List<MyObjectBuilder_Checkpoint.ModItem> mods, out string file)
{
file = null;
var workshopDir = MyFileSystem.ModsPath;
var backgrounds = new List<string>();
SeamlessClient.TryShow(workshopDir);
try
{
SeamlessClient.TryShow($"Installed Mods: ({mods.Count}) [{string.Join(", ", mods.Select(x => x.FriendlyName))}]");
foreach (var mod in mods)
{ {
SeamlessClient.TryShow("Installed Mods: " + Mods); var searchDir = mod.GetPath();
foreach (var Mod in Mods)
if (!Directory.Exists(searchDir))
continue;
var files = Directory.GetFiles(searchDir, "CustomLoadingBackground*.dds",
SearchOption.TopDirectoryOnly);
foreach (var filePath in files)
{ {
string SearchDir = Mod.GetPath(); // Adds all files containing CustomLoadingBackground to a list for later randomisation
SeamlessClient.TryShow($"{mod.FriendlyName} contains a custom loading background!");
if (!Directory.Exists(SearchDir)) backgrounds.Add(filePath);
continue;
var files = Directory.GetFiles(SearchDir, "CustomLoadingBackground*.dds", SearchOption.TopDirectoryOnly);
foreach (var file in files)
{
// Adds all files containing CustomLoadingBackground to a list for later randomisation
SeamlessClient.TryShow(Mod.FriendlyName + " contains a custom loading background!");
backgrounds.Add(file);
}
} }
// Randomly pick a loading screen from the available backgrounds
var rInt = r.Next(0, backgrounds.Count() - 1);
File = backgrounds[rInt];
return true;
}
catch (Exception ex)
{
SeamlessClient.TryShow(ex.ToString());
} }
SeamlessClient.TryShow("No installed custom loading screen!"); // Randomly pick a loading screen from the available backgrounds
return false; file = backgrounds[Random.Shared.Next(0, backgrounds.Count - 1)];
}
#endregion
private static void OnUserJoined(ref JoinResultMsg msg)
{
if (msg.JoinResult == JoinResult.OK)
{
//SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString());
//Invoke the switch event
OnJoinEvent?.Invoke(null, msg);
}
}
private static bool OnConnectToServer(MyGameServerItem server, Action<JoinResult> onDone)
{
if (SeamlessClient.IsSwitching)
return false;
return true; return true;
} }
catch (Exception ex)
/* Patch Utils */
private static MethodInfo GetMethod(Type type, string MethodName, BindingFlags Flags)
{ {
try SeamlessClient.TryShow(ex.ToString());
{
MethodInfo FoundMethod = type.GetMethod(MethodName, Flags);
if (FoundMethod == null)
throw new NullReferenceException($"Method for {MethodName} is null!");
return FoundMethod;
}
catch (Exception Ex)
{
throw Ex;
}
} }
private static FieldInfo GetField(Type type, string FieldName, BindingFlags Flags) SeamlessClient.TryShow("No installed custom loading screen!");
{ return false;
try
{
FieldInfo FoundField = type.GetField(FieldName, Flags);
if (FoundField == null)
throw new NullReferenceException($"Field for {FieldName} is null!");
return FoundField;
}
catch (Exception Ex)
{
throw Ex;
}
}
private static PropertyInfo GetProperty(Type type, string PropertyName, BindingFlags Flags)
{
try
{
PropertyInfo FoundProperty = type.GetProperty(PropertyName, Flags);
if (FoundProperty == null)
throw new NullReferenceException($"Property for {PropertyName} is null!");
return FoundProperty;
}
catch (Exception Ex)
{
throw Ex;
}
}
private static ConstructorInfo GetConstructor(Type type, BindingFlags Flags, Type[] Types)
{
try
{
ConstructorInfo FoundConstructor = type.GetConstructor(Flags, null, Types, null);
if (FoundConstructor == null)
throw new NullReferenceException($"Contructor for {type.Name} is null!");
return FoundConstructor;
}
catch (Exception Ex)
{
throw Ex;
}
}
} }
}
#endregion
}

View File

@@ -1,46 +1,32 @@
using HarmonyLib; using ProtoBuf;
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClientPlugin.Utilities namespace SeamlessClientPlugin.Utilities;
public static class Utility
{ {
public static class Utility public static byte[] Serialize<T>(T instance)
{ {
public static byte[] Serialize<T>(T instance) if (instance == null)
return null;
using (var m = new MemoryStream())
{ {
if (instance == null) // m.Seek(0, SeekOrigin.Begin);
return null; Serializer.Serialize(m, instance);
using (var m = new MemoryStream()) return m.ToArray();
{
// m.Seek(0, SeekOrigin.Begin);
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);
}
}
public static dynamic CastToReflected(this object o, Type type)
{
return Convert.ChangeType(o, type);
}
} }
}
public static T Deserialize<T>(byte[] data)
{
if (data == null)
return default;
using (var m = new MemoryStream(data))
{
return Serializer.Deserialize<T>(m);
}
}
}

View File

@@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<packages>
<package id="Lib.Harmony" version="2.2.1" targetFramework="net48" />
<package id="System.IO.Compression" version="4.3.0" targetFramework="net472" />
<package id="System.IO.Compression.ZipFile" version="4.3.0" targetFramework="net472" />
</packages>