Admin: spawn items (saved on the server), unban (menu tab + console), tabbed admin menu; dashboard needles and steering wheel synced; server browser row opens the profile

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 10:15:35 +02:00
co-authored by Claude Opus 5.5
parent ba396146f2
commit 85fbb8a3ce
5 changed files with 77 additions and 3 deletions
+33 -2
View File
@@ -240,7 +240,8 @@ public sealed class GameServer
static bool IsGameMessage(MsgType t) =>
t is MsgType.Player or MsgType.ObjStates or MsgType.ObjOwn or MsgType.ObjRelease or MsgType.FsmEvent
or MsgType.VehicleFx or MsgType.WorldTime or MsgType.MoneyDelta or MsgType.DoorState or MsgType.Chat or MsgType.VarSync;
or MsgType.VehicleFx or MsgType.WorldTime or MsgType.MoneyDelta or MsgType.DoorState or MsgType.Chat or MsgType.VarSync
or MsgType.SpawnItem;
/// <summary>Uhrzeit/Wetter gibt der Spieler mit der kleinsten ID vor (so rechnen es auch die Clients).</summary>
int WorldAuthorityId => clients.Values.Where(c => c.State == ClientState.Joined).Select(c => c.Id).DefaultIfEmpty(-1).Min();
@@ -295,6 +296,9 @@ public sealed class GameServer
{
w.Write(joinedPlayers.Count);
foreach (var p in joinedPlayers) { w.Write(p.Id); w.Write(p.Name); w.Write(p.SteamId); w.Write(p.IsAdmin); }
// Gebannte Spieler (für UNBAN) - am Ende angehängt, ältere Mods lesen hier nicht weiter
w.Write(config.BannedSteamIds.Count);
foreach (string id in config.BannedSteamIds) w.Write(id);
}));
return;
@@ -307,6 +311,7 @@ public sealed class GameServer
string reason = r.ReadString();
string result;
if (!c.IsAdmin) result = "You are not an admin on this server";
else if (action == 4) result = Unban(reason, c.Name); // "reason" enthält hier die gebannte ID
else if (!clients.TryGetValue(targetId, out var target) || target.State != ClientState.Joined) result = "Player not found";
else if (action == 3)
{
@@ -339,6 +344,11 @@ 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.SpawnItem:
if (!c.IsAdmin) return; // nur Admins dürfen Items spawnen
world.AddSpawn(m.Payload);
Log($"{c.Name} spawned {SpawnName(m.Payload)}");
break;
case MsgType.Chat:
if (m.Payload.Length > 1024) return; // Spam/Unsinn nicht weiterleiten
Log($"[chat] {c.Name}: {m.Reader().ReadString()}");
@@ -447,6 +457,21 @@ public sealed class GameServer
}
/// <summary>Chatnachricht vom Server an alle (Absender-ID 0 = "Server").</summary>
string Unban(string id, string by)
{
id = (id ?? "").Trim();
if (!config.BannedSteamIds.Remove(id)) return $"{id} is not banned";
config.Save(configPath);
Log($"{id} was unbanned by {by}");
return $"{id} is no longer banned";
}
static string SpawnName(byte[] payload)
{
try { return new BinaryReader(new MemoryStream(payload)).ReadString(); }
catch { return "an item"; }
}
void ServerChat(string text)
{
Broadcast(MsgType.Chat, Wire.WithSender(ServerId, Wire.Build(w => w.Write(text))));
@@ -470,7 +495,7 @@ public sealed class GameServer
switch (args[0].ToLowerInvariant())
{
case "help":
Log("status | players | kick <id> [reason] | ban <id> [reason] | 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> | say <text> | op <id> | deop <id> | admins | save | reset-world | reload-save | stop");
break;
case "say":
if (args.Length < 2) { Log("say <text>"); break; }
@@ -526,6 +551,12 @@ public sealed class GameServer
foreach (var c in clients.Values.Where(c => c.State == ClientState.Joined))
Log($" [{c.Id}] {c.Name} SteamID {c.SteamId} {c.Conn.RemoteAddress}");
break;
case "bans":
Log("Banned: " + (config.BannedSteamIds.Count == 0 ? "(none)" : string.Join(", ", config.BannedSteamIds)));
break;
case "unban":
Log(args.Length > 1 ? Unban(args[1], "Console") : "unban <SteamID64 or name:playername> (see 'bans')");
break;
case "kick":
case "ban":
{