Seamless rework

This commit is contained in:
Garrett
2023-08-24 21:52:31 -05:00
parent b93d5b3123
commit 5397b4e04e
14 changed files with 1143 additions and 50 deletions

View File

@@ -0,0 +1,107 @@
using HarmonyLib;
using Sandbox.Game.Entities;
using SeamlessClient.Utilities;
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.Game.Entity;
namespace SeamlessClient.Components
{
public class EntityPerformanceImprovements : ComponentBase
{
private static bool isEnabled = false;
private static ConcurrentDictionary<long, string> EntityNameReverseLookup = new ConcurrentDictionary<long, string>();
public override void Patch(Harmony patcher)
{
if (!isEnabled)
return;
var setEntityName = PatchUtils.GetMethod(PatchUtils.MyEntitiesType, "SetEntityName");
var removeName = PatchUtils.GetMethod(PatchUtils.MyEntitiesType, "RemoveName");
var isNameExists = PatchUtils.GetMethod(PatchUtils.MyEntitiesType, "IsNameExists");
var unloadData = PatchUtils.GetMethod(PatchUtils.MyEntitiesType, "UnloadData");
patcher.Patch(setEntityName, prefix: new HarmonyMethod(Get(typeof(EntityPerformanceImprovements), nameof(SetEntityName))));
patcher.Patch(removeName, prefix: new HarmonyMethod(Get(typeof(EntityPerformanceImprovements), nameof(RemoveName))));
patcher.Patch(isNameExists, prefix: new HarmonyMethod(Get(typeof(EntityPerformanceImprovements), nameof(IsNameExists))));
patcher.Patch(unloadData, postfix: new HarmonyMethod(Get(typeof(EntityPerformanceImprovements), nameof(UnloadData))));
base.Patch(patcher);
}
// reverse dictionary
private static bool SetEntityName(MyEntity myEntity, bool possibleRename)
{
if (string.IsNullOrEmpty(myEntity.Name))
return false;
if (possibleRename && EntityNameReverseLookup.ContainsKey(myEntity.EntityId))
{
var previousName = EntityNameReverseLookup[myEntity.EntityId];
if (previousName != myEntity.Name) MyEntities.m_entityNameDictionary.Remove(previousName);
}
if (MyEntities.m_entityNameDictionary.TryGetValue(myEntity.Name, out var myEntity1))
{
if (myEntity1 == myEntity)
return false;
}
else
{
MyEntities.m_entityNameDictionary[myEntity.Name] = myEntity;
EntityNameReverseLookup[myEntity.EntityId] = myEntity.Name;
}
return false;
}
private static bool RemoveName(MyEntity entity)
{
if (string.IsNullOrEmpty(entity.Name))
return false;
MyEntities.m_entityNameDictionary.Remove(entity.Name);
EntityNameReverseLookup.Remove(entity.EntityId);
return false;
}
private static bool IsNameExists(ref bool __result, MyEntity entity, string name)
{
if (string.IsNullOrEmpty(entity.Name))
{
__result = false;
return false;
}
if (MyEntities.m_entityNameDictionary.ContainsKey(name))
{
var ent = MyEntities.m_entityNameDictionary[entity.Name];
__result = ent != entity;
return false;
}
__result = false;
return false;
}
private static void UnloadData()
{
EntityNameReverseLookup.Clear();
}
}
}

View File

