diff --git a/MinecraftClient/McClient.cs b/MinecraftClient/McClient.cs index 753de486..82dfa741 100644 --- a/MinecraftClient/McClient.cs +++ b/MinecraftClient/McClient.cs @@ -368,6 +368,13 @@ namespace MinecraftClient timeoutdetector = null; } + if (!InternalConfig.InteractiveMode) + { + StopConsoleSession(); + Program.HandleFailure(null, false, ChatBot.DisconnectReason.ConnectionLost); + return; + } + if (!Config.ChatBot.AutoRelog.Enabled) { if (ReconnectionAttemptsLeft > 0) @@ -376,33 +383,22 @@ namespace MinecraftClient Thread.Sleep(5000); ReconnectionAttemptsLeft--; Program.Restart(); - } - else if (InternalConfig.InteractiveMode) - { - StopConsoleSession(); - Program.HandleFailure(); + return; } - throw new Exception("Initialization failed."); + StopConsoleSession(); + Program.HandleFailure(); + return; } - else - { - // AutoRelog is enabled - invoke its static handler to trigger reconnection. - // Use the same "Connection has been lost" message that OnConnectionLost uses - // for ConnectionLost, so it matches the default Kick_Messages. - if (AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.ConnectionLost, Translations.mcc_disconnect_lost)) - return; // AutoRelog is triggering a restart - // AutoRelog chose not to reconnect (e.g., message didn't match - // kick messages and Ignore_Kick_Message is false, or retry limit reached) - if (InternalConfig.InteractiveMode) - { - StopConsoleSession(); - Program.HandleFailure(); - } + // AutoRelog is enabled - invoke its static handler to trigger reconnection. + // Use the same "Connection has been lost" message that OnConnectionLost uses + // for ConnectionLost, so it matches the default Kick_Messages. + if (AutoRelog.OnDisconnectStatic(ChatBot.DisconnectReason.ConnectionLost, Translations.mcc_disconnect_lost)) + return; - throw new Exception("Initialization failed."); - } + StopConsoleSession(); + Program.HandleFailure(); } public void Transfer(string newHost, int newPort) @@ -493,20 +489,24 @@ namespace MinecraftClient timeoutdetector = null; } + if (!InternalConfig.InteractiveMode) + { + StopConsoleSession(); + Program.HandleFailure(null, false, ChatBot.DisconnectReason.ConnectionLost); + return; + } + if (ReconnectionAttemptsLeft > 0) { Log.Info($"Reconnecting... Attempts left: {ReconnectionAttemptsLeft}"); Thread.Sleep(5000); ReconnectionAttemptsLeft--; Program.Restart(); - } - else if (InternalConfig.InteractiveMode) - { - StopConsoleSession(); - Program.HandleFailure(); + return; } - throw new Exception("Transfer failed and reconnection attempts exhausted.", ex); + StopConsoleSession(); + Program.HandleFailure(); } finally { @@ -919,6 +919,7 @@ namespace MinecraftClient timeoutdetector = null; } + bool exitOnFailure = Program.PrepareExitOnFailure(); bool will_restart = false; switch (reason) @@ -950,7 +951,9 @@ namespace MinecraftClient { try { - will_restart |= bot.OnDisconnect(reason, message); + bool botWillRestart = bot.OnDisconnect(reason, message); + if (!exitOnFailure) + will_restart |= botWillRestart; } catch (Exception e) { diff --git a/MinecraftClient/Program.cs b/MinecraftClient/Program.cs index 71ba3142..5408f6b7 100644 --- a/MinecraftClient/Program.cs +++ b/MinecraftClient/Program.cs @@ -56,6 +56,7 @@ namespace MinecraftClient private static bool useMcVersionOnce = false; private static Thread? _restartThread = null; private static readonly object _restartLock = new(); + private static int exitOnFailurePending; private static string settingsIniPath = "MinecraftClient.ini"; // [SENTRY] @@ -916,6 +917,9 @@ namespace MinecraftClient { lock (_restartLock) { + if (Volatile.Read(ref exitOnFailurePending) != 0) + return false; + if (HasRestartPendingForAnotherThreadNoLock()) return false; @@ -962,6 +966,19 @@ namespace MinecraftClient && _restartThread != Thread.CurrentThread; } + /// + /// Marks the current failure as terminal when MCC is running under an external supervisor. + /// Further restart requests are rejected so disconnect cleanup cannot revive the process. + /// + internal static bool PrepareExitOnFailure() + { + if (InternalConfig.InteractiveMode) + return false; + + Interlocked.Exchange(ref exitOnFailurePending, 1); + return true; + } + public static void DoExit(int exitcode = 0) { WriteBackSettings(); @@ -1015,13 +1032,19 @@ namespace MinecraftClient catch { } } ConsoleIO.WriteLine(errorMessage); + } - if (disconnectReason.HasValue) - { - autoRelogHandled = true; - if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) - return; - } + if (PrepareExitOnFailure()) + { + Exit(GetFailureExitCode(disconnectReason)); + return; + } + + if (!string.IsNullOrEmpty(errorMessage) && disconnectReason.HasValue) + { + autoRelogHandled = true; + if (ChatBots.AutoRelog.OnDisconnectStatic(disconnectReason.Value, errorMessage)) + return; } if (InternalConfig.InteractiveMode) @@ -1123,20 +1146,17 @@ namespace MinecraftClient offlinePrompt.Item1.Start(); } } - else - { - // Not in interactive mode, just exit and let the calling script handle the failure - if (disconnectReason.HasValue) - { - // Return distinct exit codes for known failures. - if (disconnectReason.Value == ChatBot.DisconnectReason.UserLogout) Exit(1); - if (disconnectReason.Value == ChatBot.DisconnectReason.InGameKick) Exit(2); - if (disconnectReason.Value == ChatBot.DisconnectReason.ConnectionLost) Exit(3); - if (disconnectReason.Value == ChatBot.DisconnectReason.LoginRejected) Exit(4); - } - Exit(); - } + } + private static int GetFailureExitCode(ChatBot.DisconnectReason? disconnectReason) + { + return disconnectReason switch + { + ChatBot.DisconnectReason.InGameKick => 2, + ChatBot.DisconnectReason.ConnectionLost => 3, + ChatBot.DisconnectReason.LoginRejected => 4, + _ => 1, + }; } /// diff --git a/docs/guide/configuration.md b/docs/guide/configuration.md index 4a1b594b..008f22d3 100644 --- a/docs/guide/configuration.md +++ b/docs/guide/configuration.md @@ -685,7 +685,7 @@ Coordinate = { x = 145, y = 64, z = 2045 } - **Description:** - This setting allows you to define if your want to disable pauses on error, for using MCC in non-interactive scripts + Exit immediately with a nonzero status when a connection or login failure occurs. This bypasses MCC reconnect handling, including AutoRelog, so an external supervisor can restart MCC. - **Type:** `boolean`