|
|
@@ -0,0 +1,238 @@ |
|
|
|
using System; |
|
|
|
using System.Collections; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Reflection; |
|
|
|
using System.Runtime.CompilerServices; |
|
|
|
using CLre_server.API.Synergy.Tweaks; |
|
|
|
using Game.DataLoader; |
|
|
|
using GameNetworkLayer.Shared; |
|
|
|
using HarmonyLib; |
|
|
|
using NetworkFramework.Shared; |
|
|
|
using Svelto.DataStructures; |
|
|
|
using Svelto.ECS; |
|
|
|
using UnityEngine; |
|
|
|
using User.Server; |
|
|
|
|
|
|
|
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) |
|
|
|
{ |
|
|
|
result.Cell = item; |
|
|
|
|
|
|
|
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 API.Utility.Reflection.INetMsgServerSender_SendMessage<SerializedCLreTerrainModifyRejection> |
|
|
|
_netMessageSend = null; |
|
|
|
|
|
|
|
[HarmonyPrefix] |
|
|
|
public static bool BeforeMethodCall(int senderPlayerId, ref ISerializedNetData data, object ____netMsgServerSender) |
|
|
|
{ |
|
|
|
if (!CLre.Config.terrain_exclusion_zone) return true; |
|
|
|
if (_netMessageSend == null) |
|
|
|
{ |
|
|
|
#if DEBUG |
|
|
|
API.Utility.Logging.MetaLog("Building SendMessage delegate optimisation"); |
|
|
|
#endif |
|
|
|
_netMessageSend = 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 |
|
|
|
// signal client that stuff failed |
|
|
|
_netMessageSend(NetworkDispatcherCode.TerrainModificationFailed, ref modifyPayload, senderPlayerId); |
|
|
|
} |
|
|
|
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")}); |
|
|
|
} |
|
|
|
} |
|
|
|
} |