@@ -0,0 +1,177 @@
using HarmonyLib;
using Sandbox.Game.Gui;
using Sandbox.Graphics.GUI;
using Sandbox.Graphics;
using SeamlessClient.Utilities;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Utils;
using VRageMath;
using Sandbox.Game.World;
using Sandbox.Engine.Multiplayer;
using VRage.Game;
using Sandbox.Engine.Networking;
using VRage;
using VRage.GameServices;
namespace SeamlessClient.Components
{
public class LoadingScreenComponent : ComponentBase
{
private static MethodInfo LoadMultiplayer;
private static string _loadingScreenTexture = null;
private static string _serverName;
public override void Patch(Harmony patcher)
{
var loadingAction = PatchUtils.GetMethod(typeof(MySessionLoader), "LoadMultiplayerSession");
var loadingScreenDraw = PatchUtils.GetMethod(typeof(MyGuiScreenLoading), "DrawInternal");
LoadMultiplayer = PatchUtils.GetMethod(typeof(MySession), "LoadMultiplayer");
patcher.Patch(loadingAction, prefix: new HarmonyMethod(Get(typeof(LoadingScreenComponent), nameof(LoadMultiplayerSession))));
patcher.Patch(loadingScreenDraw, prefix: new HarmonyMethod(Get(typeof(LoadingScreenComponent), nameof(DrawInternal))));
base.Patch(patcher);
}
private static bool LoadMultiplayerSession(MyObjectBuilder_World world, MyMultiplayerBase multiplayerSession)
{
MyLog.Default.WriteLine("LoadSession() - Start");
if (!MyWorkshop.CheckLocalModsAllowed(world.Checkpoint.Mods, false))
{
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextLocalModsDisabledInMultiplayer)));
MyLog.Default.WriteLine("LoadSession() - End");
return false;
}
MyWorkshop.DownloadModsAsync(world.Checkpoint.Mods, delegate (MyGameServiceCallResult result)
{
switch (result)
{
case MyGameServiceCallResult.NotEnoughSpace:
MyGuiSandbox.AddScreen(MyGuiSandbox.CreateMessageBox(MyMessageBoxStyleEnum.Error, MyMessageBoxButtonsType.OK, messageCaption: MyTexts.Get(MyCommonTexts.MessageBoxCaptionError), messageText: MyTexts.Get(MyCommonTexts.DialogTextDownloadModsFailed_NotEnoughSpace), okButtonText: null, cancelButtonText: null, yesButtonText: null, noButtonText: null, callback: delegate
{
MySessionLoader.UnloadAndExitToMenu();
}));
break;
case MyGameServiceCallResult.OK:
MyScreenManager.CloseAllScreensNowExcept(null);
MyGuiSandbox.Update(16);
if (MySession.Static != null)
{
MySession.Static.Unload();
MySession.Static = null;
}
MySessionLoader.StartLoading(delegate
{
LoadMultiplayer.Invoke(null, new object[] { world, multiplayerSession });
});
break;
default:
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.GameServiceDisplayName))));
}
break;
}
MyLog.Default.WriteLine("LoadSession() - End");
}, delegate
{
multiplayerSession.Dispose();
MySessionLoader.UnloadAndExitToMenu();
});
return false;
}
private void OnFinished(MyGameServiceCallResult result)
{
}
private static bool DrawInternal(MyGuiScreenLoading __instance)
{
//If we dont have a custom loading screen texture, do not do the special crap below
const string mFont = "LoadingScreen";
var mTransitionAlpha = (float)typeof(MyGuiScreenBase).GetField("m_transitionAlpha", BindingFlags.Instance | BindingFlags.NonPublic).GetValue(__instance);
MyGuiManager.DrawString(mFont, "Nexus & SeamlessClient Made by: Casimir", new Vector2(0.95f, 0.95f),
MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f,
new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * mTransitionAlpha),
MyGuiDrawAlignEnum.HORISONTAL_CENTER_AND_VERTICAL_BOTTOM);
if (string.IsNullOrEmpty(_loadingScreenTexture))
return true;
var color = new Color(255, 255, 255, 250);
color.A = (byte)(color.A * mTransitionAlpha);
var 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, mTransitionAlpha)), true, true);
MyGuiManager.DrawSpriteBatch("Textures\\Gui\\Screens\\screen_background_fade.dds", outRect,
new Color(new Vector4(1f, 1f, 1f, mTransitionAlpha)), 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(mFont, loadScreen, new Vector2(0.5f, 0.95f),
MyGuiSandbox.GetDefaultTextScaleWithLanguage() * 1.1f,
new Color(MyGuiConstants.LOADING_PLEASE_WAIT_COLOR * mTransitionAlpha),
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;
}
}
}

View File

