diff --git a/Overlays.cs b/Overlays.cs
index 0cc01ff..d1d7d29 100644
--- a/Overlays.cs
+++ b/Overlays.cs
@@ -71,6 +71,7 @@ namespace MSCO
if (ConsoleOpen)
{
ChatOpen = false;
+ AdminOpen = false; // sonst liegt die Konsole über dem Admin-Menü und schluckt dessen Klicks
consoleOpenedFrame = Time.frameCount;
consoleScrollToEnd = true;
}
@@ -268,10 +269,81 @@ namespace MSCO
GUI.FocusControl(ChatControl);
}
+ // ---------------------------------------------------------------- Admin-Menü
+ // Feste Fenstergröße für alle Reiter, Reiter gleich breit, Zeilen mit fest gesetzten Rechtecken (Texte mittig).
+
+ const float AdminRow = 40f, AdminGap = 6f;
+ GUIStyle nameMid, smallMid, captionMid, pillOk, pillWarn, pillBad, pillOff, chipOn, chipOff;
+ Texture2D texBarBack, texBarFill, texBarLow;
+ Vector2 teleportScroll, vehicleScroll;
+ string partFilter = "", itemFilter = "", partCategory = "";
+ bool partProblems, fluidsOpen = true;
+
+ void EnsureAdminStyles()
+ {
+ if (nameMid != null && texBarBack != null) return;
+ nameMid = new GUIStyle(chatName) { alignment = TextAnchor.MiddleLeft, wordWrap = false, clipping = TextClipping.Clip };
+ nameMid.margin = new RectOffset();
+ nameMid.padding = new RectOffset();
+ smallMid = new GUIStyle(small) { alignment = TextAnchor.MiddleLeft, wordWrap = false, clipping = TextClipping.Clip };
+ smallMid.margin = new RectOffset();
+ smallMid.padding = new RectOffset();
+ captionMid = new GUIStyle(caption) { alignment = TextAnchor.MiddleLeft };
+ captionMid.margin = new RectOffset();
+ pillOk = Pill(UiTheme.Online);
+ pillWarn = Pill(UiTheme.Pending);
+ pillBad = Pill(UiTheme.Danger);
+ pillOff = Pill(UiTheme.Muted);
+ chipOn = UiTheme.Button(new Color(UiTheme.Accent.r, UiTheme.Accent.g, UiTheme.Accent.b, 0.25f), new Color(UiTheme.Accent.r, UiTheme.Accent.g, UiTheme.Accent.b, 0.35f), UiTheme.AccentHover, 11);
+ chipOff = UiTheme.Button(UiTheme.Secondary, UiTheme.SecondaryHover, UiTheme.Muted, 11);
+ texBarBack = UiTheme.Solid(new Color(1f, 1f, 1f, 0.1f));
+ texBarFill = UiTheme.Solid(UiTheme.Online);
+ texBarLow = UiTheme.Solid(UiTheme.Danger);
+ }
+
+ static GUIStyle Pill(Color c)
+ {
+ GUIStyle st = UiTheme.Panel9(new Color(c.r, c.g, c.b, 0.18f), 4);
+ st.normal.textColor = c;
+ st.fontSize = 10;
+ st.fontStyle = FontStyle.Bold;
+ st.alignment = TextAnchor.MiddleCenter;
+ st.padding = new RectOffset(7, 7, 0, 0);
+ return st;
+ }
+
+ /// Zeile mit Hintergrund in einer Layout-Liste reservieren; gibt das Innere zurück (12 px Rand).
+ Rect AdminRowRect(float height)
+ {
+ Rect r = GUILayoutUtility.GetRect(10, height, GUILayout.ExpandWidth(true));
+ GUI.Box(new Rect(r.x, r.y + 2, r.width, r.height - 4), GUIContent.none, rowStyle);
+ return new Rect(r.x + 12, r.y + 2, r.width - 24, r.height - 4);
+ }
+
+ /// Knöpfe von rechts nach links in einer Zeile; gibt die linke Kante des belegten Bereichs zurück.
+ static bool RightButton(ref float right, Rect row, float width, string text, GUIStyle style, bool enabled = true)
+ {
+ right -= width;
+ bool old = GUI.enabled;
+ GUI.enabled = old && enabled;
+ bool clicked = GUI.Button(new Rect(right, row.y + (row.height - 28) / 2f, width, 28), text, style);
+ GUI.enabled = old;
+ right -= AdminGap;
+ return clicked;
+ }
+
+ void Bar(Rect r, float frac, float low)
+ {
+ var back = new Rect(r.x, r.y + (r.height - 6) / 2f, r.width, 6);
+ GUI.DrawTexture(back, texBarBack);
+ GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * Mathf.Clamp01(frac)), back.height), frac < low ? texBarLow : texBarFill);
+ }
+
void DrawAdmin(MultiplayerManager mp)
{
- float width = Mathf.Min(adminTab == 2 ? 940 : 720, Screen.width - 40), height = Mathf.Min(adminTab == 2 ? 680 : 580, Screen.height - 40);
- var rect = new Rect((Screen.width - width) / 2f, (Screen.height - height) / 2f, width, height);
+ EnsureAdminStyles();
+ float width = Mathf.Min(1000, Screen.width - 40), height = Mathf.Min(720, Screen.height - 40);
+ var rect = new Rect(Mathf.Round((Screen.width - width) / 2f), Mathf.Round((Screen.height - height) / 2f), width, height);
UiTheme.DrawShadow(rect);
GUI.Box(rect, GUIContent.none, panel);
@@ -282,26 +354,23 @@ namespace MSCO
return;
}
- GUILayout.BeginArea(new Rect(rect.x + 20, rect.y + 16, rect.width - 40, rect.height - 32));
- GUILayout.BeginHorizontal();
- GUILayout.Label("Admin", title, GUILayout.ExpandWidth(true)); // nicht von den Buttons zusammendrücken lassen
- if (GUILayout.Button("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(100))) mp.RequestAdminList();
- GUILayout.Space(8);
- if (GUILayout.Button("CLOSE", secondary, GUILayout.Height(30), GUILayout.Width(90))) AdminOpen = false;
- GUILayout.EndHorizontal();
- GUILayout.Space(6);
-
- // Reiter
- GUILayout.BeginHorizontal();
- string[] tabs = { "PLAYERS", "TELEPORT", "VEHICLE", "ITEMS", "BANS" + (bannedIds != null && bannedIds.Count > 0 ? " (" + bannedIds.Count + ")" : ""), "WORLD" };
- for (int i = 0; i < tabs.Length; i++)
+ // Kopf
+ float x = rect.x + 20, w = rect.width - 40;
+ GUI.Label(new Rect(x, rect.y + 16, 300, 34), "Admin", title);
+ if (GUI.Button(new Rect(rect.xMax - 20 - 90, rect.y + 18, 90, 30), "CLOSE", secondary)) AdminOpen = false;
+ if (GUI.Button(new Rect(rect.xMax - 20 - 90 - 8 - 100, rect.y + 18, 100, 30), "REFRESH", secondary))
{
- if (GUILayout.Button(tabs[i], adminTab == i ? primary : secondary, GUILayout.Height(30), GUILayout.Width(104))) adminTab = i;
- GUILayout.Space(6);
+ mp.RequestAdminList();
+ VehicleAdmin.Refresh(true);
}
- GUILayout.EndHorizontal();
- GUILayout.Space(10);
+ // Reiter (gleich breit über die ganze Zeile)
+ string[] tabs = { "PLAYERS", "TELEPORT", "VEHICLE", "ITEMS", "BANS" + (bannedIds != null && bannedIds.Count > 0 ? " (" + bannedIds.Count + ")" : ""), "WORLD" };
+ float tabW = (w - AdminGap * (tabs.Length - 1)) / tabs.Length;
+ for (int i = 0; i < tabs.Length; i++)
+ if (GUI.Button(new Rect(x + i * (tabW + AdminGap), rect.y + 62, tabW, 32), tabs[i], adminTab == i ? primary : secondary)) adminTab = i;
+
+ GUILayout.BeginArea(new Rect(x, rect.y + 108, w, rect.height - 124));
if (adminTab == 1) DrawAdminTeleport(mp);
else if (adminTab == 2) DrawAdminVehicle(mp);
else if (adminTab == 3) DrawAdminItems(mp);
@@ -311,142 +380,65 @@ namespace MSCO
GUILayout.EndArea();
}
- Vector2 teleportScroll, vehicleScroll;
- Texture2D texBarBack, texBarFill, texBarLow;
- string partFilter = "";
+ // ---------------------------------------------------------------- PLAYERS
- /// Reiter VEHICLE: Füllstände und Teile des Satsuma reparieren/zerstören (zum Testen).
- void DrawAdminVehicle(MultiplayerManager mp)
+ void DrawAdminPlayers(MultiplayerManager mp)
{
- if (!mp.CanCheat)
+ if (mp.CanCheat)
{
- GUILayout.Label("Only works in the game.", small);
- return;
- }
- if (texBarBack == null)
- {
- texBarBack = UiTheme.Solid(new Color(1f, 1f, 1f, 0.1f));
- texBarFill = UiTheme.Solid(UiTheme.Online);
- texBarLow = UiTheme.Solid(UiTheme.Danger);
- }
-
- GUILayout.BeginHorizontal();
- if (GUILayout.Button("REPAIR ALL", primary, GUILayout.Height(30), GUILayout.Width(130)))
- {
- int n = VehicleAdmin.RepairAll();
- Log(n + " part(s) repaired");
- if (n > 0) mp.VehicleCheat("repaired the Satsuma (" + n + " parts)");
- }
- GUILayout.Space(6);
- if (GUILayout.Button("TIGHTEN ALL", secondary, GUILayout.Height(30), GUILayout.Width(110)))
- {
- int n = VehicleAdmin.SetAllBolts(true);
- Log(n + " part(s) tightened");
- if (n > 0) mp.VehicleCheat("tightened all bolts (" + n + " parts)");
- }
- GUILayout.Space(6);
- if (GUILayout.Button("LOOSEN ALL", danger, GUILayout.Height(30), GUILayout.Width(106)))
- {
- int n = VehicleAdmin.SetAllBolts(false);
- Log(n + " part(s) loosened");
- if (n > 0) mp.VehicleCheat("loosened all bolts (" + n + " parts, testing)");
- }
- GUILayout.Space(6);
- if (GUILayout.Button("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(90))) VehicleAdmin.Refresh(true);
- GUILayout.Space(12);
- GUILayout.Label("Filter", caption, GUILayout.Width(44));
- partFilter = GUILayout.TextField(partFilter ?? "", 30, field, GUILayout.Height(30));
- GUILayout.EndHorizontal();
- GUILayout.Space(8);
-
- vehicleScroll = GUILayout.BeginScrollView(vehicleScroll);
- GUILayout.Label("FLUIDS", caption);
- List fluids = VehicleAdmin.Fluids;
- if (fluids.Count == 0) GUILayout.Label("No fluids found (only works in the game).", small);
- foreach (VehicleAdmin.Fluid f in fluids)
- {
- GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(36));
- GUILayout.Label(f.Label, chatName, GUILayout.Width(170));
- Rect bar = GUILayoutUtility.GetRect(120, 36, GUILayout.ExpandWidth(true));
- float frac = f.Max > 0f ? Mathf.Clamp01(f.Value.Value / f.Max) : 0f;
- var back = new Rect(bar.x, bar.y + 15, bar.width, 6);
- GUI.DrawTexture(back, texBarBack);
- GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.25f ? texBarLow : texBarFill);
- GUILayout.Label(f.Value.Value.ToString("0.00") + " / " + f.Max.ToString("0.#"), small, GUILayout.Width(90));
- if (GUILayout.Button("FILL", secondary, GUILayout.Height(26), GUILayout.Width(64)))
+ Rect r = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true));
+ string[] labels = { "+1.000 MK", "+10.000 MK", "REFILL NEEDS", "SATSUMA TO ME" };
+ float bw = (r.width - AdminGap * 3) / 4f;
+ for (int i = 0; i < labels.Length; i++)
{
- VehicleAdmin.SetFluid(f, true);
- mp.VehicleCheat("filled up " + f.Label.ToLower());
+ if (!GUI.Button(new Rect(r.x + i * (bw + AdminGap), r.y, bw, 32), labels[i], primary)) continue;
+ if (i == 0) mp.CheatMoney(1000f);
+ else if (i == 1) mp.CheatMoney(10000f);
+ else if (i == 2) mp.CheatNeeds();
+ else mp.BringCar();
}
- GUILayout.Space(4);
- if (GUILayout.Button("EMPTY", danger, GUILayout.Height(26), GUILayout.Width(70)))
- {
- VehicleAdmin.SetFluid(f, false);
- mp.VehicleCheat("emptied " + f.Label.ToLower());
- }
- GUILayout.EndHorizontal();
- GUILayout.Space(3);
+ GUILayout.Label("Cheats are announced in the chat. GOTO teleports you to a player, BRING teleports a player to you.", small);
+ GUILayout.Space(8);
}
+ Rect reason = GUILayoutUtility.GetRect(10, 32, GUILayout.ExpandWidth(true));
+ GUI.Label(new Rect(reason.x, reason.y, 210, 32), "REASON FOR KICK / BAN (optional)", captionMid);
+ adminReason = GUI.TextField(new Rect(reason.x + 214, reason.y, reason.width - 214, 32), adminReason ?? "", 100, field);
GUILayout.Space(10);
- GUILayout.Label("PARTS (wear: 100 = new)", caption);
- string filter = (partFilter ?? "").Trim().ToLowerInvariant();
- int shown = 0;
- foreach (VehicleAdmin.Part p in VehicleAdmin.Parts)
+
+ adminScroll = GUILayout.BeginScrollView(adminScroll);
+ if (adminPlayers == null) GUILayout.Label("Loading players...", small);
+ else if (adminPlayers.Count == 0) GUILayout.Label("No players.", small);
+ else
{
- if (filter.Length > 0 && !(p.Name + " " + p.Category).ToLowerInvariant().Contains(filter)) continue;
- shown++;
- bool installed = p.Installed == null || p.Installed.Value;
- bool damaged = p.Damaged != null && p.Damaged.Value;
- GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(36));
- GUILayout.BeginVertical(GUILayout.Width(220));
- GUILayout.Label(p.Name, chatName);
- GUILayout.Label(p.Category + (installed ? "" : " · not installed"), small);
- GUILayout.EndVertical();
- Rect bar = GUILayoutUtility.GetRect(100, 36, GUILayout.ExpandWidth(true));
- if (p.Wear != null)
+ foreach (AdminPlayer p in adminPlayers)
{
- float frac = Mathf.Clamp01(p.Wear.Value / 100f);
- var back = new Rect(bar.x, bar.y + 15, bar.width, 6);
- GUI.DrawTexture(back, texBarBack);
- GUI.DrawTexture(new Rect(back.x, back.y, Mathf.Max(2f, back.width * frac), back.height), frac < 0.3f ? texBarLow : texBarFill);
- }
- string boltText = p.Tightness != null ? (p.Bolted != null && p.Bolted.Value ? " bolted" : " loose") : "";
- GUILayout.Label((damaged ? "DAMAGED" : p.Wear != null ? p.Wear.Value.ToString("0") + " %" : "OK") + boltText, damaged ? danger : small, GUILayout.Width(120));
- if (p.HasBolts && installed)
- {
- if (GUILayout.Button("TIGHTEN", secondary, GUILayout.Height(26), GUILayout.Width(80)))
+ Rect row = AdminRowRect(54);
+ bool self = p.Id == mp.LocalId;
+ GUI.Label(new Rect(row.x, row.y + 6, row.width * 0.45f, 22), p.Name + (self ? " (you)" : "") + (p.IsAdmin ? " (admin)" : ""), nameMid);
+ GUI.Label(new Rect(row.x, row.y + 26, row.width * 0.45f, 18),
+ string.IsNullOrEmpty(p.SteamId) ? "id " + p.Id : p.SteamId.StartsWith("name:") ? "no Steam" : "SteamID " + p.SteamId, smallMid);
+ float right = row.xMax;
+ bool confirming = confirmBanId == p.Id;
+ if (RightButton(ref right, row, 76, confirming ? "SURE?" : "BAN", danger, !self && !p.IsAdmin))
{
- VehicleAdmin.SetBolts(p, true);
- mp.VehicleCheat("tightened " + p.Name);
+ if (confirming) { mp.AdminAction(true, p.Id, adminReason); confirmBanId = -1; }
+ else confirmBanId = p.Id;
}
- GUILayout.Space(4);
- if (GUILayout.Button("LOOSEN", secondary, GUILayout.Height(26), GUILayout.Width(76)))
+ if (RightButton(ref right, row, 76, "KICK", secondary, !self && !p.IsAdmin)) mp.AdminAction(false, p.Id, adminReason);
+ if (mp.IsDedicated && RightButton(ref right, row, 124, p.IsAdmin ? "REMOVE ADMIN" : "MAKE ADMIN", secondary, !self)) mp.SetAdmin(p.Id, !p.IsAdmin);
+ if (mp.CanCheat)
{
- VehicleAdmin.SetBolts(p, false);
- mp.VehicleCheat("loosened " + p.Name);
+ if (RightButton(ref right, row, 76, "BRING", secondary, !self)) mp.BringPlayer(p.Id);
+ if (RightButton(ref right, row, 76, "GOTO", secondary, !self)) mp.GotoPlayer(p.Id);
}
- GUILayout.Space(4);
}
- else GUILayout.Space(164);
- if (GUILayout.Button("REPAIR", secondary, GUILayout.Height(26), GUILayout.Width(76)))
- {
- VehicleAdmin.Repair(p);
- mp.VehicleCheat("repaired " + p.Name);
- }
- GUILayout.Space(4);
- if (GUILayout.Button("BREAK", danger, GUILayout.Height(26), GUILayout.Width(70)))
- {
- VehicleAdmin.Break(p);
- mp.VehicleCheat("broke " + p.Name + " (testing)");
- }
- GUILayout.EndHorizontal();
- GUILayout.Space(3);
}
- if (shown == 0) GUILayout.Label(VehicleAdmin.Parts.Count == 0 ? "No parts found (only works in the game)." : "No part matches the filter.", small);
GUILayout.EndScrollView();
}
+ // ---------------------------------------------------------------- TELEPORT
+
void DrawAdminTeleport(MultiplayerManager mp)
{
if (!mp.CanCheat)
@@ -457,99 +449,153 @@ namespace MSCO
GUILayout.Label("Teleport yourself to a place. Players: use GOTO / BRING on the PLAYERS tab.", small);
GUILayout.Space(8);
teleportScroll = GUILayout.BeginScrollView(teleportScroll);
- const int columns = 3;
+ const int columns = 4;
for (int i = 0; i < Cheats.Places.Length; i += columns)
{
- GUILayout.BeginHorizontal();
- for (int c = 0; c < columns; c++)
+ Rect r = GUILayoutUtility.GetRect(10, 44, GUILayout.ExpandWidth(true));
+ float bw = (r.width - AdminGap * (columns - 1)) / columns;
+ for (int c = 0; c < columns && i + c < Cheats.Places.Length; c++)
{
- if (i + c < Cheats.Places.Length)
- {
- string[] place = Cheats.Places[i + c];
- if (GUILayout.Button(place[0].ToUpperInvariant(), secondary, GUILayout.Height(38)))
- mp.TeleportToPlace(place[0], place[1]);
- }
- else GUILayout.Label("", GUILayout.Height(38));
- if (c < columns - 1) GUILayout.Space(6);
+ string[] place = Cheats.Places[i + c];
+ if (GUI.Button(new Rect(r.x + c * (bw + AdminGap), r.y, bw, 38), place[0].ToUpperInvariant(), secondary))
+ mp.TeleportToPlace(place[0], place[1]);
}
- GUILayout.EndHorizontal();
- GUILayout.Space(6);
}
GUILayout.EndScrollView();
}
- void DrawAdminPlayers(MultiplayerManager mp)
+ // ---------------------------------------------------------------- VEHICLE
+
+ void DrawAdminVehicle(MultiplayerManager mp)
{
- // Cheats für den eigenen Spieler (nur im Spiel)
- if (mp.CanCheat)
+ if (!mp.CanCheat)
{
- GUILayout.BeginHorizontal();
- if (GUILayout.Button("+1.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(1000f);
- GUILayout.Space(6);
- if (GUILayout.Button("+10.000 MK", primary, GUILayout.Height(30))) mp.CheatMoney(10000f);
- GUILayout.Space(6);
- if (GUILayout.Button("REFILL NEEDS", primary, GUILayout.Height(30))) mp.CheatNeeds();
- GUILayout.Space(6);
- if (GUILayout.Button("SATSUMA TO ME", primary, GUILayout.Height(30))) mp.BringCar();
- GUILayout.EndHorizontal();
- GUILayout.Label("Cheats are announced in the chat. GOTO teleports you to a player, BRING teleports a player to you.", small);
- GUILayout.Space(8);
+ GUILayout.Label("Only works in the game.", small);
+ return;
+ }
+ List parts = VehicleAdmin.Parts;
+
+ // Werkzeugleiste: Aktionen links, Suche rechts
+ Rect bar = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true));
+ float bx = bar.x;
+ if (GUI.Button(new Rect(bx, bar.y, 120, 32), "REPAIR ALL", primary))
+ {
+ int n = VehicleAdmin.RepairAll();
+ Log(n + " part(s) repaired");
+ if (n > 0) mp.VehicleCheat("repaired the Satsuma (" + n + " parts)");
+ }
+ bx += 120 + AdminGap;
+ if (GUI.Button(new Rect(bx, bar.y, 120, 32), "TIGHTEN ALL", secondary))
+ {
+ int n = VehicleAdmin.SetAllBolts(true);
+ Log(n + " part(s) tightened");
+ if (n > 0) mp.VehicleCheat("tightened all bolts (" + n + " parts)");
+ }
+ bx += 120 + AdminGap;
+ if (GUI.Button(new Rect(bx, bar.y, 116, 32), "LOOSEN ALL", danger))
+ {
+ int n = VehicleAdmin.SetAllBolts(false);
+ Log(n + " part(s) loosened");
+ if (n > 0) mp.VehicleCheat("loosened all bolts (" + n + " parts, testing)");
+ }
+ bx += 116 + 16;
+ var search = new Rect(bx, bar.y, bar.xMax - bx, 32);
+ partFilter = GUI.TextField(search, partFilter ?? "", 30, field);
+ if (string.IsNullOrEmpty(partFilter)) GUI.Label(new Rect(search.x + 12, search.y, search.width, 32), "Search parts …", smallMid);
+ GUILayout.Space(8);
+
+ // Schnellfilter: Kategorien + nur Probleme + Füllstände ein/aus
+ var categories = new List();
+ foreach (VehicleAdmin.Part p in parts) if (!categories.Contains(p.Category)) categories.Add(p.Category);
+ Rect chips = GUILayoutUtility.GetRect(10, 30, GUILayout.ExpandWidth(true));
+ float cx = chips.x;
+ if (GUI.Button(new Rect(cx, chips.y, 104, 26), "FLUIDS " + (fluidsOpen ? "▾" : "▸"), fluidsOpen ? chipOn : chipOff)) fluidsOpen = !fluidsOpen;
+ cx += 104 + 14;
+ if (GUI.Button(new Rect(cx, chips.y, 104, 26), "PROBLEMS", partProblems ? chipOn : chipOff)) partProblems = !partProblems;
+ cx += 104 + AdminGap;
+ if (GUI.Button(new Rect(cx, chips.y, 60, 26), "ALL", partCategory == "" ? chipOn : chipOff)) partCategory = "";
+ cx += 60 + AdminGap;
+ foreach (string cat in categories)
+ {
+ string label = (cat.Length == 0 ? "OTHER" : cat).ToUpperInvariant();
+ float cw = Mathf.Max(70, chipOff.CalcSize(new GUIContent(label)).x + 20);
+ if (cx + cw > chips.xMax) break;
+ if (GUI.Button(new Rect(cx, chips.y, cw, 26), label, partCategory == cat ? chipOn : chipOff)) partCategory = partCategory == cat ? "" : cat;
+ cx += cw + AdminGap;
+ }
+ GUILayout.Space(6);
+
+ vehicleScroll = GUILayout.BeginScrollView(vehicleScroll);
+ if (fluidsOpen)
+ {
+ List fluids = VehicleAdmin.Fluids;
+ if (fluids.Count == 0) GUILayout.Label("No fluids found.", small);
+ // zwei Spalten
+ for (int i = 0; i < fluids.Count; i += 2)
+ {
+ Rect line = GUILayoutUtility.GetRect(10, AdminRow, GUILayout.ExpandWidth(true));
+ float colW = (line.width - 10) / 2f;
+ for (int c = 0; c < 2 && i + c < fluids.Count; c++)
+ {
+ VehicleAdmin.Fluid f = fluids[i + c];
+ var cell = new Rect(line.x + c * (colW + 10), line.y + 2, colW, AdminRow - 4);
+ GUI.Box(cell, GUIContent.none, rowStyle);
+ var inner = new Rect(cell.x + 12, cell.y, cell.width - 24, cell.height);
+ GUI.Label(new Rect(inner.x, inner.y, 128, inner.height), f.Label, nameMid);
+ float right = inner.xMax;
+ if (RightButton(ref right, inner, 74, "EMPTY", danger)) { VehicleAdmin.SetFluid(f, false); mp.VehicleCheat("emptied " + f.Label.ToLower()); }
+ if (RightButton(ref right, inner, 60, "FILL", secondary)) { VehicleAdmin.SetFluid(f, true); mp.VehicleCheat("filled up " + f.Label.ToLower()); }
+ GUI.Label(new Rect(right - 66, inner.y, 66, inner.height), f.Value.Value.ToString("0.0") + " / " + f.Max.ToString("0.#"), smallMid);
+ Bar(new Rect(inner.x + 132, inner.y, right - 72 - (inner.x + 132), inner.height), f.Max > 0f ? f.Value.Value / f.Max : 0f, 0.25f);
+ }
+ }
+ GUILayout.Space(10);
}
- GUILayout.Label("REASON FOR KICK / BAN (optional)", caption);
- adminReason = GUILayout.TextField(adminReason, 100, field, GUILayout.Height(32));
- GUILayout.Space(10);
-
- adminScroll = GUILayout.BeginScrollView(adminScroll);
- if (adminPlayers == null) GUILayout.Label("Loading players...", small);
- else if (adminPlayers.Count == 0) GUILayout.Label("No players.", small);
- else
+ string filter = (partFilter ?? "").Trim().ToLowerInvariant();
+ int shown = 0;
+ foreach (VehicleAdmin.Part p in parts)
{
- foreach (AdminPlayer p in adminPlayers)
- {
- GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(44));
- GUILayout.BeginVertical();
- bool you = p.Id == mp.LocalId;
- GUILayout.Label(p.Name + (you ? " (you)" : "") + (p.IsAdmin ? " (admin)" : ""), chatName);
- GUILayout.Label(string.IsNullOrEmpty(p.SteamId) ? "id " + p.Id : p.SteamId.StartsWith("name:") ? "no Steam" : "SteamID " + p.SteamId, small);
- GUILayout.EndVertical();
- GUILayout.FlexibleSpace();
+ bool installed = p.Installed == null || p.Installed.Value;
+ bool damaged = p.Damaged != null && p.Damaged.Value;
+ // "Bolted" in der Database zieht das Spiel erst später nach - die Festigkeit selbst ist aktuell
+ bool loose = installed && p.Tightness != null && p.Tightness.Value < p.MaxTightness - 0.01f;
+ bool worn = p.Wear != null && p.Wear.Value < 50f;
+ if (partCategory.Length > 0 && p.Category != partCategory) continue;
+ if (partProblems && !(damaged || loose || worn)) continue;
+ if (filter.Length > 0 && !(p.Name + " " + p.Category).ToLowerInvariant().Contains(filter)) continue;
+ shown++;
- bool self = p.Id == mp.LocalId;
- if (mp.CanCheat)
- {
- GUI.enabled = !self;
- if (GUILayout.Button("GOTO", secondary, GUILayout.Height(30), GUILayout.Width(70))) mp.GotoPlayer(p.Id);
- GUILayout.Space(6);
- if (GUILayout.Button("BRING", secondary, GUILayout.Height(30), GUILayout.Width(70))) mp.BringPlayer(p.Id);
- GUILayout.Space(12);
- }
- if (mp.IsDedicated)
- {
- // Admin geben/nehmen (braucht Steam; der Server meldet, wenn es nicht geht)
- GUI.enabled = !self;
- if (GUILayout.Button(p.IsAdmin ? "REMOVE ADMIN" : "MAKE ADMIN", secondary, GUILayout.Height(30), GUILayout.Width(120)))
- mp.SetAdmin(p.Id, !p.IsAdmin);
- GUILayout.Space(12);
- }
- GUI.enabled = !self && !p.IsAdmin;
- if (GUILayout.Button("KICK", secondary, GUILayout.Height(30), GUILayout.Width(70)))
- mp.AdminAction(false, p.Id, adminReason);
- GUILayout.Space(6);
- bool confirming = confirmBanId == p.Id;
- if (GUILayout.Button(confirming ? "SURE?" : "BAN", danger, GUILayout.Height(30), GUILayout.Width(70)))
- {
- if (confirming) { mp.AdminAction(true, p.Id, adminReason); confirmBanId = -1; }
- else confirmBanId = p.Id;
- }
- GUI.enabled = true;
- GUILayout.EndHorizontal();
- GUILayout.Space(4);
+ Rect row = AdminRowRect(AdminRow);
+ GUI.Label(new Rect(row.x, row.y, 200, row.height), p.Name, nameMid);
+ GUI.Label(new Rect(row.x + 204, row.y, 90, row.height), p.Category, smallMid);
+
+ // Zustand als Abzeichen
+ string state = !installed ? "NOT INSTALLED" : damaged ? "DAMAGED" : loose ? "LOOSE" : p.Tightness != null ? "BOLTED" : "OK";
+ GUIStyle pill = !installed ? pillOff : damaged ? pillBad : loose ? pillWarn : pillOk;
+ GUI.Label(new Rect(row.x + 298, row.y + (row.height - 18) / 2f, 104, 18), state, pill);
+
+ float right = row.xMax;
+ if (RightButton(ref right, row, 66, "BREAK", danger)) { VehicleAdmin.Break(p); mp.VehicleCheat("broke " + p.Name + " (testing)"); }
+ if (RightButton(ref right, row, 70, "REPAIR", secondary)) { VehicleAdmin.Repair(p); mp.VehicleCheat("repaired " + p.Name); }
+ bool bolts = p.HasBolts && installed;
+ if (RightButton(ref right, row, 72, "LOOSEN", secondary, bolts)) { VehicleAdmin.SetBolts(p, false); mp.VehicleCheat("loosened " + p.Name); }
+ if (RightButton(ref right, row, 76, "TIGHTEN", secondary, bolts)) { VehicleAdmin.SetBolts(p, true); mp.VehicleCheat("tightened " + p.Name); }
+
+ // Verschleiß
+ float wearX = row.x + 410;
+ if (p.Wear != null && right - wearX > 80)
+ {
+ GUI.Label(new Rect(right - 44, row.y, 44, row.height), p.Wear.Value.ToString("0") + " %", smallMid);
+ Bar(new Rect(wearX, row.y, right - 50 - wearX, row.height), p.Wear.Value / 100f, 0.3f);
}
}
+ if (shown == 0) GUILayout.Label(parts.Count == 0 ? "No parts found." : "No part matches the filter.", small);
GUILayout.EndScrollView();
}
+ // ---------------------------------------------------------------- ITEMS
+
void DrawAdminItems(MultiplayerManager mp)
{
if (!mp.CanCheat)
@@ -557,25 +603,32 @@ namespace MSCO
GUILayout.Label("Items can only be spawned in the game.", small);
return;
}
- GUILayout.Label("Click an item to spawn it in front of you. Everyone sees it, and it is saved on the server.", small);
+ Rect top = GUILayoutUtility.GetRect(10, 32, GUILayout.ExpandWidth(true));
+ GUI.Label(new Rect(top.x, top.y, top.width - 330, 32), "Click an item to spawn it in front of you (everyone sees it, saved on the server).", smallMid);
+ var search = new Rect(top.xMax - 320, top.y, 320, 32);
+ itemFilter = GUI.TextField(search, itemFilter ?? "", 30, field);
+ if (string.IsNullOrEmpty(itemFilter)) GUI.Label(new Rect(search.x + 12, search.y, search.width, 32), "Search items …", smallMid);
GUILayout.Space(8);
+
+ var items = new List();
+ string f = (itemFilter ?? "").Trim().ToLowerInvariant();
+ foreach (string item in ItemSpawner.Items) if (f.Length == 0 || item.ToLowerInvariant().Contains(f)) items.Add(item);
+
adminScroll = GUILayout.BeginScrollView(adminScroll);
- const int perRow = 4;
- for (int i = 0; i < ItemSpawner.Items.Length; i += perRow)
+ const int perRow = 5;
+ for (int i = 0; i < items.Count; i += perRow)
{
- GUILayout.BeginHorizontal();
- for (int k = i; k < Mathf.Min(i + perRow, ItemSpawner.Items.Length); k++)
- {
- string item = ItemSpawner.Items[k];
- if (GUILayout.Button(item.ToUpper(), secondary, GUILayout.Height(34), GUILayout.Width(155))) mp.SpawnItem(item);
- GUILayout.Space(6);
- }
- GUILayout.EndHorizontal();
- GUILayout.Space(6);
+ Rect r = GUILayoutUtility.GetRect(10, 42, GUILayout.ExpandWidth(true));
+ float bw = (r.width - AdminGap * (perRow - 1)) / perRow;
+ for (int k = 0; k < perRow && i + k < items.Count; k++)
+ if (GUI.Button(new Rect(r.x + k * (bw + AdminGap), r.y, bw, 36), items[i + k].ToUpper(), secondary)) mp.SpawnItem(items[i + k]);
}
+ if (items.Count == 0) GUILayout.Label("No item matches the search.", small);
GUILayout.EndScrollView();
}
+ // ---------------------------------------------------------------- WORLD
+
/// Welt zurücksetzen, Backups anlegen und wiederherstellen (dedizierter Server).
void DrawAdminWorld(MultiplayerManager mp)
{
@@ -587,31 +640,30 @@ namespace MSCO
GUILayout.Label("Everyone reconnects after a reset or restore. The current state is backed up first.", small);
GUILayout.Space(8);
- GUILayout.BeginHorizontal();
+ Rect r = GUILayoutUtility.GetRect(10, 34, GUILayout.ExpandWidth(true));
+ float bw = (r.width - AdminGap * 2) / 3f;
+ var b1 = new Rect(r.x, r.y, bw, 32);
+ var b2 = new Rect(r.x + bw + AdminGap, r.y, bw, 32);
+ var b3 = new Rect(r.x + 2 * (bw + AdminGap), r.y, bw, 32);
if (confirmWorld == "reset")
{
- if (GUILayout.Button("REALLY RESET?", danger, GUILayout.Height(32), GUILayout.Width(170))) { mp.WorldAction(5, null); confirmWorld = null; }
- GUILayout.Space(6);
- if (GUILayout.Button("CANCEL", secondary, GUILayout.Height(32), GUILayout.Width(100))) confirmWorld = null;
+ if (GUI.Button(new Rect(b1.x, b1.y, bw / 2f - 3, 32), "REALLY RESET?", danger)) { mp.WorldAction(5, null); confirmWorld = null; }
+ if (GUI.Button(new Rect(b1.x + bw / 2f + 3, b1.y, bw / 2f - 3, 32), "CANCEL", secondary)) confirmWorld = null;
}
- else if (GUILayout.Button("RESET WORLD", danger, GUILayout.Height(32), GUILayout.Width(170))) confirmWorld = "reset";
- GUILayout.Space(10);
- if (GUILayout.Button("BACKUP NOW", primary, GUILayout.Height(32), GUILayout.Width(150))) mp.WorldAction(6, null);
- GUILayout.Space(6);
+ else if (GUI.Button(b1, "RESET WORLD", danger)) confirmWorld = "reset";
+ if (GUI.Button(b2, "BACKUP NOW", primary)) mp.WorldAction(6, null);
if (confirmWorld == "point")
{
- if (GUILayout.Button("REALLY?", danger, GUILayout.Height(32), GUILayout.Width(100))) { mp.WorldAction(8, null); confirmWorld = null; }
- GUILayout.Space(4);
- if (GUILayout.Button("CANCEL", secondary, GUILayout.Height(32), GUILayout.Width(90))) confirmWorld = null;
+ if (GUI.Button(new Rect(b3.x, b3.y, bw / 2f - 3, 32), "REALLY?", danger)) { mp.WorldAction(8, null); confirmWorld = null; }
+ if (GUI.Button(new Rect(b3.x + bw / 2f + 3, b3.y, bw / 2f - 3, 32), "CANCEL", secondary)) confirmWorld = null;
}
- else if (GUILayout.Button("SET RESET POINT", secondary, GUILayout.Height(32), GUILayout.Width(170))) confirmWorld = "point";
- GUILayout.FlexibleSpace();
- GUILayout.EndHorizontal();
+ else if (GUI.Button(b3, "SET RESET POINT", secondary)) confirmWorld = "point";
+ GUILayout.Space(4);
GUILayout.Label("Reset: the whole world goes back to the save game as it was uploaded to the server (the reset point). " +
"SET RESET POINT makes the current save the new reset point.", small);
GUILayout.Space(10);
- GUILayout.Label("SAVE BACKUPS (NEWEST FIRST)", small);
+ GUILayout.Label("SAVE BACKUPS (NEWEST FIRST)", caption);
adminScroll = GUILayout.BeginScrollView(adminScroll);
if (backupNames == null) GUILayout.Label("Loading...", small);
else if (backupNames.Count == 0) GUILayout.Label("No backups yet. They are created automatically when the server takes over a save game, or with BACKUP NOW.", small);
@@ -619,18 +671,15 @@ namespace MSCO
{
foreach (string name in backupNames)
{
- GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(40));
- GUILayout.Label(PrettyBackup(name), chatName);
- GUILayout.FlexibleSpace();
+ Rect row = AdminRowRect(AdminRow);
+ GUI.Label(new Rect(row.x, row.y, 300, row.height), PrettyBackup(name), nameMid);
+ float right = row.xMax;
if (confirmWorld == name)
{
- if (GUILayout.Button("REALLY?", danger, GUILayout.Height(28), GUILayout.Width(100))) { mp.WorldAction(7, name); confirmWorld = null; }
- GUILayout.Space(4);
- if (GUILayout.Button("CANCEL", secondary, GUILayout.Height(28), GUILayout.Width(90))) confirmWorld = null;
+ if (RightButton(ref right, row, 90, "CANCEL", secondary)) confirmWorld = null;
+ if (RightButton(ref right, row, 100, "REALLY?", danger)) { mp.WorldAction(7, name); confirmWorld = null; }
}
- else if (GUILayout.Button("RESTORE", primary, GUILayout.Height(28), GUILayout.Width(100))) confirmWorld = name;
- GUILayout.EndHorizontal();
- GUILayout.Space(4);
+ else if (RightButton(ref right, row, 100, "RESTORE", secondary)) confirmWorld = name;
}
}
GUILayout.EndScrollView();
@@ -654,14 +703,13 @@ namespace MSCO
else if (bannedIds.Count == 0) GUILayout.Label("Nobody is banned.", small);
else
{
+ EnsureAdminStyles();
foreach (string id in bannedIds)
{
- GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(40));
- GUILayout.Label(id.StartsWith("name:") ? id.Substring(5) + " (no Steam)" : "SteamID " + id, chatName);
- GUILayout.FlexibleSpace();
- if (GUILayout.Button("UNBAN", primary, GUILayout.Height(28), GUILayout.Width(90))) mp.Unban(id);
- GUILayout.EndHorizontal();
- GUILayout.Space(4);
+ Rect row = AdminRowRect(AdminRow);
+ GUI.Label(new Rect(row.x, row.y, row.width - 110, row.height), id.StartsWith("name:") ? id.Substring(5) + " (no Steam)" : "SteamID " + id, nameMid);
+ float right = row.xMax;
+ if (RightButton(ref right, row, 90, "UNBAN", secondary)) mp.Unban(id);
}
}
GUILayout.EndScrollView();
diff --git a/VehicleAdmin.cs b/VehicleAdmin.cs
index 8e60f79..ecd1a3a 100644
--- a/VehicleAdmin.cs
+++ b/VehicleAdmin.cs
@@ -75,7 +75,7 @@ namespace MSCO
var part = new Part
{
Name = Pretty(t.name),
- Category = category.Replace("Database", ""),
+ Category = category.Replace("Database", "").Replace("Parts", ""),
Damaged = damaged, Installed = installed, Wear = wear,
Bolted = fsm.FsmVariables.FindFsmBool("Bolted"),
};