6 Commits

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

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

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

119
Components/ModAPI.cs Normal file
View File

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

View File

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

View File

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

View File

@@ -47,7 +47,7 @@ namespace SeamlessClient.OnlinePlayersWindow
foreach (OnlineClientServer server in servers)
{
if(server.ServerID == CurrentServer)
if (server.ServerID == CurrentServer)
{
onlineServer = server;
continue;

View File

@@ -91,7 +91,7 @@ namespace SeamlessClient.ServerSwitching
private void PauseResetTimer_Elapsed(object sender, ElapsedEventArgs e)
{
if(MySandboxGame.IsPaused)
if (MySandboxGame.IsPaused)
{
Seamless.TryShow("Game is still paused... Attempting to unpause!");
MySandboxGame.PausePop();
@@ -179,6 +179,7 @@ namespace SeamlessClient.ServerSwitching
MySandboxGame.PausePop();
}
ModAPI.ServerSwitched();
SendPlayerData.Invoke(MyMultiplayer.Static, new object[] { MyGameService.OnlineName });
isSwitch = false;
}
@@ -186,7 +187,7 @@ namespace SeamlessClient.ServerSwitching
public static bool LoadClientsFromWorld(ref List<MyObjectBuilder_Client> clients)
{
if(!isSwitch || clients == null || clients.Count == 0)
if (!isSwitch || clients == null || clients.Count == 0)
return true;
@@ -198,7 +199,7 @@ namespace SeamlessClient.ServerSwitching
var keys = m_memberData.Keys.Cast<ulong>();
for(int i = clients.Count - 1; i >= 0; i-- )
for (int i = clients.Count - 1; i >= 0; i--)
{
Seamless.TryShow($"Client {clients[i].SteamId}");
if (keys.Contains(clients[i].SteamId))
@@ -217,7 +218,7 @@ namespace SeamlessClient.ServerSwitching
private static bool ProcessAllMembersData(ref AllMembersDataMsg msg)
{
if(!isSwitch)
if (!isSwitch)
return true;
@@ -326,7 +327,8 @@ namespace SeamlessClient.ServerSwitching
UnloadServer();
SetNewMultiplayerClient();
}catch(Exception ex)
}
catch (Exception ex)
{
Seamless.TryShow(ex.ToString());
}
@@ -422,7 +424,7 @@ namespace SeamlessClient.ServerSwitching
Seamless.TryShow($"2 NexusMajor: {Seamless.NexusVersion.Major} - ConrolledEntity {MySession.Static.ControlledEntity == null} - HumanPlayer {MySession.Static.LocalHumanPlayer == null} - Character {MySession.Static.LocalCharacter == null}");
ModAPI.StartModSwitching();
//MyMultiplayer.Static.ReplicationLayer.Disconnect();
//MyMultiplayer.Static.ReplicationLayer.Dispose();
@@ -535,13 +537,13 @@ namespace SeamlessClient.ServerSwitching
List<ulong> clients = new List<ulong>();
foreach(var client in Sync.Clients.GetClients())
foreach (var client in Sync.Clients.GetClients())
{
clients.Add(client.SteamUserId);
Seamless.TryShow($"ADDING {client.SteamUserId} - {Sync.MyId}");
}
foreach(var client in clients)
foreach (var client in clients)
{
if (client == TargetServer.SteamID || client == Sync.MyId)
continue;

View File

@@ -27,12 +27,19 @@ using Sandbox.Game;
using VRage.Game.ModAPI;
using VRage.Utils;
using SeamlessClient.ServerSwitching;
using Sandbox.Game.Entities.Character;
using VRage.Game.Utils;
using VRage;
using Sandbox.Game.GameSystems.CoordinateSystem;
namespace SeamlessClient.Components
{
public class ServerSwitcherComponentOLD : ComponentBase
{
private static bool isSeamlessSwitching = false;
private static bool isSeamlessSwitching { get; set; } = false;
private static bool WaitingForClientCheck { get; set; } = false;
private static ConstructorInfo TransportLayerConstructor;
private static ConstructorInfo SyncLayerConstructor;
private static ConstructorInfo ClientConstructor;
@@ -53,9 +60,28 @@ namespace SeamlessClient.Components
private string OldArmorSkin { get; set; } = string.Empty;
public ServerSwitcherComponentOLD() { Instance = this; }
public static string SwitchingText = string.Empty;
public override void Update()
{
//Toggle waiting for client check
if (WaitingForClientCheck == false && isSeamlessSwitching)
WaitingForClientCheck = true;
if (WaitingForClientCheck && MySession.Static.LocalHumanPlayer != null)
WaitingForClientCheck = false;
if (isSeamlessSwitching || WaitingForClientCheck)
{
//SeamlessClient.TryShow("Switching Servers!");
MyRenderProxy.DebugDrawText2D(new VRageMath.Vector2(MySandboxGame.ScreenViewport.Width / 2, MySandboxGame.ScreenViewport.Height - 150), SwitchingText, VRageMath.Color.AliceBlue, 1f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER);
MyRenderProxy.DebugDrawText2D(new VRageMath.Vector2(MySandboxGame.ScreenViewport.Width / 2, MySandboxGame.ScreenViewport.Height - 200), $"Transferring to {TargetServer.Name}", VRageMath.Color.Yellow, 1.5f, MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_CENTER);
MyRenderProxy.DebugDrawLine2D(new VRageMath.Vector2((MySandboxGame.ScreenViewport.Width / 2) - 250, MySandboxGame.ScreenViewport.Height - 170), new VRageMath.Vector2((MySandboxGame.ScreenViewport.Width / 2) + 250, MySandboxGame.ScreenViewport.Height - 170), VRageMath.Color.Blue, VRageMath.Color.Green);
}
}
public override void Patch(Harmony patcher)
@@ -75,7 +101,50 @@ namespace SeamlessClient.Components
VirtualClients = PatchUtils.GetField(typeof(MySession), "VirtualClients");
patcher.Patch(onJoin, postfix: new HarmonyMethod(Get(typeof(ServerSwitcherComponentOLD), nameof(OnUserJoined))));
base.Patch(patcher);
}
public override void Initilized()
{
MyAPIGateway.Utilities.MessageEntered += Utilities_MessageEntered;
}
private void Utilities_MessageEntered(string messageText, ref bool sendToOthers)
{
if (!messageText.StartsWith("/nexus"))
return;
string[] cmd = messageText.ToLowerInvariant().Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (cmd[1] == "refreshcharacter")
{
if (MySession.Static.LocalHumanPlayer == null)
{
MyAPIGateway.Utilities?.ShowMessage("Seamless", "LocalHumanPlayer Null!");
return;
}
if (MySession.Static.LocalHumanPlayer.Character == null)
{
MyAPIGateway.Utilities?.ShowMessage("Seamless", "LocalHumanPlayerCharacter Null!");
return;
}
//None of this shit works.... 5/3/2025
MySession.Static.LocalHumanPlayer.SpawnIntoCharacter(MySession.Static.LocalHumanPlayer.Character);
MySession.Static.LocalHumanPlayer.Controller.TakeControl(MySession.Static.LocalHumanPlayer.Character);
MySession.Static.LocalHumanPlayer.Character.GetOffLadder();
MySession.Static.LocalHumanPlayer.Character.Stand();
MySession.Static.LocalHumanPlayer.Character.ResetControls();
MySession.Static.LocalHumanPlayer.Character.UpdateCharacterPhysics(true);
MyAPIGateway.Utilities?.ShowMessage("Seamless", "Character Controls Reset!");
}
}
private static void OnUserJoined(ref JoinResultMsg msg)
@@ -85,13 +154,30 @@ namespace SeamlessClient.Components
//SeamlessClient.TryShow("User Joined! Result: " + msg.JoinResult.ToString());
//Invoke the switch event
SwitchingText = "Server Responded! Removing Old Entities and forcing client connection!";
RemoveOldEntities();
ForceClientConnection();
ModAPI.ServerSwitched();
MySession.Static.LocalHumanPlayer?.Character?.Stand();
isSeamlessSwitching = false;
}
}
public void StartBackendSwitch(MyGameServerItem _TargetServer, MyObjectBuilder_World _TargetWorld)
{
if (MySession.Static.LocalCharacter != null)
{
var viewMatrix = MySession.Static.LocalCharacter.GetViewMatrix();
MySpectator.Static.SetViewMatrix(viewMatrix);
}
SwitchingText = "Starting Seamless Switch... Please wait!";
isSeamlessSwitching = true;
OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin;
TargetServer = _TargetServer;
@@ -99,13 +185,18 @@ namespace SeamlessClient.Components
MySandboxGame.Static.Invoke(delegate
{
//Pause the game/update thread while we load
MySandboxGame.IsPaused = true;
//Set camera controller to fixed spectator
MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed);
UnloadCurrentServer();
SetNewMultiplayerClient();
ModAPI.StartModSwitching();
//SeamlessClient.IsSwitching = false;
SwitchingText = "Waiting for server response...";
}, "SeamlessClient");
}
@@ -129,12 +220,13 @@ namespace SeamlessClient.Components
// Set the new SyncLayer to the MySession.Static.SyncLayer
MySessionLayer.SetValue(MySession.Static, MyMultiplayer.Static.SyncLayer);
SwitchingText = "New Multiplayer Session Set";
Seamless.TryShow("Successfully set MyMultiplayer.Static");
MySandboxGame.IsPaused = true;
Sync.Clients.SetLocalSteamId(Sync.MyId, false, MyGameService.UserName);
Sync.Players.RegisterEvents();
SwitchingText = "Registering Player Events";
}
@@ -142,12 +234,14 @@ namespace SeamlessClient.Components
private static void ForceClientConnection()
{
//Set World Settings
SetWorldSettings();
//Load force load any connected players
LoadConnectedClients();
SwitchingText = "Loaded Connected Clients";
MySector.InitEnvironmentSettings(TargetWorld.Sector.Environment);
@@ -159,10 +253,8 @@ namespace SeamlessClient.Components
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);
@@ -171,22 +263,31 @@ namespace SeamlessClient.Components
MyMultiplayer.Static.OnSessionReady();
SwitchingText = "Session Ready";
UpdateWorldGenerator();
StartEntitySync();
//Resume the game/update thread
MySandboxGame.IsPaused = false;
MyHud.Chat.RegisterChat(MyMultiplayer.Static);
GpsRegisterChat.Invoke(MySession.Static.Gpss, new object[] { MyMultiplayer.Static });
SwitchingText = "Registered Chat";
// Allow the game to start proccessing incoming messages in the buffer
MyMultiplayer.Static.StartProcessingClientMessages();
//Recreate all controls... Will fix weird gui/paint/crap
MyGuiScreenHudSpace.Static.RecreateControls(true);
SwitchingText = "Client Registered. Waiting for entities from server...";
Seamless.TryShow($"LocalHumanPlayer = {MySession.Static.LocalHumanPlayer == null}");
//MySession.Static.LocalHumanPlayer.BuildArmorSkin = OldArmorSkin;
}
@@ -351,6 +452,8 @@ namespace SeamlessClient.Components
private static void LoadConnectedClients()
{
//TargetWorld.Checkpoint.AllPlayers.Count
Seamless.TryShow($"Loading members from world... {TargetWorld.Checkpoint.AllPlayers.Count}");
LoadMembersFromWorld.Invoke(MySession.Static, new object[] { TargetWorld, MyMultiplayer.Static });
@@ -441,8 +544,7 @@ namespace SeamlessClient.Components
throw new Exception("MyMultiplayer.Static is null on unloading? dafuq?");
RemoveOldEntities();
SwitchingText = "Unloading Local Server Components";
//Try and close the quest log
MySessionComponentIngameHelp component = MySession.Static.GetComponent<MySessionComponentIngameHelp>();
component?.TryCancelObjective();
@@ -463,25 +565,39 @@ namespace SeamlessClient.Components
MyMultiplayer.Static.ReplicationLayer.Dispose();
MyMultiplayer.Static.Dispose();
MyMultiplayer.Static = null;
SwitchingText = "Local Multiplayer Disposed";
//Clear grid coord systems
ResetCoordinateSystems();
//Close any respawn screens that are open
MyGuiScreenMedicals.Close();
//MySession.Static.UnloadDataComponents();
//Unload any lingering updates queued
MyEntities.Orchestrator.Unload();
}
private void RemoveOldEntities()
private static void RemoveOldEntities()
{
foreach (var ent in MyEntities.GetEntities())
{
if (ent is MyPlanet)
{
//Re-Add planet updates
MyEntities.RegisterForUpdate(ent);
continue;
}
ent.Close();
}
}
private static void ResetCoordinateSystems()
{
AccessTools.Field(typeof(MyCoordinateSystem), "m_lastCoordSysId").SetValue(MyCoordinateSystem.Static, 1L);
AccessTools.Field(typeof(MyCoordinateSystem), "m_localCoordSystems").SetValue(MyCoordinateSystem.Static, new Dictionary<long, MyLocalCoordSys>());
}