@@ -2,6 +2,7 @@
using Sandbox.Game;
using Sandbox.Game.Gui;
using Sandbox.Game.World;
using SeamlessClient.Messages;
using SeamlessClient.Utilities;
using System;
using System.Collections.Generic;
@@ -12,7 +13,7 @@ using System.Threading.Tasks;
namespace SeamlessClient.OnlinePlayersWindow
{
public class PlayersWindow : ComponentBase
public class PlayersWindowComponent : ComponentBase
{
public override void Patch(Harmony patcher)
@@ -26,6 +27,11 @@ namespace SeamlessClient.OnlinePlayersWindow
MyPerGameSettings.GUI.PlayersScreen = typeof(OnlineNexusPlayersWindow);
}
public static void ApplyRecievedPlayers(List<OnlineServer> servers, int CurrentServer)
{
Seamless.TryShow($"Recieved {CurrentServer} - {servers.Count}");
}

View File

@@ -0,0 +1,433 @@
using HarmonyLib;
using Sandbox;
using Sandbox.Engine.Multiplayer;
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.SessionComponents;
using Sandbox.Game.World;
using Sandbox.Game.World.Generator;
using Sandbox.ModAPI;
using SeamlessClient.Components;
using SeamlessClient.OnlinePlayersWindow;
using SeamlessClient.Utilities;
using SpaceEngineers.Game.GUI;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.Game;
using VRage.Game.ModAPI;
using VRage.GameServices;
using VRage.Network;
using VRage.Utils;
using VRageRender;
using VRageRender.Messages;
namespace SeamlessClient.ServerSwitching
{
public class ServerSwitcherComponent : ComponentBase
{
private static FieldInfo RemoteAdminSettings;
private static FieldInfo AdminSettings;
private static MethodInfo UnloadProceduralWorldGenerator;
private static MethodInfo GpsRegisterChat;
private static MethodInfo LoadMembersFromWorld;
private static MethodInfo InitVirtualClients;
private static FieldInfo VirtualClients;
public static ServerSwitcherComponent Instance { get; private set; }
private MyGameServerItem TargetServer { get; set; }
private MyObjectBuilder_World TargetWorld { get; set; }
private string OldArmorSkin { get; set; } = string.Empty;
private bool needsEntityUnload = true;
public static event EventHandler<JoinResultMsg> OnJoinEvent;
public ServerSwitcherComponent()
{
Instance = this;
}
public override void Patch(Harmony patcher)
{
RemoteAdminSettings = PatchUtils.GetField(typeof(MySession), "m_remoteAdminSettings");
AdminSettings = PatchUtils.GetField(typeof(MySession), "m_adminSettings");
VirtualClients = PatchUtils.GetField(typeof(MySession), "VirtualClients");
UnloadProceduralWorldGenerator = PatchUtils.GetMethod(typeof(MyProceduralWorldGenerator), "UnloadData");
GpsRegisterChat = PatchUtils.GetMethod(typeof(MyGpsCollection), "RegisterChat");
LoadMembersFromWorld = PatchUtils.GetMethod(typeof(MySession), "LoadMembersFromWorld");
InitVirtualClients = PatchUtils.GetMethod(PatchUtils.VirtualClientsType, "Init");
var onJoin = PatchUtils.GetMethod(PatchUtils.ClientType, "OnUserJoined");
patcher.Patch(onJoin, postfix: new HarmonyMethod(Get(typeof(ServerSwitcherComponent), nameof(OnUserJoined))));
base.Patch(patcher);
}
private static void OnUserJoined(ref JoinResultMsg msg)
{
if (msg.JoinResult == JoinResult.OK)
{
Seamless.TryShow("User Joined! Result: " + msg.JoinResult.ToString());
//Invoke the switch event
OnJoinEvent?.Invoke(null, msg);
}
}
public override void Initilized()
{
base.Initilized();
}
public void StartBackendSwitch(MyGameServerItem TargetServer, MyObjectBuilder_World TargetWorld)
{
this.TargetServer = TargetServer;
this.TargetWorld = TargetWorld;
OldArmorSkin = MySession.Static.LocalHumanPlayer.BuildArmorSkin;
if (Seamless.NexusVersion.Major >= 2)
needsEntityUnload = false;
AsyncInvoke.InvokeAsync(() =>
{
if(needsEntityUnload)
MySession.Static.SetCameraController(MyCameraControllerEnum.SpectatorFixed);
UnloadServer();
SetNewMultiplayerClient();
});
}
private void UnloadServer()
{
if (MyMultiplayer.Static == null)
throw new Exception("MyMultiplayer.Static is null on unloading? dafuq?");
UnloadOldEntities();
//Close and Cancel any screens (Medical or QuestLog)
MySessionComponentIngameHelp component = MySession.Static.GetComponent<MySessionComponentIngameHelp>();
component?.TryCancelObjective();
MyGuiScreenMedicals.Close();
//Clear all old players and clients.
Sync.Clients.Clear();
Sync.Players.ClearPlayers();
//Unregister Chat
MyHud.Chat.UnregisterChat(MyMultiplayer.Static);
//Clear all local GPS Points
MySession.Static.Gpss.RemovePlayerGpss(MySession.Static.LocalPlayerId);
MyHud.GpsMarkers.Clear();
//DisconnectReplication
MyMultiplayer.Static.ReplicationLayer.Disconnect();
MyMultiplayer.Static.ReplicationLayer.Dispose();
MyMultiplayer.Static.Dispose();
MyMultiplayer.Static = null;
}
private void UnloadOldEntities()
{
foreach (var ent in MyEntities.GetEntities())
{
if (ent is MyPlanet)
continue;
//ent.Close();
}
}
private void SetNewMultiplayerClient()
{
OnJoinEvent += ServerSwitcherComponent_OnJoinEvent;
}
private void ServerSwitcherComponent_OnJoinEvent(object sender, JoinResultMsg e)
{
OnJoinEvent -= ServerSwitcherComponent_OnJoinEvent;
if (e.JoinResult != JoinResult.OK)
{
Seamless.TryShow("Failed to join the target server!");
return;
}
/* On Server Successfull Join
*
*
*/
SetWorldSettings();
InitComponents();
LoadConnectedClients();
StartEntitySync();
MyMultiplayer.Static.OnSessionReady();
MyHud.Chat.RegisterChat(MyMultiplayer.Static);
GpsRegisterChat.Invoke(MySession.Static.Gpss, new object[] { MyMultiplayer.Static });
// Allow the game to start proccessing incoming messages in the buffer
MyMultiplayer.Static.StartProcessingClientMessages();
//Recreate all controls... Will fix weird gui/paint/crap
MyGuiScreenHudSpace.Static.RecreateControls(true);
}
private void SetWorldSettings()
{
//Clear old list
MySession.Static.PromotedUsers.Clear();
MySession.Static.CreativeTools.Clear();
Dictionary<ulong, AdminSettingsEnum> AdminSettingsList = (Dictionary<ulong, AdminSettingsEnum>)RemoteAdminSettings.GetValue(MySession.Static);
AdminSettingsList.Clear();
// Set new world settings
MySession.Static.Name = MyStatControlText.SubstituteTexts(TargetWorld.Checkpoint.SessionName);
MySession.Static.Description = TargetWorld.Checkpoint.Description;
MySession.Static.Mods = TargetWorld.Checkpoint.Mods;
MySession.Static.Settings = TargetWorld.Checkpoint.Settings;
MySession.Static.CurrentPath = MyLocalCache.GetSessionSavesPath(MyUtils.StripInvalidChars(TargetWorld.Checkpoint.SessionName), contentFolder: false, createIfNotExists: false);
MySession.Static.WorldBoundaries = TargetWorld.Checkpoint.WorldBoundaries;
MySession.Static.InGameTime = MyObjectBuilder_Checkpoint.DEFAULT_DATE;
MySession.Static.ElapsedGameTime = new TimeSpan(TargetWorld.Checkpoint.ElapsedGameTime);
MySession.Static.Settings.EnableSpectator = false;
MySession.Static.Password = TargetWorld.Checkpoint.Password;
MySession.Static.PreviousEnvironmentHostility = TargetWorld.Checkpoint.PreviousEnvironmentHostility;
MySession.Static.RequiresDX = TargetWorld.Checkpoint.RequiresDX;
MySession.Static.CustomLoadingScreenImage = TargetWorld.Checkpoint.CustomLoadingScreenImage;
MySession.Static.CustomLoadingScreenText = TargetWorld.Checkpoint.CustomLoadingScreenText;
MySession.Static.CustomSkybox = TargetWorld.Checkpoint.CustomSkybox;
try
{
MySession.Static.Gpss = new MyGpsCollection();
MySession.Static.Gpss.LoadGpss(TargetWorld.Checkpoint);
}
catch (Exception ex)
{
Seamless.TryShow($"An error occured while loading GPS points! You will have an empty gps list! \n {ex.ToString()}");
}
MyRenderProxy.RebuildCullingStructure();
//MySession.Static.Toolbars.LoadToolbars(checkpoint);
Sync.Players.RespawnComponent.InitFromCheckpoint(TargetWorld.Checkpoint);
// Set new admin settings
if (TargetWorld.Checkpoint.PromotedUsers != null)
{
MySession.Static.PromotedUsers = TargetWorld.Checkpoint.PromotedUsers.Dictionary;
}
else
{
MySession.Static.PromotedUsers = new Dictionary<ulong, MyPromoteLevel>();
}
foreach (KeyValuePair<MyObjectBuilder_Checkpoint.PlayerId, MyObjectBuilder_Player> item in TargetWorld.Checkpoint.AllPlayersData.Dictionary)
{
ulong clientId = item.Key.GetClientId();
AdminSettingsEnum adminSettingsEnum = (AdminSettingsEnum)item.Value.RemoteAdminSettings;
if (TargetWorld.Checkpoint.RemoteAdminSettings != null && TargetWorld.Checkpoint.RemoteAdminSettings.Dictionary.TryGetValue(clientId, out var value))
{
adminSettingsEnum = (AdminSettingsEnum)value;
}
if (!MyPlatformGameSettings.IsIgnorePcuAllowed)
{
adminSettingsEnum &= ~AdminSettingsEnum.IgnorePcu;
adminSettingsEnum &= ~AdminSettingsEnum.KeepOriginalOwnershipOnPaste;
}
AdminSettingsList[clientId] = adminSettingsEnum;
if (!Sync.IsDedicated && clientId == Sync.MyId)
{
AdminSettings.SetValue(MySession.Static, adminSettingsEnum);
}
if (!MySession.Static.PromotedUsers.TryGetValue(clientId, out var value2))
{
value2 = MyPromoteLevel.None;
}
if (item.Value.PromoteLevel > value2)
{
MySession.Static.PromotedUsers[clientId] = item.Value.PromoteLevel;
}
if (!MySession.Static.CreativeTools.Contains(clientId) && item.Value.CreativeToolsEnabled)
{
MySession.Static.CreativeTools.Add(clientId);
}
}
MySector.InitEnvironmentSettings(TargetWorld.Sector.Environment);
string text = ((!string.IsNullOrEmpty(TargetWorld.Checkpoint.CustomSkybox)) ? TargetWorld.Checkpoint.CustomSkybox : MySector.EnvironmentDefinition.EnvironmentTexture);
MyRenderProxy.PreloadTextures(new string[1] { text }, TextureType.CubeMap);
}
private void InitComponents()
{
MyModAPIHelper.Initialize();
MySession.Static.LoadDataComponents();
//MySession.Static.LoadObjectBuildersComponents(TargetWorld.Checkpoint.SessionComponents);
MyModAPIHelper.Initialize();
// MySession.Static.LoadObjectBuildersComponents(TargetWorld.Checkpoint.SessionComponents);
UpdateWorldGenerator();
//MethodInfo A = typeof(MySession).GetMethod("LoadGameDefinition", BindingFlags.Instance | BindingFlags.NonPublic);
// A.Invoke(MySession.Static, new object[] { TargetWorld.Checkpoint });
}
private void UpdateWorldGenerator()
{
//This will re-init the MyProceduralWorldGenerator. (Not doing this will result in asteroids not rendering in properly)
//This shoud never be null
var Generator = MySession.Static.GetComponent<MyProceduralWorldGenerator>();
//Force component to unload
UnloadProceduralWorldGenerator.Invoke(Generator, null);
//Re-call the generator init
MyObjectBuilder_WorldGenerator GeneratorSettings = (MyObjectBuilder_WorldGenerator)TargetWorld.Checkpoint.SessionComponents.FirstOrDefault(x => x.GetType() == typeof(MyObjectBuilder_WorldGenerator));
if (GeneratorSettings != null)
{
//Re-initilized this component (forces to update asteroid areas like not in planets etc)
Generator.Init(GeneratorSettings);
}
//Force component to reload, re-syncing settings and seeds to the destination server
Generator.LoadData();
//We need to go in and force planets to be empty areas in the generator. This is originially done on planet init.
FieldInfo PlanetInitArgs = typeof(MyPlanet).GetField("m_planetInitValues", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
foreach (var Planet in MyEntities.GetEntities().OfType<MyPlanet>())
{
MyPlanetInitArguments args = (MyPlanetInitArguments)PlanetInitArgs.GetValue(Planet);
float MaxRadius = args.MaxRadius;
Generator.MarkEmptyArea(Planet.PositionComp.GetPosition(), MaxRadius);
}
}
private void StartEntitySync()
{
Seamless.TryShow("Requesting Player From Server");
Sync.Players.RequestNewPlayer(Sync.MyId, 0, MyGameService.UserName, null, 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 += Static_PendingReplicablesDone;
//typeof(MyGuiScreenTerminal).GetMethod("CreateTabs")
MySession.Static.LoadDataComponents();
//MyGuiSandbox.LoadData(false);
//MyGuiSandbox.AddScreen(MyGuiSandbox.CreateScreen(MyPerGameSettings.GUI.HUDScreen));
MyRenderProxy.RebuildCullingStructure();
MyRenderProxy.CollectGarbage();
Seamless.TryShow("OnlinePlayers: " + MySession.Static.Players.GetOnlinePlayers().Count);
Seamless.TryShow("Loading Complete!");
}
private void Static_PendingReplicablesDone()
{
if (MySession.Static.VoxelMaps.Instances.Count > 0)
{
MySandboxGame.AreClipmapsReady = false;
}
MyMultiplayer.Static.PendingReplicablesDone -= Static_PendingReplicablesDone;
}
private void LoadConnectedClients()
{
LoadMembersFromWorld.Invoke(MySession.Static, new object[] { TargetWorld, MyMultiplayer.Static });
//Re-Initilize Virtual clients
object VirtualClientsValue = VirtualClients.GetValue(MySession.Static);
InitVirtualClients.Invoke(VirtualClientsValue, null);
MyPlayer.PlayerId? savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
if (!savingPlayerId.HasValue)
{
Seamless.TryShow("SavingPlayerID is null! Creating Default!");
savingPlayerId = new MyPlayer.PlayerId(Sync.MyId);
}
Seamless.TryShow("Saving PlayerID: " + savingPlayerId.ToString());
Sync.Players.LoadConnectedPlayers(TargetWorld.Checkpoint, savingPlayerId);
Sync.Players.LoadControlledEntities(TargetWorld.Checkpoint.ControlledEntities, TargetWorld.Checkpoint.ControlledObject, savingPlayerId);
}
}
}

View File

@@ -21,29 +21,35 @@ namespace SeamlessClient.Messages
public class ClientMessage
{
[ProtoMember(1)] public ClientMessageType MessageType;
[ProtoMember(2)] public TransferData data;
[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 = "0";
public ClientMessage(ClientMessageType Type)
public ClientMessage(string PluginVersion)
{
MessageType = Type;
if (MyAPIGateway.Multiplayer == null || MyAPIGateway.Multiplayer.IsServer) return;
if (MyAPIGateway.Session.LocalHumanPlayer == null) return;
MessageType = ClientMessageType.FirstJoin;
IdentityID = MySession.Static?.LocalHumanPlayer?.Identity?.IdentityId ?? 0;
SteamID = MySession.Static?.LocalHumanPlayer?.Id.SteamId ?? 0;
//PluginVersion = SeamlessClient.Version;
this.PluginVersion = PluginVersion;
}
public ClientMessage()
public TransferData GetTransferData()
{
return MessageData == null ? default : MessageUtils.Deserialize<TransferData>(MessageData);
}
public OnlinePlayerData GetOnlinePlayers()
{
if (MessageData == null)
return default;
var msg = MessageUtils.Deserialize<OnlinePlayerData>(MessageData);
return msg;
}
}
}

View File

@@ -0,0 +1,57 @@
using ProtoBuf;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClient.Messages
{
[ProtoContract]
public class OnlinePlayerData
{
[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()
{
}
}
}

View File

@@ -25,6 +25,10 @@ namespace SeamlessClient.Messages
[ProtoMember(5)] public MyObjectBuilder_Gps GpsCollection;
public WorldRequest()
{
}
public WorldRequest(ulong playerId, long playerIdentity, string name)
{
this.PlayerID = playerId;
@@ -32,9 +36,7 @@ namespace SeamlessClient.Messages
this.IdentityID = playerIdentity;
}
public WorldRequest()
{
}
public void SerializeWorldData(MyObjectBuilder_World WorldData)
{

View File

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

View File

@@ -3,6 +3,7 @@ using NLog.Fluent;
using Sandbox.Game.World;
using Sandbox.ModAPI;
using SeamlessClient.Messages;
using SeamlessClient.OnlinePlayersWindow;
using SeamlessClient.ServerSwitching;
using SeamlessClient.Utilities;
using System;
@@ -13,6 +14,7 @@ using System.Net.NetworkInformation;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using VRage.GameServices;
using VRage.Plugins;
using VRage.Sync;
using VRage.Utils;
@@ -22,12 +24,17 @@ namespace SeamlessClient
public class Seamless : IPlugin
{
public static Version SeamlessVersion;
public static Version NexusVersion;
private static Harmony SeamlessPatcher;
public const ushort SeamlessClientNetId = 2936;
private List<ComponentBase> allComps = new List<ComponentBase>();
private Assembly thisAssembly;
private bool Initilized = false;
public static bool isSeamlessServer = false;
#if DEBUG
public static bool isDebug = true;
@@ -72,6 +79,7 @@ namespace SeamlessClient
}
}
}
private void PatchComponents(Harmony patcher)
{
foreach (ComponentBase component in allComps)
@@ -89,6 +97,7 @@ namespace SeamlessClient
}
}
}
private void InitilizeComponents()
{
foreach(ComponentBase component in allComps)
@@ -112,27 +121,35 @@ namespace SeamlessClient
if (!fromServer || sender != 0)
return;
if (MyAPIGateway.Multiplayer == null || MyAPIGateway.Multiplayer.IsServer) return;
if (MyAPIGateway.Session.LocalHumanPlayer == null) return;
ClientMessage msg = MessageUtils.Deserialize<ClientMessage>(data);
if (msg == null)
return;
//Get Nexus Version
NexusVersion = Version.Parse(msg.NexusVersion);
switch (msg.MessageType)
{
case ClientMessageType.FirstJoin:
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetId, MessageUtils.Serialize(new ClientMessage(ClientMessageType.FirstJoin)));
ClientMessage response = new ClientMessage(SeamlessVersion.ToString());
MyAPIGateway.Multiplayer?.SendMessageToServer(SeamlessClientNetId, MessageUtils.Serialize(response));
break;
case ClientMessageType.TransferServer:
ServerSwitcher.StartSwitching(msg.data);
StartSwitch(msg.GetTransferData());
break;
case ClientMessageType.OnlinePlayers:
case ClientMessageType.OnlinePlayers:
//Not implemented yet
var playerData = msg.GetOnlinePlayers();
PlayersWindowComponent.ApplyRecievedPlayers(playerData.OnlineServers, playerData.currentServerID);
break;
}
}
@@ -160,6 +177,31 @@ namespace SeamlessClient
public static void StartSwitch(TransferData targetServer)
{
if (targetServer.TargetServerId == 0)
{
Seamless.TryShow("This is not a valid server!");
return;
}
var server = new MyGameServerItem
{
ConnectionString = targetServer.IpAddress,
SteamID = targetServer.TargetServerId,
Name = targetServer.ServerName
};
Seamless.TryShow($"Beginning Redirect to server: {targetServer.TargetServerId}");
var world = targetServer.WorldRequest.DeserializeWorldData();
ServerSwitcherComponent.Instance.StartBackendSwitch(server, world);
}

