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:01 +02:00
co-authored by Claude Opus 5.5
parent 0c15459302
commit 2000fc677f
8 changed files with 294 additions and 33 deletions
+92 -10
View File
@@ -206,7 +206,11 @@ namespace MSCO
overlays.OpenAdmin(null);
RequestAdminList();
}
else if (session.IsHost) overlays.OpenAdmin(LocalAdminList());
else if (session.IsHost)
{
overlays.OpenAdmin(LocalAdminList());
overlays.SetBanned(new List<string>(bannedNames));
}
else overlays.Log("Only the host can kick or ban players.");
}
@@ -214,7 +218,11 @@ namespace MSCO
{
if (session == null) return;
if (dedicated) session.SendTo(NetSession.HostId, MsgType.AdminListRequest);
else if (session.IsHost) overlays.SetAdminPlayers(LocalAdminList());
else if (session.IsHost)
{
overlays.SetAdminPlayers(LocalAdminList());
overlays.SetBanned(new List<string>(bannedNames));
}
}
List<AdminPlayer> LocalAdminList()
@@ -332,6 +340,56 @@ namespace MSCO
if (result.EndsWith("next to you")) AnnounceCheat("brought the Satsuma to themselves");
}
/// <summary>Admin: Item vor sich spawnen - bei allen, und auf dem Server gespeichert.</summary>
public void SpawnItem(string item)
{
if (!CanCheat || localPlayer == null) return;
Transform head = playerCamera != null ? playerCamera.transform : localPlayer;
Vector3 forward = new Vector3(head.forward.x, 0f, head.forward.z).normalized;
if (forward.sqrMagnitude < 0.01f) forward = localPlayer.forward;
Vector3 pos = head.position + forward * 1.2f;
Quaternion rot = Quaternion.LookRotation(forward, Vector3.up);
if (!ItemSpawner.Spawn(item, pos, rot)) { overlays.Log("No '" + item + "' in this game"); return; }
rescanSoon = true;
SendGame(MsgType.SpawnItem, NetSession.Build(w =>
{
w.Write(item);
w.Write(pos.x); w.Write(pos.y); w.Write(pos.z);
w.Write(rot.x); w.Write(rot.y); w.Write(rot.z); w.Write(rot.w);
}));
AnnounceCheat("spawned " + item);
}
/// <summary>Gespawntes Item eines Admins (live oder aus dem Weltzustand) nachbauen.</summary>
static bool ApplySpawn(BinaryReader r)
{
string item = r.ReadString();
var pos = new Vector3(r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
var rot = new Quaternion(r.ReadSingle(), r.ReadSingle(), r.ReadSingle(), r.ReadSingle());
if (System.Array.IndexOf(ItemSpawner.Items, item) < 0) return false; // nur bekannte Items
return ItemSpawner.Spawn(item, pos, rot);
}
/// <summary>Admin: Bann aufheben (Server: SteamID oder name:..., P2P: Name).</summary>
public void Unban(string id)
{
if (session == null) return;
if (dedicated)
{
session.SendTo(NetSession.HostId, MsgType.AdminAction, NetSession.Build(w =>
{
w.Write((byte)4);
w.Write(-1);
w.Write(id);
}));
return;
}
if (!session.IsHost) return;
bannedNames.Remove(id);
overlays.SetBanned(new List<string>(bannedNames));
overlays.AddChat("", id + " is no longer banned", true);
}
/// <summary>Cheats für alle sichtbar im Chat melden.</summary>
void AnnounceCheat(string what)
{
@@ -807,7 +865,7 @@ namespace MSCO
{
return t == MsgType.Player || t == MsgType.ObjStates || t == MsgType.ObjOwn
|| t == MsgType.ObjRelease || t == MsgType.FsmEvent || t == MsgType.VehicleFx || t == MsgType.WorldTime || t == MsgType.MoneyDelta || t == MsgType.DoorState
|| t == MsgType.Chat || t == MsgType.VarSync;
|| t == MsgType.Chat || t == MsgType.VarSync || t == MsgType.SpawnItem;
}
void OnMessage(int peerId, Message m)
@@ -996,6 +1054,13 @@ namespace MSCO
for (int i = 0; i < count; i++)
list.Add(new AdminPlayer { Id = r.ReadInt32(), Name = r.ReadString(), SteamId = r.ReadString(), IsAdmin = r.ReadBoolean() });
overlays.SetAdminPlayers(list);
var banned = new List<string>();
if (r.BaseStream.Length - r.BaseStream.Position >= 4)
{
int bans = r.ReadInt32();
for (int i = 0; i < bans; i++) banned.Add(r.ReadString());
}
overlays.SetBanned(banned);
break;
}
@@ -1066,6 +1131,9 @@ namespace MSCO
case MsgType.VarSync:
vehicleState.Handle(from, r, objects, localId);
break;
case MsgType.SpawnItem:
if (ApplySpawn(r)) rescanSoon = true; // neues Item gleich in den Objekt-Sync aufnehmen
break;
}
}
@@ -1407,13 +1475,6 @@ namespace MSCO
if ((i + 1) % SnapshotEventsPerFrame == 0) yield return null; // nicht alles in einem Frame
}
// Abgebaute Teile sind jetzt eigene Objekte: erfassen, dann alle Positionen setzen
yield return null;
objects.ScanNow();
int applied = 0;
foreach (KeyValuePair<int, Pose> p in poses)
if (objects.ApplySnapshotPose(p.Key, p.Value.Position, p.Value.Rotation)) applied++;
// Geld: Summe aller Änderungen seit dem Spielstand des Servers
if (r.BaseStream.Length - r.BaseStream.Position >= 4) moneySync.Apply(r.ReadSingle());
@@ -1427,6 +1488,27 @@ namespace MSCO
// Fahrzeugzustand (Tank, Öl, Schrauben ...)
if (r.BaseStream.Length - r.BaseStream.Position >= 4) vehicleState.ApplySnapshot(r);
// Von Admins gespawnte Items (in der Reihenfolge des Servers, damit alle sie gleich benennen)
int spawned = 0;
if (r.BaseStream.Length - r.BaseStream.Position >= 4)
{
int spawns = r.ReadInt32();
for (int i = 0; i < spawns; i++)
{
byte[] payload = r.ReadBytes(r.ReadInt32());
try { if (ApplySpawn(new BinaryReader(new MemoryStream(payload)))) spawned++; }
catch (System.Exception e) { MscoLog.Warning("Could not spawn a saved item: " + e.Message); }
}
}
// Abgebaute Teile und gespawnte Items sind jetzt eigene Objekte: erfassen, dann alle Positionen setzen
yield return null;
objects.ScanNow();
int applied = 0;
foreach (KeyValuePair<int, Pose> p in poses)
if (objects.ApplySnapshotPose(p.Key, p.Value.Position, p.Value.Rotation)) applied++;
if (spawned > 0) MscoLog.Info(spawned + " spawned item(s) restored");
MscoLog.Info("[MSCO] World synced: " + applied + "/" + objectCount + " objects, " + eventCount + " part/bolt actions");
SetStatus("Connected");
}