diff --git a/GameServer.cs b/GameServer.cs
index f40c65b..f9f1ce7 100644
--- a/GameServer.cs
+++ b/GameServer.cs
@@ -138,7 +138,12 @@ public sealed class GameServer
Thread.Sleep(2);
}
- foreach (var c in clients.Values) c.Conn.Close("Server shutting down");
+ foreach (var c in clients.Values)
+ {
+ // Grund mitschicken: die Spieler sehen ihn im Hauptmenü
+ if (c.State == ClientState.Joined) c.Conn.Send(MsgType.Reject, Wire.Build(w => w.Write("The server was shut down.")));
+ c.Conn.Close("Server shutting down");
+ }
listener.Stop();
SaveWorld();
if (!offline)
@@ -457,6 +462,22 @@ public sealed class GameServer
}
/// Chatnachricht vom Server an alle (Absender-ID 0 = "Server").
+ ///
+ /// Alle Spieler neu verbinden lassen (nach reset-world / reload-save): sie sehen die Meldung, gehen kurz ins Menü
+ /// und laden dann den neuen Stand. Sonst liefen sie mit der alten Welt weiter.
+ ///
+ void ReconnectAll(string message)
+ {
+ int count = 0;
+ foreach (var c in clients.Values.Where(x => x.State == ClientState.Joined).ToList())
+ {
+ c.Conn.Send(MsgType.Reconnect, Wire.Build(w => w.Write(message)));
+ c.Conn.Close("Reconnect after world change");
+ count++;
+ }
+ if (count > 0) Log($"{count} player(s) will reconnect");
+ }
+
string Unban(string id, string by)
{
id = (id ?? "").Trim();
@@ -576,9 +597,10 @@ public sealed class GameServer
world.Reset();
SaveWorld();
Log("World state reset - new players get the plain save game again.");
+ ReconnectAll("The world is being reset - reconnecting...");
break;
case "reload-save":
- LoadSave();
+ if (LoadSave()) ReconnectAll("The server loaded a new save game - reconnecting...");
break;
case "stop":
case "quit":
diff --git a/Protocol.cs b/Protocol.cs
index 40fb757..4e6843b 100644
--- a/Protocol.cs
+++ b/Protocol.cs
@@ -36,7 +36,8 @@ public enum MsgType : byte
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; Client geht ins Menü und verbindet sich neu (z.B. Welt zurückgesetzt)
}
public readonly record struct Message(MsgType Type, byte[] Payload)
diff --git a/README.md b/README.md
index d611d7c..2efd6d7 100644
--- a/README.md
+++ b/README.md
@@ -129,8 +129,8 @@ Fehlende Einstellungen ergänzt der Server beim Start automatisch. Umgebungsvari
| `say ` | Chatnachricht an alle |
| `admins`, `op `, `deop <...>` | Admins anzeigen / geben / nehmen |
| `save` | Weltzustand jetzt speichern |
-| `reset-world` | alle Änderungen seit dem Spielstand verwerfen |
-| `reload-save` | Spielstand-Dateien neu einlesen |
+| `reset-world` | alle Änderungen seit dem Spielstand verwerfen – verbundene Spieler bekommen eine Meldung und verbinden sich automatisch neu |
+| `reload-save` | Spielstand-Dateien neu einlesen – verbundene Spieler verbinden sich automatisch neu |
| `stop` | speichern und beenden |
Im Spiel öffnen Admins mit `/admin` (Chat, Taste T) oder `admin` (F7-Konsole) das Admin-Menü: Kick/Ban,
diff --git a/Tests/Program.cs b/Tests/Program.cs
index fdc6696..50be6a8 100644
--- a/Tests/Program.cs
+++ b/Tests/Program.cs
@@ -153,6 +153,15 @@ try
Check(unbanned.Welcome != null, "After unban the player can join again");
unbanned.Close();
watcher.Close();
+ await Task.Delay(300);
+
+ Console.WriteLine("== reset-world makes everyone reconnect");
+ var r1 = await TestClient.Join("Resetter", 0, null);
+ server.EnqueueCommand("reset-world");
+ var rec = await r1.WaitFor(MsgType.Reconnect);
+ Check(rec != null && new BinaryReader(new MemoryStream(rec)).ReadString().Contains("reset"), "Players get the 'world is being reset' reconnect message");
+ await Task.Delay(300);
+ Check(r1.IsClosedByServer(), "Their connection is closed so they load the fresh world");
}
catch (Exception e)
{