View File

@@ -61,6 +61,10 @@
<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" />
@@ -86,16 +90,24 @@
<HintPath>GameBinaries\VRage.Math.dll</HintPath>
<Private>False</Private>
</Reference>
<Reference Include="VRage.Render">
<HintPath>GameBinaries\VRage.Render.dll</HintPath>
</Reference>
</ItemGroup>
<ItemGroup>
<Compile Include="Components\EntityPerformanceImprovements.cs" />
<Compile Include="Components\LoadingScreenComponent.cs" />
<Compile Include="Messages\OnlinePlayerData.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="OnlinePlayersWindow\PlayersWindow.cs" />
<Compile Include="Components\PlayersWindowComponent.cs" />
<Compile Include="Seamless.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ServerSwitching\ServerSwitcher.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" />
@@ -105,7 +117,9 @@
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup />
<ItemGroup>
<Folder Include="ServerSwitching\" />
</ItemGroup>
<ItemGroup>
<Content Include="SeamlessClient.xml">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>

View File

@@ -1,27 +0,0 @@
using SeamlessClient.Messages;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
using VRageMath;
namespace SeamlessClient.ServerSwitching
{
public class ServerSwitcher
{
public static void StartSwitching(TransferData targetServer)
{
}
}
}

118
Utilities/AsyncInvoke.cs Normal file
View File

