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,