135 lines
5.9 KiB
C#
135 lines
5.9 KiB
C#
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();
|
||
}
|
||
}
|
||
}
|