diff --git a/GameServer.cs b/GameServer.cs index 7eeee28..354ed3a 100644 --- a/GameServer.cs +++ b/GameServer.cs @@ -30,6 +30,8 @@ public sealed class GameServer /// SteamID von Steam bestätigt (Ticket per Web API geprüft). Nur dann gibt es Admin-Rechte. public bool SteamVerified; public bool IsAdmin; + public DateTime DestroyWindowStart; + public int DestroyCount; /// /// 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 @@ -79,6 +81,7 @@ public sealed class GameServer const int KeepSaveBackups = 5; /// So lange auf den hochgeladenen Spielstand warten, danach bekommen Wartende den gespeicherten. public double SaveUploadTimeoutSeconds { get; set; } = 15; + const int MaxDestroyPerMinute = 20; /// Ohne Master-Server (nur für automatische Tests): keine Heartbeats, keine Serverliste. readonly bool offline; @@ -410,6 +413,9 @@ public sealed class GameServer break; case MsgType.ObjDestroy: if (m.Payload.Length != 4) return; + // Niemand isst/verbraucht 20 Dinge pro Minute - mehr ist ein Fehler (z.B. Szene entladen) oder Absicht + if (DateTime.UtcNow - c.DestroyWindowStart > TimeSpan.FromMinutes(1)) { c.DestroyWindowStart = DateTime.UtcNow; c.DestroyCount = 0; } + if (++c.DestroyCount > MaxDestroyPerMinute) return; world.AddDestroyed(BitConverter.ToInt32(m.Payload, 0)); break; case MsgType.Chat: diff --git a/Tests/Program.cs b/Tests/Program.cs index c316e57..9be575c 100644 --- a/Tests/Program.cs +++ b/Tests/Program.cs @@ -133,12 +133,14 @@ try Check(await alice.WaitFor(MsgType.TrafficStates, 800) == null, "Traffic from a player who is not the world authority is ignored"); alice.Send(MsgType.TrafficStates, trafficPayload); Check(await bob.WaitFor(MsgType.TrafficStates) != null, "Traffic from the world authority is relayed"); + for (int i = 0; i < 40; i++) { int id = 1000 + i; bob.Send(MsgType.ObjDestroy, Wire.Build(w => w.Write(id))); } await Task.Delay(200); var late = await TestClient.Join("Late", 0, null); late.Send(MsgType.Ready, Array.Empty()); var lateSnap = Snapshot.Parse(await late.WaitFor(MsgType.WorldSnapshot)); Check(lateSnap.Ok && lateSnap.Appeared == 1 && lateSnap.Destroyed.Contains(777), "Players joining later get bought and consumed items: " + lateSnap.Info); + Check(lateSnap.Destroyed.Count <= 21, "Mass 'consumed' reports (e.g. scene unload) are capped: " + lateSnap.Destroyed.Count + " stored"); late.Close(); Console.WriteLine("== Last position (spawn)");