Unofficial CardLife revival project, pronounced like "celery"
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

192 lines
7.3KB

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Net;
  5. using System.Reflection;
  6. using System.Text;
  7. using CLre_server.API.Engines;
  8. using CLre_server.API.MainServer;
  9. using Game.CommonComponents;
  10. using HarmonyLib;
  11. using Svelto.DataStructures;
  12. using Svelto.ECS;
  13. using User.Server;
  14. namespace CLre_server.WebStatus
  15. {
  16. public static class StatusEndpoints
  17. {
  18. private struct ServerStateCache
  19. {
  20. public List<PlayerData> OnlinePlayers;
  21. public int MaxPlayers;
  22. public RunState State;
  23. public static ServerStateCache Empty()
  24. {
  25. return new ServerStateCache
  26. {
  27. OnlinePlayers = new List<PlayerData>(),
  28. MaxPlayers = -1,
  29. State = RunState.Initialising,
  30. };
  31. }
  32. public string Json()
  33. {
  34. // Unity's built-in JSON serializer does not work with arrays or lists :(
  35. // I miss Newtonsoft...
  36. StringBuilder sb = new StringBuilder($"{{\"PlayersMax\":{MaxPlayers},\"PlayerCount\":{OnlinePlayers.Count},\"Status\":\"{State.ToString()}\",\"OnlinePlayers\":[");
  37. foreach (PlayerData p in OnlinePlayers.ToArray())
  38. {
  39. sb.Append(UnityEngine.JsonUtility.ToJson(p));
  40. sb.Append(",");
  41. }
  42. if (OnlinePlayers.Count > 0) sb.Remove(sb.Length - 1, 1);
  43. sb.Append("]}");
  44. return sb.ToString();
  45. }
  46. }
  47. [Serializable]
  48. private struct PlayerData
  49. {
  50. public string id;
  51. public string name;
  52. public bool isDev;
  53. public float x;
  54. public float y;
  55. public float z;
  56. }
  57. private enum RunState : short
  58. {
  59. Initialising,
  60. Initialised,
  61. Online,
  62. Quitting,
  63. }
  64. private class StatusPollingEngine : ServerEnginePostBuild
  65. {
  66. public override void Ready()
  67. {
  68. API.Utility.Logging.MetaLog("StatusPolling Engine ready");
  69. pollLoop().Run();
  70. }
  71. public override IEntitiesDB entitiesDB { get; set; }
  72. public override IEntityFactory entityFactory { get; set; }
  73. private delegate void PlayerPositionFunc();
  74. private PlayerPositionFunc _playerPositionFunc = null;
  75. private IEnumerator pollLoop()
  76. {
  77. FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
  78. ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
  79. while (_clState.State != RunState.Quitting)
  80. {
  81. ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
  82. entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
  83. int index = 0;
  84. foreach (AccountIdServerNode user in accounts)
  85. {
  86. if (index < _clState.OnlinePlayers.Count)
  87. {
  88. PlayerData p = _clState.OnlinePlayers[index];
  89. p.id = user.accountId.publicId.ToString();
  90. p.name = user.accountId.displayName;
  91. p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev;
  92. _clState.OnlinePlayers[index] = p;
  93. }
  94. else
  95. {
  96. PlayerData p = default(PlayerData);
  97. p.id = user.accountId.publicId.ToString();
  98. p.name = user.accountId.displayName;
  99. p.isDev = (user.accountId.userFlags & UserFlags.Dev) == UserFlags.Dev;
  100. _clState.OnlinePlayers.Add(p);
  101. }
  102. index++;
  103. }
  104. if (index != 0) syncPlayerPositions();
  105. if (index < _clState.OnlinePlayers.Count) _clState.OnlinePlayers.RemoveRange(index, _clState.OnlinePlayers.Count - index);
  106. //API.Utility.Logging.MetaLog($"Polled {index} Online Users");
  107. yield return null;
  108. }
  109. }
  110. private void syncPlayerPositions()
  111. {
  112. if (_playerPositionFunc == null)
  113. {
  114. // build non-generic method using reflection
  115. MethodInfo method = AccessTools.Method(typeof(StatusPollingEngine), "getPlayerPositionsGeneric",
  116. generics: new[] {AccessTools.TypeByName("Game.Character.ServerCharacterPositionNode")});
  117. _playerPositionFunc = API.Utility.Reflection.BuildDelegate<PlayerPositionFunc>(method, this);
  118. }
  119. _playerPositionFunc();
  120. }
  121. #pragma warning disable 0618
  122. private void getPlayerPositionsGeneric<T>() where T : EntityView // EntityView is deprecated lol
  123. {
  124. ReadOnlyCollectionStruct<T> scpnCollection = entitiesDB.QueryEntityViews<T>(DEPRECATED_SveltoExtensions.DEPRECATED_GROUP);
  125. //API.Utility.Logging.MetaLog($"Found {scpnCollection.Count} player positions");
  126. int i = 0;
  127. foreach (T scpn in scpnCollection)
  128. {
  129. PlayerData p = _clState.OnlinePlayers[i];
  130. UnityEngine.Vector3 pos = Traverse.Create(scpn).Field<IPositionComponent>("positionComponent")
  131. .Value.position;
  132. p.x = pos.x;
  133. p.y = pos.y;
  134. p.z = pos.z;
  135. _clState.OnlinePlayers[i] = p;
  136. i++;
  137. }
  138. }
  139. #pragma warning restore 0618
  140. }
  141. private static ServerStateCache _clState = ServerStateCache.Empty();
  142. internal static void Init()
  143. {
  144. #if DEBUG
  145. API.Utility.Logging.MetaLog("Status Endpoint initialising");
  146. #endif
  147. new StatusPollingEngine();
  148. // register API event callbacks
  149. Server.Instance.InitStart += (_, __) => _clState.State = RunState.Initialising;
  150. Server.Instance.InitComplete += (_, __) =>
  151. {
  152. _clState.State = RunState.Initialised;
  153. _clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers();
  154. };
  155. Server.Instance.FrameworkReady += (_, __) =>
  156. {
  157. _clState.State = RunState.Online;
  158. _clState.MaxPlayers = Server.Instance.GameServerSettings.GetMaxPlayers();
  159. };
  160. Server.Instance.FrameworkExit += (_, __) => _clState.State = RunState.Quitting;
  161. }
  162. [WebEndpoint("/status.json")]
  163. internal static void StatusJson(HttpListenerContext ctx)
  164. {
  165. ctx.Response.Headers.Add("Content-Type", "application/json");
  166. string json = _clState.Json();
  167. #if DEBUG
  168. API.Utility.Logging.MetaLog("JSONified status: " + json);
  169. #endif
  170. byte[] output = Encoding.UTF8.GetBytes(json);
  171. ctx.Response.OutputStream.Write(output, 0, output.Length);
  172. }
  173. }
  174. }