using System; using System.Collections.Generic; using System.Threading; using UnityEngine; namespace MSCO { /// Eine Ankündigung der MSCO-Admins (Keymaster → Wartung & News, Ziel "Spiel"). public class Announcement { public string Id, Title, Text, Level; // Level: info, warning, critical } /// /// 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. /// public static class SiteStatus { const float RefreshSeconds = 300f; static readonly object gate = new object(); static List announcements = new List(); static bool maintenance; static string maintenanceMessage = ""; static int minServerLevel; // vom Keymaster (0 = unbekannt) static float nextFetch; static bool fetching; /// Aktuelle Ankündigungen (wichtigste zuerst). public static List Announcements { get { lock (gate) return new List(announcements); } } public static bool Maintenance { get { lock (gate) return maintenance; } } public static string MaintenanceMessage { get { lock (gate) return maintenanceMessage; } } /// /// Sicherheitsstufe, die Server und P2P-Hosts mindestens haben müssen: das Eingebaute oder - wenn höher - was die /// MSCO-Admins im Keymaster eingestellt haben. /// public static int RequiredServerSecurity { get { lock (gate) return Math.Max(MSCO.MinServerSecurity, minServerLevel); } } /// Neue Ankündigung seit dem letzten Abruf (für einen Hinweis im Spiel). public static event Action NewAnnouncement; static readonly HashSet seen = new HashSet(); static readonly List fresh = new List(); // neu, noch nicht gemeldet (Hauptthread) static bool firstLoad = true; /// Jedes Frame aufrufen (MultiplayerManager): lädt beim Start und dann alle 5 Minuten neu. public static void Tick() { Action handler = NewAnnouncement; List report = null; lock (gate) if (fresh.Count > 0) { report = new List(fresh); fresh.Clear(); } if (report != null && handler != null) foreach (Announcement a in report) try { handler(a); } catch { } if (fetching || Time.realtimeSinceStartup < nextFetch) return; Refresh(); } /// Sofort neu laden (z.B. beim Öffnen des Server-Browsers). 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; if (root == null) return; object v; var m = root.TryGetValue("maintenance", out v) ? v as Dictionary : null; var sec = root.TryGetValue("security", out v) ? v as Dictionary : null; var list = new List(); var items = root.TryGetValue("announcements", out v) ? v as List : null; if (items != null) foreach (object item in items) { var a = item as Dictionary; 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; } } /// Kein Steuerzeichen/Rich-Text aus dem Netz in die Oberfläche (Unity-GUI wertet <color> usw. aus). 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(); } } }