|
- using System;
- using System.Collections;
- using System.Collections.Generic;
- using System.Reflection;
- using System.Runtime.CompilerServices;
- using CLre_server.API.Synergy.Tweaks;
- using CLre_server.API.Utility;
- using Game.DataLoader;
- using GameNetworkLayer.Shared;
- using HarmonyLib;
- using NetworkFramework.Shared;
- using Svelto.DataStructures;
- using Svelto.ECS;
- using UnityEngine;
- using User.Server;
- using voxelfarm;
-
- namespace CLre_server.Tweaks
- {
- public class TerrainModificationExclusionZone
- {
- private static TerrainExclusionZoneEngine teze = null;
-
- internal static object _serverStructureExclusionZoneNode = null;
-
- internal static void Init()
- {
- if (!CLre.Config.terrain_exclusion_zone) return;
- teze = new TerrainExclusionZoneEngine();
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- internal static SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 location, int playerId)
- {
- if (_serverStructureExclusionZoneNode == null)
- {
- SerializedCLreTerrainModifyRejection result = default;
- result.Flags = RejectionFlag.InitError | RejectionFlag.Rejection;
- return result; // this shouldn't happen (I hope...)
- }
- return teze.HasExclusionZoneAtLocationWithMember(ref location, playerId, _serverStructureExclusionZoneNode);
- }
- }
-
- public class TerrainExclusionZoneEngine : API.Engines.ServerEnginePostBuild
- {
- public override void Ready()
- {
- }
-
- public SerializedCLreTerrainModifyRejection HasExclusionZoneAtLocationWithMember(ref Vector3 digPosition, int playerId, object exclusionZonesNode)
- {
- SerializedCLreTerrainModifyRejection result = default;
- // Similar to Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine.FindPotentialMatchingExclusionZones
- // Match player index to Guid
- FieldInfo f = AccessTools.Field(AccessTools.TypeByName("User.Server.AccountExclusiveGroups"), "accountGroup");
- ExclusiveGroup accountGroup = (ExclusiveGroup) f.GetValue(null);
- ReadOnlyCollectionStruct<AccountIdServerNode> accounts =
- entitiesDB.QueryEntityViews<AccountIdServerNode>(accountGroup);
- if (playerId >= accounts.Count)
- {
- // playerId isn't in connected accounts
- API.Utility.Logging.LogWarning("PlayerId isn't in connected accounts, denying terrain modification");
- result.Flags = RejectionFlag.AccountNotFound | RejectionFlag.Rejection;
- return result; // this shouldn't happen (I hope...)
- }
- Guid playerGuid = accounts[playerId].accountId.publicId;
- // find exclusion zone where terrain modification is happening
- float cellSize = dataDB.GetDefaultValue<WorldCellData>().WorldCellRadius * 0.25f;
- object structureCellId = GetCellIdFromPosition(ref digPosition, cellSize);
- // TODO optimize
- Traverse exclusionNodeData = Traverse.Create(exclusionZonesNode)
- .Field("serverStructureExclusionZoneDataComponent");
- Traverse exclusionZoneIdsByWorldCell = exclusionNodeData
- .Property("exclusionZoneIdsByWorldCell"); // Dictionary<StructureCellId, HashSet<uint>>
- Traverse exclusionZonesByUniqueId = exclusionNodeData
- .Property("exclusionZonesByUniqueId"); // Dictionary<uint, ServerStructureExclusionZone>
- bool exists = exclusionZoneIdsByWorldCell
- .Method("ContainsKey", new[] {structureCellId}).GetValue<bool>();
- if (exists)
- {
- #if DEBUG
- API.Utility.Logging.MetaLog("Exclusion zone cell found, iterating over zones...");
- #endif
- HashSet<uint> zoneIds = exclusionZoneIdsByWorldCell
- .Property<HashSet<uint>>("Item", index: new[] {structureCellId})
- .Value;
- foreach (uint item in zoneIds)
- {
- Traverse serverStructureExclusionZone = exclusionZonesByUniqueId
- .Property("Item", index: new object[] {item});
- Traverse structureExclusionZone = serverStructureExclusionZone
- .Property("structureExclusionZone");
- bool isOwner = serverStructureExclusionZone
- .Method("CheckIsOwner", playerGuid)
- .GetValue<bool>();
- #if DEBUG
- API.Utility.Logging.MetaLog($"IsOwner? {isOwner}");
- #endif
- Game.Building.AABB aabb = structureExclusionZone.Field("_exclusionZoneAabb")
- .Field<Game.Building.AABB>("_aabb").Value;
- bool isPointInAABB = IsWithin(ref digPosition, ref aabb);
- #if DEBUG
- API.Utility.Logging.MetaLog($"IsPointInAABB? {isPointInAABB}");
- API.Utility.Logging.MetaLog($"AABB max:{aabb.max}, min: {aabb.min} dig: {digPosition}");
- #endif
- if (isPointInAABB)
- {
- if (!isOwner)
- {
- result.Flags = RejectionFlag.Proximity
- | RejectionFlag.Rejection
- | RejectionFlag.Permission;
- }
- return result;
- }
- }
- }
- #if DEBUG
- API.Utility.Logging.MetaLog("Allowing player to modify terrain");
- #endif
- result.Flags = RejectionFlag.None;
- return result;
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static object GetCellIdFromPosition(ref Vector3 playerPosition, float cellSize)
- {
- // This uses decompiled code from Game.WorldGrid.StructureGridUtils:GetCellIdFromPosition
- // there's no point in calling that simple function when I have to jump through hoops with reflection
- //
- // there's also nothing particularly unique (ie copyrightable) about this code,
- // so the lawyers can suck it (also suing a benevolent project is a shitty move)
- float num = 1f / cellSize;
- int x = Mathf.CeilToInt((playerPosition.x - cellSize * 0.5f) * num);
- int y = Mathf.CeilToInt((playerPosition.y - cellSize * 0.5f) * num);
- int z = Mathf.CeilToInt((playerPosition.z - cellSize * 0.5f) * num);
- // TODO optimize
- // Create StructureCellId by jumping through hoops
- return AccessTools.TypeByName("Game.WorldGrid.StructureCellId")
- .GetConstructor(AccessTools.all, null, new[] {typeof(int), typeof(int), typeof(int)}, null)
- .Invoke(new object[] {x, y, z});
- }
-
- [MethodImpl(MethodImplOptions.AggressiveInlining)]
- private static bool IsWithin(ref Vector3 point, ref Game.Building.AABB bounds)
- {
- return point.x > bounds.min.x && point.x < bounds.max.x
- && point.y > bounds.min.y && point.y < bounds.max.y
- && point.z > bounds.min.z && point.x < bounds.max.z;
- }
- }
-
- [HarmonyPatch]
- class TerrainModificationEngineServer_RemoveTerrainInput_Patch
- {
- private static object _netMsgServerSender;
-
- // reflection caching
- private static API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
- _netMessageSend_CLre = null;
-
- [HarmonyPrefix]
- public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data, object ____netMsgServerSender)
- {
- if (!CLre.Config.terrain_exclusion_zone) return true;
- _netMsgServerSender = ____netMsgServerSender;
- if (_netMessageSend_CLre == null)
- {
- // cache reflection operations on first run
- #if DEBUG
- API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation");
- #endif
- _netMessageSend_CLre = API.Utility.Reflection
- .MethodAsDelegate<
- API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection>
- >(
- "GameNetworkLayer.Server.INetMsgServerSender:SendMessage",
- generics: new [] {typeof(SerializedCLreTerrainModifyRejection)},
- instance: ____netMsgServerSender);
- }
- #if DEBUG
- API.Utility.Logging.MetaLog("Intercepting TerrainModificationEngineServer.RemoveTerrainInput()");
- #endif
- Vector3 location = Traverse.Create(data).Property<Vector3>("hit").Value;
- API.Utility.Logging.MetaLog($"location is null? {location == Vector3.zero}");
- SerializedCLreTerrainModifyRejection modifyPayload = TerrainModificationExclusionZone.HasExclusionZoneAtLocationWithMember(ref location, senderPlayerId);
- if (!modifyPayload.Ok())
- {
- #if DEBUG
- API.Utility.Logging.MetaLog("Rejecting terrain modification");
- #endif
- // TODO optimize
- Traverse tmid = Traverse.Create(data);
- modifyPayload.resourceId = tmid.Property<uint>("resourceId").Value;
- modifyPayload.hit = location;
- //modifyPayload.materialIndex = tmid.Property<int>("materialIndex").Value;
- modifyPayload.toolKey = tmid.Property<string>("toolKey").Value;
- modifyPayload.toolMode = tmid.Property<Game.Handhelds.ToolModeType>("toolMode").Value;
- switch (modifyPayload.toolMode)
- {
- case Game.Handhelds.ToolModeType.Block:
- modifyPayload.hit.y -= 0.125f * 2; // each layer is 0.125 thick, block is 3 layers
- break;
- case Game.Handhelds.ToolModeType.Disc:
- break;
- case Game.Handhelds.ToolModeType.Voxel:
- modifyPayload.toolMode = Game.Handhelds.ToolModeType.Disc; // voxels aren't replaced properly
- break;
- }
- // signal client that stuff failed
- _netMessageSend_CLre(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId);
- // build terrain data for terrain replacement
- #if DEBUG
- API.Utility.Logging.MetaLog("Filling hole left by terrain modification");
- #endif
- }
- return modifyPayload.Ok();
- }
-
- [HarmonyTargetMethod]
- public static MethodBase Target()
- {
- return AccessTools.Method("GameServer.VoxelFarm.Server.TerrainModificationEngineServer:RemoveTerrainInput");
- }
- }
-
- [HarmonyPatch]
- class ServerStructureExclusionZoneEngine_Add_Patch
- {
- [HarmonyPostfix]
- public static void AfterMethodCall(object entityView)
- {
- TerrainModificationExclusionZone._serverStructureExclusionZoneNode = entityView;
- #if DEBUG
- API.Utility.Logging.MetaLog("Got TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
- #endif
- }
-
- [HarmonyTargetMethod]
- public static MethodBase Target()
- {
- return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Add", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
- }
- }
-
- [HarmonyPatch]
- class ServerStructureExclusionZoneEngine_Remove_Patch
- {
- [HarmonyPostfix]
- public static void AfterMethodCall()
- {
- TerrainModificationExclusionZone._serverStructureExclusionZoneNode = null;
- #if DEBUG
- API.Utility.Logging.MetaLog("Yeeted TerrainModificationExclusionZone._serverStructureExclusionZoneNode");
- #endif
- }
-
- [HarmonyTargetMethod]
- public static MethodBase Target()
- {
- return AccessTools.Method("Game.Building.ExclusionZone.ServerStructureExclusionZoneEngine:Remove", new Type[] {AccessTools.TypeByName("Game.Building.ExclusionZone.ServerStructureExclusionZonesNode")});
- }
- }
- }
|