2.10.11: security levels (no joining servers/hosts below the required level, OUTDATED in the browser), announcements and maintenance notice from the keymaster in the MSCO menu
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@@ -1,5 +1,14 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
|
## 2.10.11
|
||||||
|
|
||||||
|
- **Sicherheitsstufe:** Server und P2P-Hosts, denen MSCO-Sicherheitsupdates fehlen (Server älter als 2.10.6), kann man
|
||||||
|
nicht mehr betreten – im Server-Browser stehen sie als **OUTDATED**, statt JOIN steht dort „UPDATE REQUIRED“, und
|
||||||
|
wer trotzdem verbindet, bekommt den Grund angezeigt. Server-Betreiber: im Web-Panel auf *Update* klicken
|
||||||
|
- **Ankündigungen & Wartung:** Oben im MSCO-Menü stehen Ankündigungen des MSCO-Teams und ein Hinweis, wenn MSCO gerade
|
||||||
|
gewartet wird (mit × ausblendbar). Neue Ankündigungen während des Spielens erscheinen kurz am Bildschirmrand und
|
||||||
|
in der Konsole (F7)
|
||||||
|
|
||||||
## 2.10.10
|
## 2.10.10
|
||||||
|
|
||||||
- **Mods im Singleplayer:** Startet der Launcher mit SINGLEPLAYER, lädt MSCO nur noch die eigenen Mods
|
- **Mods im Singleplayer:** Startet der Launcher mit SINGLEPLAYER, lädt MSCO nur noch die eigenen Mods
|
||||||
|
|||||||
+54
-3
@@ -72,7 +72,12 @@ namespace MSCO
|
|||||||
string layoutError;
|
string layoutError;
|
||||||
GUIStyle dangerStyle;
|
GUIStyle dangerStyle;
|
||||||
GUIStyle windowBox, sidebarBox, rowBox, accentBox, pillStyle, pillMutedStyle, chipOnStyle, chipOffStyle;
|
GUIStyle windowBox, sidebarBox, rowBox, accentBox, pillStyle, pillMutedStyle, chipOnStyle, chipOffStyle;
|
||||||
GUIStyle featuredRowBox, pillFeaturedStyle;
|
GUIStyle featuredRowBox, pillFeaturedStyle, pillDangerStyle, newsTitleStyle;
|
||||||
|
// Ankündigungen/Wartung vom Keymaster - nur im Layout-Durchgang übernehmen (Liste kann sich im Hintergrund ändern)
|
||||||
|
List<Announcement> shownNews;
|
||||||
|
bool shownMaintenance;
|
||||||
|
string shownMaintenanceText;
|
||||||
|
readonly HashSet<string> dismissedNews = new HashSet<string>();
|
||||||
GUIStyle betaPillStyle, betaNoteStyle;
|
GUIStyle betaPillStyle, betaNoteStyle;
|
||||||
GUIStyle logoStyle, taglineStyle, navStyle, navActiveStyle, titleStyle, textStyle, captionStyle,
|
GUIStyle logoStyle, taglineStyle, navStyle, navActiveStyle, titleStyle, textStyle, captionStyle,
|
||||||
fieldStyle, primaryStyle, secondaryStyle, statusStyle, tabStyle, headerCellStyle,
|
fieldStyle, primaryStyle, secondaryStyle, statusStyle, tabStyle, headerCellStyle,
|
||||||
@@ -141,6 +146,7 @@ namespace MSCO
|
|||||||
GUI.skin.verticalScrollbar = scrollbarStyle;
|
GUI.skin.verticalScrollbar = scrollbarStyle;
|
||||||
GUI.skin.verticalScrollbarThumb = scrollThumbStyle;
|
GUI.skin.verticalScrollbarThumb = scrollThumbStyle;
|
||||||
GUILayout.BeginArea(content);
|
GUILayout.BeginArea(content);
|
||||||
|
if (!versionWarning && !pendingJoin && !mp.IsInSession) DrawAnnouncements();
|
||||||
if (versionWarning) DrawVersionWarning(mp);
|
if (versionWarning) DrawVersionWarning(mp);
|
||||||
else if (pendingJoin) DrawJoinConfirm(mp);
|
else if (pendingJoin) DrawJoinConfirm(mp);
|
||||||
else if (mp.IsInSession) DrawSession(mp);
|
else if (mp.IsInSession) DrawSession(mp);
|
||||||
@@ -358,6 +364,7 @@ namespace MSCO
|
|||||||
// Server auf diesem PC (localhost)
|
// Server auf diesem PC (localhost)
|
||||||
nameX += Pill(nameX, row, "LOCAL", pillStyle);
|
nameX += Pill(nameX, row, "LOCAL", pillStyle);
|
||||||
}
|
}
|
||||||
|
if (s.Outdated) nameX += Pill(nameX, row, "OUTDATED", pillDangerStyle); // fehlende Sicherheitsupdates
|
||||||
if (!string.IsNullOrEmpty(s.Version) && !MSCO.Compatible(s.Version))
|
if (!string.IsNullOrEmpty(s.Version) && !MSCO.Compatible(s.Version))
|
||||||
{
|
{
|
||||||
nameX += Pill(nameX, row, "v" + s.Version, pillStyle); // abweichende Version
|
nameX += Pill(nameX, row, "v" + s.Version, pillStyle); // abweichende Version
|
||||||
@@ -408,7 +415,43 @@ namespace MSCO
|
|||||||
|
|
||||||
static bool CanJoin(MultiplayerManager mp, ServerListEntry s)
|
static bool CanJoin(MultiplayerManager mp, ServerListEntry s)
|
||||||
{
|
{
|
||||||
return s.Players < s.MaxPlayers && (!s.RequireSteam || mp.SteamAvailable);
|
return !s.Outdated && s.Players < s.MaxPlayers && (!s.RequireSteam || mp.SteamAvailable);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Oben im MSCO-Menü: Wartungshinweis und Ankündigungen der MSCO-Admins (Keymaster → Wartung & News).
|
||||||
|
/// Ankündigungen lassen sich mit × für diese Sitzung ausblenden.
|
||||||
|
/// </summary>
|
||||||
|
void DrawAnnouncements()
|
||||||
|
{
|
||||||
|
if (shownNews == null || Event.current.type == EventType.Layout)
|
||||||
|
{
|
||||||
|
shownNews = SiteStatus.Announcements.FindAll(a => !dismissedNews.Contains(a.Id));
|
||||||
|
if (shownNews.Count > 3) shownNews.RemoveRange(3, shownNews.Count - 3);
|
||||||
|
shownMaintenance = SiteStatus.Maintenance;
|
||||||
|
shownMaintenanceText = SiteStatus.MaintenanceMessage;
|
||||||
|
}
|
||||||
|
if (shownMaintenance)
|
||||||
|
NewsBox("MAINTENANCE", pillFeaturedStyle, "MSCO is in maintenance right now",
|
||||||
|
string.IsNullOrEmpty(shownMaintenanceText) ? "Some things may not work for a while." : shownMaintenanceText, null);
|
||||||
|
foreach (Announcement a in shownNews)
|
||||||
|
NewsBox(a.Level == "critical" ? "IMPORTANT" : a.Level == "warning" ? "NOTICE" : "INFO",
|
||||||
|
a.Level == "critical" ? pillDangerStyle : a.Level == "warning" ? pillFeaturedStyle : pillMutedStyle, a.Title, a.Text, a.Id);
|
||||||
|
if (shownMaintenance || shownNews.Count > 0) GUILayout.Space(S(10));
|
||||||
|
}
|
||||||
|
|
||||||
|
void NewsBox(string label, GUIStyle pill, string title, string text, string id)
|
||||||
|
{
|
||||||
|
GUILayout.BeginVertical(rowBox);
|
||||||
|
GUILayout.BeginHorizontal();
|
||||||
|
GUILayout.Label(label, pill, GUILayout.Height(S(20)), GUILayout.ExpandWidth(false));
|
||||||
|
GUILayout.Space(S(8));
|
||||||
|
GUILayout.Label(title, newsTitleStyle, GUILayout.ExpandWidth(true));
|
||||||
|
if (id != null && GUILayout.Button("×", tabStyle, GUILayout.Width(S(28)), GUILayout.Height(S(22)))) dismissedNews.Add(id);
|
||||||
|
GUILayout.EndHorizontal();
|
||||||
|
if (!string.IsNullOrEmpty(text)) GUILayout.Label(text, textStyle);
|
||||||
|
GUILayout.EndVertical();
|
||||||
|
GUILayout.Space(S(6));
|
||||||
}
|
}
|
||||||
|
|
||||||
void DrawNameField(MultiplayerManager mp, float width)
|
void DrawNameField(MultiplayerManager mp, float width)
|
||||||
@@ -613,6 +656,7 @@ namespace MSCO
|
|||||||
if (official) bx += Pill(bx, badgeRow, "OFFICIAL", pillStyle);
|
if (official) bx += Pill(bx, badgeRow, "OFFICIAL", pillStyle);
|
||||||
bx += Pill(bx, badgeRow, s.RequireSteam ? "STEAM" : "NON-STEAM", s.RequireSteam ? pillStyle : pillMutedStyle);
|
bx += Pill(bx, badgeRow, s.RequireSteam ? "STEAM" : "NON-STEAM", s.RequireSteam ? pillStyle : pillMutedStyle);
|
||||||
if (s.IsLocal) bx += Pill(bx, badgeRow, "LOCAL", pillStyle);
|
if (s.IsLocal) bx += Pill(bx, badgeRow, "LOCAL", pillStyle);
|
||||||
|
if (s.Outdated) bx += Pill(bx, badgeRow, "OUTDATED", pillDangerStyle);
|
||||||
if (!MSCO.Compatible(s.Version)) bx += Pill(bx, badgeRow, "v" + s.Version, pillStyle);
|
if (!MSCO.Compatible(s.Version)) bx += Pill(bx, badgeRow, "v" + s.Version, pillStyle);
|
||||||
if (s.Tags != null)
|
if (s.Tags != null)
|
||||||
foreach (string tag in s.Tags)
|
foreach (string tag in s.Tags)
|
||||||
@@ -625,10 +669,13 @@ namespace MSCO
|
|||||||
Heart(new Rect(heartSlot.x + S(11), heartSlot.y + S(11), S(22), S(22)), s);
|
Heart(new Rect(heartSlot.x + S(11), heartSlot.y + S(11), S(22), S(22)), s);
|
||||||
GUILayout.Space(S(8));
|
GUILayout.Space(S(8));
|
||||||
GUI.enabled = CanJoin(mp, s);
|
GUI.enabled = CanJoin(mp, s);
|
||||||
if (GUILayout.Button(s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(S(44)), GUILayout.Width(S(180))))
|
if (GUILayout.Button(s.Outdated ? "UPDATE REQUIRED" : s.Players >= s.MaxPlayers ? "SERVER FULL" : "JOIN SERVER", primaryStyle, GUILayout.Height(S(44)), GUILayout.Width(S(180))))
|
||||||
mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers);
|
mp.JoinServer(s.Address, s.Port, s.Name, s.MaxPlayers);
|
||||||
GUI.enabled = true;
|
GUI.enabled = true;
|
||||||
GUILayout.EndHorizontal();
|
GUILayout.EndHorizontal();
|
||||||
|
if (s.Outdated)
|
||||||
|
GUILayout.Label("This server is missing MSCO security updates" + (string.IsNullOrEmpty(s.ServerVersion) ? "" : " (server " + s.ServerVersion + ")") +
|
||||||
|
" - you can join again once its owner has updated it.", textStyle);
|
||||||
if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle);
|
if (!string.IsNullOrEmpty(s.Description)) GUILayout.Label(s.Description, textStyle);
|
||||||
GUILayout.Space(S(10));
|
GUILayout.Space(S(10));
|
||||||
|
|
||||||
@@ -1118,6 +1165,10 @@ namespace MSCO
|
|||||||
pillFeaturedStyle = new GUIStyle(pillStyle);
|
pillFeaturedStyle = new GUIStyle(pillStyle);
|
||||||
pillFeaturedStyle.normal.background = UiTheme.Rounded(Gold, P(4));
|
pillFeaturedStyle.normal.background = UiTheme.Rounded(Gold, P(4));
|
||||||
pillFeaturedStyle.normal.textColor = Hex(0x1a, 0x12, 0x06);
|
pillFeaturedStyle.normal.textColor = Hex(0x1a, 0x12, 0x06);
|
||||||
|
pillDangerStyle = new GUIStyle(pillStyle);
|
||||||
|
pillDangerStyle.normal.background = UiTheme.Rounded(new Color(UiTheme.Danger.r, UiTheme.Danger.g, UiTheme.Danger.b, 0.25f), P(4));
|
||||||
|
pillDangerStyle.normal.textColor = UiTheme.DangerHover;
|
||||||
|
newsTitleStyle = new GUIStyle(rowNameStyle) { wordWrap = true };
|
||||||
featuredRowBox = UiTheme.Card(Color.Lerp(Field, Gold, 0.14f), P(10));
|
featuredRowBox = UiTheme.Card(Color.Lerp(Field, Gold, 0.14f), P(10));
|
||||||
|
|
||||||
chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.2f), new Color(Accent.r, Accent.g, Accent.b, 0.3f), AccentHover, P(11), FontStyle.Bold, P(11), new Color(Accent.r, Accent.g, Accent.b, 0.55f));
|
chipOnStyle = Button(new Color(Accent.r, Accent.g, Accent.b, 0.2f), new Color(Accent.r, Accent.g, Accent.b, 0.3f), AccentHover, P(11), FontStyle.Bold, P(11), new Color(Accent.r, Accent.g, Accent.b, 0.55f));
|
||||||
|
|||||||
@@ -11,13 +11,26 @@ namespace MSCO
|
|||||||
/// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit
|
/// Version der Mod. Kleine Updates: nur die dritte Stelle erhöhen (2.9.1, 2.9.2 ...) - sie bleiben mit
|
||||||
/// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
|
/// Servern und Mitspielern derselben ersten zwei Stellen kompatibel.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ModVersion = "2.10.10";
|
public const string ModVersion = "2.10.11";
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
|
/// Netzwerk-Version (die ersten beiden Stellen). Nur erhöhen, wenn sich das Protokoll inkompatibel ändert - dann
|
||||||
/// auch GameServer.ModVersion im Server.
|
/// auch GameServer.ModVersion im Server.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public const string ProtocolVersion = "2.8";
|
public const string ProtocolVersion = "2.8";
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sicherheitsstufe dieser Mod-Version - steigt mit jedem Sicherheitsupdate (gleiche Zählung wie
|
||||||
|
/// GameServer.SecurityLevel im Server). Als P2P-Host meldet die Mod sie den Mitspielern.
|
||||||
|
/// 1 = Oktober 2026.
|
||||||
|
/// </summary>
|
||||||
|
public const int SecurityLevel = 1;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Server und P2P-Hosts unter dieser Stufe fehlen Sicherheitsupdates: die Mod tritt ihnen nicht bei. Die
|
||||||
|
/// MSCO-Admins können das Minimum im Keymaster noch anheben (SiteStatus.RequiredServerSecurity).
|
||||||
|
/// </summary>
|
||||||
|
public const int MinServerSecurity = 1;
|
||||||
|
|
||||||
/// <summary>Passt eine andere Version (Server, Host, Mitspieler) zu uns? Verglichen wird die Netzwerk-Version.</summary>
|
/// <summary>Passt eine andere Version (Server, Host, Mitspieler) zu uns? Verglichen wird die Netzwerk-Version.</summary>
|
||||||
public static bool Compatible(string other)
|
public static bool Compatible(string other)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -71,6 +71,10 @@ namespace MSCO
|
|||||||
const float NeedsInterval = 10f;
|
const float NeedsInterval = 10f;
|
||||||
|
|
||||||
void ToastJoined(string name) { toasts.Add(name, "joined the game", UiTheme.Online); }
|
void ToastJoined(string name) { toasts.Add(name, "joined the game", UiTheme.Online); }
|
||||||
|
|
||||||
|
const string OutdatedServerText = "This server is outdated - it is missing MSCO security updates. You can join again once its owner has updated it (web panel -> Update).";
|
||||||
|
const string OutdatedHostText = "The host uses an outdated MSCO version that is missing security updates. Ask them to update " +
|
||||||
|
"MSCO (start the game once through the MSCO Launcher), then join again.";
|
||||||
void ToastLeft(string name) { toasts.Add(name, "left the game", UiTheme.Muted); }
|
void ToastLeft(string name) { toasts.Add(name, "left the game", UiTheme.Muted); }
|
||||||
readonly Overlays overlays = new Overlays();
|
readonly Overlays overlays = new Overlays();
|
||||||
bool isAdmin; // Server hat uns als Admin gemeldet
|
bool isAdmin; // Server hat uns als Admin gemeldet
|
||||||
@@ -108,6 +112,13 @@ namespace MSCO
|
|||||||
{
|
{
|
||||||
Application.runInBackground = true;
|
Application.runInBackground = true;
|
||||||
playerName = PlayerPrefs.GetString(PrefName, System.Environment.UserName);
|
playerName = PlayerPrefs.GetString(PrefName, System.Environment.UserName);
|
||||||
|
// Neue Ankündigung der MSCO-Admins, während man spielt: kurz einblenden und in die Konsole (F7)
|
||||||
|
SiteStatus.NewAnnouncement += a =>
|
||||||
|
{
|
||||||
|
toasts.Add(a.Title, a.Level == "critical" ? "Important announcement" : "MSCO announcement",
|
||||||
|
a.Level == "critical" ? UiTheme.Danger : a.Level == "warning" ? UiTheme.Gold : UiTheme.Accent);
|
||||||
|
MscoLog.Info("[MSCO] Announcement: " + a.Title + (string.IsNullOrEmpty(a.Text) ? "" : " - " + a.Text));
|
||||||
|
};
|
||||||
objects.Send = SendGame;
|
objects.Send = SendGame;
|
||||||
// Einkäufe / verbrauchte Items bei uns: allen melden
|
// Einkäufe / verbrauchte Items bei uns: allen melden
|
||||||
objects.ItemAppeared = b =>
|
objects.ItemAppeared = b =>
|
||||||
@@ -755,6 +766,13 @@ namespace MSCO
|
|||||||
}
|
}
|
||||||
string name = string.IsNullOrEmpty(displayName) ? info.Name : displayName;
|
string name = string.IsNullOrEmpty(displayName) ? info.Name : displayName;
|
||||||
int max = info.MaxPlayers > 0 ? info.MaxPlayers : maxPlayers;
|
int max = info.MaxPlayers > 0 ? info.MaxPlayers : maxPlayers;
|
||||||
|
// Fehlen dem Server Sicherheitsupdates, nicht beitreten (auch nicht nach Rückfrage)
|
||||||
|
if (info.Security < SiteStatus.RequiredServerSecurity)
|
||||||
|
{
|
||||||
|
DisconnectNotice = OutdatedServerText;
|
||||||
|
SetStatus("Server " + name + " is outdated (security level " + info.Security + ", required " + SiteStatus.RequiredServerSecurity + ")");
|
||||||
|
yield break;
|
||||||
|
}
|
||||||
|
|
||||||
if (!MSCO.Compatible(info.Version))
|
if (!MSCO.Compatible(info.Version))
|
||||||
{
|
{
|
||||||
@@ -1045,6 +1063,7 @@ namespace MSCO
|
|||||||
void Update()
|
void Update()
|
||||||
{
|
{
|
||||||
CheckHitch();
|
CheckHitch();
|
||||||
|
SiteStatus.Tick(); // Ankündigungen, Wartung, Mindest-Sicherheitsstufe vom Keymaster
|
||||||
CheckJoinRequest();
|
CheckJoinRequest();
|
||||||
// JOIN im Server-Browser (Launcher/Website): ohne Rückfrage verbinden, sobald das Hauptmenü steht.
|
// JOIN im Server-Browser (Launcher/Website): ohne Rückfrage verbinden, sobald das Hauptmenü steht.
|
||||||
// Server-Mods fragen weiterhin extra nach (das sind Programme).
|
// Server-Mods fragen weiterhin extra nach (das sind Programme).
|
||||||
@@ -1319,6 +1338,7 @@ namespace MSCO
|
|||||||
if (r.Contains("kick")) return "KICKED";
|
if (r.Contains("kick")) return "KICKED";
|
||||||
if (r.Contains("shutting down") || r.Contains("server stopped") || r.Contains("shut down")) return "SERVER CLOSED";
|
if (r.Contains("shutting down") || r.Contains("server stopped") || r.Contains("shut down")) return "SERVER CLOSED";
|
||||||
if (r.Contains("could not reconnect")) return "RECONNECT FAILED";
|
if (r.Contains("could not reconnect")) return "RECONNECT FAILED";
|
||||||
|
if (r.Contains("outdated")) return "UPDATE REQUIRED"; // Server/Host ohne Sicherheitsupdates
|
||||||
return "DISCONNECTED";
|
return "DISCONNECTED";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1509,6 +1529,9 @@ namespace MSCO
|
|||||||
w.Write(false); // kein dedizierter Server
|
w.Write(false); // kein dedizierter Server
|
||||||
w.Write(list.Count);
|
w.Write(list.Count);
|
||||||
foreach (PlayerInfo p in list) { w.Write(p.Id); w.Write(p.Name); }
|
foreach (PlayerInfo p in list) { w.Write(p.Id); w.Write(p.Name); }
|
||||||
|
// Unsere Sicherheitsstufe (aktuelle Mods treten veralteten Hosts nicht bei)
|
||||||
|
w.Write(MSCO.SecurityLevel);
|
||||||
|
w.Write(MSCO.ModVersion);
|
||||||
}));
|
}));
|
||||||
session.SendToAll(MsgType.PlayerJoined, NetSession.Build(w => { w.Write(peerId); w.Write(name); }), peerId);
|
session.SendToAll(MsgType.PlayerJoined, NetSession.Build(w => { w.Write(peerId); w.Write(name); }), peerId);
|
||||||
ToastJoined(name);
|
ToastJoined(name);
|
||||||
@@ -1548,6 +1571,18 @@ namespace MSCO
|
|||||||
int id = r.ReadInt32();
|
int id = r.ReadInt32();
|
||||||
AddPlayer(id, r.ReadString(), id == localId);
|
AddPlayer(id, r.ReadString(), id == localId);
|
||||||
}
|
}
|
||||||
|
// Sicherheitsstufe des Servers/Hosts (ältere schicken sie nicht = 0): darunter gleich wieder gehen
|
||||||
|
int level = r.BaseStream.Position < r.BaseStream.Length ? r.ReadInt32() : 0;
|
||||||
|
if (level < SiteStatus.RequiredServerSecurity)
|
||||||
|
{
|
||||||
|
MscoLog.Warning("[MSCO] " + (dedicated ? "Server" : "Host") + " has security level " + level + ", required " +
|
||||||
|
SiteStatus.RequiredServerSecurity + " - leaving");
|
||||||
|
serverReason = dedicated ? OutdatedServerText : OutdatedHostText;
|
||||||
|
reconnectMessage = null; // nicht immer wieder versuchen
|
||||||
|
session.Close("Outdated " + (dedicated ? "server" : "host"));
|
||||||
|
OnSessionEnded(session.CloseReason);
|
||||||
|
return;
|
||||||
|
}
|
||||||
handshakeDone = true;
|
handshakeDone = true;
|
||||||
reconnectMessage = null; // (wieder) verbunden
|
reconnectMessage = null; // (wieder) verbunden
|
||||||
reconnectAttempts = 0;
|
reconnectAttempts = 0;
|
||||||
|
|||||||
@@ -20,6 +20,10 @@ namespace MSCO
|
|||||||
public int Players;
|
public int Players;
|
||||||
public int MaxPlayers;
|
public int MaxPlayers;
|
||||||
public string Version;
|
public string Version;
|
||||||
|
public string ServerVersion = ""; // Programmversion des Servers (z.B. 2.10.6), ältere melden sie nicht
|
||||||
|
public int Security; // Sicherheitsstufe des Servers (0 = alt, ohne Sicherheitsupdates)
|
||||||
|
/// <summary>Fehlen dem Server Sicherheitsupdates? Dann tritt die Mod nicht bei.</summary>
|
||||||
|
public bool Outdated { get { return Security < SiteStatus.RequiredServerSecurity; } }
|
||||||
public bool RequireSteam = true; // false = Non-Steam-Server
|
public bool RequireSteam = true; // false = Non-Steam-Server
|
||||||
public string Owner; // Besitzer des Lizenzschlüssels
|
public string Owner; // Besitzer des Lizenzschlüssels
|
||||||
public bool Featured; // vom Keymaster-Admin hervorgehoben (steht in der Liste oben)
|
public bool Featured; // vom Keymaster-Admin hervorgehoben (steht in der Liste oben)
|
||||||
@@ -135,6 +139,8 @@ namespace MSCO
|
|||||||
Players = MiniJson.Int(o, "players"),
|
Players = MiniJson.Int(o, "players"),
|
||||||
MaxPlayers = MiniJson.Int(o, "maxPlayers"),
|
MaxPlayers = MiniJson.Int(o, "maxPlayers"),
|
||||||
Version = MiniJson.Str(o, "version", ""),
|
Version = MiniJson.Str(o, "version", ""),
|
||||||
|
ServerVersion = MiniJson.Str(o, "serverVersion", ""),
|
||||||
|
Security = MiniJson.Int(o, "security"),
|
||||||
RequireSteam = MiniJson.Bool(o, "requireSteam", true),
|
RequireSteam = MiniJson.Bool(o, "requireSteam", true),
|
||||||
Owner = MiniJson.Str(o, "owner"),
|
Owner = MiniJson.Str(o, "owner"),
|
||||||
Featured = MiniJson.Bool(o, "featured"),
|
Featured = MiniJson.Bool(o, "featured"),
|
||||||
@@ -192,6 +198,9 @@ namespace MSCO
|
|||||||
entry.MaxPlayers = info.MaxPlayers;
|
entry.MaxPlayers = info.MaxPlayers;
|
||||||
entry.Version = info.Version;
|
entry.Version = info.Version;
|
||||||
entry.Ping = info.Ping;
|
entry.Ping = info.Ping;
|
||||||
|
// Direkt vom Server - gilt auch für lokale/direkt eingegebene Server ohne Keymaster
|
||||||
|
entry.Security = info.Security;
|
||||||
|
if (!string.IsNullOrEmpty(info.ServerVersion)) entry.ServerVersion = info.ServerVersion;
|
||||||
if (info.HasProfile)
|
if (info.HasProfile)
|
||||||
{
|
{
|
||||||
entry.RequireSteam = info.RequireSteam;
|
entry.RequireSteam = info.RequireSteam;
|
||||||
@@ -218,6 +227,9 @@ namespace MSCO
|
|||||||
public string Owner;
|
public string Owner;
|
||||||
public int UptimeSeconds;
|
public int UptimeSeconds;
|
||||||
public List<string> PlayerNames;
|
public List<string> PlayerNames;
|
||||||
|
// Ab Server 2.10.6: Programmversion und Sicherheitsstufe (ältere Server: "" und 0)
|
||||||
|
public string ServerVersion;
|
||||||
|
public int Security;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>Server direkt nach Name, Spielerzahl und Version fragen (blockierend - im Hintergrund aufrufen).</summary>
|
/// <summary>Server direkt nach Name, Spielerzahl und Version fragen (blockierend - im Hintergrund aufrufen).</summary>
|
||||||
@@ -261,6 +273,11 @@ namespace MSCO
|
|||||||
int count = r.ReadInt32();
|
int count = r.ReadInt32();
|
||||||
info.PlayerNames = new List<string>();
|
info.PlayerNames = new List<string>();
|
||||||
for (int i = 0; i < count && i < 256; i++) info.PlayerNames.Add(r.ReadString());
|
for (int i = 0; i < count && i < 256; i++) info.PlayerNames.Add(r.ReadString());
|
||||||
|
if (r.BaseStream.Position < r.BaseStream.Length)
|
||||||
|
{
|
||||||
|
info.ServerVersion = r.ReadString();
|
||||||
|
info.Security = r.ReadInt32();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
+134
@@ -0,0 +1,134 @@
|
|||||||
|
using System;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Threading;
|
||||||
|
using UnityEngine;
|
||||||
|
|
||||||
|
namespace MSCO
|
||||||
|
{
|
||||||
|
/// <summary>Eine Ankündigung der MSCO-Admins (Keymaster → Wartung & News, Ziel "Spiel").</summary>
|
||||||
|
public class Announcement
|
||||||
|
{
|
||||||
|
public string Id, Title, Text, Level; // Level: info, warning, critical
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Status vom Keymaster (/api/status?for=game): Wartungsmodus, Ankündigungen und die Mindest-Sicherheitsstufe der
|
||||||
|
/// Server. Wird im Hintergrund geladen (WinHTTP, blockiert das Spiel nicht) und alle paar Minuten aufgefrischt.
|
||||||
|
/// Ohne Verbindung gilt das Eingebaute (MSCO.MinServerSecurity), Ankündigungen fehlen dann einfach.
|
||||||
|
/// </summary>
|
||||||
|
public static class SiteStatus
|
||||||
|
{
|
||||||
|
const float RefreshSeconds = 300f;
|
||||||
|
|
||||||
|
static readonly object gate = new object();
|
||||||
|
static List<Announcement> announcements = new List<Announcement>();
|
||||||
|
static bool maintenance;
|
||||||
|
static string maintenanceMessage = "";
|
||||||
|
static int minServerLevel; // vom Keymaster (0 = unbekannt)
|
||||||
|
static float nextFetch;
|
||||||
|
static bool fetching;
|
||||||
|
|
||||||
|
/// <summary>Aktuelle Ankündigungen (wichtigste zuerst).</summary>
|
||||||
|
public static List<Announcement> Announcements { get { lock (gate) return new List<Announcement>(announcements); } }
|
||||||
|
public static bool Maintenance { get { lock (gate) return maintenance; } }
|
||||||
|
public static string MaintenanceMessage { get { lock (gate) return maintenanceMessage; } }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Sicherheitsstufe, die Server und P2P-Hosts mindestens haben müssen: das Eingebaute oder - wenn höher - was die
|
||||||
|
/// MSCO-Admins im Keymaster eingestellt haben.
|
||||||
|
/// </summary>
|
||||||
|
public static int RequiredServerSecurity { get { lock (gate) return Math.Max(MSCO.MinServerSecurity, minServerLevel); } }
|
||||||
|
|
||||||
|
/// <summary>Neue Ankündigung seit dem letzten Abruf (für einen Hinweis im Spiel).</summary>
|
||||||
|
public static event Action<Announcement> NewAnnouncement;
|
||||||
|
|
||||||
|
static readonly HashSet<string> seen = new HashSet<string>();
|
||||||
|
static readonly List<Announcement> fresh = new List<Announcement>(); // neu, noch nicht gemeldet (Hauptthread)
|
||||||
|
static bool firstLoad = true;
|
||||||
|
|
||||||
|
/// <summary>Jedes Frame aufrufen (MultiplayerManager): lädt beim Start und dann alle 5 Minuten neu.</summary>
|
||||||
|
public static void Tick()
|
||||||
|
{
|
||||||
|
Action<Announcement> handler = NewAnnouncement;
|
||||||
|
List<Announcement> report = null;
|
||||||
|
lock (gate)
|
||||||
|
if (fresh.Count > 0) { report = new List<Announcement>(fresh); fresh.Clear(); }
|
||||||
|
if (report != null && handler != null)
|
||||||
|
foreach (Announcement a in report)
|
||||||
|
try { handler(a); } catch { }
|
||||||
|
if (fetching || Time.realtimeSinceStartup < nextFetch) return;
|
||||||
|
Refresh();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Sofort neu laden (z.B. beim Öffnen des Server-Browsers).</summary>
|
||||||
|
public static void Refresh()
|
||||||
|
{
|
||||||
|
if (fetching) return;
|
||||||
|
fetching = true;
|
||||||
|
nextFetch = Time.realtimeSinceStartup + RefreshSeconds;
|
||||||
|
var thread = new Thread(() =>
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
string error;
|
||||||
|
string text = WinHttp.Get(ServerBrowser.MasterUrl + "/api/status?for=game", 8000, out error);
|
||||||
|
if (text != null) Apply(text);
|
||||||
|
}
|
||||||
|
catch { }
|
||||||
|
finally { fetching = false; }
|
||||||
|
});
|
||||||
|
thread.IsBackground = true;
|
||||||
|
thread.Start();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void Apply(string text)
|
||||||
|
{
|
||||||
|
var root = MiniJson.Parse(text) as Dictionary<string, object>;
|
||||||
|
if (root == null) return;
|
||||||
|
object v;
|
||||||
|
var m = root.TryGetValue("maintenance", out v) ? v as Dictionary<string, object> : null;
|
||||||
|
var sec = root.TryGetValue("security", out v) ? v as Dictionary<string, object> : null;
|
||||||
|
var list = new List<Announcement>();
|
||||||
|
var items = root.TryGetValue("announcements", out v) ? v as List<object> : null;
|
||||||
|
if (items != null)
|
||||||
|
foreach (object item in items)
|
||||||
|
{
|
||||||
|
var a = item as Dictionary<string, object>;
|
||||||
|
if (a == null) continue;
|
||||||
|
list.Add(new Announcement
|
||||||
|
{
|
||||||
|
Id = MiniJson.Str(a, "id", ""),
|
||||||
|
Title = Clip(MiniJson.Str(a, "title", ""), 120),
|
||||||
|
Text = Clip(MiniJson.Str(a, "text", ""), 600),
|
||||||
|
Level = MiniJson.Str(a, "level", "info"),
|
||||||
|
});
|
||||||
|
if (list.Count >= 5) break;
|
||||||
|
}
|
||||||
|
lock (gate)
|
||||||
|
{
|
||||||
|
announcements = list;
|
||||||
|
maintenance = m != null && MiniJson.Bool(m, "active");
|
||||||
|
maintenanceMessage = m != null ? Clip(MiniJson.Str(m, "message", ""), 500) : "";
|
||||||
|
if (sec != null) minServerLevel = Math.Max(0, Math.Min(1000, MiniJson.Int(sec, "minServerLevel")));
|
||||||
|
foreach (Announcement a in list)
|
||||||
|
if (seen.Add(a.Id) && !firstLoad) fresh.Add(a);
|
||||||
|
firstLoad = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Kein Steuerzeichen/Rich-Text aus dem Netz in die Oberfläche (Unity-GUI wertet <color> usw. aus).</summary>
|
||||||
|
static string Clip(string s, int max)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(s)) return "";
|
||||||
|
var sb = new System.Text.StringBuilder(Math.Min(s.Length, max));
|
||||||
|
foreach (char c in s)
|
||||||
|
{
|
||||||
|
if (sb.Length >= max) break;
|
||||||
|
if (c == '<') sb.Append('‹');
|
||||||
|
else if (c == '>') sb.Append('›');
|
||||||
|
else if (c == '\n' || !char.IsControl(c)) sb.Append(c);
|
||||||
|
}
|
||||||
|
return sb.ToString();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user