@@ -0,0 +1,118 @@
using Sandbox;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace SeamlessClient.Utilities
{
public static class AsyncInvoke
{
public static Task InvokeAsync(Action action, [CallerMemberName] string caller = "SeamlessClient")
{
//Jimm thank you. This is the best
var ctx = new TaskCompletionSource<object>();
MySandboxGame.Static.Invoke(() =>
{
try
{
action.Invoke();
ctx.SetResult(null);
ctx.Task.ContinueWith(task => task.Dispose());
}
catch (Exception e)
{
ctx.SetException(e);
}
}, caller);
return ctx.Task;
}
public static Task<T> InvokeAsync<T>(Func<T> action, [CallerMemberName] string caller = "SeamlessClient")
{
//Jimm thank you. This is the best
var ctx = new TaskCompletionSource<T>();
MySandboxGame.Static.Invoke(() =>
{
try
{
ctx.SetResult(action.Invoke());
ctx.Task.ContinueWith(task => task.Dispose());
}
catch (Exception e)
{
ctx.SetException(e);
}
}, caller);
return ctx.Task;
}
public static Task<T2> InvokeAsync<T1, T2>(Func<T1, T2> action, T1 arg, [CallerMemberName] string caller = "SeamlessClient")
{
//Jimm thank you. This is the best
var ctx = new TaskCompletionSource<T2>();
MySandboxGame.Static.Invoke(() =>
{
try
{
ctx.SetResult(action.Invoke(arg));
ctx.Task.ContinueWith(task => task.Dispose());
}
catch (Exception e)
{
ctx.SetException(e);
}
}, caller);
return ctx.Task;
}
public static Task<T3> InvokeAsync<T1, T2, T3>(Func<T1, T2, T3> action, T1 arg, T2 arg2, [CallerMemberName] string caller = "SeamlessClient")
{
//Jimm thank you. This is the best
var ctx = new TaskCompletionSource<T3>();
MySandboxGame.Static.Invoke(() =>
{
try
{
ctx.SetResult(action.Invoke(arg, arg2));
ctx.Task.ContinueWith(task => task.Dispose());
}
catch (Exception e)
{
ctx.SetException(e);
}
}, caller);
return ctx.Task;
}
public static Task<T4> InvokeAsync<T1, T2, T3, T4>(Func<T1, T2, T3, T4> action, T1 arg, T2 arg2, T3 arg3, [CallerMemberName] string caller = "SeamlessClient")
{
//Jimm thank you. This is the best
var ctx = new TaskCompletionSource<T4>();
MySandboxGame.Static.Invoke(() =>
{
try
{
ctx.SetResult(action.Invoke(arg, arg2, arg3));
ctx.Task.ContinueWith(task => task.Dispose());
}
catch (Exception e)
{
ctx.SetException(e);
}
}, caller);
return ctx.Task;
}
}
}

