Admin world actions (reset, backup, restore) over AdminAction; backup list in admin list; 'backup' command; safer restore via staging; tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 16:43:48 +02:00
co-authored by Claude Opus 5.5
parent 9444ffa109
commit 0f62c17c90
4 changed files with 94 additions and 26 deletions
+73 -25
View File
@@ -357,6 +357,10 @@ public sealed class GameServer
// Gebannte Spieler (für UNBAN) - am Ende angehängt, ältere Mods lesen hier nicht weiter
w.Write(config.BannedSteamIds.Count);
foreach (string id in config.BannedSteamIds) w.Write(id);
// Spielstand-Backups (neueste zuerst) für den WORLD-Reiter
var backups = SaveBackups();
w.Write(backups.Count);
foreach (string b in backups) w.Write(Path.GetFileName(b));
}));
return;
@@ -370,6 +374,15 @@ public sealed class GameServer
string result;
if (!c.IsAdmin) result = "You are not an admin on this server";
else if (action == 4) result = Unban(reason, c.Name); // "reason" enthält hier die gebannte ID
else if (action == 5 || action == 7)
{
// Welt zurücksetzen / Backup wiederherstellen: alle (auch der Admin) verbinden danach neu
c.Conn.Send(MsgType.AdminResult, Wire.Build(w => w.Write(action == 5 ? "Resetting the world..." : "Restoring " + reason + "...")));
string outcome = action == 5 ? ResetWorld(c.Name) : RestoreSave(reason, c.Name);
if (!c.Conn.IsClosing && !c.Conn.IsClosed) c.Conn.Send(MsgType.AdminResult, Wire.Build(w => w.Write(outcome)));
return;
}
else if (action == 6) result = BackupNow(c.Name);
else if (!clients.TryGetValue(targetId, out var target) || target.State != ClientState.Joined) result = "Player not found";
else if (action == 3)
{
@@ -656,6 +669,60 @@ public sealed class GameServer
if (count > 0) Log($"{count} player(s) will reconnect");
}
/// <summary>Weltzustand zurücksetzen (Konsole oder Admin im Spiel): alle verbinden neu und laden den reinen Spielstand.</summary>
string ResetWorld(string by)
{
world.Reset();
SaveWorld();
Log($"World state reset by {by} - new players get the plain save game again.");
ReconnectAll(by == "Console" ? "The world is being reset - reconnecting..." : $"{by} reset the world - reconnecting...");
return "World reset";
}
/// <summary>Aktuellen Spielstand jetzt als Backup sichern.</summary>
string BackupNow(string by)
{
if (!Directory.Exists(savePath)) return "No save game on the server yet - nothing to back up";
try
{
BackupSave();
Log($"Save game backed up by {by}");
return "Backup created: " + Path.GetFileName(SaveBackups().FirstOrDefault() ?? "");
}
catch (Exception e) { return "Backup failed: " + e.Message; }
}
/// <summary>Backup wiederherstellen (Nummer aus 'backups' oder Name/Zeitstempel); alle verbinden neu.</summary>
string RestoreSave(string arg, string by)
{
var list = SaveBackups();
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) return "restore-save <number> (see 'backups')";
string name = Path.GetFileName(chosen);
try
{
// Erst in einen Zwischenordner kopieren: BackupSave() unten könnte alte Backups (auch dieses) löschen
string staging = Path.Combine(Path.GetDirectoryName(savePath)!, "save_restore_tmp");
if (Directory.Exists(staging)) Directory.Delete(staging, true);
Directory.CreateDirectory(staging);
foreach (string file in Directory.GetFiles(chosen)) File.Copy(file, Path.Combine(staging, Path.GetFileName(file)), true);
BackupSave(); // aktuellen Stand ebenfalls sichern, falls man zurück will
Directory.CreateDirectory(savePath);
foreach (string file in Directory.GetFiles(staging)) File.Copy(file, Path.Combine(savePath, Path.GetFileName(file)), true);
Directory.Delete(staging, true);
if (!LoadSave()) return "Could not load the restored save game";
world.ResetForNewSave();
SaveWorld();
Log($"Restored save game {name} ({by})");
ReconnectAll(by == "Console" ? "The server restored an older save game - reconnecting..."
: $"{by} restored the save game from {name} - reconnecting...");
return "Restored " + name;
}
catch (Exception e) { return "Could not restore: " + e.Message; }
}
string Unban(string id, string by)
{
id = (id ?? "").Trim();
@@ -712,7 +779,7 @@ public sealed class GameServer
switch (args[0].ToLowerInvariant())
{
case "help":
Log("status | players | kick <id> [reason] | ban <id> [reason] | bans | unban <SteamID|name:x> | backups | restore-save <n> | say <text> | op <id> | deop <id> | admins | save | reset-world | reload-save | stop");
Log("status | players | kick <id> [reason] | ban <id> [reason] | bans | unban <SteamID|name:x> | backups | backup | restore-save <n> | say <text> | op <id> | deop <id> | admins | save | reset-world | reload-save | stop");
break;
case "say":
if (args.Length < 2) { Log("say <text>"); break; }
@@ -790,10 +857,7 @@ public sealed class GameServer
SaveWorld();
break;
case "reset-world":
world.Reset();
SaveWorld();
Log("World state reset - new players get the plain save game again.");
ReconnectAll("The world is being reset - reconnecting...");
ResetWorld("Console");
break;
case "backups":
{
@@ -803,28 +867,12 @@ public sealed class GameServer
for (int i = 0; i < list.Count; i++) Log($" [{i + 1}] {Path.GetFileName(list[i])}");
break;
}
case "backup":
Log(BackupNow("Console"));
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 <number> (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); }
Log(RestoreSave(args.Length > 1 ? args[1].Trim() : "", "Console"));
break;
}
case "reload-save":
if (LoadSave()) ReconnectAll("The server loaded a new save game - reconnecting...");
break;