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:
@@ -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)
|
- **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
|
- `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**
|
**Trennen & Neu-Verbinden**
|
||||||
- Gekickt, gebannt, Server beendet, Verbindung verloren: eigenes Fenster in der Bildmitte mit **Grund** und OK
|
- 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)
|
(im Spiel wird der Grund vorher 2,5 s groß angezeigt)
|
||||||
|
|||||||
@@ -422,6 +422,18 @@ namespace MSCO
|
|||||||
return true;
|
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>
|
/// <summary>Admin: Bann aufheben (Server: SteamID oder name:..., P2P: Name).</summary>
|
||||||
public void Unban(string id)
|
public void Unban(string id)
|
||||||
{
|
{
|
||||||
@@ -1268,6 +1280,13 @@ namespace MSCO
|
|||||||
for (int i = 0; i < bans; i++) banned.Add(r.ReadString());
|
for (int i = 0; i < bans; i++) banned.Add(r.ReadString());
|
||||||
}
|
}
|
||||||
overlays.SetBanned(banned);
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -34,7 +34,7 @@ namespace MSCO
|
|||||||
AdminStatus = 23, // Server -> Client: bool, ist Admin
|
AdminStatus = 23, // Server -> Client: bool, ist Admin
|
||||||
AdminListRequest = 24, // Client -> Server: Spielerliste fürs Admin-Menü
|
AdminListRequest = 24, // Client -> Server: Spielerliste fürs Admin-Menü
|
||||||
AdminList = 25, // Server -> Client: int Anzahl, je [int id, string name, string steamId, bool admin]
|
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
|
AdminResult = 27, // Server -> Client: string Meldung
|
||||||
Chat = 28, // Chatnachricht (string)
|
Chat = 28, // Chatnachricht (string)
|
||||||
VarSync = 29, // Fahrzeugzustand: int n, je [int key, byte typ, wert]
|
VarSync = 29, // Fahrzeugzustand: int n, je [int key, byte typ, wert]
|
||||||
|
|||||||
+64
-1
@@ -95,6 +95,9 @@ namespace MSCO
|
|||||||
|
|
||||||
public void SetAdminPlayers(List<AdminPlayer> players) { adminPlayers = players; }
|
public void SetAdminPlayers(List<AdminPlayer> players) { adminPlayers = players; }
|
||||||
public void SetBanned(List<string> ids) { bannedIds = ids; }
|
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()
|
public void CloseAll()
|
||||||
{
|
{
|
||||||
@@ -288,7 +291,7 @@ namespace MSCO
|
|||||||
|
|
||||||
// Reiter
|
// Reiter
|
||||||
GUILayout.BeginHorizontal();
|
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++)
|
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;
|
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);
|
if (adminTab == 1) DrawAdminItems(mp);
|
||||||
else if (adminTab == 2) DrawAdminBans(mp);
|
else if (adminTab == 2) DrawAdminBans(mp);
|
||||||
|
else if (adminTab == 3) DrawAdminWorld(mp);
|
||||||
else DrawAdminPlayers(mp);
|
else DrawAdminPlayers(mp);
|
||||||
GUILayout.EndArea();
|
GUILayout.EndArea();
|
||||||
}
|
}
|
||||||
@@ -393,6 +397,65 @@ namespace MSCO
|
|||||||
GUILayout.EndScrollView();
|
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)
|
void DrawAdminBans(MultiplayerManager mp)
|
||||||
{
|
{
|
||||||
GUILayout.Label(mp.IsDedicated ? "Banned players cannot join this server. UNBAN lets them in again."
|
GUILayout.Label(mp.IsDedicated ? "Banned players cannot join this server. UNBAN lets them in again."
|
||||||
|
|||||||
Reference in New Issue
Block a user