View File

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

View File

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

View File

@@ -385,8 +385,6 @@ namespace SeamlessClient.OnlinePlayersWindow
Controls.Add(m_playersTable);
string servername = PlayersWindowComponent.onlineServer?.ServerName ?? "thisServer";
foreach (MyPlayer onlinePlayer in Sync.Players.GetOnlinePlayers())
{
if (onlinePlayer.Id.SerialId != 0)
@@ -403,16 +401,14 @@ namespace SeamlessClient.OnlinePlayersWindow
}
}
AddPlayer(onlinePlayer.Id.SteamId, servername);
AddPlayer(onlinePlayer.Id.SteamId, $"{MyMultiplayer.Static.HostName ?? "thisServer"}*");
}
foreach(var server in PlayersWindowComponent.allServers)
foreach (var server in PlayersWindowComponent.allServers)
{
foreach(var player in server.Players)
foreach (var player in server.Players)
AddPlayerFromOtherServer(player, server.ServerName);
}
@@ -796,7 +792,7 @@ namespace SeamlessClient.OnlinePlayersWindow
protected void Multiplayer_PlayerJoined(ulong userId, string userName)
{
AddPlayer(userId, PlayersWindowComponent.onlineServer.ServerName);
AddPlayer(userId, PlayersWindowComponent.onlineServer?.ServerName ?? "Unknown");
}
protected void Multiplayer_PlayerLeft(ulong userId, MyChatMemberStateChangeEnum arg2)

