diff --git a/NebulaNetwork/Client.cs b/NebulaNetwork/Client.cs index e627699d7..84763fc54 100644 --- a/NebulaNetwork/Client.cs +++ b/NebulaNetwork/Client.cs @@ -380,7 +380,7 @@ private void ClientSocket_OnClose(object sender, CloseEventArgs e) Log.Warn("Disconnect code: " + e.Code + ", reason:" + e.Reason); InGamePopup.ShowWarning( "Server Unavailable".Translate(), - "Could not reach the server, please try again later.".Translate(), + "Can't reach the server. Please check the network status.\n(Refer Nebula wiki for troubleshooting)".Translate(), "OK".Translate(), Multiplayer.LeaveGame); } diff --git a/NebulaNetwork/PacketProcessors/Chat/RemoteServerCommandProcessor.cs b/NebulaNetwork/PacketProcessors/Chat/RemoteServerCommandProcessor.cs index b84cd1110..ec938906d 100644 --- a/NebulaNetwork/PacketProcessors/Chat/RemoteServerCommandProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Chat/RemoteServerCommandProcessor.cs @@ -41,6 +41,7 @@ protected override void ProcessPacket(RemoteServerCommandPacket packet, NebulaCo return; } + Log.Info("Get RemoteServerCommand: " + packet.Command); if (!allowedConnections.Contains(conn) && packet.Command != RemoteServerCommand.Login) { if (!string.IsNullOrWhiteSpace(Config.Options.RemoteAccessPassword)) @@ -149,8 +150,18 @@ private string Save(string saveName) // Save game and report result to the client LastSaveTime = DateTime.Now; saveName = string.IsNullOrEmpty(saveName) ? GameSave.LastExit : saveName; - return string.Format("Save {0} : {1}".Translate(), saveName, - GameSave.SaveCurrentGame(saveName) ? "Success".Translate() : "Fail".Translate()); + bool result; + try + { + result = GameSave.SaveCurrentGame(saveName); + } + catch (Exception ex) + { + Log.Error(ex); + result = false; + } + + return string.Format("Save {0} : {1}".Translate(), saveName, result ? "Success".Translate() : "Fail".Translate()); } private static string Load(string saveName) diff --git a/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncResponseProcessor.cs b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncResponseProcessor.cs index c30392a33..8768b67d7 100644 --- a/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncResponseProcessor.cs +++ b/NebulaNetwork/PacketProcessors/Factory/Storage/StorageSyncResponseProcessor.cs @@ -38,7 +38,7 @@ protected override void ProcessPacket(StorageSyncResponsePacket packet, NebulaCo var itemProto = LDB.items.Select((int)protoId); //Imitation of UIStorageWindow.OnStorageIdChange() - Multiplayer.Session.Storage.ActiveWindowTitle.text = itemProto.name; + Multiplayer.Session.Storage.ActiveWindowTitle.text = itemProto?.name ?? ""; } Multiplayer.Session.Storage.ActiveUIStorageGrid._Free(); Multiplayer.Session.Storage.ActiveUIStorageGrid._Init(storageComponent); diff --git a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs index 6455195f9..48d4115e4 100644 --- a/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameData_Patch.cs @@ -51,8 +51,13 @@ public static bool GetOrCreateFactory_Prefix(GameData __instance, ref PlanetFact // Get the recieved bytes from the remote server that we will import if (!Multiplayer.Session.Planets.PendingFactories.TryGetValue(planet.id, out var factoryBytes)) { - // We messed up, just defer to the default behaviour on the client (will cause desync but not outright crash) - Log.Error("PendingFactories did not have value we wanted, factory will not be synced!"); + // If planet.factory is not empty, it may be called by elsewhere beside PlanetModelingManager.LoadingPlanetFactoryMain + // In this case, just use the original method which will return planet.factory + if (planet.factory == null) + { + // We messed up, just defer to the default behaviour on the client (will cause desync but not outright crash) + Log.Error("PendingFactories did not have value we wanted, factory will not be synced!"); + } return true; } diff --git a/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs b/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs index 4902b02b0..bdf8feacb 100644 --- a/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/GameHistoryData_Patch.cs @@ -131,6 +131,21 @@ public static bool UnlockTech_Prefix() return !Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.History.IsIncomingRequest; } + [HarmonyPrefix] + [HarmonyPatch(nameof(GameHistoryData.UnlockTechUnlimited))] + public static bool UnlockTechUnlimited_Prefix(GameHistoryData __instance, int techId) + { + if (!Multiplayer.IsActive || Multiplayer.Session.LocalPlayer.IsHost || Multiplayer.Session.History.IsIncomingRequest) + { + return true; + } + // If client initial the manual unlock event (metadata, sandbox), send to server and wait for the authoritative + var techState = __instance.TechState(techId); + var level = techState.hashUploaded >= techState.hashNeeded ? techState.maxLevel : techState.curLevel; + Multiplayer.Session.Network.SendPacket(new GameHistoryUnlockTechPacket(techId, level)); + return false; + } + [HarmonyPostfix] [HarmonyPatch(nameof(GameHistoryData.NotifyTechUnlock))] public static void NotifyTechUnlock_Postfix(int _techId, int _level) diff --git a/NebulaPatcher/Patches/Dynamic/UIStorageWindow_Patch.cs b/NebulaPatcher/Patches/Dynamic/UIStorageWindow_Patch.cs index dbbde4d11..36d431915 100644 --- a/NebulaPatcher/Patches/Dynamic/UIStorageWindow_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UIStorageWindow_Patch.cs @@ -20,10 +20,15 @@ public static bool OnStorageIdChange_Prefix(UIStorageWindow __instance) { return true; } + var storageComponent = __instance.factoryStorage?.storagePool[__instance.storageId]; + if (storageComponent == null || storageComponent.entityId == 0) + { + return true; + } var storageUI = __instance.storageUI; Multiplayer.Session.Storage.ActiveUIStorageGrid = storageUI; var titleText = __instance.titleText; - Multiplayer.Session.Storage.ActiveStorageComponent = __instance.factoryStorage.storagePool[__instance.storageId]; + Multiplayer.Session.Storage.ActiveStorageComponent = storageComponent; Multiplayer.Session.Storage.ActiveWindowTitle = titleText; Multiplayer.Session.Storage.ActiveBansSlider = __instance.bansSlider; Multiplayer.Session.Storage.ActiveBansValueText = __instance.bansValueText; diff --git a/NebulaPatcher/Patches/Dynamic/UITechNode_Patch.cs b/NebulaPatcher/Patches/Dynamic/UITechNode_Patch.cs index ff98d66e1..7e3138243 100644 --- a/NebulaPatcher/Patches/Dynamic/UITechNode_Patch.cs +++ b/NebulaPatcher/Patches/Dynamic/UITechNode_Patch.cs @@ -1,7 +1,6 @@ #region using HarmonyLib; -using NebulaWorld; #endregion @@ -11,31 +10,10 @@ namespace NebulaPatcher.Patches.Dynamic; public class UITechNode_Patch { [HarmonyPostfix] - [HarmonyPatch(nameof(UITechNode.UpdateInfoDynamic))] - public static void UpdateInfoDynamic_Postfix(UITechNode __instance) + [HarmonyPatch(nameof(UITechNode.DeterminePrerequisiteSuffice))] + public static void DeterminePrerequisiteSuffice_Postfix(ref bool __result) { - // Always disable the buyout button for clients. - if (!Multiplayer.IsActive || !Multiplayer.Session.LocalPlayer.IsClient) - { - return; - } - __instance.buyoutButton.transitions[0].normalColor = __instance.buyoutNormalColor1; - __instance.buyoutButton.transitions[0].mouseoverColor = __instance.buyoutMouseOverColor1; - __instance.buyoutButton.transitions[0].pressedColor = __instance.buyoutPressedColor1; - //todo: Why is this commented? Should it be uncommented or deleted? - //__instance.buyoutButton.gameObject.SetActive(false); - } - - // Always disable the buyout button for clients. - [HarmonyPrefix] - [HarmonyPatch(nameof(UITechNode.OnBuyoutButtonClick))] - public static bool OnBuyoutButtonClick_Prefix() - { - if (!Multiplayer.IsActive || !Multiplayer.Session.LocalPlayer.IsClient) - { - return true; - } - UIRealtimeTip.Popup("Only the host can do this!"); - return false; + // Skip metadata requirement of blueprint tech due to client can't get metadata currently + __result = true; } } diff --git a/NebulaPatcher/Patches/Dynamic/UITutorialTip_Patch.cs b/NebulaPatcher/Patches/Dynamic/UITutorialTip_Patch.cs new file mode 100644 index 000000000..62425dc39 --- /dev/null +++ b/NebulaPatcher/Patches/Dynamic/UITutorialTip_Patch.cs @@ -0,0 +1,23 @@ +#region + +using HarmonyLib; +using NebulaWorld; + +#endregion + +namespace NebulaPatcher.Patches.Dynamic; + +[HarmonyPatch(typeof(UITutorialTip))] +public class UITutorialTip_Patch +{ + [HarmonyPrefix] + [HarmonyPatch(nameof(UITutorialTip.PopupTutorialTip))] + public static bool PopupTutorialTip_Prefix(int tutorialId) + { + if (!Multiplayer.IsActive) return true; + + // In MP, disable tutorial tip so they don't show up when login every time + GameMain.history.UnlockTutorial(tutorialId); + return false; + } +} diff --git a/NebulaPatcher/Patches/Misc/Debugging.cs b/NebulaPatcher/Patches/Misc/Debugging.cs index 76fcc0fc4..8be257fef 100644 --- a/NebulaPatcher/Patches/Misc/Debugging.cs +++ b/NebulaPatcher/Patches/Misc/Debugging.cs @@ -40,19 +40,6 @@ public static void RemoveUnit_Postfix(int port) [HarmonyPatch(typeof(GameHistoryData))] internal class Debug_GameHistoryData_Patch { - [HarmonyPostfix] - [HarmonyPatch(nameof(GameHistoryData.EnqueueTech))] - public static void EnqueueTech_Postfix(GameHistoryData __instance, int techId) - { - if (Multiplayer.IsActive && Multiplayer.Session.History.IsIncomingRequest) - { - //Do not run if this was triggered by incoming request - return; - } - __instance.UnlockTech(techId); - GameMain.mainPlayer.mecha.corePowerGen = 10000000; - } - [HarmonyPrefix] [HarmonyPatch(nameof(GameHistoryData.dysonSphereSystemUnlocked), MethodType.Getter)] public static bool DysonSphereSystemUnlocked_Prefix(ref bool __result) diff --git a/NebulaWorld/MonoBehaviours/Local/Chat/ChatWindow.cs b/NebulaWorld/MonoBehaviours/Local/Chat/ChatWindow.cs index 20d4f84d5..4e4e623fe 100644 --- a/NebulaWorld/MonoBehaviours/Local/Chat/ChatWindow.cs +++ b/NebulaWorld/MonoBehaviours/Local/Chat/ChatWindow.cs @@ -266,6 +266,13 @@ public void ClearChat(Func filter) public void Toggle(bool forceClosed = false, bool focusField = true) { + if (Config.Options.ChatHotkey.MainKey == KeyCode.Return) + { + // If player set enter as toggle hotkey, add a check for default open => close action + // So if player is typing and hit enter, it won't close the chat window immediately + if (forceClosed == false && chatWindow.activeSelf && !string.IsNullOrEmpty(chatBox.text)) return; + } + var desiredStatus = !forceClosed && !chatWindow.activeSelf; chatWindow.SetActive(desiredStatus); notifier.gameObject.SetActive(!desiredStatus);