using System; using System.Collections; using System.Collections.Generic; using System.Net; using System.Reflection; using System.Text; using CLre_server.API.Engines; using CLre_server.API.MainServer; using Game.CommonComponents; using HarmonyLib; using Svelto.DataStructures; using Svelto.ECS; using User.Server; namespace CLre_server.WebStatus { public static class StatusEndpoints { private struct ServerStateCache { public List OnlinePlayers; public int MaxPlayers; public RunState State; public static ServerStateCache Empty() { return new ServerStateCache { OnlinePlayers = new List(), MaxPlayers = -1, State = RunState.Initialising, }; } public string Json() { // Unity's built-in JSON serializer does not work with arrays or lists :( // I miss Newtonsoft... StringBuilder sb = new StringBuilder($"{{\"PlayersMax\":{MaxPlayers},\"PlayerCount\":{OnlinePlayers.Count},\"Status\":\"{State.ToString()}\",\"OnlinePlayers\":["); foreach (PlayerData p in OnlinePlayers.ToArray()) { sb.Append(UnityEngine.JsonUtility.ToJson(p)); sb.Append(","); } if (OnlinePlayers.Count > 0) sb.Remove(sb.Length - 1, 1); sb.Append("]}"); return sb.ToString(); } } [Serializable] private struct PlayerData { public string id; public string name; public bool isDev; public float x; public float y; public float z; } private enum RunState : short { Initialising, Initialised, Online, Quitting, } private class StatusPollingEngine : ServerEnginePostBuild { public override void Ready() { API.Utility.Logging.MetaLog("StatusPolling Engine ready"); pollLoop().Run(); } public override IEntitiesDB entitiesDB { get; set; } public override IEntityFactory entityFactory { get; set; } private delegate void PlayerPositionFunc(); private PlayerPositionFunc _playerPositionFunc = null; private IEnumerator pollLoop() { FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup"); ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null); while (_clState.State != RunState.Quitting) { ReadOnlyCollectionStruct accounts = entitiesDB.QueryEntityViews(accountGroup); int index = 0; foreach (AccountIdServerNode user in accounts) { if (index < _clState.OnlinePlayers.Count) { PlayerData p = _clState.OnlinePlayers[index]; p.id = user.accountId.publicId.ToString(); p.name = user.accountId.displayName; p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev; _clState.OnlinePlayers[index] = p; } else { PlayerData p = default(PlayerData); p.id = user.accountId.publicId.ToString(); p.name = user.accountId.displayName; p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev; _clState.OnlinePlayers.Add(p); } index++; } if (index != 0) syncPlayerPositions(); if (index < _clState.OnlinePlayers.Count) _clState.OnlinePlayers.RemoveRange(index, _clState.OnlinePlayers.Count - index); //API.Utility.Logging.MetaLog($"Polled {index} Online Users"); yield return null; } } private void syncPlayerPositions() { if (_playerPositionFunc == null) { // build non-generic method using reflection MethodInfo method = AccessTools.Method(typeof(StatusPollingEngine), "getPlayerPositionsGeneric", generics: new[] {AccessTools.TypeByName("Game.Character.ServerCharacterPositionNode")}); _playerPositionFunc = API.Utility.Reflection.BuildDelegate(method, this); } _playerPositionFunc(); } #pragma warning disable 0618 private void getPlayerPositionsGeneric() where T : EntityView // EntityView is deprecated lol { ReadOnlyCollectionStruct scpnCollection = entitiesDB.QueryEntityViews(DEPRECATED_SveltoExtensions.DEPRECATED_GROUP); //API.Utility.Logging.MetaLog($"Found {scpnCollection.Count} player positions"); int i = 0; foreach (T scpn in scpnCollection) { PlayerData p = _clState.OnlinePlayers[i]; UnityEngine.Vector3 pos = Traverse.Create(scpn).Field("positionComponent") .Value.position; p.x = pos.x; p.y = pos.y; p.z = pos.z; _clState.OnlinePlayers[i] = p; i++; } } #pragma warning restore 0618 } private static ServerStateCache _clState = ServerStateCache.Empty(); internal static void Init() { #if DEBUG API.Utility.Logging.MetaLog("Status Endpoint initialising"); #endif new StatusPollingEngine(); // register API event callbacks Server.Instance.InitStart += (_, __) => _clState.State = RunState.Initialising; Server.Instance.InitComplete += (_, __) => { _clState.State = RunState.Initialised; _clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers(); }; Server.Instance.FrameworkReady += (_, __) => { _clState.State = RunState.Online; _clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers(); }; Server.Instance.FrameworkExit += (_, __) => _clState.State = RunState.Quitting; } [WebEndpoint("/status.json")] internal static void StatusJson(HttpListenerContext ctx) { ctx.Response.Headers.Add("Content-Type", "application/json"); string json = _clState.Json(); #if DEBUG API.Utility.Logging.MetaLog("JSONified status: " + json); #endif byte[] output = Encoding.UTF8.GetBytes(json); ctx.Response.OutputStream.Write(output, 0, output.Length); } } }