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.

238 lines
11KB

  1. using System;
  2. using System.Collections;
  3. using System.Collections.Generic;
  4. using System.Reflection;
  5. using System.Runtime.CompilerServices;
  6. using CLre_server.API.Synergy.Tweaks;
  7. using Game.DataLoader;
  8. using GameNetworkLayer.Shared;
  9. using HarmonyLib;
  10. using NetworkFramework.Shared;
  11. using Svelto.DataStructures;
  12. using Svelto.ECS;
  13. using UnityEngine;
  14. using User.Server;
  15. namespace CLre_server.Tweaks
  16. {
  17. public class TerrainModificationExclusionZone
  18. {
  19. private static TerrainExclusionZoneEngine teze = null;
  20. internal static object _serverStructureExclusionZoneNode = null;
  21. internal static void Init()
  22. {
  23. if (!CLre.Config.terrain_exclusion_zone) return;
  24. teze = new TerrainExclusionZoneEngine();
  25. }
  26. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  27. internal static SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 location, int playerId)
  28. {
  29. if (_serverStructureExclusionZoneNode == null)
  30. {
  31. SerializedCLreTerrainModifyRejection result = default;
  32. result.Flags = RejectionFlag.InitError | RejectionFlag.Rejection;
  33. return result; // this shouldn't happen (I hope...)
  34. }
  35. return teze.HasExclusionZoneAtLocationWithMember(ref location, playerId, _serverStructureExclusionZoneNode);
  36. }
  37. }
  38. public class TerrainExclusionZoneEngine : API.Engines.ServerEnginePostBuild
  39. {
  40. public override void Ready()
  41. {
  42. }
  43. public SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 digPosition, int playerId, object exclusionZonesNode)
  44. {
  45. SerializedCLreTerrainModifyRejection result = default;
  46. // Similar to Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine.FindPotentialMatchingExclusionZones
  47. // Match player index to Guid
  48. FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
  49. ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
  50. ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
  51. entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
  52. if (playerId >= accounts.Count)
  53. {
  54. // playerId isn't in connected accounts
  55. API.Utility.Logging.LogWarning("PlayerId isn't in connected accounts, denying terrain modification");
  56. result.Flags = RejectionFlag.AccountNotFound | RejectionFlag.Rejection;
  57. return result; // this shouldn't happen (I hope...)
  58. }
  59. Guid playerGuid = accounts[playerId].accountId.publicId;
  60. // find exclusion zone where terrain modification is happening
  61. float cellSize = dataDB.GetDefaultValue<WorldCellData>().WorldCellRadius * 0.25f;
  62. object structureCellId = GetCellIdFromPosition(ref digPosition, cellSize);
  63. // TODO optimize
  64. Traverse exclusionNodeData = Traverse.Create(exclusionZonesNode)
  65. .Field("serverStructureExclusionZoneDataComponent");
  66. Traverse exclusionZoneIdsByWorldCell = exclusionNodeData
  67. .Property("exclusionZoneIdsByWorldCell"); // Dictionary<StructureCellId, HashSet<uint>>
  68. Traverse exclusionZonesByUniqueId = exclusionNodeData
  69. .Property("exclusionZonesByUniqueId"); // Dictionary<uint, ServerStructureExclusionZone>
  70. bool exists = exclusionZoneIdsByWorldCell
  71. .Method("ContainsKey", new[] {structureCellId}).GetValue<bool>();
  72. if (exists)
  73. {
  74. #if DEBUG
  75. API.Utility.Logging.MetaLog("Exclusion zone cell found, iterating over zones...");
  76. #endif
  77. HashSet<uint> zoneIds = exclusionZoneIdsByWorldCell
  78. .Property<HashSet<uint>>("Item", index: new[] {structureCellId})
  79. .Value;
  80. foreach (uint item in zoneIds)
  81. {
  82. Traverse serverStructureExclusionZone = exclusionZonesByUniqueId
  83. .Property("Item", index: new object[] {item});
  84. Traverse structureExclusionZone = serverStructureExclusionZone
  85. .Property("structureExclusionZone");
  86. bool isOwner = serverStructureExclusionZone
  87. .Method("CheckIsOwner", playerGuid)
  88. .GetValue<bool>();
  89. #if DEBUG
  90. API.Utility.Logging.MetaLog($"IsOwner? {isOwner}");
  91. #endif
  92. Game.Building.AABB aabb = structureExclusionZone.Field("_exclusionZoneAabb")
  93. .Field<Game.Building.AABB>("_aabb").Value;
  94. bool isPointInAABB = IsWithin(ref digPosition, ref aabb);
  95. #if DEBUG
  96. API.Utility.Logging.MetaLog($"IsPointInAABB? {isPointInAABB}");
  97. API.Utility.Logging.MetaLog($"AABB max:{aabb.max}, min: {aabb.min} dig: {digPosition}");
  98. #endif
  99. if (isPointInAABB)
  100. {
  101. result.Cell = item;
  102. if (!isOwner)
  103. {
  104. result.Flags = RejectionFlag.Proximity
  105. | RejectionFlag.Rejection
  106. | RejectionFlag.Permission;
  107. }
  108. return result;
  109. }
  110. }
  111. }
  112. #if DEBUG
  113. API.Utility.Logging.MetaLog("Allowing player to modify terrain");
  114. #endif
  115. result.Flags = RejectionFlag.None;
  116. return result;
  117. }
  118. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  119. private static object GetCellIdFromPosition(ref Vector3 playerPosition, float cellSize)
  120. {
  121. // This uses decompiled code from Game.WorldGrid.StructureGridUtils:GetCellIdFromPosition
  122. // there's no point in calling that simple function when I have to jump through hoops with reflection
  123. //
  124. // there's also nothing particularly unique (ie copyrightable) about this code,
  125. // so the lawyers can suck it (also suing a benevolent project is a shitty move)
  126. float num = 1f / cellSize;
  127. int x = Mathf.CeilToInt((playerPosition.x - cellSize * 0.5f) * num);
  128. int y = Mathf.CeilToInt((playerPosition.y - cellSize * 0.5f) * num);
  129. int z = Mathf.CeilToInt((playerPosition.z - cellSize * 0.5f) * num);
  130. // TODO optimize
  131. // Create StructureCellId by jumping through hoops
  132. return AccessTools.TypeByName("Game.WorldGrid.StructureCellId")
  133. .GetConstructor(AccessTools.all, null, new[] {typeof(int), typeof(int), typeof(int)}, null)
  134. .Invoke(new object[] {x, y, z});
  135. }
  136. [MethodImpl(MethodImplOptions.AggressiveInlining)]
  137. private static bool IsWithin(ref Vector3 point, ref Game.Building.AABB bounds)
  138. {
  139. return point.x > bounds.min.x && point.x < bounds.max.x
  140. && point.y > bounds.min.y && point.y < bounds.max.y
  141. && point.z > bounds.min.z && point.x < bounds.max.z;
  142. }
  143. }
  144. [HarmonyPatch]
  145. class TerrainModificationEngineServer_RemoveTerrainInput_Patch
  146. {
  147. private static API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
  148. _netMessageSend = null;
  149. [HarmonyPrefix]
  150. public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data, object ____netMsgServerSender)
  151. {
  152. if (!CLre.Config.terrain_exclusion_zone) return true;
  153. if (_netMessageSend == null)
  154. {
  155. #if DEBUG
  156. API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation");
  157. #endif
  158. _netMessageSend = API.Utility.Reflection
  159. .MethodAsDelegate<
  160. API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
  161. >(
  162. "GameNetworkLayer.Server.INetMsgServerSender:SendMessage",
  163. generics: new [] {typeof(SerializedCLreTerrainModifyRejection)},
  164. instance: ____netMsgServerSender);
  165. }
  166. #if DEBUG
  167. API.Utility.Logging.MetaLog("Intercepting TerrainModificationEngineServer.RemoveTerrainInput()");
  168. #endif
  169. Vector3 location = Traverse.Create(data).Property<Vector3>("hit").Value;
  170. API.Utility.Logging.MetaLog($"location is null? {location == Vector3.zero}");
  171. SerializedCLreTerrainModifyRejection modifyPayload = TerrainModificationExclusionZone.HasExclusionZoneAtLocationWithMember(ref location, senderPlayerId);
  172. if (!modifyPayload.Ok())
  173. {
  174. #if DEBUG
  175. API.Utility.Logging.MetaLog("Rejecting terrain modification");
  176. #endif
  177. // signal client that stuff failed
  178. _netMessageSend(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId);
  179. }
  180. return modifyPayload.Ok();
  181. }
  182. [HarmonyTargetMethod]
  183. public static MethodBase Target()
  184. {
  185. return AccessTools.Method("GameServer.VoxelFarm.Server.TerrainModificationEngineServer:RemoveTerrainInput");
  186. }
  187. }
  188. [HarmonyPatch]
  189. class ServerStructureExclusionZoneEngine_Add_Patch
  190. {
  191. [HarmonyPostfix]
  192. public static void AfterMethodCall(object entityView)
  193. {
  194. TerrainModificationExclusionZone._serverStructureExclusionZoneNode = entityView;
  195. #if DEBUG
  196. API.Utility.Logging.MetaLog("Got TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
  197. #endif
  198. }
  199. [HarmonyTargetMethod]
  200. public static MethodBase Target()
  201. {
  202. return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Add", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
  203. }
  204. }
  205. [HarmonyPatch]
  206. class ServerStructureExclusionZoneEngine_Remove_Patch
  207. {
  208. [HarmonyPostfix]
  209. public static void AfterMethodCall()
  210. {
  211. TerrainModificationExclusionZone._serverStructureExclusionZoneNode = null;
  212. #if DEBUG
  213. API.Utility.Logging.MetaLog("Yeeted TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
  214. #endif
  215. }
  216. [HarmonyTargetMethod]
  217. public static MethodBase Target()
  218. {
  219. return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Remove", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
  220. }
  221. }
  222. }