diff --git a/GameServer.cs b/GameServer.cs
index 8ada779..c8a6e74 100644
--- a/GameServer.cs
+++ b/GameServer.cs
@@ -603,6 +603,13 @@ public sealed class GameServer
SendSaveToWaiting();
}
+ /// Vorhandene Spielstand-Backups, neueste zuerst.
+ List SaveBackups()
+ {
+ string backups = Path.Combine(Path.GetDirectoryName(savePath)!, "save_backups");
+ return Directory.Exists(backups) ? Directory.GetDirectories(backups).OrderByDescending(d => d).ToList() : new List();
+ }
+
/// Bisherigen Spielstand nach data/save_backups/Zeitstempel kopieren, die letzten Stände behalten.
void BackupSave()
{
@@ -669,7 +676,7 @@ public sealed class GameServer
switch (args[0].ToLowerInvariant())
{
case "help":
- Log("status | players | kick [reason] | ban [reason] | bans | unban | say | op | deop | admins | save | reset-world | reload-save | stop");
+ Log("status | players | kick [reason] | ban [reason] | bans | unban | backups | restore-save | say | op | deop | admins | save | reset-world | reload-save | stop");
break;
case "say":
if (args.Length < 2) { Log("say "); break; }
@@ -752,6 +759,36 @@ public sealed class GameServer
Log("World state reset - new players get the plain save game again.");
ReconnectAll("The world is being reset - reconnecting...");
break;
+ case "backups":
+ {
+ var list = SaveBackups();
+ if (list.Count == 0) { Log("No save backups yet (they are created when the server takes over a save game)"); break; }
+ Log("Save backups (newest first) - restore with: restore-save ");
+ for (int i = 0; i < list.Count; i++) Log($" [{i + 1}] {Path.GetFileName(list[i])}");
+ break;
+ }
+ case "restore-save":
+ {
+ var list = SaveBackups();
+ string arg = args.Length > 1 ? args[1].Trim() : "";
+ string chosen = int.TryParse(arg, out int n) && n >= 1 && n <= list.Count ? list[n - 1]
+ : list.FirstOrDefault(d => Path.GetFileName(d) == arg);
+ if (chosen == null) { Log("restore-save (see 'backups')"); break; }
+ try
+ {
+ BackupSave(); // aktuellen Stand ebenfalls sichern, falls man zurück will
+ foreach (string file in Directory.GetFiles(chosen)) File.Copy(file, Path.Combine(savePath, Path.GetFileName(file)), true);
+ if (LoadSave())
+ {
+ world.ResetForNewSave();
+ SaveWorld();
+ Log($"Restored save game {Path.GetFileName(chosen)}");
+ ReconnectAll("The server restored an older save game - reconnecting...");
+ }
+ }
+ catch (Exception e) { Log("Could not restore: " + e.Message); }
+ break;
+ }
case "reload-save":
if (LoadSave()) ReconnectAll("The server loaded a new save game - reconnecting...");
break;
diff --git a/README.md b/README.md
index ba29662..5b480f9 100644
--- a/README.md
+++ b/README.md
@@ -132,6 +132,7 @@ Fehlende Einstellungen ergänzt der Server beim Start automatisch. Umgebungsvari
| `save` | Weltzustand jetzt speichern |
| `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 |
+| `backups`, `restore-save ` | gesicherte Spielstände anzeigen / einen davon zurückholen (z.B. wenn der aktuelle kaputt ist) – alle verbinden sich neu |
| `stop` | speichern und beenden |
Im Spiel öffnen Admins mit `/admin` (Chat, Taste T) oder `admin` (F7-Konsole) das Admin-Menü: Kick/Ban,