Passenger: player colliders off while riding (they pushed seats/parts out, physics crashed); last position: not overwritten by the save's start point, invalid positions ignored, saved when a player leaves
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
+11
-1
@@ -30,6 +30,12 @@ public sealed class GameServer
|
|||||||
/// <summary>SteamID von Steam bestätigt (Ticket per Web API geprüft). Nur dann gibt es Admin-Rechte.</summary>
|
/// <summary>SteamID von Steam bestätigt (Ticket per Web API geprüft). Nur dann gibt es Admin-Rechte.</summary>
|
||||||
public bool SteamVerified;
|
public bool SteamVerified;
|
||||||
public bool IsAdmin;
|
public bool IsAdmin;
|
||||||
|
/// <summary>
|
||||||
|
/// Ab wann die Position des Spielers gespeichert wird: erst nachdem er seinen Weltzustand (mit seiner letzten
|
||||||
|
/// Position) bekommen und sich dorthin gesetzt hat - sonst überschreibt der Startpunkt des Spielstands
|
||||||
|
/// (Toilette) die gespeicherte Position, bevor er sie zurückbekommt.
|
||||||
|
/// </summary>
|
||||||
|
public DateTime TrackPositionFrom = DateTime.MaxValue;
|
||||||
public Task<string> Verification;
|
public Task<string> Verification;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,6 +187,7 @@ public sealed class GameServer
|
|||||||
Log($"{c.Name} left ({c.Conn.CloseReason})");
|
Log($"{c.Name} left ({c.Conn.CloseReason})");
|
||||||
Broadcast(MsgType.PlayerLeft, Wire.Build(w => w.Write(c.Id)));
|
Broadcast(MsgType.PlayerLeft, Wire.Build(w => w.Write(c.Id)));
|
||||||
HeartbeatSoon();
|
HeartbeatSoon();
|
||||||
|
if (world.Dirty) SaveWorld(); // letzte Position gleich sichern, nicht erst beim nächsten Autosave
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -268,6 +275,7 @@ public sealed class GameServer
|
|||||||
|
|
||||||
case MsgType.Ready:
|
case MsgType.Ready:
|
||||||
if (c.State != ClientState.Joined) return;
|
if (c.State != ClientState.Joined) return;
|
||||||
|
c.TrackPositionFrom = DateTime.UtcNow.AddSeconds(8);
|
||||||
c.Conn.Send(MsgType.WorldSnapshot, world.BuildSnapshot(c.SteamId));
|
c.Conn.Send(MsgType.WorldSnapshot, world.BuildSnapshot(c.SteamId));
|
||||||
// Letzter Stand von Uhrzeit und Wetter, damit der Neue nicht bis zum nächsten Update warten muss
|
// Letzter Stand von Uhrzeit und Wetter, damit der Neue nicht bis zum nächsten Update warten muss
|
||||||
if (lastWorldTime != null) c.Conn.Send(MsgType.WorldTime, Wire.WithSender(ServerId, lastWorldTime));
|
if (lastWorldTime != null) c.Conn.Send(MsgType.WorldTime, Wire.WithSender(ServerId, lastWorldTime));
|
||||||
@@ -325,7 +333,9 @@ public sealed class GameServer
|
|||||||
case MsgType.ObjStates: world.ApplyObjStates(m.Payload); break;
|
case MsgType.ObjStates: world.ApplyObjStates(m.Payload); break;
|
||||||
case MsgType.ObjRelease: world.ApplyObjRelease(m.Payload); break;
|
case MsgType.ObjRelease: world.ApplyObjRelease(m.Payload); break;
|
||||||
case MsgType.FsmEvent: world.AddFsmEvent(m.Payload); break;
|
case MsgType.FsmEvent: world.AddFsmEvent(m.Payload); break;
|
||||||
case MsgType.Player: world.ApplyPlayer(c.SteamId, m.Payload); break;
|
case MsgType.Player:
|
||||||
|
if (DateTime.UtcNow >= c.TrackPositionFrom) world.ApplyPlayer(c.SteamId, m.Payload);
|
||||||
|
break;
|
||||||
case MsgType.MoneyDelta: world.ApplyMoney(m.Payload); break;
|
case MsgType.MoneyDelta: world.ApplyMoney(m.Payload); break;
|
||||||
case MsgType.DoorState: world.ApplyDoor(m.Payload); break;
|
case MsgType.DoorState: world.ApplyDoor(m.Payload); break;
|
||||||
case MsgType.VarSync: world.ApplyVars(m.Payload); break;
|
case MsgType.VarSync: world.ApplyVars(m.Payload); break;
|
||||||
|
|||||||
+23
-1
@@ -115,6 +115,26 @@ try
|
|||||||
alice.Send(MsgType.Chat, Wire.Build(w => w.Write(new string('x', 5000))));
|
alice.Send(MsgType.Chat, Wire.Build(w => w.Write(new string('x', 5000))));
|
||||||
var longChat = await bob.WaitFor(MsgType.Chat, 800);
|
var longChat = await bob.WaitFor(MsgType.Chat, 800);
|
||||||
Check(longChat == null, "Huge chat messages are not relayed");
|
Check(longChat == null, "Huge chat messages are not relayed");
|
||||||
|
|
||||||
|
Console.WriteLine("== Last position (spawn)");
|
||||||
|
var pos1 = await TestClient.Join("Walker", 0, null);
|
||||||
|
pos1.Send(MsgType.Player, PlayerPos(-15.6f, -0.3f, 7f)); // Startpunkt des Spielstands (Toilette) - vor Ready
|
||||||
|
pos1.Send(MsgType.Ready, Array.Empty<byte>());
|
||||||
|
Check(Snapshot.Parse(await pos1.WaitFor(MsgType.WorldSnapshot)).Spawn == null, "Position before joining is not stored (no spawn yet)");
|
||||||
|
pos1.Send(MsgType.Player, PlayerPos(-15.6f, -0.3f, 7f)); // direkt nach Ready: noch nicht übernehmen
|
||||||
|
await Task.Delay(8500);
|
||||||
|
pos1.Send(MsgType.Player, PlayerPos(250f, 3f, -120f)); // echte Position nach dem Umsetzen
|
||||||
|
await Task.Delay(300);
|
||||||
|
pos1.Send(MsgType.Player, PlayerPos(2.7e9f, 9.4e10f, -8.5e8f)); // weggeschleudert - darf nicht gespeichert werden
|
||||||
|
await Task.Delay(300);
|
||||||
|
pos1.Close();
|
||||||
|
await Task.Delay(500);
|
||||||
|
var pos2 = await TestClient.Join("Walker", 0, null);
|
||||||
|
pos2.Send(MsgType.Ready, Array.Empty<byte>());
|
||||||
|
var spawn = Snapshot.Parse(await pos2.WaitFor(MsgType.WorldSnapshot)).Spawn;
|
||||||
|
Check(spawn != null && Math.Abs(spawn[0] - 250f) < 0.01f && Math.Abs(spawn[2] + 120f) < 0.01f,
|
||||||
|
"Rejoining returns the last real position, not the save's start point or a flung-away one: " + (spawn == null ? "none" : string.Join(", ", spawn)));
|
||||||
|
pos2.Close();
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -128,6 +148,7 @@ Thread.Sleep(300);
|
|||||||
try { Directory.Delete(dir, true); } catch { }
|
try { Directory.Delete(dir, true); } catch { }
|
||||||
return failed == 0 ? 0 : 1;
|
return failed == 0 ? 0 : 1;
|
||||||
|
|
||||||
|
static byte[] PlayerPos(float x, float y, float z) => Wire.Build(w => { w.Write(x); w.Write(y); w.Write(z); w.Write(0f); });
|
||||||
static string ReadString(byte[] p) => new BinaryReader(new MemoryStream(p)).ReadString();
|
static string ReadString(byte[] p) => new BinaryReader(new MemoryStream(p)).ReadString();
|
||||||
static string ReadStringAfterSender(byte[] p) { var r = new BinaryReader(new MemoryStream(p)); r.ReadInt32(); return r.ReadString(); }
|
static string ReadStringAfterSender(byte[] p) { var r = new BinaryReader(new MemoryStream(p)); r.ReadInt32(); return r.ReadString(); }
|
||||||
|
|
||||||
@@ -147,6 +168,7 @@ sealed class Snapshot
|
|||||||
public bool Ok;
|
public bool Ok;
|
||||||
public string Info;
|
public string Info;
|
||||||
public HashSet<int> ObjectIds = new();
|
public HashSet<int> ObjectIds = new();
|
||||||
|
public float[] Spawn;
|
||||||
public int Vars = -1;
|
public int Vars = -1;
|
||||||
|
|
||||||
public static Snapshot Parse(byte[] data)
|
public static Snapshot Parse(byte[] data)
|
||||||
@@ -156,7 +178,7 @@ sealed class Snapshot
|
|||||||
try
|
try
|
||||||
{
|
{
|
||||||
var r = new BinaryReader(new MemoryStream(data));
|
var r = new BinaryReader(new MemoryStream(data));
|
||||||
if (r.ReadBoolean()) { r.ReadSingle(); r.ReadSingle(); r.ReadSingle(); }
|
if (r.ReadBoolean()) s.Spawn = new[] { r.ReadSingle(), r.ReadSingle(), r.ReadSingle() };
|
||||||
int objects = r.ReadInt32();
|
int objects = r.ReadInt32();
|
||||||
for (int i = 0; i < objects; i++) { s.ObjectIds.Add(r.ReadInt32()); for (int k = 0; k < 7; k++) r.ReadSingle(); }
|
for (int i = 0; i < objects; i++) { s.ObjectIds.Add(r.ReadInt32()); for (int k = 0; k < 7; k++) r.ReadSingle(); }
|
||||||
int events = r.ReadInt32();
|
int events = r.ReadInt32();
|
||||||
|
|||||||
+4
-4
@@ -157,10 +157,10 @@ public sealed class WorldState
|
|||||||
public void ApplyPlayer(string steamId, byte[] payload)
|
public void ApplyPlayer(string steamId, byte[] payload)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(steamId) || payload.Length < 12) return;
|
if (string.IsNullOrEmpty(steamId) || payload.Length < 12) return;
|
||||||
PlayerPositions[steamId] = new[]
|
var pos = new[] { BitConverter.ToSingle(payload, 0), BitConverter.ToSingle(payload, 4), BitConverter.ToSingle(payload, 8) };
|
||||||
{
|
// Unsinnige Positionen (z.B. mit einem explodierten Auto weggeschleudert) nicht speichern
|
||||||
BitConverter.ToSingle(payload, 0), BitConverter.ToSingle(payload, 4), BitConverter.ToSingle(payload, 8)
|
if (!IsValidPose(pos, new float[] { 0, 0, 0, 1 })) return;
|
||||||
};
|
PlayerPositions[steamId] = pos;
|
||||||
Dirty = true;
|
Dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user