CLre/CLre_server/WebStatus/StatusEndpoints.cs

189 lines
7.2 KiB
C#

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<PlayerData> OnlinePlayers;
public int MaxPlayers;
public RunState State;
public static ServerStateCache Empty()
{
return new ServerStateCache
{
OnlinePlayers = new List<PlayerData>(),
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();
}
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<AccountIdServerNode> accounts =
entitiesDB.QueryEntityViews<AccountIdServerNode>(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<PlayerPositionFunc>(method, this);
}
_playerPositionFunc();
}
#pragma warning disable 0618
private void getPlayerPositionsGeneric<T>() where T : EntityView // EntityView is deprecated lol
{
ReadOnlyCollectionStruct<T> scpnCollection = entitiesDB.QueryEntityViews<T>(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<IPositionComponent>("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);
}
}
}