New game from the panel instead of an uploaded save (2.9.3)

World & Backups -> Start new game (console: new-game, also when stopped):
the save is backed up and replaced by a new-game marker (also as reset
point). Joining players are told to start a new game; the server pulls the
first player's save and from then on runs normally. Reset world returns to
a new game when the reset point is one.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-10-02 17:36:33 +02:00
co-authored by Claude Opus 5.5
parent 06a58e08d5
commit e94123fa40
9 changed files with 174 additions and 11 deletions
+68 -3
View File
@@ -136,6 +136,14 @@ public sealed class GameServer
Log(PermissionHint());
return false;
}
if (loaded.All(f => f.Key != "defaultES2File.txt") && SaveStore.IsNewGame(DataDir))
{
// Neues Spiel: es gibt (noch) keinen Spielstand - der erste Spieler startet im Spiel ein neues Spiel
saveFiles = new List<KeyValuePair<string, byte[]>>();
NewGame = true;
Log("New game: the first player to join creates a fresh world, the server then takes over its save game");
return true;
}
if (loaded.All(f => f.Key != "defaultES2File.txt"))
{
Log("No save game found in " + savePath);
@@ -143,6 +151,7 @@ public sealed class GameServer
return false;
}
saveFiles = loaded;
NewGame = false;
Log("Loaded save game (" + saveFiles.Count + " files, " + saveFiles.Sum(f => f.Value.Length) / 1024 + " KB)");
return true;
}
@@ -257,6 +266,7 @@ public sealed class GameServer
void Timers()
{
PullNewGameSave();
double now = clock.Elapsed.TotalSeconds;
if (now >= nextAutosave)
{
@@ -583,7 +593,40 @@ public sealed class GameServer
c.Conn.Send(MsgType.SaveBegin);
foreach (var f in saveFiles)
c.Conn.Send(MsgType.SaveFile, Wire.Build(w => { w.Write(f.Key); w.Write(f.Value.Length); w.Write(f.Value); }));
c.Conn.Send(MsgType.StartGame, Wire.Build(w => w.Write(false)));
// [bool neben den Host setzen, bool neues Spiel] - ältere Mods lesen nur den ersten Wert
bool newGame = NewGame && saveFiles.Count == 0;
c.Conn.Send(MsgType.StartGame, Wire.Build(w => { w.Write(false); w.Write(newGame); }));
}
/// <summary>Neues Spiel: noch kein Spielstand - der erste Spieler legt die Welt im Spiel an.</summary>
public bool NewGame { get; private set; }
DateTime nextNewGameSave = DateTime.MinValue;
/// <summary>Neues Spiel läuft: den Spielstand des ersten Spielers holen, sobald einer im Spiel ist.</summary>
void PullNewGameSave()
{
if (!NewGame || saveUpload != null || DateTime.UtcNow < nextNewGameSave) return;
nextNewGameSave = DateTime.UtcNow.AddSeconds(20);
RequestSave("new game - taking over the fresh world");
}
/// <summary>Panel/Konsole: aktuellen Spielstand sichern und ein neues Spiel starten. Alle verbinden neu.</summary>
string StartNewGame(string by)
{
try
{
SaveStore.MakeNewGame(DataDir, alsoResetPoint: true);
if (!LoadSave()) return "Could not start a new game";
}
catch (Exception e) { return "Could not start a new game: " + e.Message; }
saveUpload = null;
waitingForSave.Clear();
world.Reset();
SaveWorld();
nextNewGameSave = DateTime.UtcNow.AddSeconds(30); // erst laden lassen
Log($"New game started by {by} - the previous save game is in the backups");
ReconnectAll(by == "Console" ? "The server starts a new game - reconnecting..." : $"{by} started a new game - reconnecting...");
return "New game started - the first player to join creates the new world";
}
void SendSaveToWaiting()
@@ -656,6 +699,9 @@ public sealed class GameServer
File.WriteAllBytes(tmp, f.Value);
File.Move(tmp, target, true);
}
bool wasNewGame = NewGame;
SaveStore.ClearNewGameMarker(DataDir);
if (wasNewGame) Log($"The new game is running - took over the fresh world from {c.Name}");
if (LoadSave())
{
world.ResetForNewSave();
@@ -700,7 +746,7 @@ public sealed class GameServer
/// <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 (SaveStore.HasResetPoint(DataDir)) return;
if (SetResetPoint()) Log("Remembered the current save game as the reset point (reset-world goes back to it)");
}
@@ -719,6 +765,20 @@ public sealed class GameServer
/// </summary>
string ResetWorld(string by)
{
if (SaveStore.ResetPointIsNewGame(DataDir))
{
// Reset-Punkt ist ein neues Spiel
try { SaveStore.MakeNewGame(DataDir, alsoResetPoint: false); LoadSave(); }
catch (Exception e) { Log("Could not reset to a new game: " + e.Message); }
saveUpload = null;
waitingForSave.Clear();
world.Reset();
SaveWorld();
nextNewGameSave = DateTime.UtcNow.AddSeconds(30);
Log($"World reset by {by} - back to a new game (previous state backed up).");
ReconnectAll(by == "Console" ? "The world is being reset - reconnecting..." : $"{by} reset the world - reconnecting...");
return "World reset to a new game";
}
bool restored = false;
if (File.Exists(Path.Combine(originalPath, "defaultES2File.txt")))
{
@@ -859,7 +919,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> | set-reset-point | panel | restart | stop | exit | 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 | panel | restart | stop | exit | say <text> | op <id> | deop <id> | admins | save | reset-world | new-game | reload-save | stop");
break;
case "say":
if (args.Length < 2) { Log("say <text>"); break; }
@@ -943,6 +1003,9 @@ public sealed class GameServer
case "reset-world":
ResetWorld("Console");
break;
case "new-game":
Log(StartNewGame("Console"));
break;
case "backups":
{
var list = SaveBackups();
@@ -1060,6 +1123,7 @@ public sealed class GameServer
? File.GetLastWriteTimeUtc(Path.Combine(originalPath, "defaultES2File.txt")).ToString("o") : null;
return new
{
newGame = NewGame,
name = config.Name,
description = config.Description,
version = ModVersion,
@@ -1264,6 +1328,7 @@ public sealed class GameServer
case "save": SaveWorld(); return "World state saved";
case "backup": return BackupNow(by);
case "reset": return ResetWorld(by);
case "newgame": return StartNewGame(by);
case "restore": return RestoreSave(arg ?? "", by);
case "resetpoint":
if (!SetResetPoint()) return "No save game to use as reset point";