View File

@@ -2,6 +2,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
@@ -23,5 +24,10 @@ namespace SeamlessClient.Utilities
public virtual void Destroy() { }
public MethodInfo Get(Type type, string v)
{
return type.GetMethod(v, BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic);
}
}
}

152
Utilities/PatchUtils.cs Normal file
View File

@@ -0,0 +1,152 @@
using HarmonyLib;
using Sandbox.Game.Gui;
using Sandbox.Game.Multiplayer;
using Sandbox.Game.World.Generator;
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.GameServices;
using VRage.Network;
namespace SeamlessClient.Components
{
public class PatchUtils : ComponentBase
{
/* All internal classes Types */
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");
public static readonly Type MyEntitiesType =
Type.GetType("Sandbox.Game.Entities.MyEntities, Sandbox.Game");
public static readonly Type MySlimBlockType =
Type.GetType("Sandbox.Game.Entities.Cube.MySlimBlock, Sandbox.Game");
/* Harmony Patcher */
private static readonly Harmony Patcher = new Harmony("SeamlessClientPatcher");
/* Static Contructors */
public static ConstructorInfo ClientConstructor { get; private set; }
public static ConstructorInfo SyncLayerConstructor { get; private set; }
public static ConstructorInfo TransportLayerConstructor { get; private set; }
public static ConstructorInfo MySessionConstructor { get; private set; }
public static ConstructorInfo MyMultiplayerClientBaseConstructor { get; private set; }
/* Static FieldInfos and PropertyInfos */
public static PropertyInfo MySessionLayer { get; private set; }
public static FieldInfo AdminSettings { get; private set; }
public static FieldInfo MPlayerGpsCollection { get; private set; }
/* Static MethodInfos */
public static MethodInfo LoadPlayerInternal { get; private set; }
public static MethodInfo SendPlayerData;
public static event EventHandler<JoinResultMsg> OnJoinEvent;
/* WorldGenerator */
public static MethodInfo UnloadProceduralWorldGenerator;
public override void Patch(Harmony patcher)
{
ClientConstructor = GetConstructor(ClientType, new[] { typeof(MyGameServerItem), SyncLayerType });
SyncLayerConstructor = GetConstructor(SyncLayerType, new[] { MyTransportLayerType });
TransportLayerConstructor = GetConstructor(MyTransportLayerType, new[] { typeof(int) });
MySessionConstructor = GetConstructor(MySessionType, new[] { typeof(MySyncLayer), typeof(bool) });
MyMultiplayerClientBaseConstructor = GetConstructor(MyMultiplayerClientBase, new[] { typeof(MySyncLayer) });
/* Get Fields and Properties */
MySessionLayer = GetProperty(typeof(MySession), "SyncLayer");
AdminSettings = GetField(typeof(MySession), "m_adminSettings");
MPlayerGpsCollection = GetField(typeof(MyPlayerCollection), "m_players");
/* Get Methods */
LoadPlayerInternal = GetMethod(typeof(MyPlayerCollection), "LoadPlayerInternal");
SendPlayerData = GetMethod(ClientType, "SendPlayerData");
//MethodInfo ConnectToServer = GetMethod(typeof(MyGameService), "ConnectToServer", BindingFlags.Static | BindingFlags.Public);
base.Patch(patcher);
}
#region PatchMethods
public static MethodInfo GetMethod(Type type, string methodName)
{
var foundMethod = AccessTools.Method(type, methodName);
if (foundMethod == null)
throw new NullReferenceException($"Method for {methodName} is null!");
return foundMethod;
}
public static FieldInfo GetField(Type type, string fieldName)
{
var foundField = AccessTools.Field(type, fieldName);
if (foundField == null)
throw new NullReferenceException($"Field for {fieldName} is null!");
return foundField;
}
public static PropertyInfo GetProperty(Type type, string propertyName)
{
var foundProperty = AccessTools.Property(type, propertyName);
if (foundProperty == null)
throw new NullReferenceException($"Property for {propertyName} is null!");
return foundProperty;
}
public static ConstructorInfo GetConstructor(Type type, Type[] types)
{
var foundConstructor = AccessTools.Constructor(type, types);
if (foundConstructor == null)
throw new NullReferenceException($"Contructor for {type.Name} is null!");
return foundConstructor;
}
#endregion
}
}