Unofficial CardLife revival project, pronounced like "celery"
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

265 lignes
12KB

  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. }