diff --git a/NebulaModel/MultiplayerOptions.cs b/NebulaModel/MultiplayerOptions.cs index c6ea36fc2..a7b179ab8 100644 --- a/NebulaModel/MultiplayerOptions.cs +++ b/NebulaModel/MultiplayerOptions.cs @@ -75,14 +75,17 @@ public class MultiplayerOptions : ICloneable private bool _streamerMode = false; [DisplayName("Streamer mode")] - [Description("If enabled specific personal information like your IP address is hidden from the ingame chat.")] + [Description("If enabled specific personal information like your IP address is hidden from the ingame chat and input fields.")] public bool StreamerMode { get => _streamerMode; set { _streamerMode = value; InputField ngrokAuthTokenInput = GameObject.Find("list/scroll-view/viewport/content/Network/NgrokAuthtoken")?.GetComponentInChildren(); - UpdateNgrokAuthtokenInputFieldContentType(ref ngrokAuthTokenInput); + UpdateInputFieldContentType(ref ngrokAuthTokenInput); + + InputField hostIpInput = GameObject.Find("UI Root/Overlay Canvas/Nebula - Multiplayer Menu/Host IP Address/InputField")?.GetComponentInChildren(); + UpdateInputFieldContentType(ref hostIpInput); } } @@ -127,19 +130,19 @@ public object Clone() return MemberwiseClone(); } - private void UpdateNgrokAuthtokenInputFieldContentType(ref InputField ngrokAuthTokenInput) + private void UpdateInputFieldContentType(ref InputField inputField) { - if (ngrokAuthTokenInput != null) + if (inputField != null) { if (StreamerMode) { - ngrokAuthTokenInput.contentType = InputField.ContentType.Password; + inputField.contentType = InputField.ContentType.Password; } else { - ngrokAuthTokenInput.contentType = InputField.ContentType.Standard; + inputField.contentType = InputField.ContentType.Standard; } - ngrokAuthTokenInput.UpdateLabel(); + inputField.UpdateLabel(); } } @@ -149,7 +152,7 @@ public void ModifyInputFieldAtCreation(string displayName, ref InputField inputF { case _ngrokAuthtokenDisplayname: { - UpdateNgrokAuthtokenInputFieldContentType(ref inputField); + UpdateInputFieldContentType(ref inputField); break; } } diff --git a/NebulaNetwork/Client.cs b/NebulaNetwork/Client.cs index 5b59dbb6e..a5d623671 100644 --- a/NebulaNetwork/Client.cs +++ b/NebulaNetwork/Client.cs @@ -315,6 +315,16 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) return; } + if (e.Code == (ushort)DisconnectionReason.HostStillLoading) + { + InGamePopup.ShowWarning( + "Server Busy", + "Server is not ready to join. Please try again later.", + "OK".Translate(), + Multiplayer.LeaveGame); + return; + } + if (Multiplayer.Session.IsGameLoaded || Multiplayer.Session.IsInLobby) { InGamePopup.ShowWarning( @@ -331,6 +341,7 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) } else { + Log.Warn("Disconnect code: " + e.Code + ", reason:" + e.Reason); InGamePopup.ShowWarning( "Server Unavailable", $"Could not reach the server, please try again later.", @@ -349,17 +360,11 @@ private static void DisableNagleAlgorithm(WebSocket socket) } } + private readonly AccessTools.FieldRef fragmentsBufferRef = AccessTools.FieldRefAccess("_fragmentsBuffer"); private int GetFragmentBufferLength() { - MemoryStream buffer = (MemoryStream)AccessTools.Field(typeof(WebSocket), "_fragmentsBuffer").GetValue(clientSocket); - if (buffer != null) - { - return (int)buffer.Length; - } - else - { - return 0; - } + MemoryStream fragmentsBuffer = fragmentsBufferRef(clientSocket); + return (int)(fragmentsBuffer?.Length ?? 0); } } } diff --git a/NebulaNetwork/Server.cs b/NebulaNetwork/Server.cs index f52c25980..25b9f7723 100644 --- a/NebulaNetwork/Server.cs +++ b/NebulaNetwork/Server.cs @@ -130,6 +130,7 @@ public override void Start() { InGamePopup.ShowError("Error", "An error occurred while hosting the game: " + e.Message, "Close"); Stop(); + Multiplayer.LeaveGame(); return; } diff --git a/NebulaPatcher/NebulaPlugin.cs b/NebulaPatcher/NebulaPlugin.cs index 06ab5c62a..072d23552 100644 --- a/NebulaPatcher/NebulaPlugin.cs +++ b/NebulaPatcher/NebulaPlugin.cs @@ -35,7 +35,8 @@ private void Awake() // Read command-line arguments string[] args = Environment.GetCommandLineArgs(); - (bool didLoad, bool loadArgExists, string saveName) = (false, false, string.Empty); + bool batchmode = false; + (bool didLoad, bool loadArgExists, bool newgameArgExists, string saveName) = (false, false, false, string.Empty); for (int i = 0; i < args.Length; i++) { if (args[i] == "-server") @@ -44,6 +45,39 @@ private void Awake() Log.Info($">> Initializing dedicated server"); } + if (args[i] == "-batchmode") + { + batchmode = true; + } + + if (args[i] == "-newgame") + { + newgameArgExists = true; + if (i + 3 < args.Length) + { + if (!int.TryParse(args[i + 1], out int seed)) + { + Log.Warn($">> Can't set galaxy seed: {args[i + 1]} is not a integer"); + } + else if (!int.TryParse(args[i + 2], out int starCount)) + { + Log.Warn($">> Can't set star count: {args[i + 2]} is not a integer"); + } + else if (!float.TryParse(args[i + 3], out float resourceMultiplier)) + { + Log.Warn($">> Can't set resource multiplier: {args[i + 3]} is not a floating point number"); + } + else + { + Log.Info($">> Creating new game ({seed}, {starCount}, {resourceMultiplier:F1})"); + GameDesc gameDesc = new GameDesc(); + gameDesc.SetForNewGame(UniverseGen.algoVersion, seed, starCount, 1, resourceMultiplier); + NebulaWorld.GameStates.GameStatesManager.NewGameDesc = gameDesc; + didLoad = true; + } + } + } + if (args[i] == "-load" && i + 1 < args.Length) { loadArgExists = true; @@ -109,13 +143,25 @@ private void Awake() Log.Error($">> Can't find any save in the folder! Exiting..."); } } + else if (newgameArgExists) + { + Log.Error($">> New game parameters incorrect! Exiting...\nExpect: -newgame seed starCount resourceMltiplier"); + } else { - Log.Error(">> -load argument missing! Exiting..."); + Log.Error(">> -load or -newgame argument missing! Exiting..."); } Application.Quit(); } + if (Multiplayer.IsDedicated) + { + if (!batchmode) + { + Log.Warn("Dedicate server should start with -batchmode argument"); + } + } + try { Initialize(); @@ -152,6 +198,25 @@ public static void StartDedicatedServer(string saveName) } } + public static void StartDedicatedServer(GameDesc gameDesc) + { + // Mimic UI buttons clicking + UIMainMenu_Patch.OnMultiplayerButtonClick(); + if (gameDesc != null) + { + // Modified from DoLoadSelectedGame + Log.Info($"Starting dedicated server, create new game from parameters:"); + Log.Info($"seed={gameDesc.galaxySeed} starCount={gameDesc.starCount} resourceMultiplier={gameDesc.resourceMultiplier:F1}"); + DSPGame.StartGameSkipPrologue(gameDesc); + Log.Info($"Listening server on port {NebulaModel.Config.Options.HostPort}"); + Multiplayer.HostGame(new Server(NebulaModel.Config.Options.HostPort, true)); + if (command_ups != 0) + { + FPSController.SetFixUPS(command_ups); + } + } + } + private static async void ActivityManager_OnActivityJoin(string secret) { if(Multiplayer.IsActive) diff --git a/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs index 5c5edadd5..c757f6220 100644 --- a/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIGalaxySelect_Patch.cs @@ -174,13 +174,19 @@ public static void UpdateParametersUIDisplay_Postfix(UIGalaxySelect __instance) [HarmonyPostfix] [HarmonyPatch(nameof(UIGalaxySelect._OnUpdate))] [System.Diagnostics.CodeAnalysis.SuppressMessage("Style", "IDE1006:Naming Styles", Justification = "Original Function Name")] - public static void _OnUpdate_Postfix() + public static void _OnUpdate_Postfix(UIGalaxySelect __instance) { if (Multiplayer.IsInMultiplayerMenu) { // as we need to load and generate planets for the detail view in the lobby, update the loading process here PlanetModelingManager.ModelingPlanetCoroutine(); UIRoot.instance.uiGame.planetDetail._OnUpdate(); + if (Input.mouseScrollDelta.y != 0) + { + // zoom in/out when scrolling + float delta = (Input.mouseScrollDelta.y < 0 ? 1f : -1f) * (VFInput.shift ? 1f : 0.1f); + __instance.cameraPoser.distRatio += delta; + } } } diff --git a/NebulaPatcher/Patches/Dynamic/UIMainMenu_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIMainMenu_Patch.cs index 3b7ed1b90..3f1667b5a 100644 --- a/NebulaPatcher/Patches/Dynamic/UIMainMenu_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIMainMenu_Patch.cs @@ -59,7 +59,7 @@ public static void _OnOpen_Postfix() [HarmonyPostfix] [HarmonyPatch(nameof(UIMainMenu.OnUpdateLogButtonClick))] - public static void OnUpdateLogButtonClick_Postfix(UIMainMenu __instance) + public static void OnUpdateLogButtonClick_Postfix() { // Return to main menu when update log is opened OnMultiplayerBackButtonClick(); @@ -166,38 +166,55 @@ private static void AddMultiplayerJoinMenu() Object.Destroy(multiplayerMenu.gameObject.GetComponent()); multiplayerMenu.gameObject.name = "Nebula - Multiplayer Menu"; - multiplayerMenu.Find("star-count").gameObject.SetActive(false); - multiplayerMenu.Find("resource-multiplier").gameObject.SetActive(false); - multiplayerMenu.Find("right-group").gameObject.SetActive(false); - multiplayerMenu.Find("left-group").gameObject.SetActive(false); - multiplayerMenu.Find("property-multiplier").gameObject.SetActive(false); - multiplayerMenu.Find("seed-key").gameObject.SetActive(false); - multiplayerMenu.Find("sandbox-mode").gameObject.SetActive(false); - - Transform topTitle = multiplayerMenu.Find("top-title"); - topTitle.GetComponent().enabled = false; - topTitle.GetComponent().text = "Multiplayer"; - - Transform hostIpField = multiplayerMenu.Find("galaxy-seed"); - hostIpField.GetComponent().enabled = false; - hostIpField.GetComponent().text = "Host IP Address"; - hostIPAddressInput = hostIpField.GetComponentInChildren(); - hostIPAddressInput.onEndEdit.RemoveAllListeners(); - hostIPAddressInput.onValueChanged.RemoveAllListeners(); - //note: connectToUrl uses Dns.getHostEntry, which can only use up to 255 chars. - //256 will trigger an argument out of range exception - hostIPAddressInput.characterLimit = 255; - - string ip = "127.0.0.1"; - if (Config.Options.RememberLastIP && !string.IsNullOrWhiteSpace(Config.Options.LastIP)) + for (int i = 0; i < multiplayerMenu.childCount; i++) { - ip = Config.Options.LastIP; - } - hostIPAddressInput.text = ip; + Transform child = multiplayerMenu.GetChild(i); + if (child.name == "top-title") + { + Transform topTitle = child; + topTitle.GetComponent().enabled = false; + topTitle.GetComponent().text = "Multiplayer"; + } + else if (child.name == "galaxy-seed") + { + Transform hostIpField = child; + hostIpField.GetComponent().enabled = false; + hostIpField.GetComponent().text = "Host IP Address"; + hostIpField.name = "Host IP Address"; + hostIPAddressInput = hostIpField.GetComponentInChildren(); + hostIPAddressInput.onEndEdit.RemoveAllListeners(); + hostIPAddressInput.onValueChanged.RemoveAllListeners(); + //note: connectToUrl uses Dns.getHostEntry, which can only use up to 255 chars. + //256 will trigger an argument out of range exception + hostIPAddressInput.characterLimit = 255; + + string ip = "127.0.0.1"; + if (Config.Options.RememberLastIP && !string.IsNullOrWhiteSpace(Config.Options.LastIP)) + { + ip = Config.Options.LastIP; + } + hostIPAddressInput.text = ip; + hostIPAddressInput.contentType = Config.Options.StreamerMode ? InputField.ContentType.Password : InputField.ContentType.Standard; - Transform passwordField = Object.Instantiate(hostIpField, hostIpField.parent, false); - passwordField.localPosition = multiplayerMenu.Find("star-count").localPosition; + } + else if (child.name == "start-button") + { + OverrideButton(multiplayerMenu.Find("start-button").GetComponent(), "Join Game", OnJoinGameButtonClick); + } + else if (child.name == "cancel-button") + { + OverrideButton(multiplayerMenu.Find("cancel-button").GetComponent(), null, OnJoinGameBackButtonClick); + } + else + { + // Remove all unused elements that may be added by other mods + GameObject.Destroy(child.gameObject); + } + } + Transform passwordField = Object.Instantiate(multiplayerMenu.Find("Host IP Address"), multiplayerMenu, false); + passwordField.localPosition = galaxySelectTemplate.Find("star-count").localPosition; passwordField.GetComponent().text = "Password (optional)"; + passwordField.name = "Password (optional)"; passwordInput = passwordField.GetComponentInChildren(); passwordInput.contentType = InputField.ContentType.Password; @@ -207,10 +224,6 @@ private static void AddMultiplayerJoinMenu() passwordInput.text = Config.Options.LastClientPassword; } - OverrideButton(multiplayerMenu.Find("start-button").GetComponent(), "Join Game", OnJoinGameButtonClick); - OverrideButton(multiplayerMenu.Find("cancel-button").GetComponent(), null, OnJoinGameBackButtonClick); - multiplayerMenu.Find("random-button").gameObject.SetActive(false); - multiplayerMenu.gameObject.SetActive(false); } @@ -293,7 +306,7 @@ public static void JoinGame(string ip, string password = "") private static IEnumerator TryConnectToServer(string ip, int port, bool isIP, string password) { - InGamePopup.ShowInfo("Connecting", $"Connecting to server {ip}:{port}...", null, null); + InGamePopup.ShowInfo("Connecting", $"Connecting to server...", null, null); multiplayerMenu.gameObject.SetActive(false); // We need to wait here to have time to display the Connecting popup since the game freezes during the connection. @@ -303,7 +316,7 @@ private static IEnumerator TryConnectToServer(string ip, int port, bool isIP, st { InGamePopup.FadeOut(); //re-enabling the menu again after failed connect attempt - InGamePopup.ShowWarning("Connect failed", $"Was not able to connect to {hostIPAddressInput.text}", "OK"); + InGamePopup.ShowWarning("Connect failed", $"Was not able to connect to server", "OK"); multiplayerMenu.gameObject.SetActive(true); } else diff --git a/NebulaPatcher/Patches/Dynamic/VFPreload_Patch.cs b/NebulaPatcher/Patches/Dynamic/VFPreload_Patch.cs index b94c3e618..6aa34dd65 100644 --- a/NebulaPatcher/Patches/Dynamic/VFPreload_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/VFPreload_Patch.cs @@ -15,6 +15,7 @@ public static void InvokeOnLoad_Postfix() { if (Multiplayer.IsDedicated) { + VFAudio.audioVolume = 0f; NebulaModel.Utils.NativeInterop.HideWindow(); NebulaModel.Utils.NativeInterop.SetConsoleCtrlHandler(); // Logging to provide progression to user @@ -56,7 +57,18 @@ public static void InvokeOnLoadWorkEnded_Postfix() if (Multiplayer.IsDedicated) { - NebulaPlugin.StartDedicatedServer(NebulaWorld.GameStates.GameStatesManager.ImportedSaveName); + if (GameStatesManager.ImportedSaveName != null) + { + NebulaPlugin.StartDedicatedServer(GameStatesManager.ImportedSaveName); + } + else if (GameStatesManager.NewGameDesc != null) + { + NebulaPlugin.StartDedicatedServer(GameStatesManager.NewGameDesc); + } + else + { + Log.Warn("No game start option provided!"); + } } } } diff --git a/NebulaPatcher/Patches/Transpilers/UIVirtualStarmap_Transpiler.cs b/NebulaPatcher/Patches/Transpilers/UIVirtualStarmap_Transpiler.cs index 292bdcc1b..895452dbd 100644 --- a/NebulaPatcher/Patches/Transpilers/UIVirtualStarmap_Transpiler.cs +++ b/NebulaPatcher/Patches/Transpilers/UIVirtualStarmap_Transpiler.cs @@ -230,6 +230,24 @@ public static IEnumerable _OnLateUpdate_Transpiler(IEnumerable< } })); + // show all star/planet name when alt is pressed + matcher.End() + .SetOpcodeAndAdvance(OpCodes.Ldarg_0) + .Insert( + HarmonyLib.Transpilers.EmitDelegate>((UIVirtualStarmap starmap) => + { + if (VFInput.alt) + { + int count = starmap.clickText == "" ? starmap.starPool.Count : starmap.starPool[0].starData?.planetCount + 1 ?? 0; + for (int i = 1; i < count; i++) + { + starmap.starPool[i].nameText.gameObject.SetActive(true); + } + } + }), + new CodeInstruction(OpCodes.Ret) + ); + return matcher.InstructionEnumeration(); } diff --git a/NebulaWorld/GameStates/GameStatesManager.cs b/NebulaWorld/GameStates/GameStatesManager.cs index 328f7bf4f..9bf704e5a 100644 --- a/NebulaWorld/GameStates/GameStatesManager.cs +++ b/NebulaWorld/GameStates/GameStatesManager.cs @@ -20,6 +20,7 @@ public void Dispose() public static float RealUPS => (float)FPSController.currentUPS; public static bool DuringReconnect = false; public static string ImportedSaveName { get; set; } + public static GameDesc NewGameDesc { get; set; } public static int FragmentSize { get; set; } private static int bufferLength; diff --git a/NebulaWorld/InGamePopup.cs b/NebulaWorld/InGamePopup.cs index 796be0b48..efd6069f7 100644 --- a/NebulaWorld/InGamePopup.cs +++ b/NebulaWorld/InGamePopup.cs @@ -114,7 +114,7 @@ private static void Show(int type, string title, string message, string btn1, st private static void CreateInputField(InputField.ContentType contentType, string text) { - GameObject inputObject = GameObject.Find("UI Root/Overlay Canvas/Nebula - Multiplayer Menu/galaxy-seed/InputField"); + GameObject inputObject = GameObject.Find("UI Root/Overlay Canvas/Nebula - Multiplayer Menu/Host IP Address/InputField"); inputObject = UnityEngine.Object.Instantiate(inputObject, displayedMessage.transform.Find("Window/Body/Client")); inputObject.name = "InputField"; inputObject.transform.localPosition = new Vector3(-150, 0, 0); diff --git a/NebulaWorld/MonoBehaviours/Remote/RemotePlayerAnimation.cs b/NebulaWorld/MonoBehaviours/Remote/RemotePlayerAnimation.cs index bdd506faa..a15a10f57 100644 --- a/NebulaWorld/MonoBehaviours/Remote/RemotePlayerAnimation.cs +++ b/NebulaWorld/MonoBehaviours/Remote/RemotePlayerAnimation.cs @@ -27,7 +27,11 @@ public void UpdateState(PlayerMovement packet) packetBuffer[i] = packetBuffer[i + 1]; } packetBuffer[packetBuffer.Length - 1] = packet; - packet = packetBuffer[0]; + } + + private void Update() + { + PlayerMovement packet = packetBuffer[0]; if (packet == null || PlayerAnimator == null) { return; diff --git a/NebulaWorld/RemotePlayerModel.cs b/NebulaWorld/RemotePlayerModel.cs index ed6ecbd40..f2f5f0091 100644 --- a/NebulaWorld/RemotePlayerModel.cs +++ b/NebulaWorld/RemotePlayerModel.cs @@ -94,7 +94,7 @@ public void Destroy() PlayerInstance = null; if (StarmapTracker != null) { - Object.Destroy(StarmapTracker); + Object.Destroy(StarmapTracker.gameObject); } if (StarmapNameText != null) diff --git a/NebulaWorld/SimulatedWorld.cs b/NebulaWorld/SimulatedWorld.cs index d7d6196cc..02f70da99 100644 --- a/NebulaWorld/SimulatedWorld.cs +++ b/NebulaWorld/SimulatedWorld.cs @@ -186,7 +186,8 @@ public void SetupInitialPlayerState() { player.Data.Mecha.SandCount = GameMain.mainPlayer.sandCount; } - + // Set the name of local player in starmap from Icarus to user name + GameMain.mainPlayer.mecha.appearance.overrideName = " " + player.Data.Username + " "; // Finally we need add the local player components to the player character localPlayerMovement = GameMain.mainPlayer.gameObject.AddComponentIfMissing(); // ChatManager should continuous exsit until the game is closed