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