Admin WORLD tab: reset world, backup now, list and restore save backups

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-27 16:43:40 +02:00
co-authored by Claude Opus 5.5
parent 31e630dccb
commit bddc1efe76
4 changed files with 90 additions and 2 deletions
+6
View File
@@ -14,6 +14,12 @@ Server und Mod müssen beide 2.8 sein (Server: `git pull && docker compose up -d
- **Verkehr**: NPC-Autos fahren bei allen an derselben Stelle (der erste Spieler simuliert, die anderen folgen)
- `vehicles` in der F7-Konsole zeigt, wie viele Karosserie-Meshes, Job-Werte und Verkehrsautos synchronisiert werden
**Admin: Welt verwalten (neuer Reiter WORLD im Admin-Menü)**
- **RESET WORLD**: Weltzustand zurücksetzen, alle verbinden automatisch neu
- **BACKUP NOW**: aktuellen Server-Spielstand sichern (Server-Befehl: `backup`)
- **Backups** mit Datum/Uhrzeit, **RESTORE** holt einen älteren Stand zurück (der aktuelle wird vorher gesichert)
- Jede Aktion mit Sicherheitsabfrage; nur für Admins (vom Server geprüft)
**Trennen & Neu-Verbinden**
- Gekickt, gebannt, Server beendet, Verbindung verloren: eigenes Fenster in der Bildmitte mit **Grund** und OK
(im Spiel wird der Grund vorher 2,5 s groß angezeigt)
+19
View File
@@ -422,6 +422,18 @@ namespace MSCO
return true;
}
/// <summary>Admin (nur dedizierter Server): Welt zurücksetzen (5), Backup anlegen (6), Backup wiederherstellen (7).</summary>
public void WorldAction(byte action, string backup)
{
if (session == null || !dedicated) return;
session.SendTo(NetSession.HostId, MsgType.AdminAction, NetSession.Build(w =>
{
w.Write(action);
w.Write(-1);
w.Write(backup ?? "");
}));
}
/// <summary>Admin: Bann aufheben (Server: SteamID oder name:..., P2P: Name).</summary>
public void Unban(string id)
{
@@ -1268,6 +1280,13 @@ namespace MSCO
for (int i = 0; i < bans; i++) banned.Add(r.ReadString());
}
overlays.SetBanned(banned);
var backups = new List<string>();
if (r.BaseStream.Length - r.BaseStream.Position >= 4)
{
int n = r.ReadInt32();
for (int i = 0; i < n; i++) backups.Add(r.ReadString());
}
overlays.SetBackups(backups);
break;
}
+1 -1
View File
@@ -34,7 +34,7 @@ namespace MSCO
AdminStatus = 23, // Server -> Client: bool, ist Admin
AdminListRequest = 24, // Client -> Server: Spielerliste fürs Admin-Menü
AdminList = 25, // Server -> Client: int Anzahl, je [int id, string name, string steamId, bool admin]
AdminAction = 26, // Client -> Server: byte (1 = kick, 2 = ban), int Spieler-ID, string Grund
AdminAction = 26, // Client -> Server: byte (1 kick, 2 ban, 3 bring, 4 unban, 5 reset world, 6 backup, 7 restore backup), int Spieler-ID, string Grund/Name
AdminResult = 27, // Server -> Client: string Meldung
Chat = 28, // Chatnachricht (string)
VarSync = 29, // Fahrzeugzustand: int n, je [int key, byte typ, wert]
+64 -1
View File
@@ -95,6 +95,9 @@ namespace MSCO
public void SetAdminPlayers(List<AdminPlayer> players) { adminPlayers = players; }
public void SetBanned(List<string> ids) { bannedIds = ids; }
public void SetBackups(List<string> names) { backupNames = names; }
List<string> backupNames;
string confirmWorld; // "reset" oder Name des Backups, das wiederhergestellt werden soll
public void CloseAll()
{
@@ -288,7 +291,7 @@ namespace MSCO
// Reiter
GUILayout.BeginHorizontal();
string[] tabs = { "PLAYERS", "ITEMS", "BANS" + (bannedIds != null && bannedIds.Count > 0 ? " (" + bannedIds.Count + ")" : "") };
string[] tabs = { "PLAYERS", "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(130))) adminTab = i;
@@ -299,6 +302,7 @@ namespace MSCO
if (adminTab == 1) DrawAdminItems(mp);
else if (adminTab == 2) DrawAdminBans(mp);
else if (adminTab == 3) DrawAdminWorld(mp);
else DrawAdminPlayers(mp);
GUILayout.EndArea();
}
@@ -393,6 +397,65 @@ namespace MSCO
GUILayout.EndScrollView();
}
/// <summary>Welt zurücksetzen, Backups anlegen und wiederherstellen (dedizierter Server).</summary>
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 save 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.FlexibleSpace();
GUILayout.EndHorizontal();
GUILayout.Label("Reset: all moved objects, bought items, doors and vehicle values go back to the server's save game.", 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();
}
/// <summary>"20260927-104626" -> "27.09.2026 10:46:26".</summary>
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."