From 0ae4dae28da9282574ff1df24438f1b62855254c Mon Sep 17 00:00:00 2001 From: MrMaho Date: Sun, 27 Sep 2026 10:46:34 +0200 Subject: [PATCH] Show why you were disconnected in the main menu; reset-world / reload-save make all players reconnect automatically Co-Authored-By: Claude Opus 5.5 --- CHANGELOG.md | 2 ++ LobbyUI.cs | 32 ++++++++++++++++++-- MultiplayerManager.cs | 69 ++++++++++++++++++++++++++++++++++++++++++- NetSession.cs | 3 +- 4 files changed, 102 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c693ad3..108cf13 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,6 +23,8 @@ Server und Mod müssen beide 2.7 sein (Server: `git pull && docker compose up -d ist das Hauptmenü (mit eigenem QUIT) - Server-Profil neu: JOIN oben, Spielerliste rechts, Namensfeld ohne Steam in eigenem Kasten - Admin-ITEMS: Werkzeuge und Laden-Artikel (Ratschenkasten, Batterie, Motoröl, Kühlmittel, Bremsflüssigkeit ...) +- Nach dem Trennen (Kick, Ban, Shutdown, Timeout ...) zeigt das Hauptmenü den Grund +- `reset-world` / `reload-save` auf dem Server: alle Spieler bekommen eine Meldung und verbinden sich automatisch neu **Behoben** - Auto fiel beim Fahren eines anderen Spielers auseinander (angebaute Teile rissen an ihren Gelenken ab) diff --git a/LobbyUI.cs b/LobbyUI.cs index d484a02..a916a8b 100644 --- a/LobbyUI.cs +++ b/LobbyUI.cs @@ -72,7 +72,8 @@ namespace MSCO EnsureStyles(); bool pendingJoin = mp.PendingJoinHost != null && !mp.IsInSession; bool versionWarning = mp.PendingVersionWarning != null && !mp.IsInSession; - if (pendingJoin || versionWarning) minimized = false; // JOIN-Link / Warnung: Fenster zeigen + bool disconnected = mp.DisconnectNotice != null && !mp.IsInSession; + if (pendingJoin || versionWarning || disconnected) minimized = false; // JOIN-Link / Warnung / Trennung: Fenster zeigen if (minimized) { DrawMinimized(mp); return; } var window = new Rect((Screen.width - Width) / 2f, (Screen.height - Height) / 2f, Width, Height); @@ -84,8 +85,10 @@ namespace MSCO var content = new Rect(window.x + SidebarWidth + 32, window.y + 28, Width - SidebarWidth - 64, Height - StatusHeight - 52); GUILayout.BeginArea(content); - if (versionWarning) DrawVersionWarning(mp); + if (disconnected) DrawDisconnectNotice(mp); + else if (versionWarning) DrawVersionWarning(mp); else if (pendingJoin) DrawJoinConfirm(mp); + else if (mp.IsReconnecting) DrawReconnecting(mp); else if (mp.IsInSession) DrawSession(mp); else if (page == Page.P2P) DrawP2P(mp); else DrawServerBrowser(mp); @@ -483,6 +486,31 @@ namespace MSCO GUILayout.EndHorizontal(); } + /// Nach dem Trennen (Kick, Ban, Shutdown, Timeout ...): Grund anzeigen. + void DrawDisconnectNotice(MultiplayerManager mp) + { + GUILayout.Label("Disconnected", titleStyle); + GUILayout.Space(8); + GUILayout.BeginVertical(rowBox); + GUILayout.Space(6); + GUILayout.Label("REASON", captionStyle); + GUILayout.Label(mp.DisconnectNotice, rowNameStyle); + GUILayout.Space(6); + GUILayout.EndVertical(); + GUILayout.Space(8); + GUILayout.Label("Your own save game has been restored.", textStyle); + GUILayout.FlexibleSpace(); + if (GUILayout.Button("OK", primaryStyle, GUILayout.Height(42), GUILayout.Width(170))) mp.ClearDisconnectNotice(); + } + + void DrawReconnecting(MultiplayerManager mp) + { + GUILayout.Label("Reconnecting", titleStyle); + GUILayout.Space(8); + GUILayout.Label(mp.ReconnectMessage, rowNameStyle); + GUILayout.Label("You will be connected to the server again in a moment.", textStyle); + } + void DrawSession(MultiplayerManager mp) { GUILayout.Label(mp.IsHost ? "Hosting" : "Connected", titleStyle); diff --git a/MultiplayerManager.cs b/MultiplayerManager.cs index 9f659dc..07fd853 100644 --- a/MultiplayerManager.cs +++ b/MultiplayerManager.cs @@ -541,8 +541,18 @@ namespace MSCO SetStatus(""); } + // Letzter Server (für das automatische Neu-Verbinden nach reset-world) + string lastServerHost, lastServerName; + int lastServerPort, lastServerMax; + string reconnectMessage; // gesetzt, wenn der Server uns neu verbinden lässt + float reconnectAt; + void ConnectToServer(string host, int port, string displayName, int maxPlayers) { + lastServerHost = host; + lastServerPort = port; + lastServerName = displayName; + lastServerMax = maxPlayers; Join(host, port); if (session == null) return; connectingToServer = true; @@ -758,6 +768,18 @@ namespace MSCO UpdatePresence(); steam.Tick(); + + // Nach reset-world o.ä.: im Hauptmenü automatisch wieder mit demselben Server verbinden + if (reconnectMessage != null && session == null && MenuHelper.InMenu && Time.realtimeSinceStartup >= reconnectAt) + { + string message = reconnectMessage; + reconnectMessage = null; + if (lastServerHost != null) + { + SetStatus(message); + JoinServer(lastServerHost, lastServerPort, lastServerName, lastServerMax); + } + } if (pendingLobby.m_SteamID != 0 && MenuHelper.InMenu && session == null && steam.Init()) { Steamworks.CSteamID lobbyToJoin = pendingLobby; @@ -832,8 +854,39 @@ namespace MSCO SetStatus(p.Name + " left (" + reason + ")"); } + string serverReason; // Grund, den der Server/Host beim Trennen mitgeschickt hat + + /// Warum man zuletzt getrennt wurde - wird im Hauptmenü angezeigt, bis man OK drückt. + public string DisconnectNotice { get; private set; } + public void ClearDisconnectNotice() { DisconnectNotice = null; } + public bool IsReconnecting { get { return reconnectMessage != null && session == null; } } + public string ReconnectMessage { get { return reconnectMessage; } } + + /// Technische Gründe in verständliche Sätze übersetzen. + static string FriendlyReason(string reason) + { + if (string.IsNullOrEmpty(reason)) return "The connection was closed."; + string r = reason.ToLower(); + if (r.Contains("timed out") || r.Contains("timeout")) return "Connection lost - the server did not respond anymore (timeout)."; + if (r.Contains("network error") || r.Contains("forcibly closed") || r.Contains("connection reset")) + return "Connection lost (" + reason + ")."; + if (r == "connection closed" || r.Contains("closed")) return "The server closed the connection."; + if (r.Contains("host left")) return "The host left the game."; + return reason; + } + void OnSessionEnded(string reason) { + // Hinweis fürs Hauptmenü - außer man hat selbst auf DISCONNECT gedrückt oder verbindet gleich neu + if (reconnectMessage != null) + { + DisconnectNotice = null; + reconnectAt = Time.realtimeSinceStartup + 3f; // erst ins Menü, dann neu verbinden + } + else if (reason != "Disconnected") + DisconnectNotice = serverReason ?? FriendlyReason(reason); + serverReason = null; + nextPresenceUpdate = 0f; // Discord sofort auf "In the Menus"/Singleplayer umstellen bool wasClientInGame = session != null && !session.IsHost && MenuHelper.InGame; @@ -1003,9 +1056,13 @@ namespace MSCO } case MsgType.Reject: - session.Close((dedicated || connectingToServer ? "Rejected by server: " : "Rejected by host: ") + r.ReadString()); + { + string why = r.ReadString(); + serverReason = why; // für den Hinweis im Hauptmenü (Kick, Ban, Server voll, Shutdown ...) + session.Close((dedicated || connectingToServer ? "Rejected by server: " : "Rejected by host: ") + why); OnSessionEnded(session.CloseReason); break; + } case MsgType.WorldSnapshot: // Wird angewendet, sobald das Spiel geladen und alle Objekte erfasst sind @@ -1059,6 +1116,16 @@ namespace MSCO break; } + case MsgType.Reconnect: + { + // Server hat die Welt zurückgesetzt o.ä.: Meldung zeigen, ins Menü, dann automatisch neu verbinden + reconnectMessage = r.ReadString(); + overlays.AddChat("", reconnectMessage, true); + session.Close("Reconnecting"); + OnSessionEnded(session.CloseReason); + return; + } + case MsgType.AdminStatus: isAdmin = r.ReadBoolean(); if (isAdmin) overlays.Log("You are an admin on this server - type /admin in chat or 'admin' here."); diff --git a/NetSession.cs b/NetSession.cs index 3d3835a..df6b33e 100644 --- a/NetSession.cs +++ b/NetSession.cs @@ -40,7 +40,8 @@ namespace MSCO VarSync = 29, // Fahrzeugzustand: int n, je [int key, byte typ, wert] Teleport = 30, // Admin holt einen Spieler zu sich: 3 float Position Pong = 31, // Antwort auf Ping mit demselben Zeitstempel (Laufzeit messen) - SpawnItem = 32 // Admin spawnt ein Item: string Item-Name, 3 float Position, 4 float Drehung + SpawnItem = 32, // Admin spawnt ein Item: string Item-Name, 3 float Position, 4 float Drehung + Reconnect = 33 // Server -> Client: string Meldung; ins Menü und neu verbinden (z.B. Welt zurückgesetzt) } public struct Message