diff --git a/GameServer.cs b/GameServer.cs index 73db570..23c5980 100644 --- a/GameServer.cs +++ b/GameServer.cs @@ -448,6 +448,8 @@ public sealed class GameServer case MsgType.MoneyDelta: world.ApplyMoney(m.Payload); break; case MsgType.DoorState: world.ApplyDoor(m.Payload); break; case MsgType.VarSync: world.ApplyVars(m.Payload); break; + case MsgType.WorldVars: world.ApplyJobVars(m.Payload); break; + case MsgType.VehicleControls: world.ApplyControls(m.Payload); break; case MsgType.SpawnItem: if (!c.IsAdmin) return; // nur Admins dürfen Items spawnen world.AddSpawn(m.Payload); diff --git a/MSCO.Server.csproj b/MSCO.Server.csproj index 35ff671..239b9f1 100644 --- a/MSCO.Server.csproj +++ b/MSCO.Server.csproj @@ -7,7 +7,7 @@ enable MSCO.Server MSCO.Server - 2.9.1 + 2.9.2 MBM MSCO Dedicated Server true diff --git a/Tests/Program.cs b/Tests/Program.cs index afee727..75c4b8e 100644 --- a/Tests/Program.cs +++ b/Tests/Program.cs @@ -68,6 +68,11 @@ try Check(relay != null, "Object states are relayed to Bob"); alice.Send(MsgType.VarSync, Wire.Build(w => { w.Write(2); w.Write(42); w.Write((byte)1); w.Write(3.5f); w.Write(43); w.Write((byte)3); w.Write(true); })); await bob.WaitFor(MsgType.VarSync); + // Jobs/Laden und Schalter (Licht an, Motor läuft) werden gespeichert, damit später Beitretende sie haben + alice.Send(MsgType.WorldVars, Wire.Build(w => { w.Write(2); w.Write(7); w.Write((byte)2); w.Write(5); w.Write(8); w.Write((byte)3); w.Write(true); })); + await bob.WaitFor(MsgType.WorldVars); + alice.Send(MsgType.VehicleControls, Wire.Build(w => { w.Write(1); w.Write(99); w.Write("Lights"); })); + await bob.WaitFor(MsgType.VehicleControls); var carol = await TestClient.Join("Carol", 0, null); carol.Send(MsgType.Ready, Array.Empty()); var snap2 = Snapshot.Parse(await carol.WaitFor(MsgType.WorldSnapshot)); @@ -76,6 +81,8 @@ try Check(!snap2.ObjectIds.Contains(333), "Pose far below the map is rejected"); Check(!snap2.ObjectIds.Contains(444), "Invalid quaternion is rejected"); Check(snap2.Vars == 2, "Vehicle values are stored and sent to new players (" + snap2.Vars + ")"); + Check(snap2.JobVars == 2 && snap2.Controls.TryGetValue(99, out string lightState) && lightState == "Lights", + "Job/shop values and switch states (lights, engine) are stored for players joining later"); Console.WriteLine("== Server full"); var eve = await TestClient.Join("Eve", 0, null); @@ -583,6 +590,8 @@ sealed class Snapshot public List Destroyed = new(); public Dictionary Needs = new(); public int Vars = -1; + public int JobVars = -1; + public Dictionary Controls = new(); public static Snapshot Parse(byte[] data) { @@ -612,6 +621,10 @@ sealed class Snapshot int needs = r.ReadInt32(); for (int i = 0; i < needs; i++) s.Needs[r.ReadString()] = r.ReadSingle(); } + s.JobVars = r.ReadInt32(); + for (int i = 0; i < s.JobVars; i++) { r.ReadInt32(); byte t = r.ReadByte(); r.ReadBytes(t == 3 ? 1 : 4); } + int controls = r.ReadInt32(); + for (int i = 0; i < controls; i++) { int key = r.ReadInt32(); s.Controls[key] = r.ReadString(); } s.Ok = r.BaseStream.Position == r.BaseStream.Length; s.Info = $"{objects} objects, {events} events, money {money}, {doors} doors, {s.Vars} vars"; } diff --git a/WorldState.cs b/WorldState.cs index 058ba9e..012a84a 100644 --- a/WorldState.cs +++ b/WorldState.cs @@ -29,6 +29,10 @@ public sealed class WorldState public Dictionary Doors { get; set; } = new(); /// Fahrzeugzustand (Tank, Öl, Schrauben ...): Schlüssel -> Base64 [byte typ, wert]. public Dictionary Vars { get; set; } = new(); + /// Jobs/Laden (WorldVars): Schlüssel -> Base64 [byte typ, wert] - nur int/bool. + public Dictionary JobVars { get; set; } = new(); + /// Schalter an Fahrzeugen (Licht, Zündung, Motor ...): FSM-Schlüssel -> aktiver Zustand. + public Dictionary Controls { get; set; } = new(); /// Von Admins gespawnte Items (SpawnItem-Nachrichten, Base64), damit später Beitretende sie auch haben. public List Spawns { get; set; } = new(); public const int MaxSpawns = 2000; @@ -76,6 +80,8 @@ public sealed class WorldState MoneyDelta = 0f; Doors.Clear(); Vars.Clear(); + JobVars.Clear(); + Controls.Clear(); Appeared.Clear(); Destroyed.Clear(); Dirty = true; @@ -93,6 +99,8 @@ public sealed class WorldState MoneyDelta = 0f; Doors.Clear(); Vars.Clear(); + JobVars.Clear(); + Controls.Clear(); Dirty = true; } @@ -183,6 +191,47 @@ public sealed class WorldState Dirty = true; } + /// WorldVars (Jobs/Laden): int n, je [int key, byte typ (2 int / 3 bool), wert]. + public void ApplyJobVars(byte[] payload) + { + ChangesSinceSave++; + using var r = new BinaryReader(new MemoryStream(payload)); + int count = r.ReadInt32(); + if (count < 0 || count > 10000) return; + for (int i = 0; i < count; i++) + { + int key = r.ReadInt32(); + byte type = r.ReadByte(); + if (type != 2 && type != 3) return; + byte[] value = r.ReadBytes(type == 3 ? 1 : 4); + var all = new byte[value.Length + 1]; + all[0] = type; + value.CopyTo(all, 1); + if (JobVars.Count >= MaxVars && !JobVars.ContainsKey(key)) continue; + JobVars[key] = Convert.ToBase64String(all); + } + Dirty = true; + } + + /// VehicleControls: int n, je [int key, string zustand]. + public void ApplyControls(byte[] payload) + { + using var r = new BinaryReader(new MemoryStream(payload)); + int count = r.ReadInt32(); + if (count < 0 || count > 10000) return; + for (int i = 0; i < count; i++) + { + int key = r.ReadInt32(); + string state = r.ReadString(); + if (state.Length > 100) continue; + if (Controls.Count >= MaxControls && !Controls.ContainsKey(key)) continue; + Controls[key] = state; + } + Dirty = true; + } + + public const int MaxControls = 5000; + // Obergrenzen, damit ein fehlerhafter oder manipulierter Client world.json nicht endlos aufbläht public const int MaxEvents = 50_000; public const int MaxVars = 50_000; @@ -330,6 +379,18 @@ public sealed class WorldState w.Write(needs.Count); foreach (var kv in needs) { w.Write(kv.Key); w.Write(kv.Value); } } + + // Jobs/Laden: int n, je [int key, byte typ, wert] + w.Write(JobVars.Count); + foreach (var kv in JobVars) + { + w.Write(kv.Key); + w.Write(Convert.FromBase64String(kv.Value)); + } + + // Schalter (Licht, Zündung, Motor ...): int n, je [int key, string zustand] + w.Write(Controls.Count); + foreach (var kv in Controls) { w.Write(kv.Key); w.Write(kv.Value); } }); } }