View File

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

View File

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

View File

@@ -1,6 +1,8 @@
using HarmonyLib;
using CringePlugins.Utils;
using HarmonyLib;
using NLog.Fluent;
using Sandbox;
using Sandbox.Engine.Multiplayer;
using Sandbox.Game.Localization;
using Sandbox.Game.World;
using Sandbox.ModAPI;
@@ -37,8 +39,7 @@ namespace SeamlessClient
private bool Initilized = false;
public static bool isSeamlessServer { get; private set; } = false;
public static bool isDebug = false;
public static bool UseNewVersion = true;
public static bool UseNewVersion = false;
@@ -48,20 +49,23 @@ namespace SeamlessClient
SeamlessPatcher = new Harmony("SeamlessClientPatcher");
GetComponents();
PatchComponents(SeamlessPatcher);
MySession.LoadingStep += SessionLoaded;
}
private void SessionLoaded(LoadingProgress progress)
{
if (progress >= LoadingProgress.PROGRESS_STEP8)
SendSeamlessVersion();
}
private void GetComponents()
{
int failedCount = 0;
foreach (Type type in thisAssembly.GetTypes())
foreach (Type type in IntrospectionContext.Global.CollectDerivedTypes<ComponentBase>(typeof(Seamless).Module))
{
if (type.BaseType != typeof(ComponentBase))
continue;
try
{
ComponentBase s = (ComponentBase)Activator.CreateInstance(type);
@@ -95,23 +99,25 @@ namespace SeamlessClient
}
}
private void InitilizeComponents()
{
foreach(ComponentBase component in allComps)
foreach (ComponentBase component in allComps)
{
try
{
component.Initilized();
TryShow($"Initilized {component.GetType()}");
}catch(Exception ex)
}
catch (Exception ex)
{
TryShow(ex, $"Failed to initialize {component.GetType()}");
}
}
}
private static void MessageHandler(ushort packetID, byte[] data, ulong sender, bool fromServer)
{
//Ignore anything except dedicated server
@@ -129,23 +135,20 @@ namespace SeamlessClient
isSeamlessServer = true;
switch (msg.MessageType)
{
case ClientMessageType.FirstJoin:
Seamless.TryShow("Sending First Join!");
SendSeamlessVersion();
break;
case ClientMessageType.TransferServer:
StartSwitch(msg.GetTransferData());
break;
case ClientMessageType.OnlinePlayers:
//Not implemented yet
var playerData = msg.GetOnlinePlayers();
PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
break;
}
}
public static void SendSeamlessVersion()
{
ClientMessage response = new ClientMessage(SeamlessVersion.ToString());
@@ -161,6 +164,10 @@ namespace SeamlessClient
}
public void Update()
{
allComps.ForEach(x => x.Update());
if (MyAPIGateway.Multiplayer == null)
@@ -216,8 +223,6 @@ namespace SeamlessClient
public static void TryShow(string message)
{
if (MySession.Static?.LocalHumanPlayer != null && isDebug)

View File

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

View File

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

7
global.json Normal file
View File

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