reset-world restores the original save (reset point in data/save_original), set-reset-point; per-player needs stored and returned; tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 17:48:28 +02:00
co-authored by Claude Opus 5.5
parent bd45a6e70c
commit f23671704d
6 changed files with 162 additions and 8 deletions
+77 -5
View File
@@ -93,6 +93,7 @@ public sealed class GameServer
this.config = config;
this.configPath = configPath;
savePath = Path.Combine(dataDir, "save");
originalPath = Path.Combine(dataDir, "save_original");
worldPath = Path.Combine(dataDir, "world.json");
master = new MasterClient(MasterServer.Url);
steam = new SteamVerifier(config.SteamWebApiKey);
@@ -137,6 +138,7 @@ public sealed class GameServer
public void Run()
{
EnsureResetPoint();
listener = new TcpListener(IPAddress.Any, config.Port);
listener.Start();
Log($"Listening on port {config.Port} - {config.Name} (max {config.MaxPlayers} players)");
@@ -347,6 +349,11 @@ public sealed class GameServer
FinishSaveUpload(c, m.Reader());
return;
case MsgType.PlayerNeeds:
// Nur speichern, nicht weiterleiten - die Bedürfnisse gehören jedem selbst
if (c.State == ClientState.Joined && DateTime.UtcNow >= c.TrackPositionFrom) world.ApplyNeeds(c.SteamId, m.Payload);
return;
case MsgType.AdminListRequest:
if (c.State != ClientState.Joined) return;
if (!c.IsAdmin) { c.Conn.Send(MsgType.AdminResult, Wire.Build(w => w.Write("You are not an admin on this server"))); return; }
@@ -384,6 +391,11 @@ public sealed class GameServer
return;
}
else if (action == 6) result = BackupNow(c.Name);
else if (action == 8)
{
result = SetResetPoint() ? "The current save game is now the reset point" : "No save game to use as reset point";
if (result.StartsWith("The")) Log($"{c.Name} set the current save game as the reset point");
}
else if (!clients.TryGetValue(targetId, out var target) || target.State != ClientState.Joined) result = "Player not found";
else if (action == 3)
{
@@ -670,14 +682,66 @@ 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>
// ---------------------------------------------------------------- Reset-Punkt
// Der Spielstand, so wie er dem Server gegeben wurde (Start, --import-save, reload-save). Das automatische
// Hochladen alle paar Minuten überschreibt nur data/save - "reset-world" holt diesen Stand zurück.
string originalPath;
/// <summary>Beim Start: gibt es noch keinen Reset-Punkt, ist es der aktuelle Spielstand.</summary>
void EnsureResetPoint()
{
if (File.Exists(Path.Combine(originalPath, "defaultES2File.txt"))) return;
if (SetResetPoint()) Log("Remembered the current save game as the reset point (reset-world goes back to it)");
}
/// <summary>Aktuellen Spielstand als Reset-Punkt merken.</summary>
bool SetResetPoint()
{
if (!File.Exists(Path.Combine(savePath, "defaultES2File.txt"))) return false;
try
{
if (Directory.Exists(originalPath)) Directory.Delete(originalPath, true);
Directory.CreateDirectory(originalPath);
foreach (string file in Directory.GetFiles(savePath))
if (!file.EndsWith(".tmp")) File.Copy(file, Path.Combine(originalPath, Path.GetFileName(file)), true);
return true;
}
catch (Exception e) { Log("Could not remember the reset point: " + e.Message); return false; }
}
/// <summary>
/// Welt komplett zurücksetzen (Konsole oder Admin im Spiel): Spielstand zurück auf den Reset-Punkt, alle
/// gespeicherten Änderungen (Positionen, gekaufte Items, Türen, Fahrzeugwerte ...) weg, alle verbinden neu.
/// Der Stand davor landet als Backup in save_backups.
/// </summary>
string ResetWorld(string by)
{
bool restored = false;
if (File.Exists(Path.Combine(originalPath, "defaultES2File.txt")))
{
try
{
BackupSave();
Directory.CreateDirectory(savePath);
foreach (string name in SaveFiles)
{
string from = Path.Combine(originalPath, name), to = Path.Combine(savePath, name);
if (File.Exists(from)) File.Copy(from, to, true);
else if (File.Exists(to)) File.Delete(to); // gab es im Original nicht
}
restored = LoadSave();
}
catch (Exception e) { Log("Could not restore the reset point: " + e.Message); }
}
// Laufendes Hochladen eines Spielstands (alter Stand) verwerfen
saveUpload = null;
waitingForSave.Clear();
world.Reset();
SaveWorld();
Log($"World state reset by {by} - new players get the plain save game again.");
Log(restored ? $"World reset by {by} - back to the original save game (previous state backed up)."
: $"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";
return restored ? "World reset to the original save game" : "World reset";
}
/// <summary>Aktuellen Spielstand jetzt als Backup sichern.</summary>
@@ -780,7 +844,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 | backup | 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> | set-reset-point | say <text> | op <id> | deop <id> | admins | save | reset-world | reload-save | stop");
break;
case "say":
if (args.Length < 2) { Log("say <text>"); break; }
@@ -875,7 +939,15 @@ public sealed class GameServer
Log(RestoreSave(args.Length > 1 ? args[1].Trim() : "", "Console"));
break;
case "reload-save":
if (LoadSave()) ReconnectAll("The server loaded a new save game - reconnecting...");
if (LoadSave())
{
// Neuer Spielstand von Hand hineinkopiert: ab jetzt ist das der Reset-Punkt
SetResetPoint();
ReconnectAll("The server loaded a new save game - reconnecting...");
}
break;
case "set-reset-point":
Log(SetResetPoint() ? "The current save game is now the reset point" : "No save game to use as reset point");
break;
case "stop":
case "quit":