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.

TerrainModificationExclusionZone.cs 12KB

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