using System;
using System.Collections.Generic;
using UnityEngine;
namespace MSCO
{
/// Spieler im Admin-Menü.
public class AdminPlayer
{
public int Id;
public string Name;
public string SteamId;
public bool IsAdmin;
}
///
/// Fenster über dem Spiel: F7-Konsole (alle MSCO-Befehle), Chat (T) und Admin-Menü (/admin).
/// Solange eins davon offen ist, sind Spielerbewegung und Kamera gesperrt (InputLock).
///
public class Overlays
{
const int MaxConsoleLines = 300;
const int MaxChatLines = 60;
const float ChatFadeSeconds = 15f;
const string ConsoleControl = "msco_console";
const string ChatControl = "msco_chat";
class ChatLine
{
public string Name, Text;
public bool System;
public float Time;
}
// Konsole
public bool ConsoleOpen { get; private set; }
readonly List consoleLines = new List();
readonly List history = new List();
int historyIndex = -1;
string consoleInput = "";
Vector2 consoleScroll;
int consoleOpenedFrame = -1;
bool consoleScrollToEnd;
// Chat
public bool ChatOpen { get; private set; }
readonly List chat = new List();
string chatInput = "";
int chatOpenedFrame = -1;
// Admin
public bool AdminOpen { get; private set; }
List adminPlayers;
List bannedIds;
int adminTab;
string adminReason = "";
int confirmBanId = -1;
Vector2 adminScroll;
public bool AnyOpen { get { return ConsoleOpen || ChatOpen || AdminOpen; } }
// Stile
bool stylesReady;
GUIStyle panel, rowStyle, field, logText, caption, title, primary, secondary, danger, chatName, chatText, chatSystem, small;
// ---------------------------------------------------------------- Öffnen/Schließen
public void ToggleConsole()
{
ConsoleOpen = !ConsoleOpen;
if (ConsoleOpen)
{
ChatOpen = false;
consoleOpenedFrame = Time.frameCount;
consoleScrollToEnd = true;
}
}
public void OpenChat()
{
if (AnyOpen) return;
ChatOpen = true;
chatOpenedFrame = Time.frameCount;
chatInput = "";
}
public void OpenAdmin(List players)
{
AdminOpen = true;
ConsoleOpen = false;
ChatOpen = false;
adminPlayers = players;
confirmBanId = -1;
}
public void SetAdminPlayers(List players) { adminPlayers = players; }
public void SetBanned(List ids) { bannedIds = ids; }
public void SetBackups(List names) { backupNames = names; }
List backupNames;
string confirmWorld; // "reset" oder Name des Backups, das wiederhergestellt werden soll
public void CloseAll()
{
ConsoleOpen = ChatOpen = AdminOpen = false;
}
// ---------------------------------------------------------------- Inhalte
public void Log(string line)
{
foreach (string part in (line ?? "").Split('\n'))
{
consoleLines.Add(part);
if (consoleLines.Count > MaxConsoleLines) consoleLines.RemoveAt(0);
}
consoleScrollToEnd = true;
}
public void AddChat(string name, string text, bool system = false)
{
chat.Add(new ChatLine { Name = name, Text = text, System = system, Time = Time.realtimeSinceStartup });
if (chat.Count > MaxChatLines) chat.RemoveAt(0);
}
// ---------------------------------------------------------------- Zeichnen (OnGUI)
public void Draw(MultiplayerManager mp, bool inGame)
{
EnsureStyles();
if (inGame) DrawChat(mp);
if (ConsoleOpen) DrawConsole();
if (AdminOpen) DrawAdmin(mp);
}
void DrawConsole()
{
float width = Mathf.Min(Screen.width - 40, 960);
var rect = new Rect((Screen.width - width) / 2f, 20, width, Mathf.Min(340, Screen.height * 0.5f));
UiTheme.DrawShadow(rect);
GUI.Box(rect, GUIContent.none, panel);
GUI.Label(new Rect(rect.x + 16, rect.y + 10, 300, 20), "MSCO CONSOLE", caption);
GUI.Label(new Rect(rect.xMax - 216, rect.y + 10, 200, 20), "type 'help' - F7 to close", small);
// Tasten: Enter ausführen, Esc schließen, Hoch/Runter Verlauf
Event e = Event.current;
if (e.type == EventType.KeyDown && GUI.GetNameOfFocusedControl() == ConsoleControl)
{
if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter)
{
RunConsoleLine(consoleInput);
consoleInput = "";
e.Use();
}
else if (e.keyCode == KeyCode.Escape) { ConsoleOpen = false; e.Use(); return; }
else if (e.keyCode == KeyCode.UpArrow && history.Count > 0)
{
historyIndex = historyIndex < 0 ? history.Count - 1 : Mathf.Max(0, historyIndex - 1);
consoleInput = history[historyIndex];
e.Use();
}
else if (e.keyCode == KeyCode.DownArrow && historyIndex >= 0)
{
historyIndex++;
consoleInput = historyIndex < history.Count ? history[historyIndex] : "";
if (historyIndex >= history.Count) historyIndex = -1;
e.Use();
}
}
GUILayout.BeginArea(new Rect(rect.x + 14, rect.y + 34, rect.width - 28, rect.height - 84));
if (consoleScrollToEnd && e.type == EventType.Repaint) { consoleScroll.y = float.MaxValue; consoleScrollToEnd = false; }
consoleScroll = GUILayout.BeginScrollView(consoleScroll);
foreach (string line in consoleLines) GUILayout.Label(line, logText);
GUILayout.EndScrollView();
GUILayout.EndArea();
// Taste, mit der die Konsole geöffnet wurde, nicht als Zeichen übernehmen
if (Time.frameCount == consoleOpenedFrame && e.type == EventType.KeyDown) e.Use();
GUI.SetNextControlName(ConsoleControl);
consoleInput = GUI.TextField(new Rect(rect.x + 14, rect.yMax - 46, rect.width - 28, 34), consoleInput, 200, field);
GUI.FocusControl(ConsoleControl);
}
void RunConsoleLine(string line)
{
line = (line ?? "").Trim();
if (line.Length == 0) return;
history.Add(line);
if (history.Count > 50) history.RemoveAt(0);
historyIndex = -1;
Log("> " + line);
if (line == "clear") { consoleLines.Clear(); return; }
string[] args = line.Split(new[] { ' ' }, StringSplitOptions.RemoveEmptyEntries);
if (args.Length > 0 && args[0] == "mp") // "mp connect ..." geht auch
{
var rest = new string[args.Length - 1];
Array.Copy(args, 1, rest, 0, rest.Length);
args = rest;
}
Commands.Execute(args, Log);
}
void DrawChat(MultiplayerManager mp)
{
float now = Time.realtimeSinceStartup;
float width = Mathf.Min(460, Screen.width - 40);
float lineHeight = 20;
int maxLines = ChatOpen ? 12 : 6;
// Sichtbare Zeilen: offen = die letzten, geschlossen = nur neue (blenden aus)
var visible = new List();
for (int i = chat.Count - 1; i >= 0 && visible.Count < maxLines; i--)
if (ChatOpen || now - chat[i].Time < ChatFadeSeconds) visible.Insert(0, chat[i]);
float bottom = Screen.height * 0.62f;
float height = visible.Count * lineHeight + (ChatOpen ? 52 : 12);
var rect = new Rect(20, bottom - height, width, height);
if (ChatOpen || visible.Count > 0) GUI.Box(rect, GUIContent.none, ChatOpen ? panel : rowStyle);
Color old = GUI.color;
float y = rect.y + 6;
foreach (ChatLine line in visible)
{
float age = now - line.Time;
float alpha = ChatOpen ? 1f : Mathf.Clamp01((ChatFadeSeconds - age) / 3f);
GUI.color = new Color(1f, 1f, 1f, alpha);
if (line.System)
{
GUI.Label(new Rect(rect.x + 12, y, width - 24, lineHeight), line.Text, chatSystem);
}
else
{
Vector2 size = chatName.CalcSize(new GUIContent(line.Name + ":"));
GUI.Label(new Rect(rect.x + 12, y, size.x, lineHeight), line.Name + ":", chatName);
GUI.Label(new Rect(rect.x + 16 + size.x, y, width - 28 - size.x, lineHeight), line.Text, chatText);
}
y += lineHeight;
}
GUI.color = old;
if (!ChatOpen) return;
Event e = Event.current;
if (e.type == EventType.KeyDown && GUI.GetNameOfFocusedControl() == ChatControl)
{
if (e.keyCode == KeyCode.Return || e.keyCode == KeyCode.KeypadEnter)
{
string text = chatInput.Trim();
if (text.Length > 0)
{
if (text.StartsWith("/")) RunConsoleLine(text.Substring(1)); // z.B. /admin
else mp.SendChat(text);
}
chatInput = "";
ChatOpen = false;
e.Use();
return;
}
if (e.keyCode == KeyCode.Escape) { ChatOpen = false; e.Use(); return; }
}
// Taste, mit der der Chat geöffnet wurde (T), nicht als Zeichen übernehmen
if (Time.frameCount == chatOpenedFrame && e.type == EventType.KeyDown) e.Use();
GUI.SetNextControlName(ChatControl);
chatInput = GUI.TextField(new Rect(rect.x + 10, rect.yMax - 40, width - 20, 30), chatInput, 150, field);
GUI.FocusControl(ChatControl);
}
void DrawAdmin(MultiplayerManager mp)
{
float width = Mathf.Min(720, Screen.width - 40), height = Mathf.Min(580, Screen.height - 40);
var rect = new Rect((Screen.width - width) / 2f, (Screen.height - height) / 2f, width, height);
UiTheme.DrawShadow(rect);
GUI.Box(rect, GUIContent.none, panel);
if (Event.current.type == EventType.KeyDown && Event.current.keyCode == KeyCode.Escape)
{
AdminOpen = false;
Event.current.Use();
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++)
{
if (GUILayout.Button(tabs[i], adminTab == i ? primary : secondary, GUILayout.Height(30), GUILayout.Width(104))) adminTab = i;
GUILayout.Space(6);
}
GUILayout.EndHorizontal();
GUILayout.Space(10);
if (adminTab == 1) DrawAdminTeleport(mp);
else if (adminTab == 2) DrawAdminVehicle(mp);
else if (adminTab == 3) DrawAdminItems(mp);
else if (adminTab == 4) DrawAdminBans(mp);
else if (adminTab == 5) DrawAdminWorld(mp);
else DrawAdminPlayers(mp);
GUILayout.EndArea();
}
Vector2 teleportScroll, vehicleScroll;
Texture2D texBarBack, texBarFill, texBarLow;
string partFilter = "";
/// Reiter VEHICLE: Füllstände und Teile des Satsuma reparieren/zerstören (zum Testen).
void DrawAdminVehicle(MultiplayerManager mp)
{
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("REFRESH", secondary, GUILayout.Height(30), GUILayout.Width(100))) 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)))
{
VehicleAdmin.SetFluid(f, true);
mp.VehicleCheat("filled up " + f.Label.ToLower());
}
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.Space(10);
GUILayout.Label("PARTS (wear: 100 = new)", caption);
string filter = (partFilter ?? "").Trim().ToLowerInvariant();
int shown = 0;
foreach (VehicleAdmin.Part p in VehicleAdmin.Parts)
{
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)
{
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);
}
GUILayout.Label(damaged ? "DAMAGED" : p.Wear != null ? p.Wear.Value.ToString("0") + " %" : "OK", damaged ? danger : small, GUILayout.Width(84));
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();
}
void DrawAdminTeleport(MultiplayerManager mp)
{
if (!mp.CanCheat)
{
GUILayout.Label("Teleporting only works in the game.", small);
return;
}
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;
for (int i = 0; i < Cheats.Places.Length; i += columns)
{
GUILayout.BeginHorizontal();
for (int c = 0; c < columns; 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);
}
GUILayout.EndHorizontal();
GUILayout.Space(6);
}
GUILayout.EndScrollView();
}
void DrawAdminPlayers(MultiplayerManager mp)
{
// Cheats für den eigenen Spieler (nur im Spiel)
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("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
{
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 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);
}
}
GUILayout.EndScrollView();
}
void DrawAdminItems(MultiplayerManager mp)
{
if (!mp.CanCheat)
{
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);
GUILayout.Space(8);
adminScroll = GUILayout.BeginScrollView(adminScroll);
const int perRow = 4;
for (int i = 0; i < ItemSpawner.Items.Length; 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);
}
GUILayout.EndScrollView();
}
/// Welt zurücksetzen, Backups anlegen und wiederherstellen (dedizierter Server).
void DrawAdminWorld(MultiplayerManager mp)
{
if (!mp.IsDedicated)
{
GUILayout.Label("World reset and save backups are only available on a dedicated server.", small);
return;
}
GUILayout.Label("Everyone reconnects after a reset or restore. The current state is backed up first.", small);
GUILayout.Space(8);
GUILayout.BeginHorizontal();
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;
}
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);
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;
}
else if (GUILayout.Button("SET RESET POINT", secondary, GUILayout.Height(32), GUILayout.Width(170))) confirmWorld = "point";
GUILayout.FlexibleSpace();
GUILayout.EndHorizontal();
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);
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);
else
{
foreach (string name in backupNames)
{
GUILayout.BeginHorizontal(rowStyle, GUILayout.Height(40));
GUILayout.Label(PrettyBackup(name), chatName);
GUILayout.FlexibleSpace();
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;
}
else if (GUILayout.Button("RESTORE", primary, GUILayout.Height(28), GUILayout.Width(100))) confirmWorld = name;
GUILayout.EndHorizontal();
GUILayout.Space(4);
}
}
GUILayout.EndScrollView();
}
/// "20260927-104626" -> "27.09.2026 10:46:26".
static string PrettyBackup(string name)
{
if (name == null || name.Length != 15 || name[8] != '-') return name;
return name.Substring(6, 2) + "." + name.Substring(4, 2) + "." + name.Substring(0, 4) + " " +
name.Substring(9, 2) + ":" + name.Substring(11, 2) + ":" + name.Substring(13, 2);
}
void DrawAdminBans(MultiplayerManager mp)
{
GUILayout.Label(mp.IsDedicated ? "Banned players cannot join this server. UNBAN lets them in again."
: "Players banned from your game in this session.", small);
GUILayout.Space(8);
adminScroll = GUILayout.BeginScrollView(adminScroll);
if (bannedIds == null) GUILayout.Label("Loading...", small);
else if (bannedIds.Count == 0) GUILayout.Label("Nobody is banned.", small);
else
{
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);
}
}
GUILayout.EndScrollView();
}
// ---------------------------------------------------------------- Stile
void EnsureStyles()
{
if (stylesReady && panel.normal.background != null) return;
panel = UiTheme.Card(UiTheme.Background, 10);
rowStyle = UiTheme.Panel9(new Color(0.06f, 0.06f, 0.07f, 0.72f));
rowStyle.padding = new RectOffset(12, 12, 6, 6);
field = UiTheme.Field(14);
logText = UiTheme.Label(12, UiTheme.Text);
logText.wordWrap = true;
logText.richText = true; // Warnungen/Fehler farbig markiert (MscoLog)
logText.margin = new RectOffset(0, 0, 0, 0);
caption = UiTheme.Label(11, UiTheme.Muted, FontStyle.Bold);
title = UiTheme.Label(22, UiTheme.Text, FontStyle.Bold);
title.wordWrap = false;
small = UiTheme.Label(11, UiTheme.Muted);
primary = UiTheme.PrimaryButton();
secondary = UiTheme.Button(UiTheme.Secondary, UiTheme.SecondaryHover, UiTheme.Text, 12);
danger = UiTheme.Button(UiTheme.Danger, UiTheme.DangerHover, Color.white, 12);
chatName = UiTheme.Label(13, UiTheme.AccentHover, FontStyle.Bold, TextAnchor.MiddleLeft);
chatText = UiTheme.Label(13, UiTheme.Text, FontStyle.Normal, TextAnchor.MiddleLeft);
chatSystem = UiTheme.Label(12, UiTheme.Muted, FontStyle.Italic, TextAnchor.MiddleLeft);
stylesReady = true;
}
}
}