Server gets the current save game from a player in the game (on join and every few minutes) instead of replaying actions; vehicle fire/smoke synced, also for parked cars

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 11:15:45 +02:00
co-authored by Claude Opus 5.5
parent 57e1d3d3a8
commit 2deb6d2d1e
6 changed files with 222 additions and 8 deletions
+42 -2
View File
@@ -16,6 +16,7 @@ var config = new ServerConfig { LicenseKey = "test", Name = "Test", Port = Port,
config.Save(configPath);
var server = new GameServer(config, dir, configPath, "Tester", offline: true);
server.SaveUploadTimeoutSeconds = 1; // Test-Spieler laden meist keinen Spielstand hoch - nicht lange warten
if (!server.LoadSave()) { Console.WriteLine("FAIL: LoadSave"); return 1; }
var serverThread = new Thread(server.Run) { IsBackground = true };
serverThread.Start();
@@ -176,6 +177,37 @@ try
Check(rec != null && new BinaryReader(new MemoryStream(rec)).ReadString().Contains("reset"), "Players get the 'world is being reset' reconnect message");
await Task.Delay(300);
Check(r1.IsClosedByServer(), "Their connection is closed so they load the fresh world");
Console.WriteLine("== Joining gets the CURRENT save game from a player in the game");
var inGame = await TestClient.Join("InGame", 0, null);
inGame.Send(MsgType.Ready, Array.Empty<byte>());
await inGame.WaitFor(MsgType.WorldSnapshot);
inGame.Send(MsgType.FsmEvent, Wire.Build(w => { w.Write("SATSUMA|bolt#0"); w.Write("Screw"); w.Write("TIGHTEN"); w.Write("Tight"); w.Write(0f); w.Write(0f); w.Write(0f); }));
await Task.Delay(200);
var joinTask = TestClient.Join("Newcomer", 0, null);
var request = await inGame.WaitFor(MsgType.SaveRequest, 5000);
Check(request != null, "Server asks the player in the game for the current save");
if (request != null)
{
int reqId = BitConverter.ToInt32(request, 0);
byte[] fresh = System.Text.Encoding.UTF8.GetBytes(new string('S', 300_000) + "CURRENT-SAVE");
for (int off = 0; off < fresh.Length; off += 100_000)
{
int len = Math.Min(100_000, fresh.Length - off);
inGame.Send(MsgType.SaveUploadPart, Wire.Build(w => { w.Write(reqId); w.Write("defaultES2File.txt"); w.Write(fresh.Length); w.Write(off); w.Write(len); w.Write(fresh, off, len); }));
}
inGame.Send(MsgType.SaveUploadDone, Wire.Build(w => { w.Write(reqId); w.Write(1); }));
}
var newcomer = await joinTask;
Check(newcomer.Files.TryGetValue("defaultES2File.txt", out var got) && System.Text.Encoding.UTF8.GetString(got).EndsWith("CURRENT-SAVE"),
"Newcomer receives exactly the uploaded current save (" + (got?.Length ?? 0) + " bytes, sent in parts)");
newcomer.Send(MsgType.Ready, Array.Empty<byte>());
var fresh2 = Snapshot.Parse(await newcomer.WaitFor(MsgType.WorldSnapshot));
Check(fresh2.Ok && fresh2.Info.Contains("0 events"), "Replayed actions are cleared - they are in the save now (" + fresh2.Info + ")");
Check(Directory.Exists(Path.Combine(dir, "save_backups")) && Directory.GetDirectories(Path.Combine(dir, "save_backups")).Length > 0,
"The previous save game is kept as a backup");
inGame.Close();
newcomer.Close();
}
catch (Exception e)
{
@@ -255,6 +287,7 @@ sealed class TestClient
public int SaveFiles;
public bool GotStartGame;
public string RejectReason;
public Dictionary<string, byte[]> Files = new();
public static async Task<TestClient> Join(string name, ulong steamId, byte[] ticket, bool expectReject = false)
{
@@ -280,8 +313,15 @@ sealed class TestClient
if (c.Welcome != null) c.Id = BitConverter.ToInt32(c.Welcome, 0);
var admin = await c.WaitFor(MsgType.AdminStatus);
if (admin != null) c.AdminStatus = admin[0] != 0;
await c.WaitFor(MsgType.SaveBegin);
while (await c.WaitFor(MsgType.SaveFile, 1500) != null) c.SaveFiles++;
await c.WaitFor(MsgType.SaveBegin, 20000); // kann warten, bis ein anderer Spieler den aktuellen Spielstand hochlädt
byte[] sf;
while ((sf = await c.WaitFor(MsgType.SaveFile, 1500)) != null)
{
c.SaveFiles++;
var fr = new BinaryReader(new MemoryStream(sf));
string fname = fr.ReadString();
c.Files[fname] = fr.ReadBytes(fr.ReadInt32());
}
c.GotStartGame = await c.WaitFor(MsgType.StartGame, 1500) != null || c.SaveFiles > 0;
return c;
}